From b281fcece205721c9a6bc530c03c2cee9fbd99cd Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Sat, 14 Jan 2023 14:57:28 +0300 Subject: [PATCH 01/39] fix NFTAcceptOffer --- Xrpl/Models/Transactions/TxFormat.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Xrpl/Models/Transactions/TxFormat.cs b/Xrpl/Models/Transactions/TxFormat.cs index 6719d99..c351591 100644 --- a/Xrpl/Models/Transactions/TxFormat.cs +++ b/Xrpl/Models/Transactions/TxFormat.cs @@ -261,7 +261,10 @@ static TxFormat() }, [BinaryCodec.Types.TransactionType.NFTokenAcceptOffer] = new TxFormat { - [Field.NFTokenID] = Requirement.Required + //[Field.NFTokenID] = Requirement.Required, //no need this field + [Field.NFTokenSellOffer] = Requirement.Optional, + [Field.NFTokenBuyOffer] = Requirement.Optional, + [Field.NFTokenBrokerFee] = Requirement.Optional, }, [BinaryCodec.Types.TransactionType.UNLModify] = new TxFormat { From bcad649a0820660af4c4e86fca998c2cbcd88cbf Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Sat, 14 Jan 2023 17:16:06 +0300 Subject: [PATCH 02/39] fix Balances (1.3 H) --- Xrpl/Sugar/Autofill.cs | 29 ++++++-------- Xrpl/Sugar/Balances.cs | 86 +++++++++++++++++++++++++----------------- 2 files changed, 63 insertions(+), 52 deletions(-) diff --git a/Xrpl/Sugar/Autofill.cs b/Xrpl/Sugar/Autofill.cs index 5ae2c4a..6cc51bd 100644 --- a/Xrpl/Sugar/Autofill.cs +++ b/Xrpl/Sugar/Autofill.cs @@ -1,27 +1,20 @@ -using System; -using System.Linq; +using Newtonsoft.Json; + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Numerics; using System.Threading.Tasks; + using Xrpl.AddressCodec; +using Xrpl.Client; +using Xrpl.Client.Exceptions; using Xrpl.Models.Common; using Xrpl.Models.Ledger; using Xrpl.Models.Methods; -using System.Numerics; -using static Xrpl.AddressCodec.XrplAddressCodec; -using System.Collections.Generic; -using Xrpl.Client; -using Xrpl.Client.Exceptions; using Xrpl.Utils; -using System.Globalization; -using Newtonsoft.Json.Linq; -using Org.BouncyCastle.Math.EC.Multiplier; -using Xrpl.BinaryCodec.Types; -using System.IO; -using System.Diagnostics; -using Newtonsoft.Json; -using Org.BouncyCastle.Asn1.Ocsp; -using Xrpl.BinaryCodec.Enums; -using Org.BouncyCastle.Utilities; -using System.Security.Principal; + +using static Xrpl.AddressCodec.XrplAddressCodec; // https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/sugar/autofill.ts diff --git a/Xrpl/Sugar/Balances.cs b/Xrpl/Sugar/Balances.cs index 61adce3..82a0ab8 100644 --- a/Xrpl/Sugar/Balances.cs +++ b/Xrpl/Sugar/Balances.cs @@ -1,5 +1,9 @@ using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; + using Xrpl.Client; using Xrpl.Models.Common; using Xrpl.Models.Ledger; @@ -9,7 +13,7 @@ namespace Xrpl.Sugar { - public class BalancesSugar + public static class BalancesSugar { public class Balance { @@ -23,9 +27,20 @@ public class GetBalancesOptions public string? LedgerHash { get; set; } public LedgerIndex? LedgerIndex { get; set; } public string Peer { get; set; } - public int Limit { get; set; } + public int? Limit { get; set; } } + public static IEnumerable FormatBalances(this IEnumerable trustlines) => + trustlines.Select(Map); + + public static Balance Map(this TrustLine trustline) => + new Balance() + { + value = trustline.Balance, + currency = trustline.Currency, + issuer = trustline.Account, + }; + /// /// Get the XRP balance for an account. /// @@ -34,7 +49,7 @@ public class GetBalancesOptions /// Retrieve the account balances at a given ledgerIndex. /// Retrieve the account balances at the ledger with a given ledger_hash. /// The XRP balance of the account (as a string). - public static async Task GetXrpBalance(XrplClient client, string address, string? ledgerHash = null, LedgerIndex? lederIndex = null) + public static async Task GetXrpBalance(this XrplClient client, string address, string? ledgerHash = null, LedgerIndex? lederIndex = null) { LedgerIndex index = new LedgerIndex(LedgerIndexType.Validated); AccountInfoRequest xrpRequest = new AccountInfoRequest(address) @@ -47,38 +62,41 @@ public static async Task GetXrpBalance(XrplClient client, string address return accountInfo.AccountData.Balance.ValueAsXrp.ToString(); } - //public async Task> GetBalances(XrplClient client, string address, GetBalancesOptions options = null) - //{ - // var balances = new List(); - // var xrpPromise = Task.FromResult(""); - // if (options?.Peer == null) - // { - // xrpPromise = GetXrpBalance(client, address, options?.LedgerHash, options?.LedgerIndex); - // } + public static async Task> GetBalances(this XrplClient client, string address, GetBalancesOptions options = null) + { + var linesRequest = new AccountLinesRequest(address) + { + Command = "account_lines", + LedgerIndex = options?.LedgerIndex ?? new LedgerIndex(LedgerIndexType.Validated), + LedgerHash = options?.LedgerHash, + Peer = options?.Peer, + Limit = options?.Limit + }; - // var linesRequest = new AccountLinesRequest - // { - // Command = "account_lines", - // Account = address, - // LedgerIndex = options?.LedgerIndex ?? new LedgerIndex(LedgerIndexType.Validated), - // LedgerHash = options?.LedgerHash, - // Peer = options?.Peer, - // Limit = options?.Limit - // }; - // var linesPromise = RequestAll(linesRequest); + var response = await client.AccountLines(linesRequest); + var lines = response.TrustLines; + while (response.Marker is not null && lines.Count > 0) + { + linesRequest.Marker = response.Marker; + response = await client.AccountLines(linesRequest); + if (response.TrustLines.Count > 0) + lines.AddRange(response.TrustLines); + if (options?.Limit is not null && lines.Count >= options.Limit) + break; + } + var balances = lines.FormatBalances().ToList(); + + if (options?.Peer == null) + { + var xrp_balance = await GetXrpBalance(client, address, options?.LedgerHash, options?.LedgerIndex); + if (!string.IsNullOrWhiteSpace(xrp_balance)) + { + balances.Insert(0, new Balance { currency = "XRP", value = xrp_balance }); + } - // await Task.WhenAll(xrpPromise, linesPromise).ContinueWith(async (t) => - // { - // var xrpBalance = await xrpPromise; - // var linesResponses = await linesPromise; - // var accountLinesBalance = linesResponses.SelectMany(response => FormatBalances(response.Result.Lines)); - // if (xrpBalance != "") - // { - // balances.Add(new Balance { Currency = "XRP", Value = xrpBalance }); - // } - // balances.AddRange(accountLinesBalance); - // }); - // return balances.Take(options?.Limit ?? balances.Count).ToList(); - //} + } + + return balances; + } } } \ No newline at end of file From 383c69a0762f4aeb1887a1ab9ffd11d4c8736424 Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Sat, 14 Jan 2023 17:22:41 +0300 Subject: [PATCH 03/39] fix fee sugar (0.2 h) --- Xrpl/Sugar/GetFeeXrp.cs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/Xrpl/Sugar/GetFeeXrp.cs b/Xrpl/Sugar/GetFeeXrp.cs index 0875834..a9289dc 100644 --- a/Xrpl/Sugar/GetFeeXrp.cs +++ b/Xrpl/Sugar/GetFeeXrp.cs @@ -1,14 +1,10 @@ using System; -using System.Diagnostics; +using System.Globalization; using System.Threading.Tasks; -using Xrpl; -using Xrpl.Client.Exceptions; -using Xrpl.Models.Common; -using Xrpl.Models.Ledger; -using Xrpl.Models.Methods; -using System.Numerics; + using Xrpl.Client; using Xrpl.Client.Exceptions; +using Xrpl.Models.Methods; // https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/sugar/getFeeXrp.ts @@ -23,19 +19,20 @@ public class GetFeeXrpSugar /// Note: This is a public API that can be called directly. /// /// The Client used to connect to the ledger. - /// - // The most recently validated ledger index. - public async static Task GetFeeXrp(IXrplClient client, double? cushion = null) + /// The fee cushion to use + /// The transaction fee + public static async Task GetFeeXrp(IXrplClient client, double? cushion = null) { double feeCushion = cushion ?? client.feeCushion; ServerInfoRequest request = new ServerInfoRequest(); ServerInfo serverInfo = await client.ServerInfo(request); - double? baseFee = serverInfo.Info.ValidatedLedger.BaseFeeXrp; + double? baseFee = serverInfo.Info.ValidatedLedger?.BaseFeeXrp; if (baseFee == null) { throw new XrplException("getFeeXrp: Could not get base_fee_xrp from server_info"); } - decimal baseFeeXrp = decimal.Parse(baseFee.ToString(), System.Globalization.NumberStyles.AllowExponent); + + decimal baseFeeXrp = (decimal)baseFee; if (serverInfo.Info.LoadFactor == null) { @@ -43,13 +40,13 @@ public async static Task GetFeeXrp(IXrplClient client, double? cushion = serverInfo.Info.LoadFactor = 1; } - decimal fee = baseFeeXrp * decimal.Parse(serverInfo.Info.LoadFactor.ToString()) * ((decimal)feeCushion); + decimal fee = baseFeeXrp * (decimal)serverInfo.Info.LoadFactor * (decimal)feeCushion; // Cap fee to `client.maxFeeXRP` fee = Math.Min(fee, decimal.Parse(client.maxFeeXRP)); // Round fee to 6 decimal places // TODO: Review To Fixed - return fee.ToString(); + return fee.ToString(CultureInfo.InvariantCulture); } } } From 04a723e889595cc235fbd483a09420ddd5da6e52 Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Sat, 14 Jan 2023 17:28:20 +0300 Subject: [PATCH 04/39] to extensions (0.2 h) --- Xrpl/Sugar/Autofill.cs | 61 ++++++++++++++++++------------------ Xrpl/Sugar/GetFeeXrp.cs | 6 ++-- Xrpl/Sugar/GetLedgerIndex.cs | 4 +-- 3 files changed, 36 insertions(+), 35 deletions(-) diff --git a/Xrpl/Sugar/Autofill.cs b/Xrpl/Sugar/Autofill.cs index 6cc51bd..2883146 100644 --- a/Xrpl/Sugar/Autofill.cs +++ b/Xrpl/Sugar/Autofill.cs @@ -20,16 +20,17 @@ namespace Xrpl.Sugar { - public class AutofillSugar + public class AddressNTag { + public string ClassicAddress { get; set; } + public int? Tag { get; set; } + } - static readonly int LEDGER_OFFSET = 20; + public static class AutofillSugar + { + + const int LEDGER_OFFSET = 20; - public class AddressNTag - { - public string ClassicAddress { get; set; } - public int? Tag { get; set; } - } /// /// Autofills fields in a transaction. This will set `Sequence`, `Fee`, @@ -41,31 +42,31 @@ public class AddressNTag /// A {@link Transaction} in JSON format /// The expected number of signers for this transaction. Only used for multisigned transactions. // The autofilled transaction. - public static async Task> Autofill(IXrplClient client, Dictionary transaction, int? signersCount) + public static async Task> Autofill(this IXrplClient client, Dictionary transaction, int? signersCount) { Dictionary tx = transaction; - SetValidAddresses(tx); + tx.SetValidAddresses(); //Flags.SetTransactionFlagsToNumber(tx); List promises = new List(); bool hasTT = tx.TryGetValue("TransactionType", out var tt); if (!tx.ContainsKey("Sequence")) { - promises.Add(SetNextValidSequenceNumber(client, tx)); + promises.Add(client.SetNextValidSequenceNumber(tx)); } if (!tx.ContainsKey("Fee")) { - promises.Add(CalculateFeePerTransactionType(client, tx, signersCount ?? 0)); + promises.Add(client.CalculateFeePerTransactionType(tx, signersCount ?? 0)); } if (!tx.ContainsKey("LastLedgerSequence")) { - promises.Add(SetLatestValidatedLedgerSequence(client, tx)); + promises.Add(client.SetLatestValidatedLedgerSequence(tx)); } if (tt == "AccountDelete") { - promises.Add(CheckAccountDeleteBlockers(client, tx)); + promises.Add(client.CheckAccountDeleteBlockers(tx)); } await Task.WhenAll(promises); string jsonData = JsonConvert.SerializeObject(tx); @@ -73,24 +74,24 @@ public static async Task> Autofill(IXrplClient clien } - public static void SetValidAddresses(Dictionary tx) + public static void SetValidAddresses(this Dictionary tx) { - ValidateAccountAddress(tx, "Account", "SourceTag"); + tx.ValidateAccountAddress("Account", "SourceTag"); if (tx.ContainsKey("Destination")) { - ValidateAccountAddress(tx, "Destination", "DestinationTag"); + tx.ValidateAccountAddress("Destination", "DestinationTag"); } // DepositPreauth: - ConvertToClassicAddress(tx, "Authorize"); - ConvertToClassicAddress(tx, "Unauthorize"); + tx.ConvertToClassicAddress("Authorize"); + tx.ConvertToClassicAddress("Unauthorize"); // EscrowCancel, EscrowFinish: - ConvertToClassicAddress(tx, "Owner"); + tx.ConvertToClassicAddress("Owner"); // SetRegularKey: - ConvertToClassicAddress(tx, "RegularKey"); + tx.ConvertToClassicAddress("RegularKey"); } - public static void ValidateAccountAddress(Dictionary tx, string accountField, string tagField) + public static void ValidateAccountAddress(this Dictionary tx, string accountField, string tagField) { // if X-address is given, convert it to classic address var ainfo = tx.TryGetValue(accountField, out var aField); @@ -112,7 +113,7 @@ public static void ValidateAccountAddress(Dictionary tx, string } } - public static AddressNTag GetClassicAccountAndTag(string account, int? expectedTag) + public static AddressNTag GetClassicAccountAndTag(this string account, int? expectedTag) { if (XrplAddressCodec.IsValidXAddress(account)) { @@ -126,20 +127,20 @@ public static AddressNTag GetClassicAccountAndTag(string account, int? expectedT return new AddressNTag { ClassicAddress = account, Tag = expectedTag }; } - public static void ConvertToClassicAddress(Dictionary tx, string fieldName) + public static void ConvertToClassicAddress(this Dictionary tx, string fieldName) { if (tx.ContainsKey(fieldName)) { string account = (string)tx[fieldName]; if (account is string) { - AddressNTag addressntag = GetClassicAccountAndTag(account, null); + AddressNTag addressntag = account.GetClassicAccountAndTag(null); tx[fieldName] = addressntag.ClassicAddress; } } } - public static async Task SetNextValidSequenceNumber(IXrplClient client, Dictionary tx) + public static async Task SetNextValidSequenceNumber(this IXrplClient client, Dictionary tx) { LedgerIndex index = new LedgerIndex(LedgerIndexType.Current); AccountInfoRequest request = new AccountInfoRequest((string)tx["Account"]) { LedgerIndex = index }; @@ -147,7 +148,7 @@ public static async Task SetNextValidSequenceNumber(IXrplClient client, Dictiona tx.TryAdd("Sequence", data.AccountData.Sequence); } - public static async Task FetchAccountDeleteFee(IXrplClient client) + public static async Task FetchAccountDeleteFee(this IXrplClient client) { ServerStateRequest request = new ServerStateRequest(); ServerState data = await client.ServerState(request); @@ -160,9 +161,9 @@ public static async Task FetchAccountDeleteFee(IXrplClient client) return BigInteger.Parse(fee.ToString()); } - public static async Task CalculateFeePerTransactionType(IXrplClient client, Dictionary tx, int signersCount = 0) + public static async Task CalculateFeePerTransactionType(this IXrplClient client, Dictionary tx, int signersCount = 0) { - var netFeeXRP = await GetFeeXrpSugar.GetFeeXrp(client); + var netFeeXRP = await client.GetFeeXrp(); var netFeeDrops = XrpConversion.XrpToDrops(netFeeXRP); var baseFee = BigInteger.Parse(netFeeDrops, NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign | NumberStyles.AllowExponent); @@ -202,13 +203,13 @@ public static string ScaleValue(string value, double multiplier) return (double.Parse(value)! * multiplier).ToString(); } - public static async Task SetLatestValidatedLedgerSequence(IXrplClient client, Dictionary tx) + public static async Task SetLatestValidatedLedgerSequence(this IXrplClient client, Dictionary tx) { uint ledgerSequence = await client.GetLedgerIndex(); tx.TryAdd("LastLedgerSequence", ledgerSequence + LEDGER_OFFSET); } - public static async Task CheckAccountDeleteBlockers(IXrplClient client, Dictionary tx) + public static async Task CheckAccountDeleteBlockers(this IXrplClient client, Dictionary tx) { LedgerIndex index = new LedgerIndex(LedgerIndexType.Validated); AccountObjectsRequest request = new AccountObjectsRequest((string)tx["Account"]) diff --git a/Xrpl/Sugar/GetFeeXrp.cs b/Xrpl/Sugar/GetFeeXrp.cs index a9289dc..7991d21 100644 --- a/Xrpl/Sugar/GetFeeXrp.cs +++ b/Xrpl/Sugar/GetFeeXrp.cs @@ -10,9 +10,9 @@ namespace Xrpl.Sugar { - public class GetFeeXrpSugar + public static class GetFeeXrpSugar { - private static int NUM_DECIMAL_PLACES = 6; + private const int NUM_DECIMAL_PLACES = 6; /// /// Calculates the current transaction fee for the ledger. @@ -21,7 +21,7 @@ public class GetFeeXrpSugar /// The Client used to connect to the ledger. /// The fee cushion to use /// The transaction fee - public static async Task GetFeeXrp(IXrplClient client, double? cushion = null) + public static async Task GetFeeXrp(this IXrplClient client, double? cushion = null) { double feeCushion = cushion ?? client.feeCushion; ServerInfoRequest request = new ServerInfoRequest(); diff --git a/Xrpl/Sugar/GetLedgerIndex.cs b/Xrpl/Sugar/GetLedgerIndex.cs index 42d96ea..71cb77c 100644 --- a/Xrpl/Sugar/GetLedgerIndex.cs +++ b/Xrpl/Sugar/GetLedgerIndex.cs @@ -11,14 +11,14 @@ namespace Xrpl.Sugar { - public class GetLedgerSugar + public static class GetLedgerSugar { /// /// Returns the index of the most recently validated ledger. /// /// The Client used to connect to the ledger. // The most recently validated ledger index. - public static async Task GetLedgerIndex(IXrplClient client) + public static async Task GetLedgerIndex(this IXrplClient client) { LedgerIndex index = new LedgerIndex(LedgerIndexType.Current); LedgerRequest request = new LedgerRequest() { LedgerIndex = index }; From e26d1cd45ea66983605a2f1bb936ed613964a91b Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Sat, 14 Jan 2023 18:05:56 +0300 Subject: [PATCH 05/39] Get OrderBook Sugar (0.5 h) --- Xrpl/Client/IXrplClient.cs | 4 +- Xrpl/Sugar/GetFeeXrp.cs | 2 + Xrpl/Sugar/GetLedgerIndex.cs | 3 +- Xrpl/Sugar/GetOrderBook.cs | 114 ++++++++++++++++++++--------------- 4 files changed, 70 insertions(+), 53 deletions(-) diff --git a/Xrpl/Client/IXrplClient.cs b/Xrpl/Client/IXrplClient.cs index 587e0ea..0154510 100644 --- a/Xrpl/Client/IXrplClient.cs +++ b/Xrpl/Client/IXrplClient.cs @@ -191,7 +191,7 @@ public interface IXrplClient : IDisposable /// To be successful, the weights of the signatures must be equal or higher than the quorum of the SignerList. /// /// //todo add description - /// An response. + /// An response. Task Submit(Dictionary tx, XrplWallet wallet); /// /// The tx method retrieves information on a single transaction, by its identifying hash @@ -251,7 +251,7 @@ public interface IXrplClient : IDisposable /// The book_offers method retrieves a list of offers, also known as the order book , between two currencies /// /// An request. - /// An response. + /// An response. Task BookOffers(BookOffersRequest request); /// /// The random command provides a random number to be used as a source of entropy for random number generation by clients.
diff --git a/Xrpl/Sugar/GetFeeXrp.cs b/Xrpl/Sugar/GetFeeXrp.cs index 7991d21..9de960e 100644 --- a/Xrpl/Sugar/GetFeeXrp.cs +++ b/Xrpl/Sugar/GetFeeXrp.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.Runtime.CompilerServices; using System.Threading.Tasks; using Xrpl.Client; @@ -13,6 +14,7 @@ namespace Xrpl.Sugar public static class GetFeeXrpSugar { private const int NUM_DECIMAL_PLACES = 6; + private const int BASE_10 = 10; /// /// Calculates the current transaction fee for the ledger. diff --git a/Xrpl/Sugar/GetLedgerIndex.cs b/Xrpl/Sugar/GetLedgerIndex.cs index 71cb77c..707d1d3 100644 --- a/Xrpl/Sugar/GetLedgerIndex.cs +++ b/Xrpl/Sugar/GetLedgerIndex.cs @@ -1,7 +1,6 @@ using System; -using System.Diagnostics; using System.Threading.Tasks; -using Newtonsoft.Json; + using Xrpl.Client; using Xrpl.Models.Common; using Xrpl.Models.Ledger; diff --git a/Xrpl/Sugar/GetOrderBook.cs b/Xrpl/Sugar/GetOrderBook.cs index be27aca..5c18322 100644 --- a/Xrpl/Sugar/GetOrderBook.cs +++ b/Xrpl/Sugar/GetOrderBook.cs @@ -1,51 +1,67 @@ -//// https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/sugar/utils.ts - -//namespace Xrpl.Sugar -//{ -// public class UtilsSugar -// { -// private const int DEFAULT_LIMIT = 20; - -// private static BookOffer[] SortOffers(BookOffer[] offers) -// { -// return offers.OrderBy(offer => offer.Quality ?? 0).ToArray(); -// } - -// public async Task<(List buy, List sell)> GetOrderbook(TakerAmount takerPays, TakerAmount takerGets, int limit = DEFAULT_LIMIT, int ledgerIndex = -1, string ledgerHash = null, string taker = null) -// { -// var request = new BookOffersRequest -// { -// Command = "book_offers", -// TakerPays = takerPays, -// TakerGets = takerGets, -// LedgerIndex = ledgerIndex == -1 ? "validated" : ledgerIndex.ToString(), -// LedgerHash = ledgerHash, -// Limit = limit, -// Taker = taker -// }; -// var directOfferResults = await RequestAll(request); -// request.TakerGets = takerPays; -// request.TakerPays = takerGets; -// var reverseOfferResults = await RequestAll(request); -// var directOffers = directOfferResults.SelectMany(directOfferResult => directOfferResult.Result.Offers).ToList(); -// var reverseOffers = reverseOfferResults.SelectMany(reverseOfferResult => reverseOfferResult.Result.Offers).ToList(); -// var orders = directOffers.Concat(reverseOffers).ToList(); -// var buy = new List(); -// var sell = new List(); -// orders.ForEach(order => -// { -// if ((order.Flags & OfferFlags.lsfSell) == 0) -// { -// buy.Add(order); -// } -// else -// { -// sell.Add(order); -// } -// }); -// return (SortOffers(buy).Take(limit).ToList(), SortOffers(sell).Take(limit).ToList()); -// } -// } -//} +// https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/sugar/utils.ts + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +using Xrpl.Client; +using Xrpl.Models.Common; +using Xrpl.Models.Ledger; +using Xrpl.Models.Methods; +using Xrpl.Models.Transactions; + +namespace Xrpl.Sugar +{ + public static class GetOrderBookSugar + { + private const uint DEFAULT_LIMIT = 20; + + private static List SortOffers(List offers) + { + return offers.OrderBy(offer => offer.Quality ?? 0).ToList(); + } + + public static async Task<(List buy, List sell)> GetOrderbook(this IXrplClient Client, + TakerAmount takerPays, TakerAmount takerGets, + uint limit = DEFAULT_LIMIT, int ledgerIndex = -1, string ledgerHash = null, string taker = null) + { + var request = new BookOffersRequest + { + Command = "book_offers", + TakerPays = takerPays, + TakerGets = takerGets, + LedgerIndex = new LedgerIndex(ledgerIndex==-1? LedgerIndexType.Validated: (LedgerIndexType)ledgerIndex), + LedgerHash = ledgerHash, + Limit = limit, + Taker = taker + }; + var directOfferResults = await Client.BookOffers(request); + request.TakerGets = takerPays; + request.TakerPays = takerGets; + var reverseOfferResults = await Client.BookOffers(request); + var directOffers = directOfferResults.Offers; + var reverseOffers = reverseOfferResults.Offers; + var orders = directOffers.Concat(reverseOffers).ToList(); + var buy = new List(); + var sell = new List(); + orders.ForEach(order => + { + if ((order.Flags & OfferFlags.lsfSell) == 0) + { + buy.Add(order); + } + else + { + sell.Add(order); + } + }); + return (SortOffers(buy).Take((int)limit).ToList(), SortOffers(sell).Take((int)limit).ToList()); + } + } + +} From 42b95682d024601c12f9a57b1e1933fc24343346 Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Sat, 14 Jan 2023 19:13:24 +0300 Subject: [PATCH 06/39] submit sugar (1 h) --- Xrpl/Client/IXrplClient.cs | 4 +- Xrpl/Sugar/Submit.cs | 190 +++++++++++++++++++++++++++++++++---- 2 files changed, 172 insertions(+), 22 deletions(-) diff --git a/Xrpl/Client/IXrplClient.cs b/Xrpl/Client/IXrplClient.cs index 0154510..c7fe00c 100644 --- a/Xrpl/Client/IXrplClient.cs +++ b/Xrpl/Client/IXrplClient.cs @@ -361,13 +361,13 @@ public bool IsConnected() // SUGARS public Task> Autofill(Dictionary tx) { - return AutofillSugar.Autofill(this, tx, null); + return this.Autofill(tx, null); } /// public Task Submit(Dictionary tx, XrplWallet wallet) { - return SubmitSugar.Submit(this, tx, true, false, wallet); + return this.Submit(tx, true, false, wallet); } /// diff --git a/Xrpl/Sugar/Submit.cs b/Xrpl/Sugar/Submit.cs index 5abc7cd..7d09fe4 100644 --- a/Xrpl/Sugar/Submit.cs +++ b/Xrpl/Sugar/Submit.cs @@ -2,19 +2,26 @@ using System.Collections.Generic; using System.Diagnostics; using System.Threading.Tasks; + using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +using Xrpl.BinaryCodec; using Xrpl.Client; using Xrpl.Client.Exceptions; +using Xrpl.Models; using Xrpl.Models.Methods; using Xrpl.Models.Transactions; +using Xrpl.Utils.Hashes; using Xrpl.Wallet; // https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/sugar/submit.ts namespace Xrpl.Sugar { - public class SubmitSugar + public static class SubmitSugar { + private const int LEDGER_CLOSE_TIME = 1000; /// /// Submits a signed/unsigned transaction.
/// Steps performed on a transaction:
@@ -30,18 +37,49 @@ public class SubmitSugar /// If true, autofill a transaction. /// If true, and the transaction fails locally, do not retry or relay the transaction to other servers. /// A wallet to sign a transaction. It must be provided when submitting an unsigned transaction. - /// A Wallet derived from a seed. - public static async Task Submit( - IXrplClient client, + /// A promise that contains SubmitResponse + public static async Task Submit(this IXrplClient client, Dictionary transaction, bool autofill = false, bool failHard = false, XrplWallet wallet = null ) { - string signedTx = await SubmitSugar.GetSignedTx(client, transaction, autofill, false, wallet); + string signedTx = await client.GetSignedTx(transaction, autofill, false, wallet); return await SubmitRequest(client, signedTx, failHard); } + /// + /// Asynchronously submits a transaction and verifies that it has been included in a + /// validated ledger(or has errored/will not be included for some reason). + /// See[Reliable Transaction Submission] (https://xrpl.org/reliable-transaction-submission.html). + /// + /// A Client. + /// A transaction to autofill, sign and encode, and submit. + /// If true, autofill a transaction. + /// If true, and the transaction fails locally, do not retry or relay the transaction to other servers. + /// A wallet to sign a transaction. It must be provided when submitting an unsigned transaction. + /// A promise that contains TxResponse, that will return when the transaction has been validated. + public static async Task SubmitAndWait(this IXrplClient client, + Dictionary transaction, + bool autofill = false, + bool failHard = false, + XrplWallet wallet = null) + { + var signedTx = await client.GetSignedTx(transaction, autofill, failHard, wallet); + var lastLedger = GetLastLedgerSequence(signedTx); + if (lastLedger == null) + { + throw new ValidationException("Transaction must contain a LastLedgerSequence value for reliable submission."); + } + + var response = await client.SubmitRequest(signedTx, failHard); + var txHash = HashLedger.HashSignedTx(signedTx); + return await WaitForFinalTransactionOutcome( + client, + txHash, + lastLedger, + response.EngineResult); + } /// /// Encodes and submits a signed transaction. @@ -50,20 +88,64 @@ public static async Task Submit( /// signed Transaction /// If true, and the transaction fails locally, do not retry or relay the transaction to other servers. /// - public static async Task SubmitRequest(IXrplClient client, string signedTransaction, bool failHard) + public static async Task SubmitRequest(this IXrplClient client, object signedTransaction, bool failHard) { - //if (!isSigned(signedTransaction)) { - // throw new ValidationException('Transaction must be signed') - //} + if (!IsSigned(signedTransaction)) + { + throw new ValidationException("Transaction must be signed"); + } - //string signedTxEncoded = typeof signedTransaction === 'string' ? signedTransaction : encode(signedTransaction) - //string signedTxEncoded = BinaryCodec.Encode(signedTransaction); - string signedTxEncoded = signedTransaction; - //SubmitBlobRequest request = new SubmitBlobRequest { Command = "submit", TxBlob = signedTxEncoded, FailHard = isAccountDelete(signedTransaction) || failHard }; - SubmitRequest request = new SubmitRequest { Command = "submit", TxBlob = signedTxEncoded, FailHard = false }; + string signedTxEncoded = signedTransaction is string transaction ? transaction : XrplBinaryCodec.Encode(signedTransaction); + SubmitRequest request = new SubmitRequest { Command = "submit", TxBlob = signedTxEncoded, FailHard = failHard }; var response = await client.GRequest(request); return response; } + /// + /// The core logic of reliable submission.This polls the ledger until the result of the + /// transaction can be considered final, meaning it has either been included in a + /// validated ledger, or the transaction's lastLedgerSequence has been surpassed by the + /// latest ledger sequence (meaning it will never be included in a validated ledger). + /// + /// + /// + /// + /// + /// + /// + private static async Task WaitForFinalTransactionOutcome(this IXrplClient Client, string TxHash, uint? lastLedger, string submissionResult) + { + await Task.Delay(LEDGER_CLOSE_TIME); + var latestLedger = await Client.GetLedgerIndex(); + if (lastLedger < latestLedger) + { + throw new ValidationException( + "The latest ledger sequence ${ latestLedger } is greater than the transaction's LastLedgerSequence (${lastLedger}).\n" + + $"Preliminary result: {submissionResult}"); + } + + TransactionResponseCommon txResponse = null; + try + { + txResponse = await Client.Tx(new TxRequest(TxHash)); + + } + catch (Exception error) + { + // error is of an unknown type and hence we assert type to extract the value we need. + var message = error?.Data["Error"] as string; + if (message == "txnNotFound") + { + return await WaitForFinalTransactionOutcome(Client, TxHash, lastLedger, submissionResult); + } + throw new ValidationException($"{message} \n Preliminary result: {submissionResult}.\nFull error details: {error.Message}"); + } + if (txResponse.Validated == true) + { + return txResponse; + } + + return await WaitForFinalTransactionOutcome(Client, TxHash, lastLedger, submissionResult); + } /// /// Initializes a transaction for a submit request @@ -74,15 +156,14 @@ public static async Task SubmitRequest(IXrplClient client, string signed /// If true, and the transaction fails locally, do not retry or relay the transaction to other servers. /// A wallet to sign a transaction. It must be provided when submitting an unsigned transaction. /// A Wallet derived from a seed. - public static async Task GetSignedTx( - IXrplClient client, + public static async Task GetSignedTx(this IXrplClient client, Dictionary transaction, bool autofill = false, bool failHard = false, XrplWallet? wallet = null ) { - //if (isSigned(transaction)) + //if (IsSigned(transaction)) //{ // return transaction //} @@ -92,10 +173,9 @@ public static async Task GetSignedTx( throw new ValidationException("Wallet must be provided when submitting an unsigned transaction"); } Dictionary tx = transaction; - //let tx = - // typeof transaction === 'string' + //var tx = transaction is string // ? // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- converts JsonObject to correct Transaction type - // (decode(transaction) as unknown as Transaction) + // (decode(transaction) as unknown as TransactionCommon) // : transaction if (autofill) { @@ -103,6 +183,76 @@ public static async Task GetSignedTx( } return wallet.Sign(tx, false).TxBlob; } + + public static bool IsSigned(object transaction) + { + if (transaction is Dictionary { } tx) + { + return tx.TryGetValue("SigningPubKey", out var SigningPubKey) && SigningPubKey is not null || + tx.TryGetValue("TxnSignature", out var TxnSignature) && TxnSignature is not null; + } + else + { + var ob = XrplBinaryCodec.Encode(transaction); + var json = JObject.Parse($"{ob}"); + return json.TryGetValue("SigningPubKey", out var SigningPubKey) && !string.IsNullOrWhiteSpace(SigningPubKey.ToString()) || + json.TryGetValue("TxnSignature", out var TxnSignature) && !string.IsNullOrWhiteSpace(TxnSignature.ToString()); + } + } + /// + /// checks if there is a LastLedgerSequence as a part of the transaction + /// + /// tx + /// + public static uint? GetLastLedgerSequence(object transaction) + { + if (transaction is Dictionary { } tx) + { + return tx.TryGetValue("LastLedgerSequence", out var LastLedgerSequence) && LastLedgerSequence is uint + ? LastLedgerSequence + : null; + } + else if (transaction is TransactionCommon txc) + { + return txc.LastLedgerSequence; + } + + else + { + var ob = XrplBinaryCodec.Encode(transaction); + var json = JObject.Parse($"{ob}"); + + return json.TryGetValue("LastLedgerSequence", out var LastLedgerSequence) && uint.TryParse(LastLedgerSequence.ToString(), out var seq) + ? seq + : null; + } + + } + + /// + /// checks if the transaction is an AccountDelete transaction + /// + /// tx + /// + public static bool IsAccountDelete(object transaction) + { + if (transaction is Dictionary { } tx) + { + return tx.TryGetValue("TransactionType", out var TransactionType) && $"{TransactionType}" == "AccountDelete"; + } + else if (transaction is TransactionCommon txc) + { + return txc.TransactionType == TransactionType.AccountDelete; + } + else + { + var ob = XrplBinaryCodec.Encode(transaction); + var json = JObject.Parse($"{ob}"); + + return json.TryGetValue("TransactionType", out var TransactionType) && TransactionType.ToString() == "AccountDelete"; + } + + } } } From 60951dc89bfe36c3b9759e34daa89384d9e7dffa Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Sat, 14 Jan 2023 19:23:55 +0300 Subject: [PATCH 07/39] address sugar (0.2 h) --- Xrpl/Client/IXrplClient.cs | 2 +- Xrpl/Sugar/Utils.cs | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 Xrpl/Sugar/Utils.cs diff --git a/Xrpl/Client/IXrplClient.cs b/Xrpl/Client/IXrplClient.cs index c7fe00c..9c3eea9 100644 --- a/Xrpl/Client/IXrplClient.cs +++ b/Xrpl/Client/IXrplClient.cs @@ -602,7 +602,7 @@ public async Task GRequest(R request) public string EnsureClassicAddress(string address) { - return address; + return Xrpl.Sugar.Utils.EnsureClassicAddress(address); } #region IDisposable diff --git a/Xrpl/Sugar/Utils.cs b/Xrpl/Sugar/Utils.cs new file mode 100644 index 0000000..58b7d08 --- /dev/null +++ b/Xrpl/Sugar/Utils.cs @@ -0,0 +1,27 @@ +using Xrpl.AddressCodec; +using Xrpl.Client.Exceptions; + +namespace Xrpl.Sugar +{ + public class Utils + { + /// + /// If an address is an X-Address, converts it to a classic address. + /// + /// A classic address or X-address. + /// The account's classic address. + public static string EnsureClassicAddress(string account) + { + if (XrplAddressCodec.IsValidXAddress(account)) + { + var codec = XrplAddressCodec.XAddressToClassicAddress(account); + if (codec.Tag is not null) + throw new RippleException("This command does not support the use of a tag. Use an address without a tag."); + + // For rippled requests that use an account, always use a classic address. + return codec.ClassicAddress; + } + return account; + } + } +} From d1f651ff2b5634c570ac93149865eadf775d74f6 Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Sun, 15 Jan 2023 21:04:26 +0300 Subject: [PATCH 08/39] utils (10 H) --- Base/Xrpl.AddressCodec/Utils.cs | 6 +- Base/Xrpl.BinaryCodec/XrplBinaryCodec.cs | 12 +- Base/Xrpl.Keypairs/Ed25519/EdKeyPair.cs | 4 +- Base/Xrpl.Keypairs/K256/K256KeyPair.cs | 2 +- Base/Xrpl.Keypairs/XrplKeypairs.cs | 19 +- Tests/Xrpl.Tests/Utils/ParseNFTokenID.cs | 8 +- Xrpl/Client/RequestManager.cs | 2 +- Xrpl/Models/Common/NFTokenIDData.cs | 8 +- Xrpl/Models/Ledger/LOLedger.cs | 19 +- Xrpl/Models/Subscriptions/BaseResponse.cs | 2 +- Xrpl/Models/Transactions/Metadata.cs | 54 ++++++ Xrpl/Sugar/Balances.cs | 37 ++-- Xrpl/Utils/CreateCrossChainPayment.cs | 67 +++++++ Xrpl/Utils/Derive.cs | 19 +- Xrpl/Utils/GetBalanceChanges.cs | 218 +++++++++++++++++++++- Xrpl/Utils/Hashes/HashLedger.cs | 26 ++- Xrpl/Utils/Hashes/HashPrefix.cs | 39 +++- Xrpl/Utils/Hashes/Hashes.cs | 118 +++++++++++- Xrpl/Utils/Hashes/LedgerSpaces.cs | 7 + Xrpl/Utils/Hashes/Sha512Half.cs | 10 +- Xrpl/Utils/Hashes/ShaMap/InnerNode.cs | 105 ++++++++++- Xrpl/Utils/Hashes/ShaMap/LeafNode.cs | 54 +++++- Xrpl/Utils/Hashes/ShaMap/Node.cs | 30 ++- Xrpl/Utils/Hashes/ShaMap/SHAMap.cs | 17 ++ Xrpl/Utils/Index.cs | 66 +++++++ Xrpl/Utils/ParseNFTID.cs | 18 +- Xrpl/Utils/Quality.cs | 10 +- Xrpl/Utils/SignChannelClaim.cs | 15 -- Xrpl/Utils/SignPaymentChannelClaim.cs | 36 ++++ Xrpl/Utils/VerifyPaymentChannelClaim.cs | 25 ++- Xrpl/Utils/XrpConversion.cs | 32 +++- 31 files changed, 965 insertions(+), 120 deletions(-) create mode 100644 Xrpl/Models/Transactions/Metadata.cs create mode 100644 Xrpl/Utils/CreateCrossChainPayment.cs create mode 100644 Xrpl/Utils/Index.cs delete mode 100644 Xrpl/Utils/SignChannelClaim.cs create mode 100644 Xrpl/Utils/SignPaymentChannelClaim.cs diff --git a/Base/Xrpl.AddressCodec/Utils.cs b/Base/Xrpl.AddressCodec/Utils.cs index 80b3374..2b8ba33 100644 --- a/Base/Xrpl.AddressCodec/Utils.cs +++ b/Base/Xrpl.AddressCodec/Utils.cs @@ -6,21 +6,21 @@ namespace Xrpl.AddressCodec { - public class Utils + public static class Utils { /// /// from bytes array to hex row /// /// bytes array /// - public static string FromBytesToHex(byte[] bytes) => Hex.ToHexString(bytes).ToUpper(); + public static string FromBytesToHex(this byte[] bytes) => Hex.ToHexString(bytes).ToUpper(); /// /// hex row to bytes array /// /// hex row /// - public static byte[] FromHexToBytes(string hex) => Hex.Decode(hex); + public static byte[] FromHexToBytes(this string hex) => Hex.Decode(hex); /// /// combine bytes arrays to single array diff --git a/Base/Xrpl.BinaryCodec/XrplBinaryCodec.cs b/Base/Xrpl.BinaryCodec/XrplBinaryCodec.cs index 594af65..b0880aa 100644 --- a/Base/Xrpl.BinaryCodec/XrplBinaryCodec.cs +++ b/Base/Xrpl.BinaryCodec/XrplBinaryCodec.cs @@ -53,7 +53,7 @@ public static string Encode(object json) /// /// /// string - public static string EncodeForSigning(Dictionary json) + public static string EncodeForSigning(object json) { JToken token = JToken.FromObject(json); return SerializeJson(token, HashPrefix.TransactionSig.Bytes(), null, true); @@ -62,10 +62,12 @@ public static string EncodeForSigning(Dictionary json) /// /// Encode a `payment channel here`_ Claim to be signed. /// - /// + /// /// string The binary-encoded claim, ready to be signed. - public static string EncodeForSigningClaim(Dictionary json) + public static string EncodeForSigningClaim(object obj) { + JToken json = JToken.FromObject(obj); + byte[] prefix = Bits.GetBytes(PAYMENT_CHANNEL_CLAIM_PREFIX); byte[] channel = Hash256.FromHex((string)json["channel"]).Buffer; byte[] amount = Uint64.FromValue(int.Parse((string)json["amount"])).ToBytes(); @@ -82,7 +84,7 @@ public static string EncodeForSigningClaim(Dictionary json) /// /// /// string - public static string EncodeForMulitSigning(Dictionary json, string signingAccount) + public static string EncodeForMulitSigning(object json, string signingAccount) { string accountID = new AccountId(signingAccount).ToHex(); JToken token = JToken.FromObject(json); @@ -92,7 +94,7 @@ public static string EncodeForMulitSigning(Dictionary json, str /// /// /// - /// + /// /// string public static string SerializeJson(JToken json, byte[]? prefix = null, byte[]? suffix = null, bool signingOnly = false) { diff --git a/Base/Xrpl.Keypairs/Ed25519/EdKeyPair.cs b/Base/Xrpl.Keypairs/Ed25519/EdKeyPair.cs index 1840143..9b44be0 100644 --- a/Base/Xrpl.Keypairs/Ed25519/EdKeyPair.cs +++ b/Base/Xrpl.Keypairs/Ed25519/EdKeyPair.cs @@ -29,9 +29,9 @@ internal static IXrplKeyPair From128Seed(byte[] seed) return new EdKeyPair(publicKey, expandedPrivateKey); } - public string Id() => prefix + FromBytesToHex(this._pubBytes); + public string Id() => prefix + _pubBytes.FromBytesToHex(); - public string Pk() => prefix + FromBytesToHex(this._privBytes[0..32]); + public string Pk() => prefix + _privBytes[0..32].FromBytesToHex(); public static byte[] Sign(byte[] message, byte[] privateKey) => Chaos.NaCl.Ed25519.Sign(message, privateKey); diff --git a/Base/Xrpl.Keypairs/K256/K256KeyPair.cs b/Base/Xrpl.Keypairs/K256/K256KeyPair.cs index 798dfe8..99c0a6c 100644 --- a/Base/Xrpl.Keypairs/K256/K256KeyPair.cs +++ b/Base/Xrpl.Keypairs/K256/K256KeyPair.cs @@ -32,7 +32,7 @@ public string Id() public string Pk() { - return $"00{FromBytesToHex(this._privKey.ToByteArrayUnsigned())}"; + return $"00{_privKey.ToByteArrayUnsigned().FromBytesToHex()}"; } public static byte[] Sign(byte[] message, byte[] privateKey) diff --git a/Base/Xrpl.Keypairs/XrplKeypairs.cs b/Base/Xrpl.Keypairs/XrplKeypairs.cs index 3b14216..0224f78 100644 --- a/Base/Xrpl.Keypairs/XrplKeypairs.cs +++ b/Base/Xrpl.Keypairs/XrplKeypairs.cs @@ -1,11 +1,12 @@ using System.Security.Cryptography; +using System.Text; + using Xrpl.AddressCodec; using Xrpl.Keypairs.Ed25519; using Xrpl.Keypairs.K256; -using static Xrpl.AddressCodec.XrplCodec; + using static Xrpl.AddressCodec.Utils; -using System.Diagnostics; -using System.Text; +using static Xrpl.AddressCodec.XrplCodec; // https://github.com/XRPLF/xrpl.js/blob/main/packages/ripple-keypairs/src/index.ts @@ -75,7 +76,7 @@ public static IXrplKeyPair DeriveKeypair(string seed, string? algorithm = null, /// public static string GetAlgorithmFromKey(string key) { - byte[] data = FromHexToBytes(key); + byte[] data = key.FromHexToBytes(); return data.Length == 33 && data[0] == 0xED ? "ed25519" : "secp256k1"; } @@ -94,6 +95,12 @@ public static string Sign(byte[] message, string privateKey) return K256KeyPair.Sign(message, privateKey.FromHex()).ToHex(); } + /// Sing message + /// Hex Message + /// private key + /// + public static string Sign(string HexMessage, string privateKey) => Sign(HexMessage.FromHexToBytes(), privateKey); + public static bool Verify(byte[] message, string signature, string publicKey) { string algorithm = GetAlgorithmFromKey(publicKey); @@ -102,10 +109,12 @@ public static bool Verify(byte[] message, string signature, string publicKey) : K256KeyPair.Verify(signature.FromHex(), message, publicKey.FromHex()); } + public static bool Verify(string HexMessage, string signature, string publicKey) => Verify(HexMessage.FromHexToBytes(), signature, publicKey); + public static string DeriveAddressFromBytes(byte[] publicKeyBytes) => XrplCodec.EncodeAccountID(Utils.HashUtils.PublicKeyHash(publicKeyBytes)); public static string DeriveAddress(string publicKey) - => XrplKeypairs.DeriveAddressFromBytes(FromHexToBytes(publicKey)); + => XrplKeypairs.DeriveAddressFromBytes(publicKey.FromHexToBytes()); } } \ No newline at end of file diff --git a/Tests/Xrpl.Tests/Utils/ParseNFTokenID.cs b/Tests/Xrpl.Tests/Utils/ParseNFTokenID.cs index 86e8587..5341846 100644 --- a/Tests/Xrpl.Tests/Utils/ParseNFTokenID.cs +++ b/Tests/Xrpl.Tests/Utils/ParseNFTokenID.cs @@ -17,13 +17,13 @@ public void ParsingNFTokenID() { const string nftokenId = "000813886377BBDA772433D7FCF16A9710D9D958D9F7129F376D5FC200005026"; - NFTokenIDData nftokenIDData = ParseNFTID.GetNFTokenIDData(nftokenId); + NFTokenIDData nftokenIDData = nftokenId.ParseNFTokenID(); - Assert.AreEqual((UInt32)8, nftokenIDData.Flags); + Assert.AreEqual((uint)8, nftokenIDData.Flags); Assert.AreEqual("rwhALEr1jdhuxKqoTno8cyGXw9yLSsqC6A", nftokenIDData.Issuer); Assert.AreEqual(nftokenId, nftokenIDData.NFTokenID); - Assert.AreEqual((UInt32)3, nftokenIDData.Taxon); - Assert.AreEqual((UInt32)5000, nftokenIDData.TransferFee); + Assert.AreEqual((uint)3, nftokenIDData.Taxon); + Assert.AreEqual((uint)5000, nftokenIDData.TransferFee); } } } diff --git a/Xrpl/Client/RequestManager.cs b/Xrpl/Client/RequestManager.cs index 981ec45..afe2fc8 100644 --- a/Xrpl/Client/RequestManager.cs +++ b/Xrpl/Client/RequestManager.cs @@ -71,7 +71,7 @@ public void Resolve(Guid id, BaseResponse response) if (hasTimer) timer.Stop(); - var deserialized = JsonConvert.DeserializeObject(response.Result.ToString(), taskInfo.Type, serializerSettings); + var deserialized = JsonConvert.DeserializeObject($"{response.Result}", taskInfo.Type, serializerSettings); var setResult = taskInfo.TaskCompletionResult.GetType().GetMethod("TrySetResult"); setResult.Invoke(taskInfo.TaskCompletionResult, new[] { deserialized }); this.DeletePromise(id, taskInfo); diff --git a/Xrpl/Models/Common/NFTokenIDData.cs b/Xrpl/Models/Common/NFTokenIDData.cs index 7662dd0..c0a3dda 100644 --- a/Xrpl/Models/Common/NFTokenIDData.cs +++ b/Xrpl/Models/Common/NFTokenIDData.cs @@ -15,15 +15,15 @@ public NFTokenIDData(string nftokenId, UInt32 flags, UInt32 transferFee, string public string NFTokenID { get; set; } - public UInt32 Flags { get; set; } + public uint Flags { get; set; } - public UInt32 TransferFee { get; set; } + public uint TransferFee { get; set; } public string Issuer { get; set; } - public UInt32 Taxon { get; set; } + public uint Taxon { get; set; } - public UInt32 Sequence { get; set; } + public uint Sequence { get; set; } } } diff --git a/Xrpl/Models/Ledger/LOLedger.cs b/Xrpl/Models/Ledger/LOLedger.cs index 27c8fd4..6e024ea 100644 --- a/Xrpl/Models/Ledger/LOLedger.cs +++ b/Xrpl/Models/Ledger/LOLedger.cs @@ -65,9 +65,16 @@ public class LedgerEntity : BaseLedgerEntity //todo rename to Ledger https://git [JsonProperty("account_hash")] public string AccountHash { get; set; } - //todo not found field accountState?: LedgerEntry[] All the state information in this ledger. - //todo not found field close_flags: number A bit-map of flags relating to the closing of this ledger. - + /// + /// All the state information in this ledger. + /// + [JsonProperty("accountState")] + public List AccountState { get; set; } + /// + /// A bit-map of flags relating to the closing of this ledger. + /// + [JsonProperty("close_flags")] + public uint CloseFlags { get; set; } [JsonProperty("accounts")] public dynamic[] Accounts { get; set; } /// @@ -101,7 +108,11 @@ public class LedgerEntity : BaseLedgerEntity //todo rename to Ledger https://git [JsonProperty("ledger_index")] public string LedgerIndex { get; set; } - //todo not found field parent_close_time: number The approximate time at which the previous ledger was closed. + /// + /// The approximate time at which the previous ledger was closed. + /// + [JsonProperty("parent_close_time")] + public uint ParentCloseTime { get; set; } /// /// Unique identifying hash of the ledger that came immediately before this one /// diff --git a/Xrpl/Models/Subscriptions/BaseResponse.cs b/Xrpl/Models/Subscriptions/BaseResponse.cs index 578bf89..b1d2eaa 100644 --- a/Xrpl/Models/Subscriptions/BaseResponse.cs +++ b/Xrpl/Models/Subscriptions/BaseResponse.cs @@ -40,7 +40,7 @@ public class BaseResponse /// Some client libraries omit this field on success. /// [JsonProperty("result")] - public object Result { get; set; } + public dynamic Result { get; set; } /// /// (May be omitted) If this field is provided, the value is the string load.
/// This means the client is approaching the rate limiting threshold where the server will disconnect this client. diff --git a/Xrpl/Models/Transactions/Metadata.cs b/Xrpl/Models/Transactions/Metadata.cs new file mode 100644 index 0000000..5077b22 --- /dev/null +++ b/Xrpl/Models/Transactions/Metadata.cs @@ -0,0 +1,54 @@ +using Newtonsoft.Json; + +using System.Collections.Generic; + +using Xrpl.Client.Json.Converters; + +using Xrpl.Models.Common; + +//https://github.com/XRPLF/xrpl.js/blob/45963b70356f4609781a6396407e2211fd15bcf1/packages/xrpl/src/models/transactions/metadata.ts#L32 +namespace Xrpl.Models.Transactions +{ + //todo replace Meta in transactionCommon to this interfaces; + + public interface ICreatedNode + { + string LedgerEntryType { get; set; } + string LedgerIndex { get; set; } + Dictionary NewFields { get; set; } + } + + public interface IModifiedNode + { + string LedgerEntryType { get; set; } + string LedgerIndex { get; set; } + Dictionary FinalFields { get; set; } + Dictionary PreviousFields { get; set; } + string PreviousTxnID { get; set; } + int PreviousTxnLgrSeq { get; set; } + } + + public interface IDeletedNode + { + string LedgerEntryType { get; set; } + string LedgerIndex { get; set; } + Dictionary FinalFields { get; set; } + } + + public interface INode : ICreatedNode, IModifiedNode, IDeletedNode + { + } + + public interface TransactionMetadata + { + List AffectedNodes { get; set; } + [JsonConverter(typeof(CurrencyConverter))] + [JsonProperty("DeliveredAmount")] + Currency DeliveredAmount { get; set; } + [JsonConverter(typeof(CurrencyConverter))] + [JsonProperty("delivered_amount")] + Currency Delivered_amount { get; set; } + int TransactionIndex { get; set; } + string TransactionResult { get; set; } + } +} diff --git a/Xrpl/Sugar/Balances.cs b/Xrpl/Sugar/Balances.cs index 82a0ab8..520780c 100644 --- a/Xrpl/Sugar/Balances.cs +++ b/Xrpl/Sugar/Balances.cs @@ -13,22 +13,23 @@ namespace Xrpl.Sugar { - public static class BalancesSugar + public class Balance { - public class Balance - { - public string value { get; set; } - public string currency { get; set; } - public string issuer { get; set; } - } + public string Value { get; set; } + public string Currency { get; set; } + public string Issuer { get; set; } + } - public class GetBalancesOptions - { - public string? LedgerHash { get; set; } - public LedgerIndex? LedgerIndex { get; set; } - public string Peer { get; set; } - public int? Limit { get; set; } - } + public class GetBalancesOptions + { + public string? LedgerHash { get; set; } + public LedgerIndex? LedgerIndex { get; set; } + public string Peer { get; set; } + public int? Limit { get; set; } + } + + public static class BalancesSugar + { public static IEnumerable FormatBalances(this IEnumerable trustlines) => trustlines.Select(Map); @@ -36,9 +37,9 @@ public static IEnumerable FormatBalances(this IEnumerable tr public static Balance Map(this TrustLine trustline) => new Balance() { - value = trustline.Balance, - currency = trustline.Currency, - issuer = trustline.Account, + Value = trustline.Balance, + Currency = trustline.Currency, + Issuer = trustline.Account, }; /// @@ -91,7 +92,7 @@ public static async Task> GetBalances(this XrplClient client, stri var xrp_balance = await GetXrpBalance(client, address, options?.LedgerHash, options?.LedgerIndex); if (!string.IsNullOrWhiteSpace(xrp_balance)) { - balances.Insert(0, new Balance { currency = "XRP", value = xrp_balance }); + balances.Insert(0, new Balance { Currency = "XRP", Value = xrp_balance }); } } diff --git a/Xrpl/Utils/CreateCrossChainPayment.cs b/Xrpl/Utils/CreateCrossChainPayment.cs new file mode 100644 index 0000000..03a960b --- /dev/null +++ b/Xrpl/Utils/CreateCrossChainPayment.cs @@ -0,0 +1,67 @@ +//https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/utils/createCrossChainPayment.ts +using System.Collections.Generic; +using System.Linq; + +using Xrpl.Client.Exceptions; +using Xrpl.Models.Transactions; + +namespace Xrpl.Utils +{ + public static class CrossChainPayment + { + /// + /// Creates a cross-chain payment transaction. + /// + /// he initial payment transaction. If the transaction is + /// signed, then it will need to be re-signed.There must be no more than 2 + /// memos, since one memo is used for the sidechain destination account.The + /// destination must be the sidechain's door account. + /// the destination account on the sidechain. + /// A cross-chain payment transaction, where the mainchain door account + ///is the `Destination` and the destination account on the sidechain is encoded + ///in the memos. + /// if there are more than 2 memos. + public static Payment CreateCrossChainPayment(this Payment payment, string destAccount) //todo check it + { + var destAccountHex = destAccount.ConvertStringToHex(); + var destAccountMemo = new Memo { MemoData = destAccountHex }; + + var memos = payment.Memos?.Select(c => c.Memo).ToList() ?? new List(); + if (memos.Count > 2) + { + throw new XrplException("Cannot have more than 2 memos in a cross-chain transaction."); + } + var newMemos = new List { destAccountMemo }; + newMemos.AddRange(memos); + + var newPayment = new Payment + { + TransactionType = payment.TransactionType, + Account = payment.Account, + AccountTxnID = payment.AccountTxnID, + Amount = payment.Amount, + DeliverMin = payment.DeliverMin, + Destination = payment.Destination, + DestinationTag = payment.DestinationTag, + Fee = payment.Fee, + Flags = payment.Flags, + InvoiceID = payment.InvoiceID, + LastLedgerSequence = payment.LastLedgerSequence, + Meta = payment.Meta, + Paths = payment.Paths, + SendMax = payment.SendMax, + Sequence = payment.Sequence, + Signers = payment.Signers, + SigningPublicKey = payment.SigningPublicKey, + date = payment.date, + inLedger = payment.inLedger, + ledger_index = payment.ledger_index, + + Memos = newMemos.Select(c => new MemoWrapper() { Memo = c }).ToList(), + TransactionSignature = null + }; + + return newPayment; + } + } +} diff --git a/Xrpl/Utils/Derive.cs b/Xrpl/Utils/Derive.cs index be30114..49a35e8 100644 --- a/Xrpl/Utils/Derive.cs +++ b/Xrpl/Utils/Derive.cs @@ -3,12 +3,27 @@ // https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/utils/derive.ts //todo DO + +using Xrpl.AddressCodec; +using Xrpl.Keypairs; + namespace Xrpl.Utils { - public class Derive + public static class Derive { - public Derive() + /// + /// Derive an X-Address from a public key and a destination tag.
+ /// Options - Public key and destination tag to encode as an X-Address. + ///
+ /// The public key corresponding to an address. + /// A destination tag to encode into an X-address. False indicates no destination tag. + /// Whether this address is for use in Testnet. + /// X-Address. + /// Utilities + public static string DeriveXAddress(string publicKey, int? tag, bool test) { + var classicAddress = XrplKeypairs.DeriveAddress(publicKey); + return XrplAddressCodec.ClassicAddressToXAddress(classicAddress, tag, test); } } } diff --git a/Xrpl/Utils/GetBalanceChanges.cs b/Xrpl/Utils/GetBalanceChanges.cs index c7ac35a..94a62ea 100644 --- a/Xrpl/Utils/GetBalanceChanges.cs +++ b/Xrpl/Utils/GetBalanceChanges.cs @@ -3,13 +3,227 @@ // https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/utils/getBalanceChanges.ts //todo DO +using static Xrpl.Models.Common.Common; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Numerics; +using Xrpl.Models.Transactions; +using Xrpl.Sugar; +using Xrpl.Utils.Hashes.ShaMap; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Xrpl.Models.Common; + namespace Xrpl.Utils { - public class GetBalanceChanges + + public class BalanceChange + { + public string Account { get; set; } + public Balance Balance { get; set; } + } + + public class Fields + { + public string Account { get; set; } + public Currency Balance { get; set; } + public IssuedCurrencyAmount LowLimit { get; set; } + public IssuedCurrencyAmount HighLimit { get; set; } + public Dictionary FieldDict { get; set; } + } + + public class NormalizedNode + { + public string NodeType { get; set; } + public string LedgerEntryType { get; set; } + public string LedgerIndex { get; set; } + public Fields NewFields { get; set; } + public Fields FinalFields { get; set; } + public Fields PreviousFields { get; set; } + public string PreviousTxnID { get; set; } + public int PreviousTxnLgrSeq { get; set; } + } + + public static class GetBalanceChanges { - public GetBalanceChanges() + + //todo need help with NormalizeNodes + //public static NormalizedNode NormalizeNode(this INode affectedNode) + //{ + // var diffType = affectedNode[0]; + // NormalizedNode node = affectedNode[diffType] as NormalizedNode; + // return new NormalizedNode + // { + // NodeType = diffType, + // LedgerEntryType = node.LedgerEntryType, + // LedgerIndex = node.LedgerIndex, + // NewFields = node.NewFields, + // FinalFields = node.FinalFields, + // PreviousFields = node.PreviousFields + // }; + //} + + //public static List NormalizeNodes(this TransactionMetadata metadata) + //{ + // if (metadata.AffectedNodes.Count == 0) + // { + // return new List(); + // } + + // return metadata.AffectedNodes.Select(NormalizeNodes).ToList(); + //} + + public static List<(string account, List balances)> GroupByAccount(this List balanceChanges) + { + var grouped = balanceChanges.GroupBy(node => node.Account); + return grouped.Select(item => (item.Key, item.Select(i => i.Balance).ToList())).ToList(); + } + + public static BigInteger GetValue(object balance) //todo need to check + { + if (balance is string val) + { + return BigInteger.Parse(val, NumberStyles.AllowLeadingSign + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | NumberStyles.AllowExponent + | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture); + } + + var json = JObject.Parse(JsonConvert.SerializeObject(balance)); + return BigInteger.Parse(json["Value"].ToString(), NumberStyles.AllowLeadingSign + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | NumberStyles.AllowExponent + | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture); + } + + public static BigInteger? ComputeBalanceChange(this NormalizedNode node) + { + BigInteger? value = null; + if (node.NewFields?.Balance != null) + { + value = GetValue(node.NewFields.Balance); + } + else if (node.PreviousFields?.Balance != null && node.FinalFields?.Balance != null) + { + value = GetValue(node.FinalFields.Balance) - GetValue(node.PreviousFields.Balance); + } + + if (value is null || value.Value.IsZero) + { + return null; + } + + return value; + } + + public static (string account, Balance balance) GetXRPQuantity(this NormalizedNode node) + { + var value = ComputeBalanceChange(node); + + if (value == null) + { + return (null, null); + } + + return (node.FinalFields?.Account ?? node.NewFields?.Account, + new Balance + { + Currency = "XRP", + Value = XrpConversion.DropsToXrp(value.Value.ToString()) + }); + } + + public static BalanceChange FlipTrustlinePerspective(this BalanceChange balanceChange) + { + var negatedBalance = BigInteger.Parse( + balanceChange.Balance.Value, NumberStyles.AllowLeadingSign + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | NumberStyles.AllowExponent + | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture);//.Negate(); + return new BalanceChange + { + Account = balanceChange.Balance.Issuer, + Balance = new Balance + { + Issuer = balanceChange.Account, + Currency = balanceChange.Balance.Currency, + Value = negatedBalance.ToString() + } + }; + } + + public static List GetTrustlineQuantity(this NormalizedNode node) { + var value = ComputeBalanceChange(node); + + if (value == null) + { + return null; + } + /* + * A trustline can be created with a non-zero starting balance. + * If an offer is placed to acquire an asset with no existing trustline, + * the trustline can be created when the offer is taken. + */ + var fields = node.NewFields ?? node.FinalFields; + var result = new BalanceChange + { + Account = fields?.LowLimit?.Issuer, + Balance = new Balance + { + Issuer = fields?.HighLimit?.Issuer, + Currency = fields?.Balance.CurrencyCode, + Value = value.ToString() + } + }; + return new List { result, FlipTrustlinePerspective(result) }; } + ///// //todo need help with NormalizeNodes + ///// Computes the complete list of every balance that changed in the ledger as a result of the given transaction. + ///// + ///// Transaction metadata. + ///// Parsed balance changes. + //public static List<(string account, List balances)> GetBalanceChanges(this TransactionMetadata metadata) + //{ + // var quantities = NormalizeNodes(metadata).Select( + // node => + // { + // if (node.LedgerEntryType == "AccountRoot") + // { + // var xrpQuantity = GetXRPQuantity(node); + // if (xrpQuantity.account == null) + // { + // return new List(); + // } + + // return new List() { new BalanceChange() { Account = xrpQuantity.account, Balance = xrpQuantity.balance } }; + // } + + // if (node.LedgerEntryType == "RippleState") + // { + // var trustlineQuantity = GetTrustlineQuantity(node); + // if (trustlineQuantity == null) + // { + // return new List(); + // } + + // return trustlineQuantity; + // } + + // return new List(); + // }).ToList(); + // return GroupByAccount(quantities.SelectMany(q => q).ToList()); + //} } } diff --git a/Xrpl/Utils/Hashes/HashLedger.cs b/Xrpl/Utils/Hashes/HashLedger.cs index 26ef091..826b8de 100644 --- a/Xrpl/Utils/Hashes/HashLedger.cs +++ b/Xrpl/Utils/Hashes/HashLedger.cs @@ -1,15 +1,31 @@ -using System.Collections.Generic; -using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Linq; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + using Xrpl.BinaryCodec; using Xrpl.BinaryCodec.Hashing; +using Xrpl.BinaryCodec.ShaMapTree; using Xrpl.BinaryCodec.Util; using Xrpl.Client.Exceptions; +using Xrpl.Models.Ledger; +using Xrpl.Models.Transactions; +using Xrpl.Utils.Hashes.ShaMap; + using static Xrpl.AddressCodec.Utils; // https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/utils/hashes/hashLedger.ts namespace Xrpl.Utils.Hashes { + public interface HashLedgerHeaderOptions + { + public bool? ComputeTreeHashes { get; set; } + + } + public class HashLedger { public static string HashSignedTx(string tx) @@ -20,7 +36,8 @@ public static string HashSignedTx(string tx) { new ValidationException("The transaction must be signed to hash it."); } - return B16.Encode(Sha512.Half(input: FromHexToBytes(txBlob), prefix: (uint)HashPrefix.TransactionId)); + + return B16.Encode(Sha512.Half(input: txBlob.FromHexToBytes(), prefix: (uint)Xrpl.BinaryCodec.Hashing.HashPrefix.TransactionId)); } public static string HashSignedTx(JToken tx) @@ -31,7 +48,8 @@ public static string HashSignedTx(JToken tx) { new ValidationException("The transaction must be signed to hash it."); } - return B16.Encode(Sha512.Half(input: FromHexToBytes(txBlob), prefix: (uint)HashPrefix.TransactionId)); + + return B16.Encode(Sha512.Half(input: txBlob.FromHexToBytes(), prefix: (uint)Xrpl.BinaryCodec.Hashing.HashPrefix.TransactionId)); } } } diff --git a/Xrpl/Utils/Hashes/HashPrefix.cs b/Xrpl/Utils/Hashes/HashPrefix.cs index 3b44a00..ab45ee6 100644 --- a/Xrpl/Utils/Hashes/HashPrefix.cs +++ b/Xrpl/Utils/Hashes/HashPrefix.cs @@ -4,11 +4,42 @@ namespace Xrpl.Utils.Hashes { - public class rHashPrefix + /// + /// Prefix for hashing functions.
+ /// These prefixes are inserted before the source material used to + /// generate various hashes.This is done to put each hash in its own "space."
+ /// This way, two different types of objects with the + /// same binary data will produce different hashes.
+ /// Each prefix is a 4-byte value with the last byte set to zero + /// and the first three bytes formed from the ASCII equivalent of + /// some arbitrary string.
+ /// For example "TXN". + ///
+ public enum HashPrefix { - public rHashPrefix() - { - } + // transaction plus signature to give transaction ID 'TXN' + TRANSACTION_ID = 0x54584e00, + + // transaction plus metadata 'TND' + TRANSACTION_NODE = 0x534e4400, + + // inner node in tree 'MIN' + INNER_NODE = 0x4d494e00, + + // leaf node in tree 'MLN' + LEAF_NODE = 0x4d4c4e00, + + // inner transaction to sign 'STX' + TRANSACTION_SIGN = 0x53545800, + + // inner transaction to sign (TESTNET) 'stx' + TRANSACTION_SIGN_TESTNET = 0x73747800, + + // inner transaction to multisign 'SMT' + TRANSACTION_MULTISIGN = 0x534d5400, + + // ledger 'LWR' + LEDGER = 0x4c575200, } } diff --git a/Xrpl/Utils/Hashes/Hashes.cs b/Xrpl/Utils/Hashes/Hashes.cs index e63e59a..8c640cd 100644 --- a/Xrpl/Utils/Hashes/Hashes.cs +++ b/Xrpl/Utils/Hashes/Hashes.cs @@ -1,16 +1,22 @@ -using System.Diagnostics; -using Org.BouncyCastle.Utilities.Encoders; +using System; +using System.Globalization; +using System.Linq; +using System.Numerics; +using System.Text.RegularExpressions; + using Xrpl.AddressCodec; +using Xrpl.Client.Exceptions; // https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/utils/hashes/index.ts namespace Xrpl.Utils.Hashes { - public class Hashes + //todo double need check + public static class Hashes { const int HEX = 16; const int BYTE_LENGTH = 4; - + const byte MASK = 0xff; public static string AddressToHex(string address) { return XrplCodec.DecodeAccountID(address).ToHex(); @@ -21,15 +27,107 @@ public static string LedgerSpaceHex(LedgerSpace name) return ((int)name).ToString("X4"); } + public static string LedgerSpaceHex(string name) + { + var enums = Enum.GetValues(typeof(LedgerSpace)).Cast().ToList(); + + var res = enums.FirstOrDefault(f => f.ToString() == name).ToString(); + var val = Convert.ToString(res.ToCharArray(0, 1)[0], HEX); + while (val.Length < 4) + val = "0" + val; + return val; + } + /// + /// check currency code for HEX + /// + /// currency code + /// + public static bool IsHexCurrencyCode(this string code) => Regex.IsMatch(code, @"[0-9a-fA-F]{40}", RegexOptions.IgnoreCase); + + public static string CurrencyToHex(string currency) + { + var cur_code = currency.Trim(); + if (cur_code.Length <= 3) + return cur_code; + + if (cur_code.IsHexCurrencyCode()) + return cur_code; + + cur_code = cur_code.ConvertStringToHex(); + + if (cur_code.Length > 40) + throw new XrplException("wrong currency code format"); + + cur_code += new string('0', 40 - cur_code.Length); + + return cur_code; + + } + /// + /// Hash the given binary transaction data with the single-signing prefix.
+ /// See [Serialization Format](https://xrpl.org/serialization.html). + ///
+ /// The binary transaction blob as a hexadecimal string. + /// The hash to sign. + public static string HashTx(string txBlobHex) + { + + var prefix = HashPrefix.TRANSACTION_SIGN.ToString("X").ToUpper(); + return (prefix + txBlobHex).Sha512Half(); + } + + public static string HashPaymentChannel(string address, string dstAddress, int sequence) { - return Sha512HalfUtil.Sha512Half( - LedgerSpaceHex(LedgerSpace.Paychan) + - AddressToHex(address) + - AddressToHex(dstAddress) + - sequence.ToString("X").PadLeft(BYTE_LENGTH * 2, '0') - ); + return (LedgerSpaceHex(LedgerSpace.Paychan) + + AddressToHex(address) + + AddressToHex(dstAddress) + + sequence.ToString("X").PadLeft(BYTE_LENGTH * 2, '0')).Sha512Half(); } + + public static string HashTX(string txBlobHex) + { + string prefix = ((int)HashPrefix.TRANSACTION_SIGN).ToString("X").ToUpper(); + return (prefix + txBlobHex).Sha512Half(); + } + + public static string HashAccountRoot(string address) + { + return (LedgerSpaceHex(LedgerSpace.Account) + AddressToHex(address)).Sha512Half(); + } + + public static string HashSignerListId(string address) + { + return (LedgerSpaceHex(LedgerSpace.SignerList) + AddressToHex(address) + "00000000").Sha512Half(); + } + + public static string HashOfferId(string address, int sequence) + { + + string hexPrefix = LedgerSpaceHex(LedgerSpace.Offer).PadLeft(2, '0'); + string hexSequence = sequence.ToString("X").PadLeft(8, '0'); + string prefix = "00" + hexPrefix; + return (prefix + AddressToHex(address) + hexSequence).Sha512Half(); + } + + public static string HashTrustline(string address1, string address2, string currency) + { + string address1Hex = AddressToHex(address1); + string address2Hex = AddressToHex(address2); + + bool swap = (BigInteger.Parse(address1Hex, NumberStyles.HexNumber)>(BigInteger.Parse(address2Hex, NumberStyles.HexNumber))); + string lowAddressHex = swap ? address2Hex : address1Hex; + string highAddressHex = swap ? address1Hex : address2Hex; + + string prefix = LedgerSpaceHex(LedgerSpace.RippleState); + return (prefix + lowAddressHex + highAddressHex + CurrencyToHex(currency)).Sha512Half(); + } + + public static string HashEscrow(string address, int sequence) + { + return (LedgerSpaceHex(LedgerSpace.Escrow) + AddressToHex(address) + sequence.ToString("X").PadLeft(BYTE_LENGTH * 2, '0')).Sha512Half(); + } + } } diff --git a/Xrpl/Utils/Hashes/LedgerSpaces.cs b/Xrpl/Utils/Hashes/LedgerSpaces.cs index 96732ee..e5f8fdb 100644 --- a/Xrpl/Utils/Hashes/LedgerSpaces.cs +++ b/Xrpl/Utils/Hashes/LedgerSpaces.cs @@ -4,6 +4,13 @@ namespace Xrpl.Utils.Hashes { + /// + /// XRP Ledger namespace prefixes. + /// The XRP Ledger is a key-value store.In order to avoid name collisions, + /// names are partitioned into namespaces. + /// Each namespace is just a single character prefix. + /// See[LedgerNameSpace enum](https://github.com/ripple/rippled/blob/master/src/ripple/protocol/LedgerFormats.h#L100). + /// public enum LedgerSpace { Account = 'a', diff --git a/Xrpl/Utils/Hashes/Sha512Half.cs b/Xrpl/Utils/Hashes/Sha512Half.cs index d8c524b..27a9156 100644 --- a/Xrpl/Utils/Hashes/Sha512Half.cs +++ b/Xrpl/Utils/Hashes/Sha512Half.cs @@ -6,9 +6,15 @@ namespace Xrpl.Utils.Hashes { - public class Sha512HalfUtil + public static class Sha512HalfUtil { - static public string Sha512Half(string hex) + public const int HASH_SIZE = 64; + /// + /// Compute a sha512Half Hash of a hex string. + /// + /// Hex string to hash. + /// Hash of hex. + public static string Sha512Half(this string hex) { return Sha512.Half(input: hex.FromHex()).ToHex(); } diff --git a/Xrpl/Utils/Hashes/ShaMap/InnerNode.cs b/Xrpl/Utils/Hashes/ShaMap/InnerNode.cs index 0b74b8b..f389f4f 100644 --- a/Xrpl/Utils/Hashes/ShaMap/InnerNode.cs +++ b/Xrpl/Utils/Hashes/ShaMap/InnerNode.cs @@ -2,12 +2,113 @@ // https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/utils/hashes/SHAMap/InnerNode.ts +using System.Collections.Generic; + +using Xrpl.Client.Exceptions; + namespace Xrpl.Utils.Hashes.ShaMap { - public class InnerNode + public class InnerNode : Node { - public InnerNode() + public readonly string HEX_ZERO = "0000000000000000000000000000000000000000000000000000000000000000"; + + public const int SLOT_MAX = 15; + public const int HEX = 16; + + public Dictionary Leaves { get; set; } + public NodeType Type { get; set; } + public int Depth { get; set; } + public bool Empty { get; set; } + + public InnerNode(int depth = 0) { + Leaves = new Dictionary(); + Type = NodeType.INNER; + Depth = depth; + Empty = true; + } + + /// + public override void AddItem(string tag, Node node) + { + var existingNode = GetNode(int.Parse($"{tag[Depth]}", System.Globalization.NumberStyles.HexNumber)); + + if (existingNode == null) + { + SetNode(int.Parse($"{tag[Depth]}", System.Globalization.NumberStyles.HexNumber), node); + return; + } + + if (existingNode is InnerNode) + { + existingNode.AddItem(tag, node); + } + else if (existingNode is LeafNode leaf) + { + if (leaf.Tag == tag) + { + throw new XrplException("Tried to add a node to a SHAMap that was already in there."); + } + else + { + var newInnerNode = new InnerNode(Depth + 1); + newInnerNode.AddItem(leaf.Tag, leaf); + newInnerNode.AddItem(tag, node); + SetNode(int.Parse($"{tag[Depth]}", System.Globalization.NumberStyles.HexNumber), newInnerNode); + } + } + } + /// + /// Overwrite the node that is currently in a given slot. + /// + /// A number 0-15. + /// To place. + /// If slot is out of range. + public void SetNode(int slot, Node node) + { + if (slot is < 0 or > SLOT_MAX) + { + throw new XrplException("Invalid slot: slot must be between 0-15."); + } + Leaves[slot] = node; + Empty = false; + } + /// + /// Get the node that is currently in a given slot. + /// + /// A number 0-15. + /// Node currently in a slot. + /// If slot is out of range. + public Node GetNode(int slot) + { + if (slot is < 0 or > SLOT_MAX) + { + throw new XrplException("Invalid slot: slot must be between 0-15."); + } + return Leaves[slot]; + } + + /// + public override string Hash + { + get + { + if (Empty) + { + return HEX_ZERO; + } + + string hex = ""; + for (var iter = 0; iter <= SLOT_MAX; iter++) + { + Node child = Leaves[iter]; + string hash = child == null ? HEX_ZERO : child.Hash; + hex += hash; + } + + string prefix = HashPrefix.INNER_NODE.ToString("X"); + return Sha512HalfUtil.Sha512Half(prefix + hex); + } } } } diff --git a/Xrpl/Utils/Hashes/ShaMap/LeafNode.cs b/Xrpl/Utils/Hashes/ShaMap/LeafNode.cs index 5184941..1e8040b 100644 --- a/Xrpl/Utils/Hashes/ShaMap/LeafNode.cs +++ b/Xrpl/Utils/Hashes/ShaMap/LeafNode.cs @@ -2,12 +2,62 @@ // https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/utils/hashes/SHAMap/LeafNode.ts +using Xrpl.Client.Exceptions; + namespace Xrpl.Utils.Hashes.ShaMap { - public class LeafNode + public class LeafNode : Node { - public LeafNode() + public string Tag { get; set; } + public NodeType Type { get; set; } + public string Data { get; set; } + + /// + /// Leaf node in a SHAMap tree. + /// + /// Equates to a ledger entry `index`. + /// Hex of account state, transaction etc. + /// One of TYPE_ACCOUNT_STATE, TYPE_TRANSACTION_MD etc. + public LeafNode(string tag, string data, NodeType type) + { + Tag = tag; + Data = data; + Type = type; + } + + /// + public override void AddItem(string tag, Node node) + { + throw new XrplException("Cannot call addItem on a LeafNode"); + //AddItem(tag, node); + } + + /// + public override string Hash { + get + { + switch (Type) + { + case NodeType.ACCOUNT_STATE: + { + var leafPrefix = HashPrefix.LEAF_NODE.ToString("X"); + return Sha512HalfUtil.Sha512Half(leafPrefix + Data + Tag); + } + case NodeType.TRANSACTION_NO_METADATA: + { + var txIDPrefix = HashPrefix.TRANSACTION_ID.ToString("X"); + return Sha512HalfUtil.Sha512Half(txIDPrefix + Data); + } + case NodeType.TRANSACTION_METADATA: + { + var txNodePrefix = HashPrefix.TRANSACTION_NODE.ToString("X"); + return Sha512HalfUtil.Sha512Half(txNodePrefix + Data + Tag); + } + default: + throw new XrplException("Tried to hash a SHAMap node of unknown type."); + } + } } } } diff --git a/Xrpl/Utils/Hashes/ShaMap/Node.cs b/Xrpl/Utils/Hashes/ShaMap/Node.cs index 6b09297..eb3a850 100644 --- a/Xrpl/Utils/Hashes/ShaMap/Node.cs +++ b/Xrpl/Utils/Hashes/ShaMap/Node.cs @@ -1,14 +1,30 @@ - - -// https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/utils/hashes/SHAMap/node.ts +// https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/utils/hashes/SHAMap/node.ts namespace Xrpl.Utils.Hashes.ShaMap { - public class Node + /// + /// Abstract base class for SHAMapNode. + /// + public abstract class Node + { + /// + /// Adds an item to the InnerNode. + /// + /// Equates to a ledger entry `index`. + /// Node to add. + public abstract void AddItem(string tag, Node node); + /// + /// Get the hash of a LeafNode. + /// + /// Hash of the LeafNode. + public abstract string Hash { get; } + } + public enum NodeType { - public Node() - { - } + INNER = 1, + TRANSACTION_NO_METADATA = 2, + TRANSACTION_METADATA = 3, + ACCOUNT_STATE = 4, } } diff --git a/Xrpl/Utils/Hashes/ShaMap/SHAMap.cs b/Xrpl/Utils/Hashes/ShaMap/SHAMap.cs index c4b9ede..a317772 100644 --- a/Xrpl/Utils/Hashes/ShaMap/SHAMap.cs +++ b/Xrpl/Utils/Hashes/ShaMap/SHAMap.cs @@ -6,9 +6,26 @@ namespace Xrpl.Utils.Hashes.ShaMap { public class SHAMap { + public InnerNode Root { get; set; } + + /// SHAMap tree constructor. public SHAMap() { + Root = new InnerNode(0); } + + /// Add an item to the SHAMap. + /// @param tag - Index of the Node to add. + /// @param data - Data to insert into the tree. + /// @param type - Type of the node to add. + public void AddItem(string tag, string data, NodeType type) + { + Root.AddItem(tag, new LeafNode(tag, data, type)); + } + + /// Get the hash of the SHAMap. + /// @returns The hash of the root of the SHAMap. + public string Hash => this.Root.Hash; } } diff --git a/Xrpl/Utils/Index.cs b/Xrpl/Utils/Index.cs new file mode 100644 index 0000000..00f3818 --- /dev/null +++ b/Xrpl/Utils/Index.cs @@ -0,0 +1,66 @@ +using Newtonsoft.Json.Linq; + +using System; + +using Xrpl.AddressCodec; +using Xrpl.BinaryCodec; +using Xrpl.BinaryCodec.Ledger; +using Xrpl.Keypairs; +using Xrpl.Models.Subscriptions; +using Xrpl.Models.Transactions; + +//https://github.com/XRPLF/xrpl.js/blob/45963b70356f4609781a6396407e2211fd15bcf1/packages/xrpl/src/utils/index.ts + +namespace Xrpl.Utils +{ + public static class Utilities + { + public static bool IsValidSecret(string secret) + { + try + { + XrplKeypairs.DeriveKeypair(secret); + return true; + } + catch (Exception) + { + return false; + } + } + + public static string Encode(this TransactionCommon transactionOrLedgerEntry) + { + return XrplBinaryCodec.Encode(transactionOrLedgerEntry); + } + + public static string EncodeForSigning(this TransactionCommon transaction) + { + return XrplBinaryCodec.EncodeForSigning(transaction); + } + + public static string EncodeForSigningClaim(this PaymentChannelClaim paymentChannelClaim) + { + return XrplBinaryCodec.EncodeForSigningClaim(paymentChannelClaim); + } + + public static string EncodeForMultiSigning(this TransactionCommon transaction, string signer) + { + return XrplBinaryCodec.EncodeForMulitSigning(transaction, signer); + } + + public static JToken Decode(string hex) + { + return XrplBinaryCodec.Decode(hex); + } + + public static bool IsValidAddress(string address) + { + return XrplAddressCodec.IsValidXAddress(address) || XrplCodec.IsValidClassicAddress(address); + } + + public static bool HasNextPage(this BaseResponse response) + { + return response.Result.ContainsKey("marker"); + } + } +} diff --git a/Xrpl/Utils/ParseNFTID.cs b/Xrpl/Utils/ParseNFTID.cs index a196a84..5c4803b 100644 --- a/Xrpl/Utils/ParseNFTID.cs +++ b/Xrpl/Utils/ParseNFTID.cs @@ -1,5 +1,5 @@ using System; -using System.Reflection.Emit; + using Xrpl.AddressCodec; using Xrpl.Client.Exceptions; using Xrpl.Models.Common; @@ -29,12 +29,12 @@ public static class ParseNFTID /// The scrambled or unscrambled taxon (The XOR is both the encoding and decoding). /// The account sequence when the token was minted. Used as a psuedorandom seed. /// The opposite taxon. If the taxon was scrambled it becomes unscrambled, and vice versa. - public static UInt32 UnscrambleTaxon(UInt32 taxon, UInt32 tokenSeq) + public static uint UnscrambleTaxon(uint taxon, uint tokenSeq) { - return (UInt32)((taxon ^ (384160001 * tokenSeq + 2459)) % 4294967296); + return (uint)((taxon ^ (384160001 * tokenSeq + 2459)) % 4294967296); } - public static NFTokenIDData GetNFTokenIDData(string nftokenID) + public static NFTokenIDData ParseNFTokenID(this string nftokenID) { const int expectedLength = 64; if (nftokenID.Length != expectedLength) @@ -43,13 +43,13 @@ public static NFTokenIDData GetNFTokenIDData(string nftokenID) $", but expected a token with length ${expectedLength}"); } - UInt32 flags = Convert.ToUInt32(nftokenID.Substring(0, 4), 16); - UInt32 transferFee = Convert.ToUInt32(nftokenID.Substring(4, 4), 16); + uint flags = Convert.ToUInt32(nftokenID.Substring(0, 4), 16); + uint transferFee = Convert.ToUInt32(nftokenID.Substring(4, 4), 16); string scrambledTaxon = nftokenID.Substring(48, 8); - UInt32 sequence = Convert.ToUInt32(nftokenID.Substring(56, 8), 16); - UInt32 taxon = UnscrambleTaxon(Convert.ToUInt32(scrambledTaxon, 16), sequence); + uint sequence = Convert.ToUInt32(nftokenID.Substring(56, 8), 16); + uint taxon = UnscrambleTaxon(Convert.ToUInt32(scrambledTaxon, 16), sequence); - string issuer = XrplCodec.EncodeAccountID(AddressCodec.Utils.FromHexToBytes(nftokenID.Substring(8, 40))); + string issuer = XrplCodec.EncodeAccountID(nftokenID.Substring(8, 40).FromHexToBytes()); return new NFTokenIDData(nftokenID, flags, transferFee, issuer, taxon, sequence); } diff --git a/Xrpl/Utils/Quality.cs b/Xrpl/Utils/Quality.cs index ebd199d..529c172 100644 --- a/Xrpl/Utils/Quality.cs +++ b/Xrpl/Utils/Quality.cs @@ -1,15 +1,11 @@ - +// https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/utils/quality.ts -// https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/utils/quality.ts - -//todo DO namespace Xrpl.Utils { public class Quality { - public Quality() - { - } + + //todo need help with this... } } diff --git a/Xrpl/Utils/SignChannelClaim.cs b/Xrpl/Utils/SignChannelClaim.cs deleted file mode 100644 index 99572a1..0000000 --- a/Xrpl/Utils/SignChannelClaim.cs +++ /dev/null @@ -1,15 +0,0 @@ - - -// https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/utils/signPaymentChannelClaim.ts - -//todo DO -namespace Xrpl.Utils -{ - public class SignPaymentChannelClaim - { - public SignPaymentChannelClaim() - { - } - } -} - diff --git a/Xrpl/Utils/SignPaymentChannelClaim.cs b/Xrpl/Utils/SignPaymentChannelClaim.cs new file mode 100644 index 0000000..c6d1c74 --- /dev/null +++ b/Xrpl/Utils/SignPaymentChannelClaim.cs @@ -0,0 +1,36 @@ + + +// https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/utils/signPaymentChannelClaim.ts + +//todo DO +using Xrpl.Keypairs; +using Xrpl.Models.Transactions; + +namespace Xrpl.Utils +{ + public static class SignPmntChannelClaim + { + + /// + /// Sign a payment channel claim. + /// + /// Channel identifier specified by the paymentChannelClaim. + /// Amount specified by the paymentChannelClaim. + /// Private Key to sign paymentChannelClaim with. + /// True if the channel is valid. + public static string SignPaymentChannelClaim(string channel, string amount, string privateKey) + { + + var payment = new PaymentChannelClaim + { + Channel = channel, + Amount = XrpConversion.XrpToDrops(amount) + }; + + var signingData = payment.EncodeForSigningClaim(); + + return XrplKeypairs.Sign(signingData, privateKey); + } + } +} + diff --git a/Xrpl/Utils/VerifyPaymentChannelClaim.cs b/Xrpl/Utils/VerifyPaymentChannelClaim.cs index cf9df4b..0b82f6d 100644 --- a/Xrpl/Utils/VerifyPaymentChannelClaim.cs +++ b/Xrpl/Utils/VerifyPaymentChannelClaim.cs @@ -4,12 +4,33 @@ //todo DO +using Xrpl.Keypairs; +using Xrpl.Models.Transactions; + namespace Xrpl.Utils { - public class VerifyPaymentChannelClaim + public static class VerifyPmntChannelClaim { - public VerifyPaymentChannelClaim() + + /// + /// Verify the signature of a payment channel claim. + /// + /// Channel identifier specified by the paymentChannelClaim. + /// Amount specified by the paymentChannelClaim. + /// Signature produced from signing paymentChannelClaim. + /// Public key that signed the paymentChannelClaim. + /// True if the channel is valid. + /// Utilities + public static bool VerifyPaymentChannelClaim(string channel, string amount, string signature, string publicKey) { + var payment = new PaymentChannelClaim + { + Channel = channel, + Amount = XrpConversion.XrpToDrops(amount) + }; + + var signingData = payment.EncodeForSigningClaim(); + return XrplKeypairs.Verify(signingData, signature, publicKey); } } } diff --git a/Xrpl/Utils/XrpConversion.cs b/Xrpl/Utils/XrpConversion.cs index 6347d82..aa5409a 100644 --- a/Xrpl/Utils/XrpConversion.cs +++ b/Xrpl/Utils/XrpConversion.cs @@ -94,7 +94,13 @@ public static string DropsToXrp(string dropsToConvert) * decimal point followed by zeros, e.g. '1.00'. * Important: specify base BASE_10 to avoid exponential notation, e.g. '1e-7'. */ - string drops = BigInteger.Parse(dropsToConvert, NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign | NumberStyles.AllowExponent).ToRadixString(BASE_TEN); + string drops = BigInteger.Parse(dropsToConvert, NumberStyles.AllowLeadingSign + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | NumberStyles.AllowExponent + | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture).ToRadixString(BASE_TEN); // check that the value is valid and actually a number if (!(dropsToConvert is string) && drops != null) { @@ -122,7 +128,13 @@ public static string DropsToXrp(string dropsToConvert) //} // TODO: SHOULD BE BASE 10 - return ((decimal)BigInteger.Parse(dropsToConvert, NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign | NumberStyles.AllowExponent) / (decimal)new BigInteger(DROPS_PER_XRP)).ToString(); + return ((decimal)BigInteger.Parse(dropsToConvert, NumberStyles.AllowLeadingSign + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | NumberStyles.AllowExponent + | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture) / (decimal)new BigInteger(DROPS_PER_XRP)).ToString(); //return ((decimal)BigInteger.Parse(dropsToConvert, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture) / (decimal)new BigInteger(DROPS_PER_XRP)).ToString("F"+BASE_TEN).TrimEnd('0'); } @@ -145,7 +157,13 @@ public static string XrpToDrops(string xrpToConvert) { // Important: specify base BASE_TEN to avoid exponential notation, e.g. '1e-7'. // TODO: SHOULD BE BASE 10 - string xrp = decimal.Parse(xrpToConvert, NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign | NumberStyles.AllowExponent).ToString(); + string xrp = decimal.Parse(xrpToConvert, NumberStyles.AllowLeadingSign + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | NumberStyles.AllowExponent + | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture).ToString(); // check that the value is valid and actually a number if (!(xrpToConvert is string) && xrp != null) { @@ -165,7 +183,13 @@ public static string XrpToDrops(string xrpToConvert) throw new ValidationException($"xrpToDrops: value '{xrp}' has too many decimal places."); } // TODO: SHOULD BE BASE 10 - return new BigInteger(decimal.Parse(xrpToConvert, NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign | NumberStyles.AllowExponent) * (decimal)new BigInteger(DROPS_PER_XRP)).ToString(); + return new BigInteger(decimal.Parse(xrpToConvert, NumberStyles.AllowLeadingSign + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | NumberStyles.AllowExponent + | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture) * (decimal)new BigInteger(DROPS_PER_XRP)).ToString(); } } } \ No newline at end of file From 73646c50b60a7d135f4ba031aa6d9ede00be84f1 Mon Sep 17 00:00:00 2001 From: dangell7 Date: Sat, 21 Jan 2023 23:11:24 -0500 Subject: [PATCH 09/39] fix subscribe test --- .../Xrpl.Tests/Client/TestWebSocketClient.cs | 147 +++++------------- Xrpl/Client/IXrplClient.cs | 2 +- Xrpl/Client/connection.cs | 3 +- 3 files changed, 38 insertions(+), 114 deletions(-) diff --git a/Tests/Xrpl.Tests/Client/TestWebSocketClient.cs b/Tests/Xrpl.Tests/Client/TestWebSocketClient.cs index 66601a0..151d4a0 100644 --- a/Tests/Xrpl.Tests/Client/TestWebSocketClient.cs +++ b/Tests/Xrpl.Tests/Client/TestWebSocketClient.cs @@ -32,11 +32,10 @@ public void TestSome() var response = JsonConvert.DeserializeObject>(message); var message1 = JsonConvert.SerializeObject(response); var response1 = JsonConvert.DeserializeObject(message1); - Debug.WriteLine(response1); } [TestMethod] - public async Task _TestSubscribeWebSocket() + public async Task TestSubscribeWebSocket() { bool isTested = false; @@ -44,133 +43,56 @@ public async Task _TestSubscribeWebSocket() var server = "wss://xrplcluster.com/"; - var client = WebSocketClient.Create(server); - - //client.OnConnected += (ws, t) => - //{ - // Console.WriteLine($"CONNECTED"); - //}; - - //client.OnConnectionException += (ws, ex) => - //{ - // Console.WriteLine($"CONNECTION EXCEPTION: {ex.Message}"); - // isFinished = true; - //}; - - //client.OnException += (ws, ex) => - //{ - // Console.WriteLine($"EXCEPTION: {ex.Message}"); - // isFinished = true; - //}; - - //client.OnDisconnect += (ws, code) => - //{ - // Console.WriteLine($"DISCONNECTED: {code}"); - // isFinished = true; - //}; - - //client.OnMessageReceived += (ws, message) => - //{ - // Console.WriteLine($"MESSAGE RECEIVED: {message}"); - // Dictionary json = JsonConvert.DeserializeObject>(message); - // if (json["type"] == "ledgerClosed") - // { - // isTested = true; - // isFinished = true; - // } - //}; + var client = new XrplClient(server); - Timer timer = new Timer(5000); - timer.Elapsed += (sender, e) => - { - Debug.WriteLine("TIMEOUT!!"); - client.Dispose(); - isFinished = true; - }; - timer.Start(); - - _ = client.Connect(); - - //while (!client.State == WebSocketState.Open) - //{ - // Debug.WriteLine($"CONNECTING... {DateTime.Now}"); - // System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1)); - //} - - var request = new SubscribeRequest() + client.OnConnected += async () => { - Streams = new List(new[] + Console.WriteLine("CONNECTED"); + var subscribe = await client.Subscribe( + new SubscribeRequest() + { + Streams = new List(new[] { "ledger", }) + }); }; - var serializerSettings = new JsonSerializerSettings(); - serializerSettings.NullValueHandling = NullValueHandling.Ignore; - serializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc; - serializerSettings.FloatParseHandling = FloatParseHandling.Double; - serializerSettings.FloatFormatHandling = FloatFormatHandling.DefaultValue; - string jsonString = JsonConvert.SerializeObject(request, serializerSettings); - client.SendMessage(jsonString); - - Debug.WriteLine($"BEFORE: {DateTime.Now}"); - while (!isFinished) + client.OnError += (error, errorMessage, message, data) => { - Debug.WriteLine($"WAITING: {DateTime.Now}"); - System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1)); - } - Debug.WriteLine($"AFTER: {DateTime.Now}"); - Debug.WriteLine($"IS FINISHED: {isFinished}"); - Debug.WriteLine($"IS TESTER: {isTested}"); - Assert.IsTrue(isTested); - } - + Console.WriteLine($"CONN ERROR: {error}"); + Console.WriteLine($"CONN ERROR MESSAGE: {errorMessage}"); + Console.WriteLine($"CONN MESSAGE: {message}"); + Console.WriteLine($"CONN ERROR DATA: {data}"); + isFinished = true; + return Task.CompletedTask; + }; - [TestMethod] - public async Task _TestWebSocketClientTimeout() - { - bool isFinished = false; + client.OnDisconnect += (code) => + { + Console.WriteLine($"DISCONNECTED: {code}"); + isFinished = true; + return Task.CompletedTask; + }; - var server = "wss://xrplcluster.com/"; + client.OnLedgerClosed += (message) => + { + Console.WriteLine($"LEDGER CLOSED: {message}"); + isFinished = true; + isTested = true; + return Task.CompletedTask; + }; - var client = WebSocketClient.Create(server); - - //client.OnConnected += (ws, t) => - //{ - // Debug.WriteLine($"CONNECTED"); - // Assert.IsNotNull(t); - // Assert.IsFalse(t.IsCancellationRequested); - // isFinished = true; - //}; - - //client.OnConnectionException += (ws, ex) => - //{ - // Debug.WriteLine($"CONNECTION EXCEPTION: {ex.Message}"); - // isFinished = true; - //}; - - //client.OnException += (ws, ex) => - //{ - // Debug.WriteLine($"EXCEPTION: {ex.Message}"); - // //Debug.WriteLine(message); - // isFinished = true; - //}; - - //client.OnDisconnect += (ws, code) => - //{ - // Debug.WriteLine($"DISCONNECTED: {code}"); - // isFinished = true; - //}; - - Timer timer = new Timer(2000); + Timer timer = new Timer(5000); timer.Elapsed += (sender, e) => { + Debug.WriteLine("TIMEOUT!!"); client.Dispose(); isFinished = true; }; timer.Start(); - client.Connect(); + await client.Connect(); Debug.WriteLine($"BEFORE: {DateTime.Now}"); @@ -180,6 +102,9 @@ public async Task _TestWebSocketClientTimeout() System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1)); } Debug.WriteLine($"AFTER: {DateTime.Now}"); + Debug.WriteLine($"IS FINISHED: {isFinished}"); + Debug.WriteLine($"IS TESTER: {isTested}"); + Assert.IsTrue(isTested); } } } diff --git a/Xrpl/Client/IXrplClient.cs b/Xrpl/Client/IXrplClient.cs index 9c3eea9..47be500 100644 --- a/Xrpl/Client/IXrplClient.cs +++ b/Xrpl/Client/IXrplClient.cs @@ -27,7 +27,7 @@ namespace Xrpl.Client public delegate Task OnError(string error, string errorMessage, string message, dynamic data); public delegate Task OnConnected(); public delegate Task OnDisconnect(int? code); - public delegate Task OnLedgerClosed(object response); + public delegate Task OnLedgerClosed(LedgerStream response); public delegate Task OnTransaction(TransactionStream response); public delegate Task OnManifestReceived(ValidationStream response); public delegate Task OnPeerStatusChange(PeerStatusStream response); diff --git a/Xrpl/Client/connection.cs b/Xrpl/Client/connection.cs index e73fdad..5b1d547 100644 --- a/Xrpl/Client/connection.cs +++ b/Xrpl/Client/connection.cs @@ -347,7 +347,6 @@ private async Task IOnMessage(string message) try { data = JsonConvert.DeserializeObject(message); - Console.WriteLine(message); } catch (Exception error) { @@ -370,7 +369,7 @@ private async Task IOnMessage(string message) { case ResponseStreamType.ledgerClosed: { - object response = JsonConvert.DeserializeObject(message.ToString()); + var response = JsonConvert.DeserializeObject(message); if (OnLedgerClosed is not null) await OnLedgerClosed?.Invoke(response)!; From 088698ff5bdd20913d9dcac1ebf377beecde3633 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Sun, 2 Apr 2023 14:58:18 +0300 Subject: [PATCH 10/39] xumm numbers to seed --- Tests/Xrpl.Tests/Wallet/WalletTest.cs | 68 ++++++++++++++++++++++++++- Xrpl/Wallet/XrplWallet.cs | 16 ++++++- Xrpl/Wallet/XummExtension.cs | 60 +++++++++++++++++++++++ 3 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 Xrpl/Wallet/XummExtension.cs diff --git a/Tests/Xrpl.Tests/Wallet/WalletTest.cs b/Tests/Xrpl.Tests/Wallet/WalletTest.cs index 5a6147b..0dedbd2 100644 --- a/Tests/Xrpl.Tests/Wallet/WalletTest.cs +++ b/Tests/Xrpl.Tests/Wallet/WalletTest.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Diagnostics; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; @@ -754,3 +755,68 @@ public void TestVerifyMainnet() } } } +namespace Xrpl.Tests.Wallet.Tests.XummNumbers +{ + [TestClass] + public class TestUXummNumbers + { + string[] xummNumbers = new[] { "556863", "404730", "402495", "038856", "113360", "465825", "112585", "283320" }; + + string wallet_num = "rNUhe55ffGjezrVwTQfpL73aP5qKdofZMy"; + string wallet_seed = "snjnsXBywtRUzVQagnjfXHwo97x1E"; + private string wallet_private_key = "00FC5D3ACE7236F40683947E68575316959D07C2425EE12F41E534EA4295BABFAA"; + + [TestMethod] + public void TestVerify_EntropyFromXummNumbers() + { + XrplWallet result = XrplWallet.FromXummNumbers(xummNumbers); + //sEdVLhsR1xkLWWLX9KbErLTo6EEaHFi WRONG SEED + //rJP8D3Mpntnp7T1YZyz51xwtdeYKcz5hpR WRONG ADDRESS + + Assert.AreEqual(result.Seed, wallet_seed); + Assert.AreEqual(result.ClassicAddress, wallet_num); + Assert.AreEqual(result.PrivateKey, wallet_private_key); + } + + [TestMethod] + public void TestVerify_InValid_EntropyFromXummNumbers() + { + var numbers = new string[8]; + Array.Copy(xummNumbers,numbers,8); + numbers[0] = "556862"; + Assert.ThrowsException(() => XrplWallet.FromXummNumbers(numbers), "Wrong numbers"); + } + + [TestMethod] + public void TestVerify_CheckXummSum() + { + var number = xummNumbers[0]; + var position = 0; + var valid_sum = XummExtension.CheckXummSum(position, number); + Assert.AreEqual(true, valid_sum); + + number = xummNumbers[2]; + position = 2; + valid_sum = XummExtension.CheckXummSum(position, number); + Assert.AreEqual(true, valid_sum); + + number = xummNumbers[6]; + position = 6; + valid_sum = XummExtension.CheckXummSum(position, number); + Assert.AreEqual(true, valid_sum); + + number = xummNumbers[7]; + position = 7; + valid_sum = XummExtension.CheckXummSum(position, number); + Assert.AreEqual(true, valid_sum); + } + [TestMethod] + public void TestVerify_InValid_CheckXummSum() + { + var number = xummNumbers[3]; + var position = 2; + var valid_sum = XummExtension.CheckXummSum(position, number); + Assert.AreNotEqual(true, valid_sum); + } + } +} diff --git a/Xrpl/Wallet/XrplWallet.cs b/Xrpl/Wallet/XrplWallet.cs index 12bdfca..807ef7a 100644 --- a/Xrpl/Wallet/XrplWallet.cs +++ b/Xrpl/Wallet/XrplWallet.cs @@ -45,7 +45,7 @@ public XrplWallet(string publicKey, string privateKey, string? masterAddress = n { this.PublicKey = publicKey; this.PrivateKey = privateKey; - this.ClassicAddress = masterAddress != null ? masterAddress : XrplKeypairs.DeriveAddress(publicKey); + this.ClassicAddress = masterAddress ?? XrplKeypairs.DeriveAddress(publicKey); this.Seed = seed; } @@ -78,11 +78,23 @@ public static XrplWallet FromSeed(string seed, string? masterAddress = null, str /// A Wallet derived from an entropy. public static XrplWallet FromEntropy(byte[] entropy, string? masterAddress = null, string? algorithm = null) { - string falgorithm = algorithm != null ? algorithm : XrplWallet.DEFAULT_ALGORITHM; + string falgorithm = algorithm ?? XrplWallet.DEFAULT_ALGORITHM; string seed = XrplKeypairs.GenerateSeed(entropy, falgorithm); return XrplWallet.DeriveWallet(seed, masterAddress, falgorithm); } + /// + /// Creates a Wallet from xumm numbers. + /// + /// A Wallet from xumm numbers. + public static XrplWallet FromXummNumbers(string[] numbers) + { + byte[] entropy = XummExtension.EntropyFromXummNumbers(numbers); + return FromEntropy(entropy); + } + + + /// /// Derive a Wallet from a seed. /// diff --git a/Xrpl/Wallet/XummExtension.cs b/Xrpl/Wallet/XummExtension.cs new file mode 100644 index 0000000..71a1f6f --- /dev/null +++ b/Xrpl/Wallet/XummExtension.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Xrpl.Wallet +{ + public static class XummExtension + { + /// + /// generate Entropy from xumm numbers + /// + /// xumm numbers + /// byte[] Entropy + /// when wrong has wrong digits + public static byte[] EntropyFromXummNumbers(string[] numbers) + { + if (!CheckXummNumbers(numbers)) + throw new ArgumentException("Wrong numbers"); + + var vals = numbers.Select(x => $"0000{int.Parse(x.Substring(0, 5)):X}"[^4..]).ToArray(); + + var buffer = new List(); + foreach (var val in vals) + { + var v = Enumerable.Range(0, val.Length) + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(val.Substring(x, 2), 16)) + .ToArray(); + buffer.AddRange(v); + + } + return buffer.ToArray(); + } + + /// + /// xumm numbers validation + /// + /// xum numbers + /// + public static bool CheckXummNumbers(string[] numbers) => numbers.Select((n, i) => CheckXummSum(i, n)).All(c => c); + + /// + /// xumm validation for part od numbers + /// + /// numbers position + /// xum numbers + /// + public static bool CheckXummSum(int position, string number) + { + if (number.Length != 6) + return false; + + var checkSum = int.Parse(number[5..]); + var value = int.Parse(number[..5]); + var sum = value * (position * 2 + 1) % 9; + return sum == checkSum; + } + + } +} From 7adf06d64d21cd29bc4ef650f304da4f7aeec20b Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Sun, 11 Jun 2023 18:27:40 +0300 Subject: [PATCH 11/39] fix fields name, missing fields, connection events, lost transactions --- .../TestsClients/Test.ClonsoleApp/Program.cs | 12 +-- Tests/Xrpl.Tests/Client/TestSubscribe.cs | 16 ++-- Tests/Xrpl.Tests/Integration/SetupIClient.cs | 6 +- Tests/Xrpl.Tests/SetupClient.cs | 6 +- Tests/Xrpl.Tests/Wallet/WalletTest.cs | 69 +++++++++++++++- Xrpl/Client/IXrplClient.cs | 61 +++++++------- .../Json/Converters/LedgerObjectConverter.cs | 18 +++-- .../Json/Converters/TransactionConverter.cs | 9 ++- Xrpl/Client/connection.cs | 32 +++++--- Xrpl/Models/Common/Currency.cs | 68 +++++++++++++--- Xrpl/Models/Enums.cs | 17 +++- Xrpl/Models/Ledger/LOAccountRoot.cs | 21 ++++- Xrpl/Models/Ledger/LONFTokenOffer.cs | 81 +++++++++++++++++++ Xrpl/Models/Ledger/LONFTokenPage.cs | 57 +++++++++++++ Xrpl/Models/Methods/AccountLines.cs | 16 +++- Xrpl/Models/Methods/AccountNFTs.cs | 20 ++++- Xrpl/Models/Transactions/AccountSet.cs | 18 +++-- Xrpl/Models/Transactions/Common.cs | 32 +++++++- Xrpl/Models/Transactions/EnableAmendment.cs | 40 +++++++++ Xrpl/Models/Transactions/SetFee.cs | 67 +++++++++++++++ Xrpl/Models/Transactions/UNLModify.cs | 55 +++++++++++++ Xrpl/Models/Utils/Flags.cs | 2 +- Xrpl/Utils/StringConversion.cs | 2 + Xrpl/Wallet/XrplWallet.cs | 26 +++++- Xrpl/Wallet/XummExtension.cs | 65 +++++++++++++++ Xrpl/Xrpl.csproj | 2 +- 26 files changed, 718 insertions(+), 100 deletions(-) create mode 100644 Xrpl/Models/Ledger/LONFTokenOffer.cs create mode 100644 Xrpl/Models/Ledger/LONFTokenPage.cs create mode 100644 Xrpl/Models/Transactions/EnableAmendment.cs create mode 100644 Xrpl/Models/Transactions/SetFee.cs create mode 100644 Xrpl/Models/Transactions/UNLModify.cs create mode 100644 Xrpl/Wallet/XummExtension.cs diff --git a/Tests/TestsClients/Test.ClonsoleApp/Program.cs b/Tests/TestsClients/Test.ClonsoleApp/Program.cs index 9bf2f41..7c80e30 100644 --- a/Tests/TestsClients/Test.ClonsoleApp/Program.cs +++ b/Tests/TestsClients/Test.ClonsoleApp/Program.cs @@ -56,7 +56,7 @@ static async Task SubmitTestTx() var client = new XrplClient("wss://s.altnet.rippletest.net:51233"); - client.OnConnected += async () => + client.connection.OnConnected += async () => { Console.WriteLine("CONNECTED"); }; @@ -97,7 +97,7 @@ static async Task WebsocketTest() var client = new XrplClient(server); - client.OnConnected += async () => + client.connection.OnConnected += async () => { Console.WriteLine("CONNECTED"); var subscribe = await client.Subscribe( @@ -110,14 +110,14 @@ static async Task WebsocketTest() }); }; - client.OnDisconnect += (code) => + client.connection.OnDisconnect += (code) => { Console.WriteLine($"DISCONECTED CODE: {code}"); Console.WriteLine("DISCONECTED"); return Task.CompletedTask; }; - client.OnError += (errorCode, errorMessage, error, data) => + client.connection.OnError += (errorCode, errorMessage, error, data) => { Console.WriteLine(errorCode); Console.WriteLine(errorMessage); @@ -125,13 +125,13 @@ static async Task WebsocketTest() return Task.CompletedTask; }; - client.OnTransaction += Response => + client.connection.OnTransaction += Response => { Console.WriteLine(Response.Transaction.TransactionType.ToString()); return Task.CompletedTask; }; - client.OnLedgerClosed += r => + client.connection.OnLedgerClosed += r => { Console.WriteLine($"MESSAGE RECEIVED: {r}"); isFinished = true; diff --git a/Tests/Xrpl.Tests/Client/TestSubscribe.cs b/Tests/Xrpl.Tests/Client/TestSubscribe.cs index f4f815d..83e1687 100644 --- a/Tests/Xrpl.Tests/Client/TestSubscribe.cs +++ b/Tests/Xrpl.Tests/Client/TestSubscribe.cs @@ -67,7 +67,7 @@ public async Task TestUnsubscribe() public void TestEmitsTransaction() { bool isDone = false; - runner.client.OnTransaction += r => + runner.client.connection.OnTransaction += r => { Assert.IsTrue(r.Type == ResponseStreamType.transaction); isDone = true; @@ -86,7 +86,7 @@ public void TestEmitsTransaction() [TestMethod] public void TestEmitsLedger() { - runner.client.OnLedgerClosed += r => + runner.client.connection.OnLedgerClosed += r => { //Assert.IsTrue(r.Type == ResponseStreamType.ledgerClosed); return Task.CompletedTask; @@ -99,7 +99,7 @@ public void TestEmitsLedger() [TestMethod] public void TestEmitsPeerStatusChange() { - runner.client.OnPeerStatusChange += r => + runner.client.connection.OnPeerStatusChange += r => { Assert.IsTrue(r.Type == ResponseStreamType.consensusPhase); return Task.CompletedTask; @@ -112,7 +112,7 @@ public void TestEmitsPeerStatusChange() [TestMethod] public void TestEmitsPathFind() { - runner.client.OnPathFind += r => + runner.client.connection.OnPathFind += r => { Assert.IsTrue(r.Type == ResponseStreamType.path_find); return Task.CompletedTask; @@ -125,7 +125,7 @@ public void TestEmitsPathFind() [TestMethod] public void TestEmitsValidationReceived() { - runner.client.OnManifestReceived += r => + runner.client.connection.OnManifestReceived += r => { Assert.IsTrue(r.Type == ResponseStreamType.validationReceived); return Task.CompletedTask; @@ -162,20 +162,20 @@ public async Task TestSubscribe() var client = new XrplClient(server); - client.OnConnected += () => + client.connection.OnConnected += () => { Console.WriteLine("CONNECTED"); return Task.CompletedTask; }; - client.OnDisconnect += (code) => + client.connection.OnDisconnect += (code) => { Console.WriteLine($"DISCONNECTED: {code}"); isFinished = true; return Task.CompletedTask; }; - client.OnLedgerClosed += (message) => + client.connection.OnLedgerClosed += (message) => { Console.WriteLine($"MESSAGE RECEIVED: {message}"); //Dictionary json = JsonConvert.DeserializeObject>(message); diff --git a/Tests/Xrpl.Tests/Integration/SetupIClient.cs b/Tests/Xrpl.Tests/Integration/SetupIClient.cs index 9d69c17..c698245 100644 --- a/Tests/Xrpl.Tests/Integration/SetupIClient.cs +++ b/Tests/Xrpl.Tests/Integration/SetupIClient.cs @@ -18,17 +18,17 @@ public async Task SetupClient(string serverUrl) wallet = XrplWallet.Generate(); var promise = new TaskCompletionSource(); client = new XrplClient(serverUrl); - client.OnConnected += () => + client.connection.OnConnected += () => { Console.WriteLine($"SetupIntegration CONNECTED"); return Task.CompletedTask; }; - client.OnDisconnect += (code) => + client.connection.OnDisconnect += (code) => { Console.WriteLine($"SetupIntegration DISCONNECTED: {code}"); return Task.CompletedTask; }; - client.OnError += (error, errorMessage, message, data) => + client.connection.OnError += (error, errorMessage, message, data) => { Console.WriteLine($"SetupIntegration ERROR: {message}"); return Task.CompletedTask; diff --git a/Tests/Xrpl.Tests/SetupClient.cs b/Tests/Xrpl.Tests/SetupClient.cs index 9f58758..2ee0722 100644 --- a/Tests/Xrpl.Tests/SetupClient.cs +++ b/Tests/Xrpl.Tests/SetupClient.cs @@ -29,17 +29,17 @@ public async Task SetupClient() Timer timer = new Timer(25000); timer.Elapsed += (sender, e) => tcpListenerThread.Abort(); client = new XrplClient($"ws://127.0.0.1:{port}"); - client.OnConnected += () => + client.connection.OnConnected += () => { Debug.WriteLine("SETUP CLIENT: CONECTED"); return Task.CompletedTask; }; - client.OnDisconnect += (code) => + client.connection.OnDisconnect += (code) => { Debug.WriteLine("SETUP CLIENT: DISCONECTED"); return Task.CompletedTask; }; - client.OnError += (e, em, m, d) => + client.connection.OnError += (e, em, m, d) => { Debug.WriteLine($"SETUP CLIENT: ERROR: {e}"); return Task.CompletedTask; diff --git a/Tests/Xrpl.Tests/Wallet/WalletTest.cs b/Tests/Xrpl.Tests/Wallet/WalletTest.cs index 5a6147b..6bd79cc 100644 --- a/Tests/Xrpl.Tests/Wallet/WalletTest.cs +++ b/Tests/Xrpl.Tests/Wallet/WalletTest.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Diagnostics; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; @@ -754,3 +755,69 @@ public void TestVerifyMainnet() } } } +namespace Xrpl.Tests.Wallet.Tests.XummNumbers +{ + [TestClass] + public class TestUXummNumbers + { + string[] xummNumbers = new[] { "556863", "404730", "402495", "038856", "113360", "465825", "112585", "283320" }; + + string wallet_num = "rNUhe55ffGjezrVwTQfpL73aP5qKdofZMy"; + string wallet_seed = "snjnsXBywtRUzVQagnjfXHwo97x1E"; + private string wallet_private_key = "00FC5D3ACE7236F40683947E68575316959D07C2425EE12F41E534EA4295BABFAA"; + + [TestMethod] + public void TestVerify_EntropyFromXummNumbers() + { + XrplWallet result = XrplWallet.FromXummNumbers(xummNumbers, "secp256k1"); + //sEdVLhsR1xkLWWLX9KbErLTo6EEaHFi WRONG SEED + //rJP8D3Mpntnp7T1YZyz51xwtdeYKcz5hpR WRONG ADDRESS + + Assert.AreEqual(result.Seed, wallet_seed); + Assert.AreEqual(result.ClassicAddress, wallet_num); + Assert.AreEqual(result.PrivateKey, wallet_private_key); + } + + [TestMethod] + public void TestVerify_InValid_EntropyFromXummNumbers() + { + var numbers = new string[8]; + Array.Copy(xummNumbers, numbers, 8); + numbers[0] = "556862"; + Assert.ThrowsException(() => XrplWallet.FromXummNumbers(numbers), "Wrong numbers"); + } + + [TestMethod] + public void TestVerify_CheckXummSum() + { + var number = xummNumbers[0]; + var position = 0; + var valid_sum = XummExtension.CheckXummSum(position, number); + Assert.AreEqual(true, valid_sum); + + number = xummNumbers[2]; + position = 2; + valid_sum = XummExtension.CheckXummSum(position, number); + Assert.AreEqual(true, valid_sum); + + number = xummNumbers[6]; + position = 6; + valid_sum = XummExtension.CheckXummSum(position, number); + Assert.AreEqual(true, valid_sum); + + number = xummNumbers[7]; + position = 7; + valid_sum = XummExtension.CheckXummSum(position, number); + Assert.AreEqual(true, valid_sum); + } + [TestMethod] + public void TestVerify_InValid_CheckXummSum() + { + var number = xummNumbers[3]; + var position = 2; + var valid_sum = XummExtension.CheckXummSum(position, number); + Assert.AreNotEqual(true, valid_sum); + } + } +} + diff --git a/Xrpl/Client/IXrplClient.cs b/Xrpl/Client/IXrplClient.cs index 587e0ea..05261d9 100644 --- a/Xrpl/Client/IXrplClient.cs +++ b/Xrpl/Client/IXrplClient.cs @@ -41,15 +41,15 @@ public interface IXrplClient : IDisposable double feeCushion { get; set; } string maxFeeXRP { get; set; } - event OnError OnError; - event OnConnected OnConnected; - event OnDisconnect OnDisconnect; - event OnLedgerClosed OnLedgerClosed; - event OnTransaction OnTransaction; - event OnManifestReceived OnManifestReceived; - event OnPeerStatusChange OnPeerStatusChange; - event OnConsensusPhase OnConsensusPhase; - event OnPathFind OnPathFind; + //event OnError OnError; + //event OnConnected OnConnected; + //event OnDisconnect OnDisconnect; + //event OnLedgerClosed OnLedgerClosed; + //event OnTransaction OnTransaction; + //event OnManifestReceived OnManifestReceived; + //event OnPeerStatusChange OnPeerStatusChange; + //event OnConsensusPhase OnConsensusPhase; + //event OnPathFind OnPathFind; #region Server /// the url @@ -193,6 +193,7 @@ public interface IXrplClient : IDisposable /// //todo add description /// An response. Task Submit(Dictionary tx, XrplWallet wallet); + Task Submit(ITransactionCommon tx, XrplWallet wallet); /// /// The tx method retrieves information on a single transaction, by its identifying hash /// @@ -292,15 +293,15 @@ public class ClientOptions : ConnectionOptions public double feeCushion { get; set; } public string maxFeeXRP { get; set; } - public event OnError OnError; - public event OnConnected OnConnected; - public event OnDisconnect OnDisconnect; - public event OnLedgerClosed OnLedgerClosed; - public event OnTransaction OnTransaction; - public event OnManifestReceived OnManifestReceived; - public event OnPeerStatusChange OnPeerStatusChange; - public event OnConsensusPhase OnConsensusPhase; - public event OnPathFind OnPathFind; + //public event OnError OnError; + //public event OnConnected OnConnected; + //public event OnDisconnect OnDisconnect; + //public event OnLedgerClosed OnLedgerClosed; + //public event OnTransaction OnTransaction; + //public event OnManifestReceived OnManifestReceived; + //public event OnPeerStatusChange OnPeerStatusChange; + //public event OnConsensusPhase OnConsensusPhase; + //public event OnPathFind OnPathFind; ///// Current web socket client state //public WebSocketState SocketState => client.State; @@ -318,15 +319,15 @@ public XrplClient(string server, ClientOptions? options = null) maxFeeXRP = options?.maxFeeXRP ?? "2"; connection = new Connection(server, options); - connection.OnError += (e, em, m, d) => OnError(e, em, m, d); - connection.OnConnected += () => OnConnected(); - connection.OnDisconnect += (c) => OnDisconnect(c); - connection.OnLedgerClosed += (s) => OnLedgerClosed(s); - connection.OnTransaction += (s) => OnTransaction(s); - connection.OnManifestReceived += (s) => OnManifestReceived(s); - connection.OnPeerStatusChange += (s) => OnPeerStatusChange(s); - connection.OnConsensusPhase += (s) => OnConsensusPhase(s); - connection.OnPathFind += (s) => OnPathFind(s); + //connection.OnError += (e, em, m, d) => OnError?.Invoke(e, em, m, d); + //connection.OnConnected += () => OnConnected?.Invoke(); + //connection.OnDisconnect += (c) => OnDisconnect?.Invoke(c); + //connection.OnLedgerClosed += (s) => OnLedgerClosed?.Invoke(s); + //connection.OnTransaction += (s) => OnTransaction?.Invoke(s); + //connection.OnManifestReceived += (s) => OnManifestReceived?.Invoke(s); + //connection.OnPeerStatusChange += (s) => OnPeerStatusChange?.Invoke(s); + //connection.OnConsensusPhase += (s) => OnConsensusPhase?.Invoke(s); + //connection.OnPathFind += (s) => OnPathFind?.Invoke(s); } /// @@ -369,6 +370,12 @@ public Task Submit(Dictionary tx, XrplWallet wallet) { return SubmitSugar.Submit(this, tx, true, false, wallet); } + /// + public Task Submit(ITransactionCommon tx, XrplWallet wallet) + { + Dictionary txJson = JsonConvert.DeserializeObject>(tx.ToJson()); + return SubmitSugar.Submit(this, txJson, true, false, wallet); + } /// public Task GetLedgerIndex() diff --git a/Xrpl/Client/Json/Converters/LedgerObjectConverter.cs b/Xrpl/Client/Json/Converters/LedgerObjectConverter.cs index a4bc381..a0d16bc 100644 --- a/Xrpl/Client/Json/Converters/LedgerObjectConverter.cs +++ b/Xrpl/Client/Json/Converters/LedgerObjectConverter.cs @@ -3,6 +3,7 @@ using Newtonsoft.Json.Linq; using Xrpl.Models; using Xrpl.Models.Ledger; +using Xrpl.Models.Methods; namespace Xrpl.Client.Json.Converters { @@ -31,10 +32,11 @@ public static BaseLedgerEntry GetBaseRippleLO(LedgerEntryType type, object field LedgerEntryType.PayChannel => JsonConvert.DeserializeObject($"{field}"), LedgerEntryType.RippleState => JsonConvert.DeserializeObject($"{field}"), LedgerEntryType.SignerList => JsonConvert.DeserializeObject($"{field}"), + LedgerEntryType.NFTokenOffer => JsonConvert.DeserializeObject($"{field}"), //LedgerEntryType.NegativeUNL => expr, //LedgerEntryType.NFTokenOffer => expr, - //LedgerEntryType.NFTokenPage => expr, - //LedgerEntryType.Ticket => expr, + LedgerEntryType.NFTokenPage => JsonConvert.DeserializeObject($"{field}"), + LedgerEntryType.Ticket => JsonConvert.DeserializeObject($"{field}"), //LedgerEntryType.Check => expr, //LedgerEntryType.DepositPreauth => expr, _ => throw new ArgumentOutOfRangeException() @@ -81,8 +83,12 @@ public BaseLedgerEntry Create(Type objectType, JObject jObject) return new LORippleState(); case "LOSignerList": return new LOSignerList(); - // case "Ticket": - // return new LOTicket(); + case "LONFTokenOffer": + return new LONFTokenOffer(); + case "LONFTokenPage": + return new LONFTokenPage(); + case "LOTicket": + return new LOTicket(); } string ledgerEntryType = jObject.Property("LedgerEntryType")?.Value.ToString(); @@ -99,8 +105,8 @@ public BaseLedgerEntry Create(Type objectType, JObject jObject) "RippleState" => new LORippleState(), "SignerList" => new LOSignerList(), //"NegativeUNL" => new NegativeUNL(), - //"NFTokenOffer" => new NFTokenOffer(), - //"NFTokenPage" => new NFTokenPage(), + "NFTokenOffer" => new LONFTokenOffer(), + "NFTokenPage" => new LONFTokenPage(), "Ticket" => new LOTicket(), "Check" => new LOCheck(), "DepositPreauth" => new LODepositPreauth(), diff --git a/Xrpl/Client/Json/Converters/TransactionConverter.cs b/Xrpl/Client/Json/Converters/TransactionConverter.cs index ef36d76..857f80e 100644 --- a/Xrpl/Client/Json/Converters/TransactionConverter.cs +++ b/Xrpl/Client/Json/Converters/TransactionConverter.cs @@ -36,9 +36,9 @@ public ITransactionResponseCommon Create(Type objectType, JObject jObject) { "AccountSet" => new AccountSetResponse(), "AccountDelete" => new AccountDeleteResponse(), - "CheckCancel" => new AccountDeleteResponse(), - "CheckCash" => new AccountDeleteResponse(), - "CheckCreate" => new AccountDeleteResponse(), + "CheckCancel" => new CheckCancelResponse(), + "CheckCash" => new CheckCashResponse(), + "CheckCreate" => new CheckCancelResponse(), "DepositPreauth" => new DepositPreauthResponse(), "EscrowCancel" => new EscrowCancelResponse(), "EscrowCreate" => new EscrowCreateResponse(), @@ -58,6 +58,9 @@ public ITransactionResponseCommon Create(Type objectType, JObject jObject) "SignerListSet" => new SignerListSetResponse(), "TicketCreate" => new TicketCreateResponse(), "TrustSet" => new TrustSetResponse(), + "EnableAmendment" => new EnableAmendmentResponse(), + "SetFee" => new SetFeeResponse(), + "UNLModify" => new UNLModifyResponse(), _ => throw new Exception("Can't create transaction type" + transactionType) }; } diff --git a/Xrpl/Client/connection.cs b/Xrpl/Client/connection.cs index e73fdad..ce6b123 100644 --- a/Xrpl/Client/connection.cs +++ b/Xrpl/Client/connection.cs @@ -161,14 +161,16 @@ public async Task Connect() ws.OnConnect(async (ws) => { await OnceOpen(); }); - ws.OnConnectionError(async (e, ws) => { + ws.OnConnectionError(async (e, ws) => + { timer.Stop(); await OnConnectionFailed(e); }); ws.OnMessageReceived(async (m, ws) => { await IOnMessage(m); }); //ws.OnError(async (e, ws) => { await OnConnectionFailed(e); }); - ws.OnDisconnect(async (ws) => { + ws.OnDisconnect(async (ws) => + { timer.Stop(); await OnceClose(1000); }); @@ -284,7 +286,8 @@ private async Task OnceOpen() //this.retryConnectionBackoff.reset(); //this.startHeartbeatInterval(); this.connectionManager.ResolveAllAwaiting(); - await this.OnConnected?.Invoke(); + if (OnConnected is not null) + await this.OnConnected?.Invoke(); } catch (Exception error) { @@ -373,7 +376,7 @@ private async Task IOnMessage(string message) object response = JsonConvert.DeserializeObject(message.ToString()); if (OnLedgerClosed is not null) - await OnLedgerClosed?.Invoke(response)!; + await OnLedgerClosed.Invoke(response)!; break; } case ResponseStreamType.validationReceived: @@ -381,7 +384,7 @@ private async Task IOnMessage(string message) var response = JsonConvert.DeserializeObject(message); if (OnManifestReceived is not null) - await OnManifestReceived?.Invoke(response)!; + await OnManifestReceived.Invoke(response)!; break; } case ResponseStreamType.transaction: @@ -389,7 +392,7 @@ private async Task IOnMessage(string message) var response = JsonConvert.DeserializeObject(message); if (OnTransaction is not null) - await OnTransaction?.Invoke(response)!; + await OnTransaction.Invoke(response)!; break; } case ResponseStreamType.peerStatusChange: @@ -397,7 +400,7 @@ private async Task IOnMessage(string message) var response = JsonConvert.DeserializeObject(message); if (OnPeerStatusChange is not null) - await OnPeerStatusChange?.Invoke(response)!; + await OnPeerStatusChange.Invoke(response)!; break; } case ResponseStreamType.consensusPhase: @@ -405,7 +408,7 @@ private async Task IOnMessage(string message) var response = JsonConvert.DeserializeObject(message); if (OnConsensusPhase is not null) - await OnConsensusPhase?.Invoke(response)!; + await OnConsensusPhase.Invoke(response)!; break; } case ResponseStreamType.path_find: @@ -413,7 +416,14 @@ private async Task IOnMessage(string message) var response = JsonConvert.DeserializeObject(message); if (OnPathFind is not null) - await OnPathFind?.Invoke(response)!; + await OnPathFind.Invoke(response)!; + break; + } + case ResponseStreamType.error: + { + var response = JsonConvert.DeserializeObject(message); + if (OnError is not null) + await OnError.Invoke(response.Error, response.ErrorMessage, response.ErrorCode, response); break; } default: @@ -429,12 +439,12 @@ private async Task IOnMessage(string message) catch (XrplException error) { if (OnError is not null) - await OnError?.Invoke("error", "badMessage", error.Message, error); + await OnError.Invoke("error", "badMessage", error.Message, error); } catch (Exception error) { if (OnError is not null) - await OnError?.Invoke("error", "badMessage", error.Message, error); + await OnError.Invoke("error", "badMessage", error.Message, error); } } } diff --git a/Xrpl/Models/Common/Currency.cs b/Xrpl/Models/Common/Currency.cs index 11c2e05..9c0e4f1 100644 --- a/Xrpl/Models/Common/Currency.cs +++ b/Xrpl/Models/Common/Currency.cs @@ -1,6 +1,9 @@ using Newtonsoft.Json; +using System; using System.Globalization; +using System.Text.RegularExpressions; + using Xrpl.Client.Extensions; //https://xrpl.org/currency-formats.html#currency-formats @@ -51,23 +54,54 @@ public Currency() /// Readable currency name /// [JsonIgnore] - public string CurrencyValidName => CurrencyCode is { Length: > 0 } row ? row.Length > 3 ? row.FromHexString().Trim('\0') : row : string.Empty; + public string CurrencyValidName => CurrencyCode is { Length: > 0 } row + ? row.Length > 3 + ? IsHexCurrencyCode(row) + ? row.FromHexString().Trim('\0') + : row + : row + : string.Empty; /// /// decimal currency amount (drops for XRP) /// [JsonIgnore] public decimal ValueAsNumber { - get => string.IsNullOrWhiteSpace(Value) - ? 0 - : decimal.Parse(Value, - NumberStyles.AllowLeadingSign - | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent) - | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) - | (NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) - | NumberStyles.AllowExponent - | NumberStyles.AllowDecimalPoint, - CultureInfo.InvariantCulture); + get + { + try + { + return string.IsNullOrEmpty(Value) + ? 0 + : decimal.Parse( + Value, + NumberStyles.AllowLeadingSign + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | NumberStyles.AllowExponent + | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture); + + } + catch (Exception e) + { + try + { + var num = double.Parse(Value, (NumberStyles.Float & NumberStyles.AllowExponent) | NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture); + var valid = $"{num:#########e00}"; + if (valid.Contains('+')) + return decimal.MaxValue; + else + return 0; + } + catch (Exception exception) + { + Console.WriteLine(exception); + throw; + } + } + } set => Value = value.ToString(CurrencyCode == "XRP" ? "G0" @@ -100,5 +134,17 @@ public decimal? ValueAsXrp } } } + /// + /// check currency code for HEX + /// + /// currency code + /// + public static bool IsHexCurrencyCode(string code) => Regex.IsMatch(code, @"[0-9a-fA-F]{40}", RegexOptions.IgnoreCase); + #region Overrides of Object + + public override string ToString() => CurrencyValidName == "XRP" ? $"XRP: {ValueAsXrp:0.######}" : $"{CurrencyValidName}: {ValueAsNumber:0.###############}"; + + #endregion + } } diff --git a/Xrpl/Models/Enums.cs b/Xrpl/Models/Enums.cs index 5f3d777..35f73b1 100644 --- a/Xrpl/Models/Enums.cs +++ b/Xrpl/Models/Enums.cs @@ -55,7 +55,22 @@ public enum TransactionType /// Set aside one or more sequence numbers as Tickets. TicketCreate, /// Add or modify a trust line. - TrustSet + TrustSet, + /// + /// An EnableAmendment pseudo-transaction marks a change in the status of a proposed amendment when it:
+ /// * Gains supermajority approval from validators.
+ /// * Loses supermajority approval.
+ /// * Is enabled on the XRP Ledger protocol. + ///
+ EnableAmendment, + /// + /// A SetFee pseudo-transaction marks a change in transaction cost or reserve requirements as a result of Fee Voting. + /// + SetFee, + /// + /// A UNLModify pseudo-transaction marks a change to the Negative UNL, indicating that a trusted validator has gone offline or come back online. + /// + UNLModify } /// /// Each ledger version's state data is a set of ledger objects, sometimes called ledger entries, diff --git a/Xrpl/Models/Ledger/LOAccountRoot.cs b/Xrpl/Models/Ledger/LOAccountRoot.cs index 2797de2..1330df8 100644 --- a/Xrpl/Models/Ledger/LOAccountRoot.cs +++ b/Xrpl/Models/Ledger/LOAccountRoot.cs @@ -53,7 +53,24 @@ public enum AccountRootFlags : uint /// This account can only receive funds from transactions it sends, and from preauthorized accounts.
/// (It has DepositAuth enabled.) ///
- lsfDepositAuth = 16777216 + lsfDepositAuth = 16777216, + /// + /// This account blocks incoming trust lines. (Requires the DisallowIncoming amendment .) + /// + lsfDisallowIncomingTrustline = 536870912, + /// + /// This account blocks incoming Payment Channels. (Requires the DisallowIncoming amendment .) + /// + lsfDisallowIncomingPayChan = 268435456, + /// + /// This account blocks incoming NFTokenOffers. (Requires the DisallowIncoming amendment .) + /// + lsfDisallowIncomingNFTokenOffer = 67108864, + /// + /// This account blocks incoming Checks. (Requires the DisallowIncoming amendment .) + /// + lsfDisallowIncomingCheck = 134217728, + } /// /// The AccountRoot object type describes a single account, its settings, and XRP balance. @@ -140,7 +157,7 @@ public LOAccountRoot() /// /// (Optional) Another account that is authorized to mint non-fungible tokens on behalf of this account. /// - public uint? NFTokenMinter { get; set; } + public string? NFTokenMinter { get; set; } /// /// (Optional) How many Tickets this account owns in the ledger. /// This is updated automatically to ensure that the account stays within the hard limit of 250 Tickets at a time. diff --git a/Xrpl/Models/Ledger/LONFTokenOffer.cs b/Xrpl/Models/Ledger/LONFTokenOffer.cs new file mode 100644 index 0000000..9f944c5 --- /dev/null +++ b/Xrpl/Models/Ledger/LONFTokenOffer.cs @@ -0,0 +1,81 @@ +using Newtonsoft.Json; + +using System; + +using Xrpl.Client.Json.Converters; +using Xrpl.Models.Common; +using Xrpl.Models.Ledger; + +namespace Xrpl.Models.Methods +{ + public enum NFTokenOffer + { + /// + /// If enabled, the offer is a sell offer. + /// Otherwise, the offer is a buy offer. + /// + lsfSellNFToken = 0x00000001 + } + public class LONFTokenOffer : BaseLedgerEntry + { + + public LONFTokenOffer() + { + //The type of ledger object (0x0074). + LedgerEntryType = LedgerEntryType.NFTokenOffer; + } + /// + /// A set of flags associated with this object, used to specify various options or settings. Flags are listed in the table below. + /// + public uint Flags { get; set; } + + /// + /// Amount expected or offered for the NFToken. If the token has the lsfOnlyXRP flag set, the amount must be specified in XRP.
+ /// Sell offers that specify assets other than XRP must specify a non-zero amount.
+ /// Sell offers that specify XRP can be 'free' (that is, the Amount field can be equal to "0"). + ///
+ [JsonConverter(typeof(CurrencyConverter))] + public Currency Amount { get; set; } + + /// + /// The AccountID for which this offer is intended. If present, only that account can accept the offer. + /// + public string Destination { get; set; } + /// + /// The time after which the offer is no longer active. The value is the number of seconds since the Ripple Epoch. + /// + [JsonConverter(typeof(RippleDateTimeConverter))] + public DateTime? Expiration { get; set; } + /// + /// Internal bookkeeping, indicating the page inside the token buy or sell offer directory, as appropriate, where this token is being tracked. + /// This field allows the efficient deletion of offers. + /// + public string NFTokenOfferNode { get; set; } + /// + /// NFTokenID of the NFToken object referenced by this offer. + /// + public string NFTokenID { get; set; } + /// + /// Owner of the account that is creating and owns the offer. + /// Only the current Owner of an NFToken can create an offer to sell an NFToken, + /// but any account can create an offer to buy an NFToken. + /// + public string Owner { get; set; } + /// + /// Internal bookkeeping, indicating the page inside the owner directory where this token is being tracked. + /// This field allows the efficient deletion of offers. + /// + public string OwnerNode { get; set; } + /// + /// Identifying hash of the transaction that most recently modified this object. + /// + [JsonProperty("PreviousTxnID")] + public string PreviousTransactionId { get; set; } + /// + /// Index of the ledger that contains the transaction that most recently modified this object. + /// + [JsonProperty("PreviousTxnLgrSeq")] + public uint PreviousTransactionLedgerSequence { get; set; } + + } +} diff --git a/Xrpl/Models/Ledger/LONFTokenPage.cs b/Xrpl/Models/Ledger/LONFTokenPage.cs new file mode 100644 index 0000000..2a7cdb0 --- /dev/null +++ b/Xrpl/Models/Ledger/LONFTokenPage.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using Xrpl.Models.Ledger; + +namespace Xrpl.Models.Methods; + +public class LONFTokenPage : BaseLedgerEntry +{ + + public LONFTokenPage() + { + //The type of ledger object (0x0074). + LedgerEntryType = LedgerEntryType.NFTokenPage; + } + public string Flags { get; set; } + /// + /// The locator of the next page, if any. Details about this field and how it should be used are outlined below. + /// + public string NFTokenPage { get; set; } + + /// + /// The collection of NFToken objects contained in this NFTokenPage object. + /// This specification places an upper bound of 32 NFToken objects per page. + /// Objects are sorted from low to high with the NFTokenID used as the sorting parameter.. + /// + public List NFTokens { get; set; } + + /// + /// The locator of the previous page, if any. Details about this field and how it should be used are outlined below. + /// + public string PreviousPageMin { get; set; } + /// + /// Identifies the transaction ID of the transaction that most recently modified this NFTokenPage object. + /// + public String PreviousTxnID { get; set; } + /// + /// The sequence of the ledger that contains the transaction that most recently modified this NFTokenPage object. + /// + public long PreviousTxnLgrSeq { get; set; } +} + + +public class NFTPage +{ + public string Flags { get; set; } + public List> NFTokens { get; set; } + + public List NFTs => NFTokens.SelectMany(c => c.Values).ToList(); +} + +public class NFToken +{ + public string NFTokenID { get; set; } + public string URI { get; set; } +} \ No newline at end of file diff --git a/Xrpl/Models/Methods/AccountLines.cs b/Xrpl/Models/Methods/AccountLines.cs index 5c70f7c..0b06304 100644 --- a/Xrpl/Models/Methods/AccountLines.cs +++ b/Xrpl/Models/Methods/AccountLines.cs @@ -98,7 +98,13 @@ public class TrustLine /// The maximum amount of currency that the issuer account is willing to owe the perspective account. ///
[JsonIgnore] - public decimal LimitAsNumber => decimal.Parse(Limit, NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture); + public double LimitAsNumber => double.Parse(Limit, NumberStyles.AllowLeadingSign + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | NumberStyles.AllowExponent + | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture); /// /// The maximum amount of currency that the issuer account is willing to owe the perspective account. /// @@ -106,7 +112,13 @@ public class TrustLine public string LimitPeer { get; set; } [JsonIgnore] - public decimal LimitPeerAsNumber => decimal.Parse(LimitPeer, NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture); + public decimal LimitPeerAsNumber => decimal.Parse(LimitPeer, NumberStyles.AllowLeadingSign + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | NumberStyles.AllowExponent + | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture); /// /// Rate at which the account values incoming balances on this trust line, as a ratio of this value per 1 billion units.
/// (For example, a value of 500 million represents a 0.5:1 ratio.)
diff --git a/Xrpl/Models/Methods/AccountNFTs.cs b/Xrpl/Models/Methods/AccountNFTs.cs index 1e3ceb9..7cc33c1 100644 --- a/Xrpl/Models/Methods/AccountNFTs.cs +++ b/Xrpl/Models/Methods/AccountNFTs.cs @@ -51,6 +51,20 @@ public class AccountNFTs //todo rename to response ///
public class NFT { + /// + /// A bit-map of boolean flags enabled for this NFToken.
+ /// See NFToken Flags for possible values. + ///
+ [JsonProperty("Flags")] + public string Flags { get; set; } + /// + /// The TransferFee value specifies the percentage fee, in units of 1/100,000, charged by the issuer for secondary sales of the token. + /// Valid values for this field are between 0 and 50,000, inclusive. + /// A value of 1 is equivalent to 0.001% or 1/10 of a basis point (bps), allowing transfer rates between 0% and 50%. + /// + [JsonProperty("TransferFee")] + public string TransferFee { get; set; } + [JsonProperty("account")] public string Account { get; set; } //todo unknown field /// @@ -61,7 +75,7 @@ public class NFT /// /// The unique identifier of this NFToken, in hexadecimal. /// - [JsonProperty("nft_id")] + [JsonProperty("NFTokenID")] public string NFTokenID { get; set; } /// /// The unscrambled version of this token's taxon. Several tokens with the same taxon might represent instances of a limited series. @@ -71,7 +85,7 @@ public class NFT /// /// The URI data associated with this NFToken, in hexadecimal. /// - [JsonProperty("uri")] + [JsonProperty("URI")] public string URI { get; set; } [JsonIgnore] @@ -84,8 +98,6 @@ public class NFT /// [JsonProperty("nft_serial")] public string NFTSerial { get; set; } - - //todo not found field Flags: number (https://xrpl.org/nftoken.html#nftoken-flags) } /// /// The `account_nfts` method retrieves all of the NFTs currently owned by the specified account. diff --git a/Xrpl/Models/Transactions/AccountSet.cs b/Xrpl/Models/Transactions/AccountSet.cs index e0cf164..991eb58 100644 --- a/Xrpl/Models/Transactions/AccountSet.cs +++ b/Xrpl/Models/Transactions/AccountSet.cs @@ -5,13 +5,13 @@ using System.Linq; using System.Threading.Tasks; using Xrpl.Client.Exceptions; - +// https://github.com/XRPLF/xrpl.js/blob/b20c05c3680d80344006d20c44b4ae1c3b0ffcac/packages/xrpl/src/models/transactions/accountSet.ts#L11 namespace Xrpl.Models.Transactions { /// /// Enum for AccountSet Flags. /// - public enum AccountSetTfFlags //todo rename to AccountSetAsfFlags https://github.com/XRPLF/xrpl.js/blob/b20c05c3680d80344006d20c44b4ae1c3b0ffcac/packages/xrpl/src/models/transactions/accountSet.ts#L11 + public enum AccountSetAsfFlags { /// /// Require a destination tag to send transactions to this account. @@ -97,6 +97,8 @@ public AccountSet(string account) : this() public uint? TransferRate { get; set; } /// public uint? TickSize { get; set; } + /// + public string NFTokenMinter { get; set; } } /// @@ -136,7 +138,10 @@ public interface IAccountSet : ITransactionCommon /// Valid values are 3 to 15 inclusive, or 0 to disable. /// uint? TickSize { get; set; } - + /// + /// Optional) Another account that can mint NFTokens for you. + /// + public string NFTokenMinter { get; set; } //todo not found field NFTokenMinter?: string } @@ -157,6 +162,9 @@ public class AccountSetResponse : TransactionResponseCommon, IAccountSet public uint? TransferRate { get; set; } /// public uint? TickSize { get; set; } + + /// + public string NFTokenMinter { get; set; } } public partial class Validation @@ -178,7 +186,7 @@ public static async Task ValidateAccountSet(Dictionary tx) if (ClearFlag is not uint { } flag ) throw new ValidationException("AccountSet: invalid ClearFlag"); - if (Enum.GetValues().All(c => (uint)c != flag)) + if (Enum.GetValues().All(c => (uint)c != flag)) throw new ValidationException("AccountSet: invalid ClearFlag"); } if (tx.TryGetValue("Domain", out var Domain) && Domain is not string { }) @@ -195,7 +203,7 @@ public static async Task ValidateAccountSet(Dictionary tx) if (SetFlag is not uint { }) throw new ValidationException("AccountSet: invalid SetFlag"); - if (Enum.GetValues().All(c => (uint)c != SetFlag)) + if (Enum.GetValues().All(c => (uint)c != SetFlag)) throw new ValidationException("AccountSet: missing field Destination"); } diff --git a/Xrpl/Models/Transactions/Common.cs b/Xrpl/Models/Transactions/Common.cs index 35f938f..2a36265 100644 --- a/Xrpl/Models/Transactions/Common.cs +++ b/Xrpl/Models/Transactions/Common.cs @@ -14,6 +14,7 @@ using Xrpl.Models.Common; using Xrpl.Models.Ledger; using Xrpl.Models.Utils; + using Index = Xrpl.Models.Utils.Index; // https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/models/transactions/common.ts @@ -77,7 +78,7 @@ public static bool IsSigner(dynamic signer) /// Whether the Memo is malformed. public static bool IsMemo(dynamic memo) { - if (memo is not Dictionary { } value) + if (memo is not Dictionary { } value) return false; var size = value.Count; @@ -251,6 +252,7 @@ public abstract class TransactionCommon : ITransactionCommon //todo rename to Ba public uint? LastLedgerSequence { get; set; } /// public List Memos { get; set; } + /// public uint? Sequence { get; set; } /// @@ -298,6 +300,11 @@ public string ToJson() return JsonConvert.SerializeObject(this, serializerSettings); } + /// + public uint? SourceTag { get; set; } + /// + public uint? TicketSequence { get; set; } + //todo not found fields - SourceTag?: number, TicketSequence?: number } @@ -576,6 +583,8 @@ public interface ITransactionCommon /// Additional arbitrary information used to identify this transaction. /// List Memos { get; set; } + [JsonIgnore] + public string MemoValue => Memos is not { } memos ? null : memos.Aggregate(string.Empty, (current, memo) => current + $"{memo.Memo.MemoData.FromHexString()}"); /// /// Transaction metadata is a section of data that gets added to a transaction after it is processed.
@@ -622,7 +631,17 @@ public interface ITransactionCommon ///
/// string ToJson(); - //todo not found fields - SourceTag: Number (UInt32), TicketSequence:Number(UInt32), TxnSignature:string + /// + /// (Optional) Arbitrary integer used to identify the reason for this payment, or a sender on whose behalf this transaction is made.
+ /// Conventionally, a refund should specify the initial payment's SourceTag as the refund payment's DestinationTag. + ///
+ public uint? SourceTag { get; set; } + /// + /// (Optional) The sequence number of the ticket to use in place of a Sequence number.
+ /// If this is provided, Sequence must be 0. Cannot be used with AccountTxnID. + ///
+ public uint? TicketSequence { get; set; } + } /// @@ -654,6 +673,8 @@ public abstract class TransactionResponseCommon : BaseTransactionResponse, ITran /// public List Memos { get; set; } + [JsonIgnore] + public string MemoValue => Memos is not { } memos ? null : memos.Aggregate(string.Empty, (current, memo) => current + $"{memo.Memo.MemoData.FromHexString()}"); /// public uint? Sequence { get; set; } /// @@ -681,8 +702,13 @@ public string ToJson() JsonSerializerSettings serializerSettings = new JsonSerializerSettings(); serializerSettings.NullValueHandling = NullValueHandling.Ignore; serializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc; - + return JsonConvert.SerializeObject(this, serializerSettings); } + + /// + public uint? SourceTag { get; set; } + /// + public uint? TicketSequence { get; set; } } } diff --git a/Xrpl/Models/Transactions/EnableAmendment.cs b/Xrpl/Models/Transactions/EnableAmendment.cs new file mode 100644 index 0000000..1a342c4 --- /dev/null +++ b/Xrpl/Models/Transactions/EnableAmendment.cs @@ -0,0 +1,40 @@ +//https://xrpl.org/enableamendment.html +namespace Xrpl.Models.Transactions +{ + public class EnableAmendment : TransactionCommon, IEnableAmendment + { + public EnableAmendment() + { + TransactionType = TransactionType.EnableAmendment; + } + + /// + public string Amendment { get; set; } + + /// + public uint LedgerSequence { get; set; } + } + + public interface IEnableAmendment : ITransactionCommon + { + /// + /// A unique identifier for the amendment.
+ /// This is not intended to be a human-readable name.
+ /// See Amendments for a list of known amendments. + ///
+ string Amendment { get; set; } + /// + /// The ledger index where this pseudo-transaction appears.
+ /// This distinguishes the pseudo-transaction from other occurrences of the same change. + ///
+ uint LedgerSequence { get; set; } + } + + public class EnableAmendmentResponse : TransactionResponseCommon, IEnableAmendment + { + /// + public string Amendment { get; set; } + /// + public uint LedgerSequence { get; set; } + } +} diff --git a/Xrpl/Models/Transactions/SetFee.cs b/Xrpl/Models/Transactions/SetFee.cs new file mode 100644 index 0000000..54bba13 --- /dev/null +++ b/Xrpl/Models/Transactions/SetFee.cs @@ -0,0 +1,67 @@ +//https://xrpl.org/setfee.html +namespace Xrpl.Models.Transactions +{ + public class SetFee : TransactionCommon, ISetFee + { + public SetFee() + { + TransactionType = TransactionType.SetFee; + } + + /// + public string BaseFee { get; set; } + + /// + public uint ReferenceFeeUnits { get; set; } + + /// + public uint ReserveBase { get; set; } + + /// + public uint ReserveIncrement { get; set; } + + /// + public uint LedgerSequence { get; set; } + } + + public interface ISetFee : ITransactionCommon + { + /// + /// The charge, in drops of XRP, for the reference transaction, as hex.
+ /// (This is the transaction cost before scaling for load.) + ///
+ string BaseFee { get; set; } + /// + /// (Omitted for some historical SetFee pseudo-transactions)
+ /// The index of the ledger version where this pseudo-transaction appears.
+ /// This distinguishes the pseudo-transaction from other occurrences of the same change. + ///
+ uint LedgerSequence { get; set; } + /// + /// The cost, in fee units, of the reference transaction + /// + uint ReferenceFeeUnits { get; set; } + /// + /// The base reserve, in drops + /// + uint ReserveBase { get; set; } + /// + /// The incremental reserve, in drops + /// + uint ReserveIncrement { get; set; } + } + + public class SetFeeResponse : TransactionResponseCommon, ISetFee + { + /// + public string BaseFee { get; set; } + /// + public uint LedgerSequence { get; set; } + /// + public uint ReferenceFeeUnits { get; set; } + /// + public uint ReserveBase { get; set; } + /// + public uint ReserveIncrement { get; set; } + } +} diff --git a/Xrpl/Models/Transactions/UNLModify.cs b/Xrpl/Models/Transactions/UNLModify.cs new file mode 100644 index 0000000..faaec50 --- /dev/null +++ b/Xrpl/Models/Transactions/UNLModify.cs @@ -0,0 +1,55 @@ +//https://xrpl.org/unlmodify.html +namespace Xrpl.Models.Transactions +{ + public class UNLModify : TransactionCommon, IUNLModify + { + public UNLModify() + { + //The value 0x0066, mapped to the string UNLModify, indicates that this object is an UNLModify pseudo-transaction + TransactionType = TransactionType.UNLModify; + } + + /// + public string UNLModifyValidator { get; set; } + + /// + public uint UNLModifyDisabling { get; set; } + + /// + public uint LedgerSequence { get; set; } + } + + public interface IUNLModify : ITransactionCommon + { + /// + /// The validator to add or remove, as identified by its master public key. + /// + string UNLModifyValidator { get; set; } + + /// + /// If 1, this change represents adding a validator to the Negative UNL.
+ /// If 0, this change represents removing a validator from the Negative UNL.
+ /// (No other values are allowed.) + ///
+ uint UNLModifyDisabling { get; set; } + + /// + /// The ledger index where this pseudo-transaction appears.
+ /// This distinguishes the pseudo-transaction from other occurrences of the same change. + ///
+ uint LedgerSequence { get; set; } + } + + public class UNLModifyResponse : TransactionResponseCommon, IUNLModify + { + /// + public string UNLModifyValidator { get; set; } + + /// + public uint UNLModifyDisabling { get; set; } + + /// + public uint LedgerSequence { get; set; } + } + +} diff --git a/Xrpl/Models/Utils/Flags.cs b/Xrpl/Models/Utils/Flags.cs index 741ab2b..a67e243 100644 --- a/Xrpl/Models/Utils/Flags.cs +++ b/Xrpl/Models/Utils/Flags.cs @@ -71,7 +71,7 @@ public static uint ConvertAccountSetFlagsToNumber(dynamic flags) { if (flags is not Dictionary flag) return 0; - return ReduceFlags(flag, Enum.GetValues().ToDictionary(c => c.ToString(), c => (uint)c)); + return ReduceFlags(flag, Enum.GetValues().ToDictionary(c => c.ToString(), c => (uint)c)); } public static uint ConvertOfferCreateFlagsToNumber(dynamic flags) diff --git a/Xrpl/Utils/StringConversion.cs b/Xrpl/Utils/StringConversion.cs index f342a9c..8be82e0 100644 --- a/Xrpl/Utils/StringConversion.cs +++ b/Xrpl/Utils/StringConversion.cs @@ -1,6 +1,8 @@ using System; using System.Globalization; using System.Text; +using System.Text.RegularExpressions; + using Org.BouncyCastle.Utilities.Encoders; // https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/utils/stringConversion.ts diff --git a/Xrpl/Wallet/XrplWallet.cs b/Xrpl/Wallet/XrplWallet.cs index 12bdfca..61fe97e 100644 --- a/Xrpl/Wallet/XrplWallet.cs +++ b/Xrpl/Wallet/XrplWallet.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using System.Collections.Generic; @@ -6,6 +7,7 @@ using Xrpl.BinaryCodec; using Xrpl.Client.Exceptions; using Xrpl.Keypairs; +using Xrpl.Models.Transactions; using Xrpl.Utils.Hashes; // https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/Wallet/index.ts @@ -132,6 +134,18 @@ public SignatureResult Sign(Dictionary transaction, bool multis //this.checkTxSerialization(serialized, tx); return new SignatureResult(serialized, HashLedger.HashSignedTx(serialized)); } + /// + /// Signs a transaction offline. + /// + /// A transaction to be signed offline. + /// Specify true/false to use multisign or actual address (classic/x-address) to make multisign tx request. + /// + /// A Wallet derived from the seed. + public SignatureResult Sign(ITransactionCommon tx, bool multisign = false, string? signingFor = null) + { + Dictionary txJson = JsonConvert.DeserializeObject>(tx.ToJson()); + return Sign(txJson, multisign, signingFor); + } /// /// Verifies a signed transaction offline. @@ -156,6 +170,14 @@ public string ComputeSignature(Dictionary transaction, string p string encoded = XrplBinaryCodec.EncodeForSigning(transaction); return XrplKeypairs.Sign(AddressCodec.Utils.FromHexToBytes(encoded), privateKey); } - + /// + /// Creates a Wallet from xumm numbers. + /// + /// A Wallet from xumm numbers. + public static XrplWallet FromXummNumbers(string[] numbers, string algorithm = "secp256k1") + { + byte[] entropy = XummExtension.EntropyFromXummNumbers(numbers); + return FromEntropy(entropy,null, algorithm); + } } } \ No newline at end of file diff --git a/Xrpl/Wallet/XummExtension.cs b/Xrpl/Wallet/XummExtension.cs new file mode 100644 index 0000000..f19c835 --- /dev/null +++ b/Xrpl/Wallet/XummExtension.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Xrpl.Wallet +{ + /// + /// xumm from number extensions + /// + public static class XummExtension + { + /// + /// generate Entropy from xumm numbers + /// + /// xumm numbers + /// byte[] Entropy + /// when wrong has wrong digits + public static byte[] EntropyFromXummNumbers(string[] numbers) + { + if (!CheckXummNumbers(numbers)) + throw new ArgumentException("Wrong numbers"); + + var vals = numbers.Select(x => $"0000{int.Parse(x.Substring(0, 5)):X}"[^4..]).ToArray(); + + var buffer = new List(); + foreach (var val in vals) + { + var v = Enumerable.Range(0, val.Length) + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(val.Substring(x, 2), 16)) + .ToArray(); + buffer.AddRange(v); + + } + return buffer.ToArray(); + } + + /// + /// xumm numbers validation + /// + /// xum numbers + /// + public static bool CheckXummNumbers(string[] numbers) => numbers.Select((n, i) => CheckXummSum(i, n)).All(c => c); + + /// + /// xumm validation for part od numbers + /// + /// numbers position + /// xum numbers + /// + public static bool CheckXummSum(int position, string number) + { + if (number.Length != 6) + return false; + + var checkSum = int.Parse(number[5..]); + var value = int.Parse(number[..5]); + var sum = value * (position * 2 + 1) % 9; + return sum == checkSum; + } + + } +} diff --git a/Xrpl/Xrpl.csproj b/Xrpl/Xrpl.csproj index 34fcf2f..36d369b 100644 --- a/Xrpl/Xrpl.csproj +++ b/Xrpl/Xrpl.csproj @@ -2,7 +2,7 @@ - 9.0 + latest net6.0 Xrpl true From 2969b0a835b702d536f1b11b9bb82e972b778580 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Sun, 11 Jun 2023 19:11:52 +0300 Subject: [PATCH 12/39] TxFormat fix --- Xrpl/Models/Transactions/TxFormat.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Xrpl/Models/Transactions/TxFormat.cs b/Xrpl/Models/Transactions/TxFormat.cs index c351591..dfdd34e 100644 --- a/Xrpl/Models/Transactions/TxFormat.cs +++ b/Xrpl/Models/Transactions/TxFormat.cs @@ -24,6 +24,7 @@ public TxFormat() this[Field.Fee] = Requirement.Required; this[Field.Sequence] = Requirement.Required; this[Field.SigningPubKey] = Requirement.Required; + this[Field.TicketSequence] = Requirement.Optional; this[Field.Flags] = Requirement.Optional; this[Field.SourceTag] = Requirement.Optional; @@ -134,7 +135,8 @@ static TxFormat() [Field.TransferRate] = Requirement.Optional, [Field.SetFlag] = Requirement.Optional, [Field.ClearFlag] = Requirement.Optional, - [Field.TickSize] = Requirement.Optional + [Field.TickSize] = Requirement.Optional, + [Field.NFTokenMinter] = Requirement.Optional }, [BinaryCodec.Types.TransactionType.EscrowCancel] = new TxFormat { @@ -161,7 +163,8 @@ static TxFormat() [BinaryCodec.Types.TransactionType.TicketCreate] = new TxFormat { [Field.Target] = Requirement.Optional, - [Field.Expiration] = Requirement.Optional + [Field.Expiration] = Requirement.Optional, + [Field.TicketCount] = Requirement.Required }, // 11 [BinaryCodec.Types.TransactionType.SignerListSet] = new TxFormat @@ -245,7 +248,8 @@ static TxFormat() }, [BinaryCodec.Types.TransactionType.NFTokenBurn] = new TxFormat { - [Field.NFTokenID] = Requirement.Required + [Field.NFTokenID] = Requirement.Required, + [Field.Owner] = Requirement.Optional }, [BinaryCodec.Types.TransactionType.NFTokenCreateOffer] = new TxFormat { From 72151fbfbb3a5fc39930a2752454cfce11b70ee8 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Wed, 28 Jun 2023 10:38:44 +0300 Subject: [PATCH 13/39] Meta serializer --- Xrpl/Client/Json/Converters/MetaBinaryConverter.cs | 2 +- Xrpl/Sugar/Submit.cs | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Xrpl/Client/Json/Converters/MetaBinaryConverter.cs b/Xrpl/Client/Json/Converters/MetaBinaryConverter.cs index f21cc48..fcba25d 100644 --- a/Xrpl/Client/Json/Converters/MetaBinaryConverter.cs +++ b/Xrpl/Client/Json/Converters/MetaBinaryConverter.cs @@ -16,7 +16,7 @@ public class MetaBinaryConverter : JsonConverter /// Cannot write this object type public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { - throw new NotImplementedException(); + serializer.Serialize(writer, value); } /// read from json object /// json reader diff --git a/Xrpl/Sugar/Submit.cs b/Xrpl/Sugar/Submit.cs index 7d09fe4..692c6ae 100644 --- a/Xrpl/Sugar/Submit.cs +++ b/Xrpl/Sugar/Submit.cs @@ -90,10 +90,11 @@ public static async Task SubmitAndWait(this IXrplClie /// public static async Task SubmitRequest(this IXrplClient client, object signedTransaction, bool failHard) { - if (!IsSigned(signedTransaction)) - { - throw new ValidationException("Transaction must be signed"); - } + //todo activate after fix + //if (!IsSigned(signedTransaction)) + //{ + // throw new ValidationException("Transaction must be signed"); + //} string signedTxEncoded = signedTransaction is string transaction ? transaction : XrplBinaryCodec.Encode(signedTransaction); SubmitRequest request = new SubmitRequest { Command = "submit", TxBlob = signedTxEncoded, FailHard = failHard }; From 64b9d9cace8140a08b0180fd343dde1435daeaad Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Sun, 30 Jul 2023 23:42:27 +0300 Subject: [PATCH 14/39] submit from server with sequence --- Xrpl/Models/Transactions/Submit.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Xrpl/Models/Transactions/Submit.cs b/Xrpl/Models/Transactions/Submit.cs index d4bb002..f94f811 100644 --- a/Xrpl/Models/Transactions/Submit.cs +++ b/Xrpl/Models/Transactions/Submit.cs @@ -33,6 +33,11 @@ public class Submit //todo rename to SubmitResponse extends BaseResponse [JsonProperty("tx_blob")] public string TxBlob { get; set; } + /// + /// Next account sequence number. + /// + [JsonProperty("account_sequence_next")] + public uint? AccountSequenceNext { get; set; } /// /// The complete transaction in JSON format. /// From 2d6467882d3289c25684ad43de1c4ac1948fb993 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Sun, 30 Jul 2023 23:43:27 +0300 Subject: [PATCH 15/39] quality must be decimal (23 digits after dot) --- Xrpl/Models/Transactions/BookOffers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Xrpl/Models/Transactions/BookOffers.cs b/Xrpl/Models/Transactions/BookOffers.cs index 9bff168..83226ca 100644 --- a/Xrpl/Models/Transactions/BookOffers.cs +++ b/Xrpl/Models/Transactions/BookOffers.cs @@ -175,6 +175,6 @@ public decimal AmountEach /// For fairness, offers that have the same quality are automatically taken first-in, first-out. /// [JsonProperty("quality")] - public double? Quality { get; set; } + public decimal? Quality { get; set; } } } From ace8f073292906aa475662a8f4b7afd5c27dbc33 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Fri, 4 Aug 2023 19:57:13 +0300 Subject: [PATCH 16/39] limit must be double --- Xrpl/Models/Methods/AccountLines.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Xrpl/Models/Methods/AccountLines.cs b/Xrpl/Models/Methods/AccountLines.cs index 0b06304..2625e84 100644 --- a/Xrpl/Models/Methods/AccountLines.cs +++ b/Xrpl/Models/Methods/AccountLines.cs @@ -112,7 +112,7 @@ public class TrustLine public string LimitPeer { get; set; } [JsonIgnore] - public decimal LimitPeerAsNumber => decimal.Parse(LimitPeer, NumberStyles.AllowLeadingSign + public double LimitPeerAsNumber => double.Parse(LimitPeer, NumberStyles.AllowLeadingSign | (NumberStyles.AllowLeadingSign & NumberStyles.AllowDecimalPoint) | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent) | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) From 9d9c8c2186dcdf31c9984b140698777782acf36f Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Sun, 6 Aug 2023 17:40:33 +0300 Subject: [PATCH 17/39] FundWallet to interface --- Xrpl/Wallet/FundWallet.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Xrpl/Wallet/FundWallet.cs b/Xrpl/Wallet/FundWallet.cs index 823180d..1f7784a 100644 --- a/Xrpl/Wallet/FundWallet.cs +++ b/Xrpl/Wallet/FundWallet.cs @@ -105,7 +105,7 @@ public static class FaucetNetwork public static readonly string NFTDevnet = "faucet-nft.ripple.com"; } - public static async Task FundWallet(XrplClient client, XrplWallet? wallet = null, string? faucetHost = null) + public static async Task FundWallet(IXrplClient client, XrplWallet? wallet = null, string? faucetHost = null) { //if (!client.IsConnected()) //{ @@ -138,7 +138,7 @@ public static async Task FundWallet(XrplClient client, XrplWallet? walle public static async Task ReturnPromise( Dictionary options, - XrplClient client, + IXrplClient client, double startingBalance, XrplWallet walletToFund, string postBody @@ -161,7 +161,7 @@ string postBody } public static Dictionary GetHTTPOptions( - XrplClient client, + IXrplClient client, byte[] postBody, string hostname ) @@ -182,7 +182,7 @@ string hostname public static async Task OnEnd( HttpResponseMessage response, byte[] chunks, - XrplClient client, + IXrplClient client, double startingBalance, XrplWallet walletToFund ) @@ -212,7 +212,7 @@ XrplWallet walletToFund } public static async Task ProcessSuccessfulResponse( - XrplClient client, + IXrplClient client, string body, double startingBalance, XrplWallet walletToFund @@ -267,7 +267,7 @@ await GetUpdatedBalance( private static double _originalBalance; private static string _address; - private static XrplClient _client; + private static IXrplClient _client; private static async void OnTimedEventAsync(Object source, ElapsedEventArgs e) { @@ -310,7 +310,7 @@ private static async void OnTimedEventAsync(Object source, ElapsedEventArgs e) } public static async Task GetUpdatedBalance( - XrplClient client, + IXrplClient client, string address, double originalBalance ) @@ -330,7 +330,7 @@ double originalBalance return finalBalance; } - public static string GetFaucetHost(XrplClient client) + public static string GetFaucetHost(IXrplClient client) { string connectionUrl = client.Url(); // 'altnet' for Ripple Testnet server and 'testnet' for XRPL Labs Testnet server From 6aca1a0b58c33547754e44cdaf9803a8a1498ec2 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Sun, 6 Aug 2023 17:51:32 +0300 Subject: [PATCH 18/39] catch exception type must be XrplException --- Xrpl/Wallet/FundWallet.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Xrpl/Wallet/FundWallet.cs b/Xrpl/Wallet/FundWallet.cs index 1f7784a..475b2a1 100644 --- a/Xrpl/Wallet/FundWallet.cs +++ b/Xrpl/Wallet/FundWallet.cs @@ -287,6 +287,10 @@ private static async void OnTimedEventAsync(Object source, ElapsedEventArgs e) try { newBalance = Convert.ToDouble(await _client.GetXrpBalance(_address)); + } + catch (XrplException err) + { + } catch (RippleException err) { From a42d2ba29fc75a8c90f30d757ed0b369a90a15e2 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Sun, 20 Aug 2023 21:26:21 +0300 Subject: [PATCH 19/39] max and min decimal fix --- Xrpl/Models/Common/Currency.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Xrpl/Models/Common/Currency.cs b/Xrpl/Models/Common/Currency.cs index 8435d2d..91bbbec 100644 --- a/Xrpl/Models/Common/Currency.cs +++ b/Xrpl/Models/Common/Currency.cs @@ -90,10 +90,11 @@ public decimal ValueAsNumber { var num = double.Parse(Value, (NumberStyles.Float & NumberStyles.AllowExponent) | NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture); var valid = $"{num:#########e00}"; - if (valid.Contains('+')) - return decimal.MaxValue; - else + if (valid.Contains("e-")) return 0; + if (valid.Contains('-')) + return decimal.MinValue; + return decimal.MaxValue; } catch (Exception exception) { From 6478d9317bbbfeca69736bb5221d66e8e93341ae Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Sun, 20 Aug 2023 21:28:25 +0300 Subject: [PATCH 20/39] NFTokenPage parser fix --- .../Json/Converters/LONFTokenConverter.cs | 51 +++++++++++++++++++ Xrpl/Models/Ledger/LONFTokenPage.cs | 14 ++--- 2 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 Xrpl/Client/Json/Converters/LONFTokenConverter.cs diff --git a/Xrpl/Client/Json/Converters/LONFTokenConverter.cs b/Xrpl/Client/Json/Converters/LONFTokenConverter.cs new file mode 100644 index 0000000..a8d5be9 --- /dev/null +++ b/Xrpl/Client/Json/Converters/LONFTokenConverter.cs @@ -0,0 +1,51 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Xrpl.Models.Ledger; +using Xrpl.Models.Methods; + +namespace Xrpl.Client.Json.Converters; + +/// +/// json converter +/// +public class LONFTokenConverter : JsonConverter +{ + + /// + /// write to json object + /// + /// writer + /// value + /// json serializer + /// Can't create ledger type + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + + + /// read from json object + /// json reader + /// object type + /// object value + /// json serializer + /// + /// Cannot convert value + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + JObject jObject = JObject.Load(reader); + var value = jObject.GetValue("NFToken"); + var target = new NFToken(); + serializer.Populate(value.CreateReader(), target); + + return target; + } + + public override bool CanConvert(Type objectType) + { + throw new NotImplementedException(); + } + + public override bool CanWrite => false; +} \ No newline at end of file diff --git a/Xrpl/Models/Ledger/LONFTokenPage.cs b/Xrpl/Models/Ledger/LONFTokenPage.cs index 2a7cdb0..c060b0a 100644 --- a/Xrpl/Models/Ledger/LONFTokenPage.cs +++ b/Xrpl/Models/Ledger/LONFTokenPage.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; +using Xrpl.Client.Json.Converters; using Xrpl.Models.Ledger; namespace Xrpl.Models.Methods; @@ -25,7 +27,7 @@ public LONFTokenPage() /// This specification places an upper bound of 32 NFToken objects per page. /// Objects are sorted from low to high with the NFTokenID used as the sorting parameter.. ///
- public List NFTokens { get; set; } + public List NFTokens { get; set; } /// /// The locator of the previous page, if any. Details about this field and how it should be used are outlined below. @@ -41,15 +43,7 @@ public LONFTokenPage() public long PreviousTxnLgrSeq { get; set; } } - -public class NFTPage -{ - public string Flags { get; set; } - public List> NFTokens { get; set; } - - public List NFTs => NFTokens.SelectMany(c => c.Values).ToList(); -} - +[JsonConverter(typeof(LONFTokenConverter))] public class NFToken { public string NFTokenID { get; set; } From d32069d09fb4a0e4e0adca18867a2214a95461a2 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Thu, 21 Sep 2023 20:43:24 +0300 Subject: [PATCH 21/39] update packages --- .../Xrpl.AddressCodec.csproj | 2 +- Base/Xrpl.BinaryCodec/Xrpl.BinaryCodec.csproj | 2 +- Base/Xrpl.Keypairs/Xrpl.Keypairs.csproj | 2 +- .../TestsClients/Test.ClonsoleApp/Program.cs | 119 +++++++++++++++++- .../Xrpl.AddressCodec.Test.csproj | 8 +- .../Xrpl.BinaryCodec.Test.csproj | 8 +- .../Xrpl.Keypairs.Test.csproj | 8 +- Tests/Xrpl.Tests/Xrpl.Tests.csproj | 8 +- Xrpl/Client/IXrplClient.cs | 17 ++- Xrpl/Client/connection.cs | 14 ++- Xrpl/Models/Methods/AccountNFTs.cs | 2 +- Xrpl/Models/Transactions/CheckCash.cs | 9 +- Xrpl/Models/Transactions/Common.cs | 11 ++ Xrpl/Xrpl.csproj | 2 +- 14 files changed, 185 insertions(+), 27 deletions(-) diff --git a/Base/Xrpl.AddressCodec/Xrpl.AddressCodec.csproj b/Base/Xrpl.AddressCodec/Xrpl.AddressCodec.csproj index cbe4cd0..708704f 100644 --- a/Base/Xrpl.AddressCodec/Xrpl.AddressCodec.csproj +++ b/Base/Xrpl.AddressCodec/Xrpl.AddressCodec.csproj @@ -21,7 +21,7 @@ bin\Debug\net6.0\XrplCSharp.xml - + diff --git a/Base/Xrpl.BinaryCodec/Xrpl.BinaryCodec.csproj b/Base/Xrpl.BinaryCodec/Xrpl.BinaryCodec.csproj index d8aab92..063bc38 100644 --- a/Base/Xrpl.BinaryCodec/Xrpl.BinaryCodec.csproj +++ b/Base/Xrpl.BinaryCodec/Xrpl.BinaryCodec.csproj @@ -21,7 +21,7 @@ bin\Debug\net6.0\XrplCSharp.xml - + diff --git a/Base/Xrpl.Keypairs/Xrpl.Keypairs.csproj b/Base/Xrpl.Keypairs/Xrpl.Keypairs.csproj index d8aab92..063bc38 100644 --- a/Base/Xrpl.Keypairs/Xrpl.Keypairs.csproj +++ b/Base/Xrpl.Keypairs/Xrpl.Keypairs.csproj @@ -21,7 +21,7 @@ bin\Debug\net6.0\XrplCSharp.xml - + diff --git a/Tests/TestsClients/Test.ClonsoleApp/Program.cs b/Tests/TestsClients/Test.ClonsoleApp/Program.cs index 7c80e30..bff9dd9 100644 --- a/Tests/TestsClients/Test.ClonsoleApp/Program.cs +++ b/Tests/TestsClients/Test.ClonsoleApp/Program.cs @@ -85,8 +85,7 @@ static async Task SubmitTestTx() // sign and submit the transaction Dictionary txJson = JsonConvert.DeserializeObject>(tx.ToJson()); Submit response = await client.Submit(txJson, wallet); - Console.WriteLine(response); - + Console.WriteLine(response.EngineResult); } static async Task WebsocketTest() @@ -148,6 +147,121 @@ static async Task WebsocketTest() await client.Disconnect(); } + static async Task WebsocketChangeServerTest() + { + bool isFinished = false; + var server1 = "wss://s1.ripple.com/"; + var server2 = "wss://s2.ripple.com/"; + var server3 = "wss://xrplcluster.com/"; + + var client = new XrplClient(server1); + + client.connection.OnConnected += async () => + { + Console.WriteLine("CONNECTED"); + var subscribe = await client.Subscribe( + new SubscribeRequest() + { + Streams = new List(new[] + { + "ledger", + }) + }); + }; + + client.connection.OnDisconnect += (code) => + { + Console.WriteLine($"DISCONECTED CODE: {code}"); + return Task.CompletedTask; + }; + + client.connection.OnError += (errorCode, errorMessage, error, data) => + { + Console.WriteLine(errorCode); + Console.WriteLine(errorMessage); + Console.WriteLine(data); + return Task.CompletedTask; + }; + + client.connection.OnTransaction += Response => + { + Console.WriteLine(Response.Transaction.TransactionType.ToString()); + return Task.CompletedTask; + }; + + client.connection.OnLedgerClosed += r => + { + Console.WriteLine($"MESSAGE RECEIVED: {r}"); + isFinished = true; + return Task.CompletedTask; + }; + + await client.Connect(); + + while (!isFinished) + { + Debug.WriteLine($"WAITING: {DateTime.Now}"); + System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1)); + } + + await Task.Delay(3000); + isFinished = false; + + await client.connection.ChangeServer(server2); + while (!isFinished) + { + Debug.WriteLine($"WAITING: {DateTime.Now}"); + System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1)); + } + await Task.Delay(3000); + isFinished = false; + + await client.connection.ChangeServer(server3); + while (!isFinished) + { + Debug.WriteLine($"WAITING: {DateTime.Now}"); + System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1)); + } + await Task.Delay(3000); + isFinished = false; + + await client.connection.ChangeServer(server1); + while (!isFinished) + { + Debug.WriteLine($"WAITING: {DateTime.Now}"); + System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1)); + } + await Task.Delay(3000); + isFinished = false; + + await client.connection.ChangeServer(server2); + while (!isFinished) + { + Debug.WriteLine($"WAITING: {DateTime.Now}"); + System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1)); + } + await Task.Delay(3000); + isFinished = false; + + await client.connection.ChangeServer(server3); + while (!isFinished) + { + Debug.WriteLine($"WAITING: {DateTime.Now}"); + System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1)); + } + await Task.Delay(3000); + isFinished = false; + + await client.connection.ChangeServer(server1); + while (!isFinished) + { + Debug.WriteLine($"WAITING: {DateTime.Now}"); + System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1)); + } + await Task.Delay(3000); + + await client.Disconnect(); + } static async Task Main(string[] args) { @@ -156,6 +270,7 @@ static async Task Main(string[] args) //WalletGenerate(); //await SubmitTestTx(); //await WebsocketTest(); + await WebsocketChangeServerTest(); } } } diff --git a/Tests/Xrpl.AddressCodec.Test/Xrpl.AddressCodec.Test.csproj b/Tests/Xrpl.AddressCodec.Test/Xrpl.AddressCodec.Test.csproj index 6440d53..69cb365 100644 --- a/Tests/Xrpl.AddressCodec.Test/Xrpl.AddressCodec.Test.csproj +++ b/Tests/Xrpl.AddressCodec.Test/Xrpl.AddressCodec.Test.csproj @@ -14,11 +14,11 @@ - - - + + + - + diff --git a/Tests/Xrpl.BinaryCodec.Test/Xrpl.BinaryCodec.Test.csproj b/Tests/Xrpl.BinaryCodec.Test/Xrpl.BinaryCodec.Test.csproj index d1610fa..6a67243 100644 --- a/Tests/Xrpl.BinaryCodec.Test/Xrpl.BinaryCodec.Test.csproj +++ b/Tests/Xrpl.BinaryCodec.Test/Xrpl.BinaryCodec.Test.csproj @@ -14,11 +14,11 @@ - - - + + + - + diff --git a/Tests/Xrpl.Keypairs.Test/Xrpl.Keypairs.Test.csproj b/Tests/Xrpl.Keypairs.Test/Xrpl.Keypairs.Test.csproj index 399d491..f78f8b2 100644 --- a/Tests/Xrpl.Keypairs.Test/Xrpl.Keypairs.Test.csproj +++ b/Tests/Xrpl.Keypairs.Test/Xrpl.Keypairs.Test.csproj @@ -14,11 +14,11 @@ - - - + + + - + diff --git a/Tests/Xrpl.Tests/Xrpl.Tests.csproj b/Tests/Xrpl.Tests/Xrpl.Tests.csproj index 2c780c9..6312d13 100644 --- a/Tests/Xrpl.Tests/Xrpl.Tests.csproj +++ b/Tests/Xrpl.Tests/Xrpl.Tests.csproj @@ -14,11 +14,11 @@ - - - + + + - + diff --git a/Xrpl/Client/IXrplClient.cs b/Xrpl/Client/IXrplClient.cs index 479da5a..ed60d99 100644 --- a/Xrpl/Client/IXrplClient.cs +++ b/Xrpl/Client/IXrplClient.cs @@ -15,6 +15,8 @@ using Xrpl.Sugar; using Xrpl.Wallet; using static Xrpl.Client.Connection; +using static Xrpl.Client.XrplClient; + using BookOffers = Xrpl.Models.Transactions.BookOffers; using Submit = Xrpl.Models.Transactions.Submit; @@ -277,6 +279,7 @@ public interface IXrplClient : IDisposable Task> Autofill(Dictionary tx); Task GetLedgerIndex(); Task GetXrpBalance(string address); + Task ChangeServer(string server, ClientOptions? options = null); } @@ -330,6 +333,18 @@ public XrplClient(string server, ClientOptions? options = null) //connection.OnPathFind += (s) => OnPathFind?.Invoke(s); } + public async Task ChangeServer(string server, ClientOptions? options = null) + { + if (!IsValidWss(server)) + { + throw new Exception("Invalid WSS Server Url"); + } + feeCushion = options?.feeCushion ?? 1.2; + maxFeeXRP = options?.maxFeeXRP ?? "2"; + + await connection.ChangeServer(server, options); + } + /// public string Url() { @@ -374,7 +389,7 @@ public Task Submit(Dictionary tx, XrplWallet wallet) public Task Submit(ITransactionCommon tx, XrplWallet wallet) { Dictionary txJson = JsonConvert.DeserializeObject>(tx.ToJson()); - return SubmitSugar.Submit(this, txJson, true, false, wallet); + return this.Submit(txJson, true, false, wallet); } /// diff --git a/Xrpl/Client/connection.cs b/Xrpl/Client/connection.cs index a389574..7e73fca 100644 --- a/Xrpl/Client/connection.cs +++ b/Xrpl/Client/connection.cs @@ -100,13 +100,13 @@ static WebSocketClient CreateWebSocket(string url, ConnectionOptions config) int CONNECTION_TIMEOUT = 5; int INTENTIONAL_DISCONNECT_CODE = 4000; - public readonly string url; + public string url { get; private set; } public WebSocketClient ws; private int? reconnectTimeoutID = null; private int? heartbeatIntervalID = null; - public readonly ConnectionOptions config; + public ConnectionOptions config { get; private set; } public RequestManager requestManager = new RequestManager(); public ConnectionManager connectionManager = new ConnectionManager(); @@ -119,6 +119,16 @@ public Connection(string server, ConnectionOptions? options = null) } + public async Task ChangeServer(string server, ConnectionOptions? options = null) + { + await Disconnect(); + url = server; + config = options ?? new ConnectionOptions(); + config.timeout = TIMEOUT * 1000; + config.connectionTimeout = CONNECTION_TIMEOUT * 1000; + await Task.Delay(3000); + await Connect(); + } public bool IsConnected() { return this.State() == WebSocketState.Open; diff --git a/Xrpl/Models/Methods/AccountNFTs.cs b/Xrpl/Models/Methods/AccountNFTs.cs index 7cc33c1..1fa61b0 100644 --- a/Xrpl/Models/Methods/AccountNFTs.cs +++ b/Xrpl/Models/Methods/AccountNFTs.cs @@ -80,7 +80,7 @@ public class NFT /// /// The unscrambled version of this token's taxon. Several tokens with the same taxon might represent instances of a limited series. /// - [JsonProperty("token_taxon")] + [JsonProperty("NFTokenTaxon")] public uint NFTokenTaxon { get; set; } /// /// The URI data associated with this NFToken, in hexadecimal. diff --git a/Xrpl/Models/Transactions/CheckCash.cs b/Xrpl/Models/Transactions/CheckCash.cs index 9e15e6a..cfd7047 100644 --- a/Xrpl/Models/Transactions/CheckCash.cs +++ b/Xrpl/Models/Transactions/CheckCash.cs @@ -1,7 +1,8 @@ using System.Collections.Generic; using System.Threading.Tasks; - +using Newtonsoft.Json; using Xrpl.Client.Exceptions; +using Xrpl.Client.Json.Converters; using Xrpl.Models.Common; @@ -19,8 +20,10 @@ public CheckCash() /// public string CheckID { get; set; } /// + [JsonConverter(typeof(CurrencyConverter))] public Currency? Amount { get; set; } /// + [JsonConverter(typeof(CurrencyConverter))] public Currency? DeliverMin { get; set; } } @@ -41,6 +44,7 @@ public interface ICheckCash : ITransactionCommon /// You.
/// must provide either this field or DeliverMin. ///
+ [JsonConverter(typeof(CurrencyConverter))] Currency? Amount { get; set; } /// /// Redeem the Check for at least this amount and for as much as possible.
@@ -48,6 +52,7 @@ public interface ICheckCash : ITransactionCommon /// transaction.
/// You must provide either this field or Amount. ///
+ [JsonConverter(typeof(CurrencyConverter))] Currency? DeliverMin { get; set; } } @@ -57,9 +62,11 @@ public class CheckCashResponse : TransactionResponseCommon, ICheckCash /// public string CheckID { get; set; } /// + [JsonConverter(typeof(CurrencyConverter))] public Currency? Amount { get; set; } /// + [JsonConverter(typeof(CurrencyConverter))] public Currency? DeliverMin { get; set; } } diff --git a/Xrpl/Models/Transactions/Common.cs b/Xrpl/Models/Transactions/Common.cs index 2a36265..dc2cd88 100644 --- a/Xrpl/Models/Transactions/Common.cs +++ b/Xrpl/Models/Transactions/Common.cs @@ -411,6 +411,17 @@ public class Meta ///
public string TransactionResult { get; set; } + /// + /// OfferID for create NFT offers. + /// + [JsonProperty("offer_id")] + public string OfferID { get; set; } + /// + /// NFTokenID for nft accept offer. + /// + [JsonProperty("nftoken_id")] + public string NFTokenID { get; set; } + /// /// (Omitted for non-Payment transactions) The Currency Amount actually received by the Destination account.
/// Use this field to determine how much was delivered, regardless of whether the transaction is a partial payment.
diff --git a/Xrpl/Xrpl.csproj b/Xrpl/Xrpl.csproj index d701df5..4c3ccdc 100644 --- a/Xrpl/Xrpl.csproj +++ b/Xrpl/Xrpl.csproj @@ -22,7 +22,7 @@ bin\Debug\net6.0\XrplCSharp.xml - + From 0ab583ccf487d7a2f82ce6f81853a42bc3886384 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Sun, 15 Oct 2023 21:08:29 +0300 Subject: [PATCH 22/39] amm branch copied --- .../Binary/BinarySerializer.cs | 1 + Base/Xrpl.BinaryCodec/Binary/BytesList.cs | 3 +- Base/Xrpl.BinaryCodec/Enums/Field.cs | 29 +- Base/Xrpl.BinaryCodec/Enums/FieldType.cs | 1 + Base/Xrpl.BinaryCodec/Enums/FromJson.cs | 1 + Base/Xrpl.BinaryCodec/Enums/FromParser.cs | 1 + Base/Xrpl.BinaryCodec/Enums/IssueField.cs | 12 + .../Enums/SerializedEnumItem.cs | 1 + Base/Xrpl.BinaryCodec/Exceptions.cs | 20 ++ Base/Xrpl.BinaryCodec/ISerializedType.cs | 36 --- Base/Xrpl.BinaryCodec/ShaMapTree/ShaMap.cs | 1 + Base/Xrpl.BinaryCodec/Types/Currency.cs | 19 -- Base/Xrpl.BinaryCodec/Types/EngineResult.cs | 27 +- .../Xrpl.BinaryCodec/Types/ISerializedType.cs | 124 ++++++++ .../Types/InvalidJsonException.cs | 25 -- Base/Xrpl.BinaryCodec/Types/Issue.cs | 106 +++++++ .../Xrpl.BinaryCodec/Types/LedgerEntryType.cs | 3 +- Base/Xrpl.BinaryCodec/Types/StObject.cs | 4 +- .../Xrpl.BinaryCodec/Types/TransactionType.cs | 8 + Base/Xrpl.BinaryCodec/Types/UnissuedAmount.cs | 28 -- Tests/Xrpl.Tests/Models/TestAMMBid.cs | 266 ++++++++++++++++++ Tests/Xrpl.Tests/Models/TestAMMCreate.cs | 92 ++++++ Tests/Xrpl.Tests/Models/TestAMMDeposit.cs | 156 ++++++++++ Tests/Xrpl.Tests/Models/TestAMMVote.cs | 79 ++++++ Tests/Xrpl.Tests/Models/TestAMMWithdraw.cs | 167 +++++++++++ Xrpl/Client/IXrplClient.cs | 4 +- .../Json/Converters/CurrencyConverter.cs | 57 ++++ Xrpl/Models/Common/Currency.cs | 5 + Xrpl/Models/Common/LedgerIndex.cs | 7 +- Xrpl/Models/Enums.cs | 30 +- Xrpl/Models/Ledger/LOAmm.cs | 132 +++++++++ Xrpl/Models/Ledger/LONegativeUNL.cs | 40 +++ Xrpl/Models/Methods/AMMInfo.cs | 112 ++++++++ Xrpl/Models/Transactions/AMMBid.cs | 150 ++++++++++ Xrpl/Models/Transactions/AMMCreate.cs | 105 +++++++ Xrpl/Models/Transactions/AMMDelete.cs | 88 ++++++ Xrpl/Models/Transactions/AMMDeposit.cs | 183 ++++++++++++ Xrpl/Models/Transactions/AMMVote.cs | 80 ++++++ Xrpl/Models/Transactions/AMMWithdraw.cs | 210 ++++++++++++++ Xrpl/Models/Transactions/Common.cs | 18 ++ Xrpl/Models/Transactions/TxFormat.cs | 44 +++ Xrpl/Models/Transactions/Validation.cs | 15 + Xrpl/Models/Utils/Flags.cs | 15 +- 43 files changed, 2382 insertions(+), 123 deletions(-) create mode 100644 Base/Xrpl.BinaryCodec/Enums/IssueField.cs delete mode 100644 Base/Xrpl.BinaryCodec/ISerializedType.cs create mode 100644 Base/Xrpl.BinaryCodec/Types/ISerializedType.cs delete mode 100644 Base/Xrpl.BinaryCodec/Types/InvalidJsonException.cs create mode 100644 Base/Xrpl.BinaryCodec/Types/Issue.cs delete mode 100644 Base/Xrpl.BinaryCodec/Types/UnissuedAmount.cs create mode 100644 Tests/Xrpl.Tests/Models/TestAMMBid.cs create mode 100644 Tests/Xrpl.Tests/Models/TestAMMCreate.cs create mode 100644 Tests/Xrpl.Tests/Models/TestAMMDeposit.cs create mode 100644 Tests/Xrpl.Tests/Models/TestAMMVote.cs create mode 100644 Tests/Xrpl.Tests/Models/TestAMMWithdraw.cs create mode 100644 Xrpl/Models/Ledger/LOAmm.cs create mode 100644 Xrpl/Models/Ledger/LONegativeUNL.cs create mode 100644 Xrpl/Models/Methods/AMMInfo.cs create mode 100644 Xrpl/Models/Transactions/AMMBid.cs create mode 100644 Xrpl/Models/Transactions/AMMCreate.cs create mode 100644 Xrpl/Models/Transactions/AMMDelete.cs create mode 100644 Xrpl/Models/Transactions/AMMDeposit.cs create mode 100644 Xrpl/Models/Transactions/AMMVote.cs create mode 100644 Xrpl/Models/Transactions/AMMWithdraw.cs diff --git a/Base/Xrpl.BinaryCodec/Binary/BinarySerializer.cs b/Base/Xrpl.BinaryCodec/Binary/BinarySerializer.cs index 625bf1b..2ccec0b 100644 --- a/Base/Xrpl.BinaryCodec/Binary/BinarySerializer.cs +++ b/Base/Xrpl.BinaryCodec/Binary/BinarySerializer.cs @@ -1,5 +1,6 @@ using System; using Xrpl.BinaryCodec.Enums; +using Xrpl.BinaryCodec.Types; //https://github.com/XRPLF/xrpl.js/blob/8a9a9bcc28ace65cde46eed5010eb8927374a736/packages/ripple-binary-codec/src/serdes/binary-serializer.ts#L52 diff --git a/Base/Xrpl.BinaryCodec/Binary/BytesList.cs b/Base/Xrpl.BinaryCodec/Binary/BytesList.cs index 037903e..dd4da49 100644 --- a/Base/Xrpl.BinaryCodec/Binary/BytesList.cs +++ b/Base/Xrpl.BinaryCodec/Binary/BytesList.cs @@ -42,13 +42,14 @@ public void Put(byte[] bytes) } /// Get all bytes /// Bytes - public byte[] Bytes() + public byte[] ToBytes() { var n = BytesLength(); var bytes = new byte[n]; AddBytes(bytes, 0); return bytes; } + /// Hex Lookup public static string[] HexLookup = new string[256]; static BytesList() diff --git a/Base/Xrpl.BinaryCodec/Enums/Field.cs b/Base/Xrpl.BinaryCodec/Enums/Field.cs index 27fc6d4..48b63b1 100644 --- a/Base/Xrpl.BinaryCodec/Enums/Field.cs +++ b/Base/Xrpl.BinaryCodec/Enums/Field.cs @@ -85,6 +85,7 @@ private bool IsVlEncodedType() public static readonly TransactionTypeField TransactionType = new TransactionTypeField(nameof(TransactionType), 2); public static readonly Uint16Field SignerWeight = new Uint16Field(nameof(SignerWeight), 3); public static readonly Uint16Field TransferFee = new Uint16Field(nameof(TransferFee), 4); + public static readonly Uint16Field TradingFee = new Uint16Field(nameof(TradingFee), 4); public static readonly Uint16Field Version = new Uint16Field(nameof(Version), 16); public static readonly Uint16Field HookStateChangeCount = new Uint16Field(nameof(HookStateChangeCount), 17); public static readonly Uint16Field HookStateEmitCount = new Uint16Field(nameof(HookStateEmitCount), 18); @@ -139,7 +140,10 @@ private bool IsVlEncodedType() public static readonly Uint32Field BurnedTokens = new Uint32Field(nameof(BurnedTokens), 44); public static readonly Uint32Field HookStateCount = new Uint32Field(nameof(HookStateCount), 45); public static readonly Uint32Field EmitGeneration = new Uint32Field(nameof(EmitGeneration), 46); - + + public static readonly Uint32Field VoteWeight = new Uint32Field(nameof(VoteWeight), 47); + public static readonly Uint32Field DiscountedFee = new Uint32Field(nameof(DiscountedFee), 48); + public static readonly Uint64Field IndexNext = new Uint64Field(nameof(IndexNext), 1); public static readonly Uint64Field IndexPrevious = new Uint64Field(nameof(IndexPrevious), 2); public static readonly Uint64Field BookNode = new Uint64Field(nameof(BookNode), 3); @@ -178,6 +182,7 @@ private bool IsVlEncodedType() public static readonly Hash256Field EmitParentTxnID = new Hash256Field(nameof(EmitParentTxnID), 11); public static readonly Hash256Field EmitNonce = new Hash256Field(nameof(EmitNonce), 12); public static readonly Hash256Field EmitHookHash = new Hash256Field(nameof(EmitHookHash), 13); + public static readonly Hash256Field AMMID = new Hash256Field(nameof(AMMID), 14); public static readonly Hash256Field BookDirectory = new Hash256Field(nameof(BookDirectory), 16); public static readonly Hash256Field InvoiceID = new Hash256Field(nameof(InvoiceID), 17); public static readonly Hash256Field Nickname = new Hash256Field(nameof(Nickname), 18); @@ -207,10 +212,20 @@ private bool IsVlEncodedType() public static readonly AmountField Fee = new AmountField(nameof(Fee), 8); public static readonly AmountField SendMax = new AmountField(nameof(SendMax), 9); public static readonly AmountField DeliverMin = new AmountField(nameof(DeliverMin), 10); + public static readonly AmountField Amount2 = new AmountField(nameof(Amount2), 11); + public static readonly AmountField BidMin = new AmountField(nameof(BidMin), 12); + public static readonly AmountField BidMax = new AmountField(nameof(BidMax), 13); public static readonly AmountField MinimumOffer = new AmountField(nameof(MinimumOffer), 16); public static readonly AmountField RippleEscrow = new AmountField(nameof(RippleEscrow), 17); public static readonly AmountField DeliveredAmount = new AmountField(nameof(DeliveredAmount), 18); public static readonly AmountField NFTokenBrokerFee = new AmountField(nameof(NFTokenBrokerFee), 19); + //public static readonly AmountField HookCallbackFee = new AmountField(nameof(HookCallbackFee), 20); + public static readonly AmountField LPTokenOut = new AmountField(nameof(LPTokenOut), 20); + public static readonly AmountField LPTokenIn = new AmountField(nameof(LPTokenIn), 21); + public static readonly AmountField EPrice = new AmountField(nameof(EPrice), 22); + public static readonly AmountField Price = new AmountField(nameof(Price), 23); + public static readonly AmountField LPTokenBalance = new AmountField(nameof(LPTokenBalance), 24); + public static readonly AmountField HookCallbackFee = new AmountField(nameof(HookCallbackFee), 20); public static readonly BlobField PublicKey = new BlobField(nameof(PublicKey), 1); @@ -247,6 +262,7 @@ private bool IsVlEncodedType() public static readonly AccountIdField RegularKey = new AccountIdField(nameof(RegularKey), 8); public static readonly AccountIdField NFTokenMinter = new AccountIdField(nameof(NFTokenMinter), 9); public static readonly AccountIdField EmitCallback = new AccountIdField(nameof(EmitCallback), 10); + public static readonly AccountIdField AMMAccount = new AccountIdField(nameof(AMMAccount), 11); public static readonly AccountIdField HookAccount = new AccountIdField(nameof(HookAccount), 16); public static readonly Vector256Field Indexes = new Vector256Field(nameof(Indexes), 1); @@ -257,6 +273,9 @@ private bool IsVlEncodedType() public static readonly PathSetField Paths = new PathSetField(nameof(Paths), 1); + public static readonly IssueField Asset = new IssueField(nameof(Asset), 3); + public static readonly IssueField Asset2 = new IssueField(nameof(Asset2), 4); + public static readonly StObjectField TransactionMetaData = new StObjectField(nameof(TransactionMetaData), 2); public static readonly StObjectField CreatedNode = new StObjectField(nameof(CreatedNode), 3); public static readonly StObjectField DeletedNode = new StObjectField(nameof(DeletedNode), 4); @@ -278,7 +297,10 @@ private bool IsVlEncodedType() public static readonly StObjectField HookDefinition = new StObjectField(nameof(HookDefinition), 22); public static readonly StObjectField HookParameter = new StObjectField(nameof(HookParameter), 23); public static readonly StObjectField HookGrant = new StObjectField(nameof(HookGrant), 24); - + public static readonly StObjectField VoteEntry = new StObjectField(nameof(VoteEntry), 25); + public static readonly StObjectField AuctionSlot = new StObjectField(nameof(AuctionSlot), 27); + public static readonly StObjectField AuthAccount = new StObjectField(nameof(AuthAccount), 28); + public static readonly StArrayField Signers = new StArrayField(nameof(Signers), 3, isSigningField: false); public static readonly StArrayField SignerEntries = new StArrayField(nameof(SignerEntries), 4); public static readonly StArrayField Template = new StArrayField(nameof(Template), 5); @@ -288,12 +310,13 @@ private bool IsVlEncodedType() public static readonly StArrayField Memos = new StArrayField(nameof(Memos), 9); public static readonly StArrayField NFTokens = new StArrayField(nameof(NFTokens), 10); public static readonly StArrayField Hooks = new StArrayField(nameof(Hooks), 11); + public static readonly StArrayField VoteSlots = new StArrayField(nameof(VoteSlots), 14); public static readonly StArrayField Majorities = new StArrayField(nameof(Majorities), 16); public static readonly StArrayField DisabledValidators = new StArrayField(nameof(DisabledValidators), 17); public static readonly StArrayField HookExecutions = new StArrayField(nameof(HookExecutions), 18); public static readonly StArrayField HookParameters = new StArrayField(nameof(HookParameters), 19); public static readonly StArrayField HookGrants = new StArrayField(nameof(HookGrants), 19); - + public static readonly StArrayField AuthAccounts = new StArrayField(nameof(AuthAccounts), 26); public static readonly Field Generic = new Field(nameof(Generic), 0, FieldType.Unknown, isSigningField: false); public static readonly Field Invalid = new Field(nameof(Invalid), -1, FieldType.Unknown, isSigningField: false); diff --git a/Base/Xrpl.BinaryCodec/Enums/FieldType.cs b/Base/Xrpl.BinaryCodec/Enums/FieldType.cs index 491f9e2..6657b22 100644 --- a/Base/Xrpl.BinaryCodec/Enums/FieldType.cs +++ b/Base/Xrpl.BinaryCodec/Enums/FieldType.cs @@ -24,6 +24,7 @@ public FieldType(string name, int ordinal) : base(name, ordinal) public static readonly FieldType Hash160 = new FieldType(nameof(Hash160), 17); public static readonly FieldType PathSet = new FieldType(nameof(PathSet), 18); public static readonly FieldType Vector256 = new FieldType(nameof(Vector256), 19); + public static readonly FieldType Issue = new FieldType(nameof(Issue), 24); public static readonly FieldType Transaction = new FieldType(nameof(Transaction), 10001); public static readonly FieldType LedgerEntry = new FieldType(nameof(LedgerEntry), 10002); public static readonly FieldType Validation = new FieldType(nameof(Validation), 10003); diff --git a/Base/Xrpl.BinaryCodec/Enums/FromJson.cs b/Base/Xrpl.BinaryCodec/Enums/FromJson.cs index e62eb3c..9b0cecb 100644 --- a/Base/Xrpl.BinaryCodec/Enums/FromJson.cs +++ b/Base/Xrpl.BinaryCodec/Enums/FromJson.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json.Linq; +using Xrpl.BinaryCodec.Types; namespace Xrpl.BinaryCodec.Enums { diff --git a/Base/Xrpl.BinaryCodec/Enums/FromParser.cs b/Base/Xrpl.BinaryCodec/Enums/FromParser.cs index 2fab429..b5ddaaa 100644 --- a/Base/Xrpl.BinaryCodec/Enums/FromParser.cs +++ b/Base/Xrpl.BinaryCodec/Enums/FromParser.cs @@ -1,4 +1,5 @@ using Xrpl.BinaryCodec.Binary; +using Xrpl.BinaryCodec.Types; namespace Xrpl.BinaryCodec.Enums { diff --git a/Base/Xrpl.BinaryCodec/Enums/IssueField.cs b/Base/Xrpl.BinaryCodec/Enums/IssueField.cs new file mode 100644 index 0000000..6c0f98f --- /dev/null +++ b/Base/Xrpl.BinaryCodec/Enums/IssueField.cs @@ -0,0 +1,12 @@ +//https://xrpl.org/serialization.html#uint-fields +namespace Xrpl.BinaryCodec.Enums +{ + public class IssueField : Field + { + public IssueField(string name, int nthOfType, + bool isSigningField = true, bool isSerialised = true) : + base(name, nthOfType, FieldType.Uint8, + isSigningField, isSerialised) + { } + } +} diff --git a/Base/Xrpl.BinaryCodec/Enums/SerializedEnumItem.cs b/Base/Xrpl.BinaryCodec/Enums/SerializedEnumItem.cs index e2c5f0d..f3c30b9 100644 --- a/Base/Xrpl.BinaryCodec/Enums/SerializedEnumItem.cs +++ b/Base/Xrpl.BinaryCodec/Enums/SerializedEnumItem.cs @@ -2,6 +2,7 @@ using System.Runtime.InteropServices; using Newtonsoft.Json.Linq; using Xrpl.BinaryCodec.Binary; +using Xrpl.BinaryCodec.Types; using Xrpl.BinaryCodec.Util; namespace Xrpl.BinaryCodec.Enums diff --git a/Base/Xrpl.BinaryCodec/Exceptions.cs b/Base/Xrpl.BinaryCodec/Exceptions.cs index 8f56aa0..7f02def 100644 --- a/Base/Xrpl.BinaryCodec/Exceptions.cs +++ b/Base/Xrpl.BinaryCodec/Exceptions.cs @@ -8,4 +8,24 @@ public BinaryCodecException() { } public BinaryCodecException(string message) : base(message){ } } + /// + /// Thrown when JSON is not valid. + /// + public class InvalidJsonException : Exception + { + /// + public InvalidJsonException() + { + } + + /// + public InvalidJsonException(string message) : base(message) + { + } + + /// + public InvalidJsonException(string message, Exception innerException) : base(message, innerException) + { + } + } } diff --git a/Base/Xrpl.BinaryCodec/ISerializedType.cs b/Base/Xrpl.BinaryCodec/ISerializedType.cs deleted file mode 100644 index 1a3c014..0000000 --- a/Base/Xrpl.BinaryCodec/ISerializedType.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Linq; -using Newtonsoft.Json.Linq; -using Xrpl.BinaryCodec.Binary; -using Xrpl.BinaryCodec.Util; - -namespace Xrpl.BinaryCodec -{ - - public interface ISerializedType - { - /// to bytes Sink - /// bytes Sink container - void ToBytes(IBytesSink sink); - /// Get the JSON representation of this type - JToken ToJson(); - } - /// extension for ISerializedType - public static class StExtensions - { - /// object to hex string - /// Serialized type - /// - public static string ToHex(this ISerializedType st) - { - BytesList list = new BytesList(); - st.ToBytes(list); - return list.BytesHex(); - } - public static string ToDebuggedHex(this ISerializedType st) - { - BytesList list = new BytesList(); - st.ToBytes(list); - return list.RawList().Aggregate("", (a, b) => a + ',' + B16.Encode(b)); - } - } -} \ No newline at end of file diff --git a/Base/Xrpl.BinaryCodec/ShaMapTree/ShaMap.cs b/Base/Xrpl.BinaryCodec/ShaMapTree/ShaMap.cs index b00b93b..c57e461 100644 --- a/Base/Xrpl.BinaryCodec/ShaMapTree/ShaMap.cs +++ b/Base/Xrpl.BinaryCodec/ShaMapTree/ShaMap.cs @@ -1,5 +1,6 @@ using System.Linq; using Newtonsoft.Json.Linq; +using Xrpl.BinaryCodec.Enums; using Xrpl.BinaryCodec.Types; namespace Xrpl.BinaryCodec.ShaMapTree diff --git a/Base/Xrpl.BinaryCodec/Types/Currency.cs b/Base/Xrpl.BinaryCodec/Types/Currency.cs index dcf4206..45dc192 100644 --- a/Base/Xrpl.BinaryCodec/Types/Currency.cs +++ b/Base/Xrpl.BinaryCodec/Types/Currency.cs @@ -163,24 +163,5 @@ public override string ToString() { return new Currency(parser.Read(20)); } - - //public static UnissuedAmount operator /(decimal v, Currency c) - //{ - // if (c == Xrp) - // { - // v *= 1e6m; - // } - // return new UnissuedAmount(v, c); - //} - - public static Issue operator /(Currency c, AccountId ac) - { - return new Issue(); //todo ? - } - } - - public class Issue - { - //todo ? } } \ No newline at end of file diff --git a/Base/Xrpl.BinaryCodec/Types/EngineResult.cs b/Base/Xrpl.BinaryCodec/Types/EngineResult.cs index 37b9e7e..8a3dde1 100644 --- a/Base/Xrpl.BinaryCodec/Types/EngineResult.cs +++ b/Base/Xrpl.BinaryCodec/Types/EngineResult.cs @@ -2,7 +2,7 @@ //todo not found doc -namespace Xrpl.BinaryCodec.Types +namespace Xrpl.BinaryCodec.Enums { public class EngineResult : SerializedEnumItem { @@ -58,6 +58,11 @@ private static EngineResult Add(string name, int ordinal, string description) public static readonly EngineResult temUNCERTAIN = Add(nameof(temUNCERTAIN), -272, "In process of determining result. Never returned."); public static readonly EngineResult temUNKNOWN = Add(nameof(temUNKNOWN), -271, "The transactions requires logic not implemented yet."); + public static readonly EngineResult temSEQ_AND_TICKET = Add(nameof(temSEQ_AND_TICKET), -263, ""); + public static readonly EngineResult temBAD_NFTOKEN_TRANSFER_FEE = Add(nameof(temBAD_NFTOKEN_TRANSFER_FEE), -262, ""); + public static readonly EngineResult temBAD_AMM_OPTIONS = Add(nameof(temBAD_AMM_OPTIONS), -261, ""); + public static readonly EngineResult temBAD_AMM_TOKENS = Add(nameof(temBAD_AMM_TOKENS), -260, ""); + public static readonly EngineResult tefFAILURE = Add(nameof(tefFAILURE), -199, "Failed to apply."); public static readonly EngineResult tefALREADY = Add(nameof(tefALREADY), -198, "The exact transaction was already in this ledger."); public static readonly EngineResult tefBAD_ADD_AUTH = Add(nameof(tefBAD_ADD_AUTH), -197, "Not authorized to add account."); @@ -83,6 +88,9 @@ private static EngineResult Add(string name, int ordinal, string description) public static readonly EngineResult terPRE_SEQ = Add(nameof(terPRE_SEQ), -92, "Missing/inapplicable prior transaction."); public static readonly EngineResult terLAST = Add(nameof(terLAST), -91, "Process last."); public static readonly EngineResult terNO_RIPPLE = Add(nameof(terNO_RIPPLE), -90, "Process last."); + public static readonly EngineResult terQUEUED = Add(nameof(terQUEUED), -89, ""); + public static readonly EngineResult terPRE_TICKET = Add(nameof(terPRE_TICKET), -88, ""); + public static readonly EngineResult terNO_AMM = Add(nameof(terNO_AMM), -87, ""); public static readonly EngineResult tesSUCCESS = Add(nameof(tesSUCCESS), 0, "The transaction was applied."); public static readonly EngineResult tecCLAIM = Add(nameof(tecCLAIM), 100, "Fee claimed. Sequence used. No action."); @@ -116,7 +124,22 @@ private static EngineResult Add(string name, int ordinal, string description) public static readonly EngineResult tecDST_TAG_NEEDED = Add(nameof(tecDST_TAG_NEEDED), 143, "A destination tag is required."); public static readonly EngineResult tecINTERNAL = Add(nameof(tecINTERNAL), 144, "An internal error has occurred during processing."); public static readonly EngineResult tecOVERSIZE = Add(nameof(tecOVERSIZE), 145, "Object exceeded serialization limits."); - + + public static readonly EngineResult tecCANT_ACCEPT_OWN_NFTOKEN_OFFER = Add(nameof(tecCANT_ACCEPT_OWN_NFTOKEN_OFFER), 158, ""); + public static readonly EngineResult tecINSUFFICIENT_FUNDS = Add(nameof(tecINSUFFICIENT_FUNDS), 159, ""); + public static readonly EngineResult tecOBJECT_NOT_FOUND = Add(nameof(tecOBJECT_NOT_FOUND), 160, ""); + public static readonly EngineResult tecINSUFFICIENT_PAYMENT = Add(nameof(tecINSUFFICIENT_PAYMENT), 161, ""); + public static readonly EngineResult tecUNFUNDED_AMM = Add(nameof(tecUNFUNDED_AMM), 162, ""); + public static readonly EngineResult tecAMM_BALANCE = Add(nameof(tecAMM_BALANCE), 163, ""); + public static readonly EngineResult tecAMM_FAILED_DEPOSIT = Add(nameof(tecAMM_FAILED_DEPOSIT), 164, ""); + public static readonly EngineResult tecAMM_FAILED_WITHDRAW = Add(nameof(tecAMM_FAILED_WITHDRAW), 165, ""); + public static readonly EngineResult tecAMM_INVALID_TOKENS = Add(nameof(tecAMM_INVALID_TOKENS), 166, ""); + public static readonly EngineResult tecAMM_EXISTS = Add(nameof(tecAMM_EXISTS), 167, ""); + public static readonly EngineResult tecAMM_FAILED_BID = Add(nameof(tecAMM_FAILED_BID), 168, ""); + public static readonly EngineResult tecAMM_FAILED_VOTE = Add(nameof(tecAMM_FAILED_VOTE), 169, ""); + + + // ReSharper restore InconsistentNaming public bool ShouldClaimFee() { diff --git a/Base/Xrpl.BinaryCodec/Types/ISerializedType.cs b/Base/Xrpl.BinaryCodec/Types/ISerializedType.cs new file mode 100644 index 0000000..6b542db --- /dev/null +++ b/Base/Xrpl.BinaryCodec/Types/ISerializedType.cs @@ -0,0 +1,124 @@ +using System; +using System.Linq; +using Newtonsoft.Json.Linq; +using Xrpl.BinaryCodec.Binary; +using Xrpl.BinaryCodec.Util; + +namespace Xrpl.BinaryCodec.Types +{ + public class SerializedType + { + protected byte[] Buffer; + + //public SerializedType(byte[] buffer) + //{ + // this.Buffer = buffer ?? new byte[0]; + //} + + //public static SerializedType FromParser(BinaryParser parser, int? hint = null) + //{ + // throw new Exception("fromParser not implemented"); + // //return FromParser(parser, hint); + //} + + //public static SerializedType From(SerializedType value) + //{ + // throw new Exception("from not implemented"); + // //return From(value); + //} + + public void ToBytesSink(BytesList list) + { + list.Put(Buffer); + } + + public string ToHex() + { + return ToBytes().ToHex(); + } + + public byte[] ToBytes() + { + if (Buffer != null) + { + return Buffer; + } + var bl = new BytesList(); + ToBytesSink(bl); + return bl.ToBytes(); + } + + public object ToJson() + { + return ToHex(); + } + + public override string ToString() + { + return ToHex(); + } + } + + public class Comparable : SerializedType + { + //public Comparable(byte[] bytes) : base(bytes) {} + + public bool Lt(Comparable other) + { + return CompareTo(other) < 0; + } + + public bool Eq(Comparable other) + { + return CompareTo(other) == 0; + } + + public bool Gt(Comparable other) + { + return CompareTo(other) > 0; + } + + public bool Gte(Comparable other) + { + return CompareTo(other) > -1; + } + + public bool Lte(Comparable other) + { + return CompareTo(other) < 1; + } + + public int CompareTo(Comparable other) + { + throw new Exception($"cannot compare {ToString()} and {other.ToString()}"); + } + } + + public interface ISerializedType + { + /// to bytes Sink + /// bytes Sink container + void ToBytes(IBytesSink sink); + /// Get the JSON representation of this type + JToken ToJson(); + } + /// extension for ISerializedType + public static class StExtensions + { + /// object to hex string + /// Serialized type + /// + public static string ToHex(this ISerializedType st) + { + BytesList list = new BytesList(); + st.ToBytes(list); + return list.BytesHex(); + } + public static string ToDebuggedHex(this ISerializedType st) + { + BytesList list = new BytesList(); + st.ToBytes(list); + return list.RawList().Aggregate("", (a, b) => a + ',' + B16.Encode(b)); + } + } +} \ No newline at end of file diff --git a/Base/Xrpl.BinaryCodec/Types/InvalidJsonException.cs b/Base/Xrpl.BinaryCodec/Types/InvalidJsonException.cs deleted file mode 100644 index f6cedf6..0000000 --- a/Base/Xrpl.BinaryCodec/Types/InvalidJsonException.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; - -namespace Xrpl.BinaryCodec.Types -{ - /// - /// Thrown when JSON is not valid. - /// - public class InvalidJsonException : Exception - { - /// - public InvalidJsonException() - { - } - - /// - public InvalidJsonException(string message) : base(message) - { - } - - /// - public InvalidJsonException(string message, Exception innerException) : base(message, innerException) - { - } - } -} \ No newline at end of file diff --git a/Base/Xrpl.BinaryCodec/Types/Issue.cs b/Base/Xrpl.BinaryCodec/Types/Issue.cs new file mode 100644 index 0000000..fe0d47b --- /dev/null +++ b/Base/Xrpl.BinaryCodec/Types/Issue.cs @@ -0,0 +1,106 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Linq; +using System.Text.Json.Nodes; +using Xrpl.BinaryCodec.Binary; +using Xrpl.BinaryCodec.Util; + +// https://github.com/XRPLF/xrpl.js/blob/amm/packages/ripple-binary-codec/src/types/issue.ts + +namespace Xrpl.BinaryCodec.Types +{ + /// + /// Hash with a width of 160 bits + /// + public class Issue: ISerializedType + { + public readonly byte[] _Bytes; + + public class IssueObject + { + public string Currency { get; set; } + public string Issuer { get; set; } + } + + /// + /// Type guard for AmountObject + /// + public static bool IsIssueObject(JObject arg) + { + var keys = arg.Properties().Select(p => p.Name).ToList(); + keys.Sort(); + if (keys.Count == 1) + { + return keys[0] == "currency"; + } + return keys.Count == 2 && keys[0] == "currency" && keys[1] == "issuer"; + } + + public static readonly Issue ZERO_ISSUED_CURRENCY = new Issue(new byte[20]); + + private Issue(byte[] buffer) + { + this._Bytes = buffer; + } + + public static implicit operator Issue(byte[] buffer) + { + Contract.Assert(buffer.Length == 20, "buffer should be 20 bytes"); + return new Issue(buffer ?? ZERO_ISSUED_CURRENCY._Bytes); + } + + /// + /// Read an amount from a BinaryParser + /// + /// BinaryParser to read the Amount from + /// An Amount object + public static Issue FromParser(BinaryParser parser) + { + var currency = parser.Read(20); + if (new Currency(currency).ToString() == "XRP") + { + return new Issue(currency); + } + var currencyAndIssuer = new byte[40]; + Buffer.BlockCopy(currency, 0, currencyAndIssuer, 0, 20); + Buffer.BlockCopy(parser.Read(20), 0, currencyAndIssuer, 20, 20); + return new Issue(currencyAndIssuer); + } + + /// + /// Get the JSON representation of this Amount + /// + /// the JSON interpretation of this.bytes + public IssueObject ToJson() + { + var parser = new BufferParser(this.ToString()); + var currency = Currency.FromParser(parser) as Currency; + + if (currency.ToString() == "XRP") + { + return new IssueObject { Currency = currency.ToString() }; + } + + var issuer = AccountId.FromParser(parser) as AccountId; + + return new IssueObject + { + Currency = currency.ToString(), + Issuer = issuer.ToString() + }; + } + + public void ToBytes(IBytesSink sink) + { + throw new NotImplementedException(); + } + + JToken ISerializedType.ToJson() + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Base/Xrpl.BinaryCodec/Types/LedgerEntryType.cs b/Base/Xrpl.BinaryCodec/Types/LedgerEntryType.cs index 5ae599f..e71c8e1 100644 --- a/Base/Xrpl.BinaryCodec/Types/LedgerEntryType.cs +++ b/Base/Xrpl.BinaryCodec/Types/LedgerEntryType.cs @@ -1,9 +1,8 @@ using Newtonsoft.Json.Linq; -using Xrpl.BinaryCodec.Enums; //todo not found doc -namespace Xrpl.BinaryCodec.Types +namespace Xrpl.BinaryCodec.Enums { public class LedgerEntryType : SerializedEnumItem { diff --git a/Base/Xrpl.BinaryCodec/Types/StObject.cs b/Base/Xrpl.BinaryCodec/Types/StObject.cs index 3b460d8..6a235b7 100644 --- a/Base/Xrpl.BinaryCodec/Types/StObject.cs +++ b/Base/Xrpl.BinaryCodec/Types/StObject.cs @@ -227,7 +227,7 @@ public byte[] SigningData() var list = new BytesList(); list.Put(HashPrefix.TransactionSig.Bytes()); ToBytes(list, f => f.IsSigningField); - return list.Bytes(); + return list.ToBytes(); } /// /// this object to bytes array @@ -237,7 +237,7 @@ public byte[] ToBytes() { var list = new BytesList(); ToBytes(list, f => f.IsSerialised); - return list.Bytes(); + return list.ToBytes(); } /// /// add field to this object diff --git a/Base/Xrpl.BinaryCodec/Types/TransactionType.cs b/Base/Xrpl.BinaryCodec/Types/TransactionType.cs index f9d1c6c..d7a7d91 100644 --- a/Base/Xrpl.BinaryCodec/Types/TransactionType.cs +++ b/Base/Xrpl.BinaryCodec/Types/TransactionType.cs @@ -80,6 +80,14 @@ private static TransactionType Add(string name, int ordinal) public static readonly TransactionType NFTokenCancelOffer = Add(nameof(NFTokenCancelOffer), 28); /// This transaction accepts an existing offer to buy or sell an existing NFT. public static readonly TransactionType NFTokenAcceptOffer = Add(nameof(NFTokenAcceptOffer), 29); + + public static readonly TransactionType AMMCreate = Add(nameof(AMMCreate), 35); + public static readonly TransactionType AMMDeposit = Add(nameof(AMMDeposit), 36); + public static readonly TransactionType AMMWithdraw = Add(nameof(AMMWithdraw), 37); + public static readonly TransactionType AMMVote = Add(nameof(AMMVote), 38); + public static readonly TransactionType AMMBid = Add(nameof(AMMBid), 39); + public static readonly TransactionType AMMDelete = Add(nameof(AMMDelete), 40); + // ... /// /// This system-generated transaction type is used to update the status of the various amendments.
diff --git a/Base/Xrpl.BinaryCodec/Types/UnissuedAmount.cs b/Base/Xrpl.BinaryCodec/Types/UnissuedAmount.cs deleted file mode 100644 index cc907dd..0000000 --- a/Base/Xrpl.BinaryCodec/Types/UnissuedAmount.cs +++ /dev/null @@ -1,28 +0,0 @@ - - -//todo not found doc - -//namespace Xrpl.BinaryCodecLib.Types -//{ -// public class UnissuedAmount -// { -// private readonly Currency _currency; -// private readonly decimal _value; - -// public UnissuedAmount(decimal value, Currency currency) -// { -// _value = value; -// _currency = currency; -// } - -// public static Amount operator / (UnissuedAmount ui, AccountId issuer) -// { -// return new Amount(ui._value, ui._currency, issuer); -// } - -// public static implicit operator Amount(UnissuedAmount a) -// { -// return new Amount(a._value, a._currency); -// } -// } -//} \ No newline at end of file diff --git a/Tests/Xrpl.Tests/Models/TestAMMBid.cs b/Tests/Xrpl.Tests/Models/TestAMMBid.cs new file mode 100644 index 0000000..25d30e7 --- /dev/null +++ b/Tests/Xrpl.Tests/Models/TestAMMBid.cs @@ -0,0 +1,266 @@ + + +// https://github.com/XRPLF/xrpl.js/blob/amm-beta/packages/xrpl/test/models/AMMBid.ts + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using System.Collections.Generic; +using System.Threading.Tasks; + +using Xrpl.Client.Exceptions; +using Xrpl.Models.Transactions; + +namespace XrplTests.Xrpl.Models +{ + [TestClass] + public class TestUAMMBid + { + public static Dictionary bid; + + [ClassInitialize] + public static void MyClassInitialize(TestContext testContext) + { + bid = new Dictionary + { + {"TransactionType", "AMMBid"}, + {"Account", "rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm"}, + {"Asset", new Dictionary(){{"currency","XRP"}}}, + {"Asset2", new Dictionary(){{"currency","ETH"},{"issuer", "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd" } }}, + {"BidMin", "5"}, + {"BidMax", "10"}, + {"AuthAccounts", new List>() { + new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", "rNZdsTBP5tH1M6GHC6bTreHAp6ouP8iZSh" } + }} + }, + new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", "rfpFv97Dwu89FTyUwPjtpZBbuZxTqqgTmH" } + }} + }, + new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", "rzzYHPGb8Pa64oqxCzmuffm122bitq3Vb" } + }} + }, + new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", "rhwxHxaHok86fe4LykBom1jSJ3RYQJs1h4" } + }} + }, } + }, + {"Sequence", 1337u}, + }; + } + + [TestMethod] + public async Task TestVerifyValid() + { + //verifies valid AMMBid + await Validation.Validate(bid); + + + + + + + + + + //throws w/ missing field Asset + bid.Remove("Asset"); + await Assert.ThrowsExceptionAsync(() => Validation.Validate(bid), "AMMBid: missing field Asset"); + bid["Asset"] = new Dictionary() { { "currency", "XRP" } }; + //throws w/ Asset must be an Issue + bid["Asset"] = 1234; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(bid), "AMMBid: Asset must be an Issue"); + bid["Asset"] = new Dictionary() { { "currency", "XRP" } }; + //throws w/ missing field Asset2 + bid.Remove("Asset2"); + await Assert.ThrowsExceptionAsync(() => Validation.Validate(bid), "AMMBid: missing field Asset2"); + bid["Asset2"] = new Dictionary() { { "currency", "ETH" }, { "issuer", "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd" } }; + //throws w/ Asset2 must be an Issue + bid["Asset2"] = 1234; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(bid), "AMMBid: Asset2 must be an Issue"); + bid["Asset2"] = new Dictionary() { { "currency", "ETH" }, { "issuer", "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd" } }; + + //throws w/ BidMin must be an Amount + bid["BidMin"] = 5; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(bid), "AMMBid: BidMin must be an Amount"); + bid["BidMin"] = "5"; + + //throws w/ BidMax must be an Amount + bid["BidMax"] = 10; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(bid), "AMMBid: BidMax must be an Amount"); + bid["BidMax"] = "10"; + + //throws w/ AuthAccounts length must not be greater than 4 + bid["AuthAccounts"] = new List>() + { + new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", "rNZdsTBP5tH1M6GHC6bTreHAp6ouP8iZSh" } + }} + }, new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", "rfpFv97Dwu89FTyUwPjtpZBbuZxTqqgTmH" } + }} + }, new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", "rzzYHPGb8Pa64oqxCzmuffm122bitq3Vb" } + }} + }, new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", "rhwxHxaHok86fe4LykBom1jSJ3RYQJs1h4" } + }} + }, new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", "r3X6noRsvaLapAKCG78zAtWcbhB3sggS1s" } + }} + }, + }; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(bid), "AMMBid: invalid ClearFlag - no ERROR"); + + //throws w/ AuthAccounts must be an AuthAccount array + bid["AuthAccounts"] = 1234; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(bid), "AMMBid: AuthAccounts must be an AuthAccount array"); + + bid["AuthAccounts"] = new List>() + { + new Dictionary() + { + {"AuthAccount",null} + }, new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", "rfpFv97Dwu89FTyUwPjtpZBbuZxTqqgTmH" } + }} + }, new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", "rzzYHPGb8Pa64oqxCzmuffm122bitq3Vb" } + }} + }, new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", "rhwxHxaHok86fe4LykBom1jSJ3RYQJs1h4" } + }} + } + }; + + //throws w/ invalid AuthAccounts when AuthAccount is undefined + await Assert.ThrowsExceptionAsync(() => Validation.Validate(bid), "AMMBid: invalid AuthAccounts"); + //throws w/ invalid AuthAccounts when AuthAccount is not an object + bid["AuthAccounts"] = new List>() + { + new Dictionary() + { + {"AuthAccount",1234} + }, new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", "rfpFv97Dwu89FTyUwPjtpZBbuZxTqqgTmH" } + }} + }, new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", "rzzYHPGb8Pa64oqxCzmuffm122bitq3Vb" } + }} + }, new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", "rhwxHxaHok86fe4LykBom1jSJ3RYQJs1h4" } + }} + } + }; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(bid), "AMMBid: invalid AuthAccounts"); + // throws w/ invalid AuthAccounts when AuthAccount.Account is not a string + bid["AuthAccounts"] = new List>() + { + new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", 1234 } + }} + }, new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", "rfpFv97Dwu89FTyUwPjtpZBbuZxTqqgTmH" } + }} + }, new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", "rzzYHPGb8Pa64oqxCzmuffm122bitq3Vb" } + }} + }, new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", "rhwxHxaHok86fe4LykBom1jSJ3RYQJs1h4" } + }} + } + }; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(bid), "AMMBid: invalid AuthAccounts"); + //throws w/ AuthAccounts must not include sender's address + bid["AuthAccounts"] = new List>() + { + new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", bid["Account"] } + }} + }, new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", "rfpFv97Dwu89FTyUwPjtpZBbuZxTqqgTmH" } + }} + }, new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", "rzzYHPGb8Pa64oqxCzmuffm122bitq3Vb" } + }} + }, new Dictionary() + { + {"AuthAccount",new Dictionary() + { + { "Account", "rhwxHxaHok86fe4LykBom1jSJ3RYQJs1h4" } + }} + } + }; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(bid), "AMMBid: AuthAccounts must not include sender's address"); + + } + } +} + diff --git a/Tests/Xrpl.Tests/Models/TestAMMCreate.cs b/Tests/Xrpl.Tests/Models/TestAMMCreate.cs new file mode 100644 index 0000000..7410598 --- /dev/null +++ b/Tests/Xrpl.Tests/Models/TestAMMCreate.cs @@ -0,0 +1,92 @@ +// https://github.com/XRPLF/xrpl.js/blob/amm-beta/packages/xrpl/test/models/AMMCreate.ts + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using System.Collections.Generic; +using System.Threading.Tasks; + +using Xrpl.Client.Exceptions; +using Xrpl.Models.Transactions; + +namespace XrplTests.Xrpl.Models +{ + [TestClass] + public class TestUAMMCreate + { + public static Dictionary ammCreate; + + [ClassInitialize] + public static void MyClassInitialize(TestContext testContext) + { + ammCreate = new Dictionary + { + {"TransactionType", "AMMCreate"}, + {"Account", "rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm"}, + {"Amount", "1000"}, + {"Amount2", new Dictionary() + { + {"currency","USD"}, + {"issuer","rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + {"value","1000"}, + }}, + {"TradingFee", 12u}, + {"Sequence", 1337u}, + }; + } + + [TestMethod] + public async Task TestVerifyValid() + { + //verifies valid AMMCreate + await Validation.Validate(ammCreate); + + //throws w/ missing Amount + ammCreate.Remove("Amount"); + await Assert.ThrowsExceptionAsync(() => Validation.Validate(ammCreate), "AMMCreate: missing field Amount"); + ammCreate["Amount"] = "1000"; + //throws w/ Amount must be an Amount + ammCreate["Amount"] = 1000; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(ammCreate), "AMMCreate: Amount must be an Amount"); + ammCreate["Amount"] = "1000"; + + //throws w/ missing Amount2 + ammCreate.Remove("Amount2"); + await Assert.ThrowsExceptionAsync(() => Validation.Validate(ammCreate), "AMMCreate: missing field Amount2"); + ammCreate["Amount2"] = new Dictionary() + { + {"currency","USD"}, + {"issuer","rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + {"value","1000"}, + }; + //throws w/ Amount must be an Amount2 + ammCreate["Amount2"] = 1000; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(ammCreate), "AMMCreate: Amount2 must be an Amount"); + ammCreate["Amount"] = new Dictionary() + { + {"currency","USD"}, + {"issuer","rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + {"value","1000"}, + }; + //throws w/ missing TradingFee + ammCreate.Remove("TradingFee"); + await Assert.ThrowsExceptionAsync(() => Validation.Validate(ammCreate), "AMMCreate: missing field TradingFee"); + ammCreate["TradingFee"] = 12u; + //throws w/ TradingFee must be a number + ammCreate["TradingFee"] = "12"; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(ammCreate), "AMMCreate: TradingFee must be a number"); + ammCreate["TradingFee"] = 12u; + + //throws when TradingFee is greater than 1000 + ammCreate["TradingFee"] = 1001u; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(ammCreate), "AMMCreate: TradingFee must be between 0 and 1000"); + ammCreate["TradingFee"] = 12u; + //throws when TradingFee is greater than 1000 + ammCreate["TradingFee"] = -1; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(ammCreate), "AMMCreate: TradingFee must be between 0 and 1000"); + ammCreate["TradingFee"] = 12u; + + } + } +} + + diff --git a/Tests/Xrpl.Tests/Models/TestAMMDeposit.cs b/Tests/Xrpl.Tests/Models/TestAMMDeposit.cs new file mode 100644 index 0000000..56a0dfe --- /dev/null +++ b/Tests/Xrpl.Tests/Models/TestAMMDeposit.cs @@ -0,0 +1,156 @@ +// https://github.com/XRPLF/xrpl.js/blob/amm/packages/xrpl/test/models/AMMDeposit.ts + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using System.Collections.Generic; +using System.Threading.Tasks; + +using Xrpl.Client.Exceptions; +using Xrpl.Models.Transactions; + +namespace XrplTests.Xrpl.Models +{ + [TestClass] + public class TestUAMMDeposit + { + public static Dictionary LPTokenOut; + public static Dictionary deposit; + + [ClassInitialize] + public static void MyClassInitialize(TestContext testContext) + { + LPTokenOut = new Dictionary() + { + { "currency", "B3813FCAB4EE68B3D0D735D6849465A9113EE048" }, + { "issuer", "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg" }, + { "value", "1000" }, + }; + deposit = new Dictionary + { + {"TransactionType", "AMMDeposit"}, + {"Account", "rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm"}, + {"Asset", new Dictionary(){{"currency","XRP"}}}, + {"Asset2", new Dictionary(){{"currency","ETH"},{"issuer", "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd" } }}, + {"Sequence", 1337u}, + {"Flags", 0u}, + }; + } + + [TestMethod] + public async Task TestVerifyValid() + { + //verifies valid AMMDeposit with LPTokenOut + deposit["LPTokenOut"] = LPTokenOut; + deposit["Flags"] = AMMDepositFlags.tfLPToken; + await Validation.Validate(deposit); + deposit.Remove("LPTokenOut"); + deposit["Flags"] = 0u; + + + //verifies valid AMMDeposit with Amount + deposit["Amount"] = "1000"; + deposit["Flags"] = AMMDepositFlags.tfSingleAsset; + await Validation.Validate(deposit); + deposit.Remove("Amount"); + deposit["Flags"] = 0u; + + //verifies valid AMMDeposit with Amount and Amount2 + deposit["Amount"] = "1000"; + deposit["Amount2"] = new Dictionary() + { + {"currency","ETH"}, + {"issuer","rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"}, + {"value","2.5"}, + }; + deposit["Flags"] = AMMDepositFlags.tfTwoAsset; + await Validation.Validate(deposit); + deposit.Remove("Amount"); + deposit.Remove("Amount2"); + deposit["Flags"] = 0u; + + + //verifies valid AMMDeposit with Amount and LPTokenOut + deposit["Amount"] = "1000"; + deposit["LPTokenOut"] = LPTokenOut; + deposit["Flags"] = AMMDepositFlags.tfOneAssetLPToken; + await Validation.Validate(deposit); + deposit.Remove("Amount"); + deposit.Remove("LPTokenOut"); + deposit["Flags"] = 0u; + + //verifies valid AMMDeposit with Amount and EPrice + deposit["Amount"] = "1000"; + deposit["EPrice"] = "25"; + deposit["Flags"] = AMMDepositFlags.tfLimitLPToken; + await Validation.Validate(deposit); + deposit.Remove("Amount"); + deposit.Remove("EPrice"); + deposit["Flags"] = 0u; + + //throws w/ missing field Asset + deposit.Remove("Asset"); + await Assert.ThrowsExceptionAsync(() => Validation.Validate(deposit), "AMMDeposit: missing field Asset"); + deposit["Asset"] = new Dictionary() { { "currency", "XRP" } }; + //throws w/ Asset must be an Issue + deposit["Asset"] = 1234; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(deposit), "AMMDeposit: Asset must be an Issue"); + deposit["Asset"] = new Dictionary() { { "currency", "XRP" } }; + + //throws w/ missing field Asset + deposit.Remove("Asset2"); + await Assert.ThrowsExceptionAsync(() => Validation.Validate(deposit), "AMMDeposit: missing field Asset2"); + deposit["Asset2"] = new Dictionary() { { "currency", "XRP" } }; + //throws w/ Asset must be an Issue + deposit["Asset2"] = 1234; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(deposit), "AMMDeposit: Asset2 must be an Issue"); + deposit["Asset2"] = new Dictionary() { { "currency", "ETH" }, { "issuer", "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd" } }; + + //throws w/ must set at least LPTokenOut or Amount + await Assert.ThrowsExceptionAsync(() => Validation.Validate(deposit), "AMMDeposit: must set at least LPTokenOut or Amount"); + + //throws w/ must set Amount with Amount2 + deposit["Amount2"] = new Dictionary() + { + { "currency", "ETH" }, + { "issuer", "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd" }, + { "value", "2.5" }, + }; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(deposit), "AMMDeposit: must set Amount with Amount2"); + deposit.Remove("Amount2"); + + //throws w/ must set Amount with EPrice + deposit["EPrice"] = "25"; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(deposit), "AMMDeposit: must set Amount with EPrice"); + deposit.Remove("EPrice"); + + //throws w/ LPTokenOut must be an IssuedCurrencyAmount + deposit["LPTokenOut"] = 1234; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(deposit), "AMMDeposit: LPTokenOut must be an IssuedCurrencyAmount"); + deposit.Remove("LPTokenOut"); + + //throws w/ Amount must be an Amount + deposit["Amount"] = 1234; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(deposit), "AMMDeposit: Amount must be an Amount"); + deposit.Remove("Amount"); + + //throws w/ Amount2 must be an Amount + deposit["Amount"] = "1000"; + deposit["Amount2"] = 1234; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(deposit), "AMMDeposit: Amount2 must be an Amount"); + deposit.Remove("Amount"); + deposit.Remove("Amount2"); + + //throws w/ EPrice must be an Amount + deposit["Amount"] = "1000"; + deposit["EPrice"] = 1234; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(deposit), "AMMDeposit: EPrice must be an Amount"); + deposit.Remove("Amount"); + deposit.Remove("EPrice"); + + + } + } +} + + + diff --git a/Tests/Xrpl.Tests/Models/TestAMMVote.cs b/Tests/Xrpl.Tests/Models/TestAMMVote.cs new file mode 100644 index 0000000..318964d --- /dev/null +++ b/Tests/Xrpl.Tests/Models/TestAMMVote.cs @@ -0,0 +1,79 @@ +// https://github.com/XRPLF/xrpl.js/blob/amm-beta/packages/xrpl/test/models/AMMVote.ts + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using System.Collections.Generic; +using System.Threading.Tasks; + +using Xrpl.Client.Exceptions; +using Xrpl.Models.Transactions; + +namespace XrplTests.Xrpl.Models +{ + [TestClass] + public class TestUAMMVote + { + public static Dictionary vote; + + [ClassInitialize] + public static void MyClassInitialize(TestContext testContext) + { + vote = new Dictionary + { + {"TransactionType", "AMMVote"}, + {"Account", "rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm"}, + {"Asset", new Dictionary(){{"currency","XRP"}}}, + {"Asset2", new Dictionary(){{"currency","ETH"},{"issuer", "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd" } }}, + {"TradingFee", 25u}, + {"Sequence", 1337u}, + }; + } + + [TestMethod] + public async Task TestVerifyValid() + { + //verifies valid AMMVote + await Validation.Validate(vote); + + //throws w/ missing field Asset + vote.Remove("Asset"); + await Assert.ThrowsExceptionAsync(() => Validation.Validate(vote), "AMMVote: missing field Asset"); + vote["Asset"] = new Dictionary() { { "currency", "XRP" } }; + //throws w/ Asset must be an Issue + vote["Asset"] = 1234; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(vote), "AMMVote: Asset must be an Issue"); + vote["Asset"] = new Dictionary() { { "currency", "XRP" } }; + + //throws w/ missing field Asset + vote.Remove("Asset2"); + await Assert.ThrowsExceptionAsync(() => Validation.Validate(vote), "AMMVote: missing field Asset2"); + vote["Asset2"] = new Dictionary() { { "currency", "XRP" } }; + //throws w/ Asset must be an Issue + vote["Asset2"] = 1234; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(vote), "AMMVote: Asset2 must be an Issue"); + vote["Asset2"] = new Dictionary() { { "currency", "ETH" }, { "issuer", "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd" } }; + + //throws w/ missing TradingFee + vote.Remove("TradingFee"); + await Assert.ThrowsExceptionAsync(() => Validation.Validate(vote), "AMMVote: missing field TradingFee"); + vote["TradingFee"] = 12u; + //throws w/ TradingFee must be a number + vote["TradingFee"] = "12"; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(vote), "AMMVote: TradingFee must be a number"); + vote["TradingFee"] = 12u; + + //throws when TradingFee is greater than 1000 + vote["TradingFee"] = 1001u; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(vote), "AMMVote: TradingFee must be between 0 and 1000"); + vote["TradingFee"] = 12u; + //throws when TradingFee is greater than 1000 + vote["TradingFee"] = -1; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(vote), "AMMVote: TradingFee must be between 0 and 1000"); + vote["TradingFee"] = 12u; + + } + } +} + + + diff --git a/Tests/Xrpl.Tests/Models/TestAMMWithdraw.cs b/Tests/Xrpl.Tests/Models/TestAMMWithdraw.cs new file mode 100644 index 0000000..cae7316 --- /dev/null +++ b/Tests/Xrpl.Tests/Models/TestAMMWithdraw.cs @@ -0,0 +1,167 @@ +// https://github.com/XRPLF/xrpl.js/blob/amm-beta/packages/xrpl/test/models/AMMWithdraw.ts + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using System.Collections.Generic; +using System.Threading.Tasks; + +using Xrpl.Client.Exceptions; +using Xrpl.Models.Transactions; + +namespace XrplTests.Xrpl.Models +{ + [TestClass] + public class TestUAMMWithdraw + { + public static Dictionary LPTokenIn; + public static Dictionary withdraw; + + [ClassInitialize] + public static void MyClassInitialize(TestContext testContext) + { + LPTokenIn = new Dictionary() + { + { "currency", "B3813FCAB4EE68B3D0D735D6849465A9113EE048" }, + { "issuer", "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg" }, + { "value", "1000" }, + }; + withdraw = new Dictionary + { + {"TransactionType", "AMMWithdraw"}, + {"Account", "rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm"}, + {"Asset", new Dictionary(){{"currency","XRP"}}}, + {"Asset2", new Dictionary(){{"currency","ETH"},{"issuer", "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd" } }}, + {"Sequence", 1337u}, + {"Flags", 0u}, + }; + } + + [TestMethod] + public async Task TestVerifyValid() + { + //verifies valid AMMWithdraw with LPTokenIn + withdraw["LPTokenIn"] = LPTokenIn; + withdraw["Flags"] = AMMWithdrawFlags.tfLPToken; + await Validation.Validate(withdraw); + withdraw.Remove("LPTokenIn"); + withdraw["Flags"] = 0u; + + + //verifies valid AMMWithdraw with Amount + withdraw["Amount"] = "1000"; + withdraw["Flags"] = AMMWithdrawFlags.tfSingleAsset; + await Validation.Validate(withdraw); + withdraw.Remove("Amount"); + withdraw["Flags"] = 0u; + + //verifies valid AMMWithdraw with Amount and Amount2 + withdraw["Amount"] = "1000"; + withdraw["Amount2"] = new Dictionary() + { + {"currency","ETH"}, + {"issuer","rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"}, + {"value","2.5"}, + }; + withdraw["Flags"] = AMMWithdrawFlags.tfTwoAsset; + await Validation.Validate(withdraw); + withdraw.Remove("Amount"); + withdraw.Remove("Amount2"); + withdraw["Flags"] = 0u; + + + //verifies valid AMMWithdraw with Amount and LPTokenIn + withdraw["Amount"] = "1000"; + withdraw["LPTokenIn"] = LPTokenIn; + withdraw["Flags"] = AMMWithdrawFlags.tfOneAssetLPToken; + await Validation.Validate(withdraw); + withdraw.Remove("Amount"); + withdraw.Remove("LPTokenIn"); + withdraw["Flags"] = 0u; + + //verifies valid AMMWithdraw with Amount and EPrice + withdraw["Amount"] = "1000"; + withdraw["EPrice"] = "25"; + withdraw["Flags"] = AMMWithdrawFlags.tfLimitLPToken; + await Validation.Validate(withdraw); + withdraw.Remove("Amount"); + withdraw.Remove("EPrice"); + withdraw["Flags"] = 0u; + + //verifies valid AMMWithdraw one asset withdraw all + withdraw["Amount"] = "1000"; + withdraw["Flags"] = AMMWithdrawFlags.tfOneAssetWithdrawAll; + await Validation.Validate(withdraw); + withdraw.Remove("Amount"); + withdraw["Flags"] = 0u; + + //verifies valid AMMWithdraw withdraw all + withdraw["Flags"] = AMMWithdrawFlags.tfWithdrawAll; + await Validation.Validate(withdraw); + withdraw["Flags"] = 0u; + + + //throws w/ missing field Asset + withdraw.Remove("Asset"); + await Assert.ThrowsExceptionAsync(() => Validation.Validate(withdraw), "AMMWithdraw: missing field Asset"); + withdraw["Asset"] = new Dictionary() { { "currency", "XRP" } }; + //throws w/ Asset must be an Issue + withdraw["Asset"] = 1234; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(withdraw), "AMMWithdraw: Asset must be an Issue"); + withdraw["Asset"] = new Dictionary() { { "currency", "XRP" } }; + + //throws w/ missing field Asset2 + withdraw.Remove("Asset2"); + await Assert.ThrowsExceptionAsync(() => Validation.Validate(withdraw), "AMMWithdraw: missing field Asset2"); + withdraw["Asset2"] = new Dictionary() { { "currency", "XRP" } }; + //throws w/ Asset must be an Issue + withdraw["Asset2"] = 1234; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(withdraw), "AMMWithdraw: Asset2 must be an Issue"); + withdraw["Asset2"] = new Dictionary() { { "currency", "ETH" }, { "issuer", "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd" } }; + + //throws w/ must set Amount with Amount2 + withdraw["Amount2"] = new Dictionary() + { + { "currency", "ETH" }, + { "issuer", "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd" }, + { "value", "2.5" }, + }; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(withdraw), "AMMWithdraw: must set Amount with Amount2"); + withdraw.Remove("Amount2"); + + //throws w/ must set Amount with EPrice + withdraw["EPrice"] = "25"; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(withdraw), "AMMWithdraw: must set Amount with EPrice"); + withdraw.Remove("EPrice"); + + //throws w/ LPTokenIn must be an IssuedCurrencyAmount + withdraw["LPTokenIn"] = 1234; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(withdraw), "AMMWithdraw: LPTokenIn must be an IssuedCurrencyAmount"); + withdraw.Remove("LPTokenIn"); + + //throws w/ Amount must be an Amount + withdraw["Amount"] = 1234; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(withdraw), "AMMWithdraw: Amount must be an Amount"); + withdraw.Remove("Amount"); + + //throws w/ Amount2 must be an Amount + withdraw["Amount"] = "1000"; + withdraw["Amount2"] = 1234; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(withdraw), "AMMWithdraw: Amount2 must be an Amount"); + withdraw.Remove("Amount"); + withdraw.Remove("Amount2"); + + //throws w/ EPrice must be an Amount + withdraw["Amount"] = "1000"; + withdraw["EPrice"] = 1234; + await Assert.ThrowsExceptionAsync(() => Validation.Validate(withdraw), "AMMWithdraw: EPrice must be an Amount"); + withdraw.Remove("Amount"); + withdraw.Remove("EPrice"); + + + } + } +} + + + + diff --git a/Xrpl/Client/IXrplClient.cs b/Xrpl/Client/IXrplClient.cs index ed60d99..6c0d51c 100644 --- a/Xrpl/Client/IXrplClient.cs +++ b/Xrpl/Client/IXrplClient.cs @@ -388,7 +388,9 @@ public Task Submit(Dictionary tx, XrplWallet wallet) /// public Task Submit(ITransactionCommon tx, XrplWallet wallet) { - Dictionary txJson = JsonConvert.DeserializeObject>(tx.ToJson()); + var json = tx.ToJson(); + //var json = JsonConvert.SerializeObject(tx); + Dictionary txJson = JsonConvert.DeserializeObject>(json); return this.Submit(txJson, true, false, wallet); } diff --git a/Xrpl/Client/Json/Converters/CurrencyConverter.cs b/Xrpl/Client/Json/Converters/CurrencyConverter.cs index 61c1c18..e1033f3 100644 --- a/Xrpl/Client/Json/Converters/CurrencyConverter.cs +++ b/Xrpl/Client/Json/Converters/CurrencyConverter.cs @@ -62,4 +62,61 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist /// bool result public override bool CanConvert(Type objectType) => objectType == typeof(Currency); } + /// currency json converter + public class IssuedCurrencyConverter : JsonConverter + { + /// + /// write to json object + /// + /// writer + /// value + /// json serializer + /// Cannot write this object type + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value is Common.IssuedCurrency currency) + { + if (currency.Currency == "XRP") + { + JToken t = JToken.FromObject(new Common.XRP()); + t.WriteTo(writer); + } + else + { + JToken t = JToken.FromObject(currency); + t.WriteTo(writer); + } + } + else + { + throw new NotSupportedException("Cannot write this object type"); + } + } + /// read from json object + /// json reader + /// object type + /// object value + /// json serializer + /// + /// Cannot convert value + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, + JsonSerializer serializer) + { + return reader.TokenType switch + { + JsonToken.Null => null, + JsonToken.String => new Common.IssuedCurrency() + { + Currency = "XRP", + }, + + JsonToken.StartObject => serializer.Deserialize(reader), + _ => throw new NotSupportedException("Cannot convert value " + objectType) + }; + } + /// Can convert object to currency + /// object type + /// bool result + public override bool CanConvert(Type objectType) => objectType == typeof(Common.IssuedCurrency); + } } diff --git a/Xrpl/Models/Common/Currency.cs b/Xrpl/Models/Common/Currency.cs index 91bbbec..63782c9 100644 --- a/Xrpl/Models/Common/Currency.cs +++ b/Xrpl/Models/Common/Currency.cs @@ -144,6 +144,11 @@ public decimal? ValueAsXrp #region Overrides of Object public override string ToString() => CurrencyValidName == "XRP" ? $"XRP: {ValueAsXrp:0.######}" : $"{CurrencyValidName}: {ValueAsNumber:0.###############}"; + public override bool Equals(object o) => o is Currency model && model.Issuer == Issuer && model.CurrencyCode == CurrencyCode; + + public static bool operator ==(Currency c1, Currency c2) => c1.Equals(c2); + + public static bool operator !=(Currency c1, Currency c2) => !c1.Equals(c2); #endregion diff --git a/Xrpl/Models/Common/LedgerIndex.cs b/Xrpl/Models/Common/LedgerIndex.cs index df8c34f..6e8116b 100644 --- a/Xrpl/Models/Common/LedgerIndex.cs +++ b/Xrpl/Models/Common/LedgerIndex.cs @@ -1,4 +1,9 @@ -using Xrpl.Models.Ledger; +using System.Collections.Generic; +using System.Globalization; +using Newtonsoft.Json; +using Xrpl.BinaryCodec.Types; +using Xrpl.Models.Ledger; +using Xrpl.Client.Extensions; //https://xrpl.org/ledger-header.html#ledger-index //https://github.com/XRPLF/xrpl.js/blob/76b73e16a97e1a371261b462ee1a24f1c01dbb0c/packages/xrpl/src/models/common/index.ts diff --git a/Xrpl/Models/Enums.cs b/Xrpl/Models/Enums.cs index 35f73b1..4ded38f 100644 --- a/Xrpl/Models/Enums.cs +++ b/Xrpl/Models/Enums.cs @@ -70,7 +70,32 @@ public enum TransactionType /// /// A UNLModify pseudo-transaction marks a change to the Negative UNL, indicating that a trusted validator has gone offline or come back online. /// - UNLModify + UNLModify, + /// AMMBid is used for submitting a vote for the trading fee of an AMM Instance. + AMMBid, + /// + /// AMMCreate is used to create AccountRoot and the corresponding AMM ledger entries. + /// + AMMCreate, + /// + /// Delete an empty Automated Market Maker (AMM) instance that could not be fully deleted automatically. + /// + AMMDelete, + /// + /// AMMDeposit is the deposit transaction used to add liquidity to the AMM instance pool, + /// thus obtaining some share of the instance's pools in the form of LPTokenOut. + /// + AMMDeposit, + /// + /// AMMVote is used for submitting a vote for the trading fee of an AMM Instance. + /// + AMMVote, + /// + /// AMMWithdraw is the withdraw transaction used to remove liquidity from the AMM + /// instance pool, thus redeeming some share of the pools that one owns in the form + /// of LPTokenIn. + /// + AMMWithdraw, } /// /// Each ledger version's state data is a set of ledger objects, sometimes called ledger entries, @@ -144,7 +169,8 @@ public enum LedgerEntryType /// /// A record of preauthorization for sending payments to an account that requires authorization. /// - DepositPreauth + DepositPreauth, + AMM } public enum StreamType diff --git a/Xrpl/Models/Ledger/LOAmm.cs b/Xrpl/Models/Ledger/LOAmm.cs new file mode 100644 index 0000000..2319fbb --- /dev/null +++ b/Xrpl/Models/Ledger/LOAmm.cs @@ -0,0 +1,132 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +using Xrpl.Client.Json.Converters; +using Xrpl.Models.Common; + +namespace Xrpl.Models.Ledger +{ + public class LOAmm : BaseLedgerEntry + { + public LOAmm() + { + LedgerEntryType = LedgerEntryType.AccountRoot; + } + /// + /// The account that tracks the balance of LPTokens between the AMM instance via Trustline. + /// + public string AMMAccount { get; set; } + /// + /// Specifies one of the pool assets (XRP or token) of the AMM instance. + /// + [JsonConverter(typeof(CurrencyConverter))] + public Currency Asset { get; set; } + /// + /// Specifies the other pool asset of the AMM instance. + /// + [JsonConverter(typeof(CurrencyConverter))] + public Currency Asset2 { get; set; } + /// + /// Details of the current owner of the auction slot. + /// + public AuctionSlot AuctionSlot { get; set; } + /// + /// The total outstanding balance of liquidity provider tokens from this AMM instance.
+ /// The holders of these tokens can vote on the AMM's trading fee in proportion to their holdings, + /// or redeem the tokens for a share of the AMM's assets which grows with the trading fees collected. + ///
+ [JsonConverter(typeof(CurrencyConverter))] + public Currency LPTokenBalance { get; set; } + /// + /// Specifies the fee, in basis point, to be charged to the traders for the trades + /// executed against the AMM instance.
+ /// Trading fee is a percentage of the trading volume.
+ /// Valid values for this field are between 0 and 1000 inclusive.
+ /// A value of 1 is equivalent to 1/10 bps or 0.001%, allowing trading fee + /// between 0% and 1%.
+ /// This field is required. + ///
+ public uint TradingFee { get; set; } + /// + /// A list of vote objects, representing votes on the pool's trading fee.. + /// + public List VoteSlots { get; set; } + /// + /// The ledger index of the current in-progress ledger, which was used when + /// retrieving this information. + /// + public int? LedgerCurrentIndex { get; set; } + /// + /// True if this data is from a validated ledger version;
+ /// if omitted or set to false, this data is not final. + ///
+ public bool? Validated { get; set; } + + } + + public interface IAuthAccount + { + public string Account { get; set; } + } + public class AuthAccount : IAuthAccount + { + public string Account { get; set; } + } + + public interface IVoteEntry + { + public string Account { get; set; } + public uint TradingFee { get; set; } + public uint VoteWeight { get; set; } + } + public class VoteEntry : IVoteEntry + { + [JsonProperty("account")] + public string Account { get; set; } + [JsonProperty("trading_fee")] + public uint TradingFee { get; set; } + [JsonProperty("vote_weight")] + public uint VoteWeight { get; set; } + } + /// + /// Details of the current owner of the auction slot. + /// + public class AuctionSlot + { + /// + /// The current owner of this auction slot. + /// + [JsonProperty("account")] + public string Account { get; set; } + /// + /// A list of at most 4 additional accounts that are authorized to trade at the discounted fee for this AMM instance. + /// + [JsonProperty("auth_accounts")] + public List AuthAccounts { get; set; } + /// + /// The trading fee to be charged to the auction owner, in the same format as TradingFee.
+ /// By default this is 0, meaning that the auction owner can trade at no fee instead of the standard fee for this AMM. + ///
+ [JsonProperty("discounted_fee")] + public uint DiscountedFee { get; set; } + /// + /// The time when this slot expires, in seconds since the Ripple Epoch. + /// + [JsonProperty("expiration")] + public uint Expiration { get; set; } + /// + /// The amount the auction owner paid to win this slot, in LPTokens. + /// + [JsonProperty("price")] + [JsonConverter(typeof(CurrencyConverter))] + public Currency Price { get; set; } + /// + /// The current 72-minute time interval this auction slot is in, from 0 to 19. + /// The auction slot expires after 24 hours (20 intervals of 72 minutes) + /// and affects the cost to outbid the current holder and how much the current holder is refunded if someone outbids them. + /// + [JsonProperty("time_interval")] + public uint TimeInterval { get; set; } + } + +} \ No newline at end of file diff --git a/Xrpl/Models/Ledger/LONegativeUNL.cs b/Xrpl/Models/Ledger/LONegativeUNL.cs new file mode 100644 index 0000000..ba05d7a --- /dev/null +++ b/Xrpl/Models/Ledger/LONegativeUNL.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; + +namespace Xrpl.Models.Ledger +{ + /// https://github.com/XRPLF/xrpl.js/blob/98f8223b23def3229a42e56d39db42d8a65f506b/packages/xrpl/src/models/ledger/NegativeUNL.ts#L3 + /// + /// The NegativeUNL object type contains the current status of the Negative UNL, + /// a list of trusted validators currently believed to be offline. + /// + public class LONegativeUNL : BaseLedgerEntry + { + public LONegativeUNL() + { + LedgerEntryType = LedgerEntryType.NegativeUNL; + } + /// + /// A list of trusted validators that are currently disabled. + /// + public List DisabledValidators { get; set; } + /// + /// The public key of a trusted validator that is scheduled to be disabled in the next flag ledger. + /// + public string ValidatorToDisable { get; set; } + /// + /// The public key of a trusted validator in the Negative UNL that is scheduled to be re-enabled in the next flag ledger. + /// + public string ValidatorToReEnable { get; set; } + } + public interface IDisabledValidator + { + public long FirstLedgerSequence { get; set; } + public string PublicKey { get; set; } + } + public class DisabledValidator : IDisabledValidator + { + public long FirstLedgerSequence { get; set; } + public string PublicKey { get; set; } + } + +} \ No newline at end of file diff --git a/Xrpl/Models/Methods/AMMInfo.cs b/Xrpl/Models/Methods/AMMInfo.cs new file mode 100644 index 0000000..ae573af --- /dev/null +++ b/Xrpl/Models/Methods/AMMInfo.cs @@ -0,0 +1,112 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +using Xrpl.Client.Json.Converters; +using Xrpl.Models.Common; +using Xrpl.Models.Ledger; +using Xrpl.Models.Subscriptions; + +//https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/models/ledger/AMM.ts +namespace Xrpl.Models.Methods +{ + /// + /// The `amm_info` command retrieves information about an AMM instance. + /// + /// Returns an . + public class AMMInfoRequest : BaseLedgerRequest + { + public AMMInfoRequest() + { + Command = "amm_info"; + } + + [JsonProperty("amm_info")] + public string? AmmAccount { get; set; } + /// + /// Specifies one of the pool assets (XRP or token) of the AMM instance.
+ /// Both asset and asset2 must be defined to specify an AMM instance. + ///
+ [JsonProperty("asset")] + [JsonConverter(typeof(CurrencyConverter))] + public Currency Asset { get; set; } + /// + /// Specifies the other pool asset of the AMM instance.
+ /// Both asset and asset2 must be defined to specify an AMM instance. + ///
+ [JsonProperty("asset2")] + [JsonConverter(typeof(CurrencyConverter))] + public Currency Asset2 { get; set; } + } + + /// + /// Response expected from an . + /// + public class AMMInfoResponse : BaseResponse + { + /// + /// The account that tracks the balance of LPTokens between the AMM instance via Trustline. + /// + [JsonProperty("auction_slot")] + public string Account { get; set; } + /// + /// Specifies one of the pool assets (XRP or token) of the AMM instance. + /// + [JsonProperty("amount")] + [JsonConverter(typeof(CurrencyConverter))] + public Currency Amount { get; set; } + /// + /// Specifies the other pool asset of the AMM instance. + /// + [JsonProperty("amount2")] + public Currency Amount2 { get; set; } + [JsonConverter(typeof(CurrencyConverter))] + [JsonProperty("asset_frozen")] + public bool? AssetFrozen { get; set; } + [JsonProperty("asset2_frozen")] + public bool? Asset2Frozen { get; set; } + /// + /// Details of the current owner of the auction slot. + /// + [JsonProperty("auction_slot")] + public AuctionSlot AuctionSlot { get; set; } + /// + /// The total outstanding balance of liquidity provider tokens from this AMM instance.
+ /// The holders of these tokens can vote on the AMM's trading fee in proportion to their holdings, + /// or redeem the tokens for a share of the AMM's assets which grows with the trading fees collected. + ///
+ [JsonProperty("lp_token")] + [JsonConverter(typeof(CurrencyConverter))] + public Currency LPTokenBalance { get; set; } + /// + /// Specifies the fee, in basis point, to be charged to the traders for the trades + /// executed against the AMM instance.
+ /// Trading fee is a percentage of the trading volume.
+ /// Valid values for this field are between 0 and 1000 inclusive.
+ /// A value of 1 is equivalent to 1/10 bps or 0.001%, allowing trading fee + /// between 0% and 1%.
+ /// This field is required. + ///
+ [JsonProperty("trading_fee")] + public uint TradingFee { get; set; } + /// + /// Keeps a track of up to eight active votes for the instance. + /// + [JsonProperty("vote_slots")] + public List VoteSlots { get; set; } + /// + /// The ledger index of the ledger version that was used to generate this response. + /// + [JsonProperty("ledger_index")] + public int? LedgerIndex { get; set; } + /// + ///The identifying hash of the ledger that was used to generate this response. + /// + [JsonProperty("ledger_hash")] + public string? LedgerHash { get; set; } + /// + /// True if this data is from a validated ledger version;
+ /// if omitted or set to false, this data is not final. + ///
+ public bool? Validated { get; set; } + } +} diff --git a/Xrpl/Models/Transactions/AMMBid.cs b/Xrpl/Models/Transactions/AMMBid.cs new file mode 100644 index 0000000..8aa4142 --- /dev/null +++ b/Xrpl/Models/Transactions/AMMBid.cs @@ -0,0 +1,150 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +using Xrpl.Client.Exceptions; +using Xrpl.Models.Ledger; +using Xrpl.Models.Methods; + +// https://github.com/XRPLF/xrpl.js/blob/amm/packages/xrpl/src/models/transactions/AMMBid.ts + +namespace Xrpl.Models.Transactions +{ + + /// + /// AMMBid is used for submitting a vote for the trading fee of an AMM Instance. + /// Any XRPL account that holds LPToken for an AMM instance may submit this + /// transaction to vote for the trading fee for that instance. + /// + public class AMMBid : TransactionCommon, IAMMBid + { + public AMMBid() + { + TransactionType = TransactionType.AMMBid; + } + + /// + public Xrpl.Models.Common.Currency Asset { get; set; } + /// + public Xrpl.Models.Common.Currency Asset2 { get; set; } + /// + public Xrpl.Models.Common.Currency? BidMin { get; set; } + /// + public Xrpl.Models.Common.Currency? BidMax { get; set; } + /// + public List AuthAccounts { get; set; } + } + /// + /// AMMBid is used for submitting a vote for the trading fee of an AMM Instance. + /// Any XRPL account that holds LPToken for an AMM instance may submit this + /// transaction to vote for the trading fee for that instance. + /// + public interface IAMMBid : ITransactionCommon + { + /// + /// Specifies one of the pool assets (XRP or token) of the AMM instance. + /// + public Xrpl.Models.Common.Currency Asset { get; set; } + /// + /// Specifies the other pool asset of the AMM instance. + /// + public Xrpl.Models.Common.Currency Asset2 { get; set; } + /// + /// This field represents the minimum price that the bidder wants to pay for the slot. + /// It is specified in units of LPToken.If specified let BidMin be X and let + /// the slot-price computed by price scheduling algorithm be Y, then bidder always pays + /// the max(X, Y). + /// + public Xrpl.Models.Common.Currency? BidMin { get; set; } + /// + /// This field represents the maximum price that the bidder wants to pay for the slot. + /// It is specified in units of LPToken. + /// + public Xrpl.Models.Common.Currency? BidMax { get; set; } + /// + /// This field represents an array of XRPL account IDs that are authorized to trade + /// at the discounted fee against the AMM instance. + /// A maximum of four accounts can be provided. + /// + public List AuthAccounts { get; set; } + } + + public partial class Validation + { + private const int MAX_AUTH_ACCOUNTS = 4; + /// + /// Verify the form and type of an AMMBid at runtime. + /// + /// An AMMBid Transaction. + /// + /// When the AMMBid is Malformed. + public static async Task ValidateAMMBid(Dictionary tx) + { + await Common.ValidateBaseTransaction(tx); + + if (!tx.TryGetValue("Asset", out var Asset1) || Asset1 is null) + { + throw new ValidationException("AMMBid: missing field Asset"); + } + + if (!Xrpl.Models.Transactions.Common.IsIssue(Asset1)) + { + throw new ValidationException("AMMBid: Asset must be an Issue"); + } + + if (!tx.TryGetValue("Asset2", out var Asset2) || Asset2 is null) + { + throw new ValidationException("AMMBid: missing field Asset2"); + } + + if (!Xrpl.Models.Transactions.Common.IsIssue(Asset2)) + { + throw new ValidationException("AMMBid: Asset2 must be an Issue"); + } + + if (tx.TryGetValue("BidMin", out var BidMin) && BidMin is not null && !Common.IsAmount(BidMin)) + { + throw new ValidationException("AMMBid: BidMin must be an Amount"); + } + + if (tx.TryGetValue("BidMax", out var BidMax) && BidMax is not null && !Common.IsAmount(BidMax)) + { + throw new ValidationException("AMMBid: BidMax must be an Amount"); + } + + if (tx.TryGetValue("AuthAccounts", out var AuthAccounts) && AuthAccounts is not null) + { + if (AuthAccounts is not List> auth_accounts) + { + throw new ValidationException("AMMBid: AuthAccounts must be an AuthAccount array"); + } + if (auth_accounts.Count > MAX_AUTH_ACCOUNTS) + { + throw new ValidationException($"AMMBid: AuthAccounts length must not be greater than {MAX_AUTH_ACCOUNTS}"); + } + + ValidateAuthAccounts(tx["Account"], auth_accounts); + } + } + + public static bool ValidateAuthAccounts(string senderAddress, List> authAccounts) + { + foreach (var account in authAccounts) + { + if (!account.TryGetValue("AuthAccount", out var auth) || auth is not Dictionary { } auth_acc) + throw new ValidationException("AMMBid: invalid AuthAccounts"); + + if (!auth_acc.TryGetValue("Account", out var acc) || acc is null) + throw new ValidationException("AMMBid: invalid AuthAccounts"); + if (acc is not string { }) + throw new ValidationException("AMMBid: invalid AuthAccounts"); + if (acc is string { } s && s == senderAddress) + throw new ValidationException("AMMBid: AuthAccounts must not include sender's address"); + } + + return true; + } + } +} + diff --git a/Xrpl/Models/Transactions/AMMCreate.cs b/Xrpl/Models/Transactions/AMMCreate.cs new file mode 100644 index 0000000..7597a9f --- /dev/null +++ b/Xrpl/Models/Transactions/AMMCreate.cs @@ -0,0 +1,105 @@ +#nullable enable +using System.Collections.Generic; +using System.Threading.Tasks; + +using Xrpl.BinaryCodec.Types; +using Xrpl.Client.Exceptions; +using Xrpl.Models.Methods; + +namespace Xrpl.Models.Transactions +{ + + /// + /// AMMCreate is used to create AccountRoot and the corresponding AMM ledger entries. + /// This allows for the creation of only one AMM instance per unique asset pair. + /// + public class AMMCreate : TransactionCommon, IAMMCreate + { + public AMMCreate() + { + TransactionType = TransactionType.AMMCreate; + } + + /// + public Xrpl.Models.Common.Currency Amount { get; set; } + /// + public Xrpl.Models.Common.Currency Amount2 { get; set; } + /// + public uint TradingFee { get; set; } + } + /// + /// AMMCreate is used to create AccountRoot and the corresponding AMM ledger entries. + /// This allows for the creation of only one AMM instance per unique asset pair. + /// + public interface IAMMCreate : ITransactionCommon + { + /// + /// Specifies one of the pool assets (XRP or token) of the AMM instance. + /// + public Xrpl.Models.Common.Currency Amount { get; set; } + /// + /// Specifies the other pool asset of the AMM instance. + /// + public Xrpl.Models.Common.Currency Amount2 { get; set; } + /// + /// Specifies the fee, in basis point, to be charged + /// to the traders for the trades executed against the AMM instance. + /// Trading fee is a percentage of the trading volume. + /// Valid values for this field are between 0 and 1000 inclusive. + /// A value of 1 is equivalent to 1/10 bps or 0.001%, allowing trading fee + /// between 0% and 1%. + /// + public uint TradingFee { get; set; } + } + + public partial class Validation + { + public const uint AMM_MAX_TRADING_FEE = 1000; + /// + /// Verify the form and type of an AMMCreate at runtime. + /// + /// An AMMCreate Transaction. + /// + /// When the AMMCreate is Malformed. + public static async Task ValidateAMMCreate(Dictionary tx) + { + await Common.ValidateBaseTransaction(tx); + + if (!tx.TryGetValue("Amount", out var Amount1) || Amount1 is null) + { + throw new ValidationException("AMMCreate: missing field Amount"); + } + + if (!Xrpl.Models.Transactions.Common.IsAmount(Amount1)) + { + throw new ValidationException("AMMCreate: Amount must be an Amount"); + } + + if (!tx.TryGetValue("Amount2", out var Amount2) || Amount2 is null) + { + throw new ValidationException("AMMCreate: missing field Amount2"); + } + + if (!Xrpl.Models.Transactions.Common.IsAmount(Amount2)) + { + throw new ValidationException("AMMCreate: Amount2 must be an Amount"); + } + + if (!tx.TryGetValue("TradingFee", out var TradingFee) || TradingFee is null) + { + throw new ValidationException("AMMCreate: missing field TradingFee"); + } + + if (TradingFee is not uint fee) + { + throw new ValidationException("AMMCreate: TradingFee must be a number"); + } + + if (fee is < 0 or > AMM_MAX_TRADING_FEE) + { + throw new ValidationException($"AMMCreate: TradingFee must be between 0 and {AMM_MAX_TRADING_FEE}"); + } + } + } +} + diff --git a/Xrpl/Models/Transactions/AMMDelete.cs b/Xrpl/Models/Transactions/AMMDelete.cs new file mode 100644 index 0000000..9b38bba --- /dev/null +++ b/Xrpl/Models/Transactions/AMMDelete.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xrpl.Client.Exceptions; + +namespace Xrpl.Models.Transactions +{ + /// + /// Delete an empty Automated Market Maker (AMM) instance that could not be fully deleted automatically. + /// Tip: The AMMWithdraw transaction automatically tries to delete an AMM, along with associated ledger + /// entries such as empty trust lines, if it withdrew all the assets from the AMM's pool. + /// However, if there are too many trust lines to the AMM account to remove in one transaction, + /// it may stop before fully removing the AMM.Similarly, an AMMDelete transaction removes up to + /// a maximum number of trust lines; in extreme cases, it may take several AMMDelete transactions + /// to fully delete the trust lines and the associated AMM. + /// In all cases, the AMM ledger entry and AMM account are deleted by the last such transaction. + /// + public class AMMDelete : TransactionCommon, IAMMDelete + { + public AMMDelete() + { + TransactionType = TransactionType.AMMDelete; + } + + /// + public Xrpl.Models.Common.Currency Asset { get; set; } + /// + public Xrpl.Models.Common.Currency Asset2 { get; set; } + /// + public uint TradingFee { get; set; } + } + /// + /// Delete an empty Automated Market Maker (AMM) instance that could not be fully deleted automatically. + /// Tip: The AMMWithdraw transaction automatically tries to delete an AMM, along with associated ledger + /// entries such as empty trust lines, if it withdrew all the assets from the AMM's pool. + /// However, if there are too many trust lines to the AMM account to remove in one transaction, + /// it may stop before fully removing the AMM.Similarly, an AMMDelete transaction removes up to + /// a maximum number of trust lines; in extreme cases, it may take several AMMDelete transactions + /// to fully delete the trust lines and the associated AMM. + /// In all cases, the AMM ledger entry and AMM account are deleted by the last such transaction. + /// + public interface IAMMDelete : ITransactionCommon + { + /// + /// The definition for one of the assets in the AMM's pool. + /// + public Xrpl.Models.Common.Currency Asset { get; set; } + /// + /// The definition for the other asset in the AMM's pool. + /// + public Xrpl.Models.Common.Currency Asset2 { get; set; } + } + + public partial class Validation + { + /// + /// Verify the form and type of an AMMDelete at runtime. + /// + /// An AMMDelete Transaction. + /// + /// When the AMMDelete is Malformed. + public static async Task ValidateAMMDelete(Dictionary tx) + { + await Common.ValidateBaseTransaction(tx); + + if (!tx.TryGetValue("Asset", out var Asset) || Asset is null) + { + throw new ValidationException("AMMDelete: missing field Asset"); + } + + if (!Xrpl.Models.Transactions.Common.IsAmount(Asset)) + { + throw new ValidationException("AMMDelete: Asset must be a Currency"); + } + if (!tx.TryGetValue("Asset2", out var Asset2) || Asset2 is null) + { + throw new ValidationException("AMMDelete: missing field Asset2"); + } + + if (!Xrpl.Models.Transactions.Common.IsAmount(Asset2)) + { + throw new ValidationException("AMMDelete: Asset2 must be a Currency"); + } + } + } +} diff --git a/Xrpl/Models/Transactions/AMMDeposit.cs b/Xrpl/Models/Transactions/AMMDeposit.cs new file mode 100644 index 0000000..d6349bf --- /dev/null +++ b/Xrpl/Models/Transactions/AMMDeposit.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using static Xrpl.Models.Common.Common; +using Xrpl.BinaryCodec.Types; +using Xrpl.Client.Exceptions; + +namespace Xrpl.Models.Transactions +{ + /// + /// Enum representing values for AMMDeposit Transaction Flags. + /// + public enum AMMDepositFlags : uint + { + /// + /// Perform a double-asset deposit and receive the specified amount of LP Tokens. + /// + tfLPToken = 65536,//0x00010000 + /// + /// Perform a single-asset deposit with a specified amount of the asset to deposit. + /// + tfSingleAsset = 524288,//0x00080000 + /// + /// Perform a double-asset deposit with specified amounts of both assets. + /// + tfTwoAsset = 1048576,//0x00100000 + /// + /// Perform a single-asset deposit and receive the specified amount of LP Tokens. + /// + tfOneAssetLPToken = 2097152,//0x00200000 + /// + /// Perform a single-asset deposit with a specified effective price. + /// + tfLimitLPToken = 4194304 //0x00400000 + }; + + //public interface AMMDepositFlagsInterface : GlobalFlags + //{ + // bool? tfLPToken { get; set; } + // bool? tfSingleAsset { get; set; } + // bool? tfTwoAsset { get; set; } + // bool? tfOneAssetLPToken { get; set; } + // bool? tfLimitLPToken { get; set; } + //} + + /// + /// AMMDeposit is the deposit transaction used to add liquidity to the AMM instance pool, + /// thus obtaining some share of the instance's pools in the form of LPTokenOut. + /// The following are the recommended valid combinations: + /// - LPTokenOut + /// - Amount + /// - Amount and Amount2 + /// - Amount and LPTokenOut + /// - Amount and EPrice + /// + public class AMMDeposit : TransactionCommon, IAMMDeposit + { + public AMMDeposit() + { + TransactionType = TransactionType.AMMDeposit; + } + /// + public Xrpl.Models.Common.Currency Asset { get; set; } + + /// + public Xrpl.Models.Common.Currency Asset2 { get; set; } + + /// + public Xrpl.Models.Common.Currency? LPTokenOut { get; set; } + + /// + public Xrpl.Models.Common.Currency? Amount { get; set; } + + /// + public Xrpl.Models.Common.Currency? Amount2 { get; set; } + + /// + public Xrpl.Models.Common.Currency? EPrice { get; set; } + } + + /// + /// AMMDeposit is the deposit transaction used to add liquidity to the AMM instance pool, + /// thus obtaining some share of the instance's pools in the form of LPTokenOut. + /// The following are the recommended valid combinations: + /// - LPTokenOut + /// - Amount + /// - Amount and Amount2 + /// - Amount and LPTokenOut + /// - Amount and EPrice + /// + public interface IAMMDeposit : ITransactionCommon + { + /// + /// Specifies one of the pool assets (XRP or token) of the AMM instance. + /// + public Xrpl.Models.Common.Currency Asset { get; set; } + + /// + /// Specifies the other pool asset of the AMM instance. + /// + public Xrpl.Models.Common.Currency Asset2 { get; set; } + + /// + /// Specifies the amount of shares of the AMM instance pools that the trader wants to redeem or trade in. + /// + public Xrpl.Models.Common.Currency? LPTokenOut { get; set; } + + /// + /// Specifies one of the pool assets (XRP or token) of the AMM instance to deposit more of its value. + /// + public Xrpl.Models.Common.Currency? Amount { get; set; } + + /// + /// Specifies the other pool asset of the AMM instance to deposit more of its value. + /// + public Xrpl.Models.Common.Currency? Amount2 { get; set; } + + /// + /// Specifies the maximum effective-price that LPTokenOut can be traded out. + /// + public Xrpl.Models.Common.Currency? EPrice { get; set; } + } + + + public partial class Validation + { + /// + /// Verify the form and type of an AMMDeposit at runtime. + /// + /// An AMMDeposit Transaction. + /// + /// When the AMMDeposit is Malformed. + public static async Task ValidateAMMDeposit(Dictionary tx) + { + await Common.ValidateBaseTransaction(tx); + + if (!tx.TryGetValue("Asset", out var Asset1) || Asset1 is null) + { + throw new ValidationException("AMMDeposit: missing field Asset"); + } + + if (!Xrpl.Models.Transactions.Common.IsIssue(Asset1)) + { + throw new ValidationException("AMMDeposit: Asset must be an Issue"); + } + + if (!tx.TryGetValue("Asset2", out var Asset2) || Asset2 is null) + { + throw new ValidationException("AMMDeposit: missing field Asset2"); + } + + if (!Xrpl.Models.Transactions.Common.IsIssue(Asset2)) + { + throw new ValidationException("AMMDeposit: Asset2 must be an Issue"); + } + + tx.TryGetValue("Amount", out var Amount); + tx.TryGetValue("Amount2", out var Amount2); + tx.TryGetValue("EPrice", out var EPrice); + tx.TryGetValue("LPTokenOut", out var LPTokenOut); + + if (Amount2 is not null && Amount is null) + throw new ValidationException("AMMDeposit: must set Amount with Amount2"); + if (EPrice is not null && Amount is null) + throw new ValidationException("AMMDeposit: must set Amount with EPrice"); + if (LPTokenOut is null && Amount is null) + throw new ValidationException("AMMDeposit: must set at least LPTokenOut or Amount"); + + if (LPTokenOut is not null && !Common.IsIssuedCurrency(LPTokenOut)) + throw new ValidationException("AMMDeposit: LPTokenOut must be an IssuedCurrencyAmount"); + if (Amount is not null && !Common.IsAmount(Amount)) + throw new ValidationException("AMMDeposit: Amount must be an Amount"); + if (Amount2 is not null && !Common.IsAmount(Amount2)) + throw new ValidationException("AMMDeposit: Amount2 must be an Amount"); + if (EPrice is not null && !Common.IsAmount(EPrice)) + throw new ValidationException("AMMDeposit: EPrice must be an Amount"); + } + } + +} diff --git a/Xrpl/Models/Transactions/AMMVote.cs b/Xrpl/Models/Transactions/AMMVote.cs new file mode 100644 index 0000000..db6387c --- /dev/null +++ b/Xrpl/Models/Transactions/AMMVote.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +using Xrpl.Client.Exceptions; + +namespace Xrpl.Models.Transactions +{ + public class AMMVote : TransactionCommon, IAMMVote + { + public AMMVote() + { + TransactionType = TransactionType.AMMVote; + } + /// + public Xrpl.Models.Common.Currency Asset { get; set; } + /// + public Xrpl.Models.Common.Currency Asset2 { get; set; } + /// + public uint TradingFee { get; set; } + } + + public interface IAMMVote : ITransactionCommon + { + /// + /// Specifies one of the pool assets (XRP or token) of the AMM instance. + /// + public Xrpl.Models.Common.Currency Asset { get; set; } + /// + /// Specifies the other pool asset of the AMM instance. + /// + public Xrpl.Models.Common.Currency Asset2 { get; set; } + /// + /// Specifies the fee, in basis point. + /// Valid values for this field are between 0 and 1000 inclusive. + /// A value of 1 is equivalent to 1/10 bps or 0.001%, allowing trading fee + /// between 0% and 1%. This field is required. + /// + public uint TradingFee { get; set; } + } + public partial class Validation + { + /// + /// Verify the form and type of an AMMVote at runtime. + /// + /// An AMMVote Transaction. + /// + /// When the AMMVote is Malformed. + public static async Task ValidateAMMVote(Dictionary tx) + { + await Common.ValidateBaseTransaction(tx); + tx.TryGetValue("Asset", out var Asset); + tx.TryGetValue("Asset2", out var Asset2); + tx.TryGetValue("TradingFee", out var TradingFee); + + if (Asset is null) + throw new ValidationException("AMMVote: missing field Asset"); + + if (!Xrpl.Models.Transactions.Common.IsIssue(Asset)) + throw new ValidationException("AMMVote: Asset must be an Issue"); + + if (Asset2 is null) + throw new ValidationException("AMMVote: missing field Asset2"); + + if (!Xrpl.Models.Transactions.Common.IsIssue(Asset2)) + throw new ValidationException("AMMVote: Asset2 must be an Issue"); + if(TradingFee is null) + throw new ValidationException("AMMVote: missing field TradingFee"); + if (TradingFee is not uint fee) + { + throw new ValidationException("AMMVote: TradingFee must be a number"); + } + + if (fee is < 0 or > AMM_MAX_TRADING_FEE) + { + throw new ValidationException($"AMMVote: TradingFee must be between 0 and {AMM_MAX_TRADING_FEE}"); + } + } + } + +} diff --git a/Xrpl/Models/Transactions/AMMWithdraw.cs b/Xrpl/Models/Transactions/AMMWithdraw.cs new file mode 100644 index 0000000..f4937cc --- /dev/null +++ b/Xrpl/Models/Transactions/AMMWithdraw.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; +using static Xrpl.Models.Common.Common; +using Xrpl.Client.Exceptions; +using Xrpl.Models.Common; +using Xrpl.Client.Json.Converters; + +namespace Xrpl.Models.Transactions +{ + /// + /// Enum representing values for AMMWithdrawFlags Transaction Flags + /// + /// Transaction Flags + public enum AMMWithdrawFlags : uint + { + /// + /// Perform a double-asset withdrawal and receive the specified amount of LP Tokens. + /// + tfLPToken = 65536,//0x00010000 + /// + /// Perform a double-asset withdrawal returning all your LP Tokens. + /// + tfWithdrawAll =131072,//0x00020000 + /// + /// Perform a single-asset withdrawal returning all of your LP Tokens. + /// + tfOneAssetWithdrawAll = 262144,//0x00040000 + /// + /// Perform a single-asset withdrawal with a specified amount of the asset to withdrawal. + /// + tfSingleAsset = 524288,//0x00080000 + /// + /// Perform a double-asset withdrawal with specified amounts of both assets. + /// + tfTwoAsset = 1048576,//0x00100000 + /// + /// Perform a single-asset withdrawal and receive the specified amount of LP Tokens. + /// + tfOneAssetLPToken = 2097152,//0x00200000 + /// + /// Perform a single-asset withdrawal with a specified effective price. + /// + tfLimitLPToken = 4194304 //0x00400000 + } + + //public interface AMMWithdrawFlagsInterface : GlobalFlags + //{ + // bool? tfLPToken { get; set; } + // bool? tfWithdrawAll { get; set; } + // bool? tfOneAssetWithdrawAll { get; set; } + // bool? tfSingleAsset { get; set; } + // bool? tfTwoAsset { get; set; } + // bool? tfOneAssetLPToken { get; set; } + // bool? tfLimitLPToken { get; set; } + //} + + /// + /// AMMWithdraw is the withdraw transaction used to remove liquidity from the AMM + /// instance pool, thus redeeming some share of the pools that one owns in the form + /// of LPTokenIn. + /// + public class AMMWithdraw : TransactionCommon, IAMMWithdraw + { + public AMMWithdraw() + { + TransactionType = TransactionType.AMMWithdraw; + } + #region Implementation of IAMMWithdraw + + /// + [JsonConverter(typeof(IssuedCurrencyConverter))] + public IssuedCurrency Asset { get; set; } + + /// + [JsonConverter(typeof(IssuedCurrencyConverter))] + public IssuedCurrency Asset2 { get; set; } + + /// + [JsonConverter(typeof(CurrencyConverter))] + public Currency LPTokenIn { get; set; } + + /// + [JsonConverter(typeof(CurrencyConverter))] + public Currency Amount { get; set; } + + /// + [JsonConverter(typeof(CurrencyConverter))] + public Currency Amount2 { get; set; } + + /// + [JsonConverter(typeof(CurrencyConverter))] + public Currency EPrice { get; set; } + + #endregion + } + /// + /// AMMWithdraw is the withdraw transaction used to remove liquidity from the AMM + /// instance pool, thus redeeming some share of the pools that one owns in the form + /// of LPTokenIn. + /// + public interface IAMMWithdraw : ITransactionCommon + { + /// + /// Specifies one of the pool assets (XRP or token) of the AMM instance. + /// + IssuedCurrency Asset { get; set; } + + /// + /// Specifies the other pool asset of the AMM instance. + /// + IssuedCurrency Asset2 { get; set; } + + /// + /// Specifies the amount of shares of the AMM instance pools that the trader + /// wants to redeem or trade in. + /// + Currency LPTokenIn { get; set; } + + /// + /// Specifies one of the pools assets that the trader wants to remove. + /// If the asset is XRP, then the Amount is a string specifying the number of drops. + /// Otherwise it is an IssuedCurrencyAmount object. + /// + Currency Amount { get; set; } + + /// + /// Specifies the other pool asset that the trader wants to remove. + /// + Currency Amount2 { get; set; } + + /// + /// Specifies the effective-price of the token out after successful execution of + /// the transaction. + /// + Currency EPrice { get; set; } + } + public partial class Validation + { + + /// + /// Verify the form and type of an AMMWithdraw at runtime. + /// + /// An AMMWithdraw Transaction. + /// When the AMMWithdraw is Malformed. + public static async Task ValidateAMMWithdraw(Dictionary tx) + { + await Common.ValidateBaseTransaction(tx); + + tx.TryGetValue("Asset", out var Asset); + tx.TryGetValue("Asset2", out var Asset2); + tx.TryGetValue("Amount", out var Amount); + tx.TryGetValue("Amount2", out var Amount2); + tx.TryGetValue("EPrice", out var EPrice); + tx.TryGetValue("LPTokenIn", out var LPTokenIn); + + if (Asset is null) + { + throw new ValidationException("AMMWithdraw: missing field Asset"); + } + + if (!Common.IsIssue(Asset)) + { + throw new ValidationException("AMMWithdraw: Asset must be an Issue"); + } + + if (Asset2 is null) + { + throw new ValidationException("AMMWithdraw: missing field Asset2"); + } + + if (!Common.IsIssue(Asset2)) + { + throw new ValidationException("AMMWithdraw: Asset2 must be an Issue"); + } + + if (Amount2 is not null && Amount is null) + { + throw new ValidationException("AMMWithdraw: must set Amount with Amount2"); + } + else if (EPrice is not null && Amount is null) + { + throw new ValidationException("AMMWithdraw: must set Amount with EPrice"); + } + + if (LPTokenIn is not null && !Common.IsIssuedCurrency(LPTokenIn)) + { + throw new ValidationException("AMMWithdraw: LPTokenIn must be an IssuedCurrencyAmount"); + } + + if (Amount is not null && !Common.IsAmount(Amount)) + { + throw new ValidationException("AMMWithdraw: Amount must be an Amount"); + } + + if (Amount2 is not null && !Common.IsAmount(Amount2)) + { + throw new ValidationException("AMMWithdraw: Amount2 must be an Amount"); + } + + if (EPrice is not null && !Common.IsAmount(EPrice)) + { + throw new ValidationException("AMMWithdraw: EPrice must be an Amount"); + } + } + } +} diff --git a/Xrpl/Models/Transactions/Common.cs b/Xrpl/Models/Transactions/Common.cs index dc2cd88..052bc40 100644 --- a/Xrpl/Models/Transactions/Common.cs +++ b/Xrpl/Models/Transactions/Common.cs @@ -125,6 +125,24 @@ public static double ParseAmountValue(dynamic amount) CultureInfo.InvariantCulture); } /// + /// Verify the form and type of an Issue at runtime. + /// + /// The object to check the form and type of. + /// Whether the Issue is malformed. + public static bool IsIssue(dynamic input) + { + if (!IsRecord(input)) + return false; + if (input is not Dictionary issue) + return false; + + + var length = issue.Count; + issue.TryGetValue("currency", out var currency); + issue.TryGetValue("issuer", out var issuer); + return (length == 1 && currency == "XRP") || (length == 2 && currency is string && issuer is string); + } + /// /// Verify the common fields of a transaction.
/// The validate functionality will be optional, and will check transaction form at runtime. /// This should be called any time a transaction will be verified. diff --git a/Xrpl/Models/Transactions/TxFormat.cs b/Xrpl/Models/Transactions/TxFormat.cs index dfdd34e..ae35423 100644 --- a/Xrpl/Models/Transactions/TxFormat.cs +++ b/Xrpl/Models/Transactions/TxFormat.cs @@ -278,6 +278,50 @@ static TxFormat() [Field.ReserveBase] = Requirement.Required, [Field.ReserveIncrement] = Requirement.Required }, + [BinaryCodec.Types.TransactionType.AMMBid] = new TxFormat + { + [Field.Asset] = Requirement.Required, + [Field.Asset2] = Requirement.Required, + [Field.BidMin] = Requirement.Optional, + [Field.BidMax] = Requirement.Optional, + [Field.AuthAccounts] = Requirement.Optional + }, + [BinaryCodec.Types.TransactionType.AMMCreate] = new TxFormat + { + [Field.Amount] = Requirement.Required, + [Field.Amount2] = Requirement.Required, + [Field.TradingFee] = Requirement.Required, + }, + [BinaryCodec.Types.TransactionType.AMMDelete] = new TxFormat + { + [Field.Asset] = Requirement.Required, + [Field.Asset2] = Requirement.Required, + }, + [BinaryCodec.Types.TransactionType.AMMDeposit] = new TxFormat + { + [Field.Asset] = Requirement.Required, + [Field.Asset2] = Requirement.Required, + [Field.Amount] = Requirement.Optional, + [Field.Amount2] = Requirement.Optional, + [Field.EPrice] = Requirement.Optional, + [Field.LPTokenOut] = Requirement.Optional, + }, + [BinaryCodec.Types.TransactionType.AMMVote] = new TxFormat + { + [Field.Asset] = Requirement.Required, + [Field.Asset2] = Requirement.Required, + [Field.TradingFee] = Requirement.Required, + }, + [BinaryCodec.Types.TransactionType.AMMWithdraw] = new TxFormat + { + [Field.Asset] = Requirement.Required, + [Field.Asset2] = Requirement.Required, + [Field.Amount] = Requirement.Optional, + [Field.Amount2] = Requirement.Optional, + [Field.EPrice] = Requirement.Optional, + [Field.LPTokenIn] = Requirement.Optional, + }, + }; } } diff --git a/Xrpl/Models/Transactions/Validation.cs b/Xrpl/Models/Transactions/Validation.cs index 81df0f2..0f7e6ef 100644 --- a/Xrpl/Models/Transactions/Validation.cs +++ b/Xrpl/Models/Transactions/Validation.cs @@ -132,6 +132,21 @@ public static async Task Validate(Dictionary tx) await ValidateTrustSet(tx); break; + case "AMMBid": + await ValidateAMMBid(tx); + break; + case "AMMDeposit": + await ValidateAMMDeposit(tx); + break; + case "AMMCreate": + await ValidateAMMCreate(tx); + break; + case "AMMVote": + await ValidateAMMVote(tx); + break; + case "AMMWithdraw": + await ValidateAMMWithdraw(tx); + break; default: throw new ValidationException($"Invalid field TransactionType: {type}"); } diff --git a/Xrpl/Models/Utils/Flags.cs b/Xrpl/Models/Utils/Flags.cs index a67e243..3cb111a 100644 --- a/Xrpl/Models/Utils/Flags.cs +++ b/Xrpl/Models/Utils/Flags.cs @@ -61,12 +61,25 @@ public static void SetTransactionFlagsToNumber(Dictionary tx) //TransactionType.SignerListSet => expr, //TransactionType.TicketCreate => expr, "TrustSet" => ConvertTrustSetFlagsToNumber(Flags), + "AMMDeposit" => ConvertAMMDepositFlagsToNumber(Flags), + "AMMWithdraw" => ConvertAMMWithdrawFlagsToNumber(Flags), _ => 0 }; break; } } - + public static uint ConvertAMMDepositFlagsToNumber(dynamic flags) + { + if (flags is not Dictionary flag) + return 0; + return ReduceFlags(flag, Enum.GetValues().ToDictionary(c => c.ToString(), c => (uint)c)); + } + public static uint ConvertAMMWithdrawFlagsToNumber(dynamic flags) + { + if (flags is not Dictionary flag) + return 0; + return ReduceFlags(flag, Enum.GetValues().ToDictionary(c => c.ToString(), c => (uint)c)); + } public static uint ConvertAccountSetFlagsToNumber(dynamic flags) { if (flags is not Dictionary flag) From a6b09629b7929ae8cd210b9ea409f0f3a3cc88c4 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Tue, 17 Oct 2023 10:38:53 +0300 Subject: [PATCH 23/39] add definitions.json --- Base/Xrpl.BinaryCodec/Enums/definitions.json | 2846 ++++++++++++++++++ 1 file changed, 2846 insertions(+) create mode 100644 Base/Xrpl.BinaryCodec/Enums/definitions.json diff --git a/Base/Xrpl.BinaryCodec/Enums/definitions.json b/Base/Xrpl.BinaryCodec/Enums/definitions.json new file mode 100644 index 0000000..b6b48f4 --- /dev/null +++ b/Base/Xrpl.BinaryCodec/Enums/definitions.json @@ -0,0 +1,2846 @@ +{ + "TYPES": { + "Done": -1, + "Unknown": -2, + "NotPresent": 0, + "UInt16": 1, + "UInt32": 2, + "UInt64": 3, + "Hash128": 4, + "Hash256": 5, + "Amount": 6, + "Blob": 7, + "AccountID": 8, + "STObject": 14, + "STArray": 15, + "UInt8": 16, + "Hash160": 17, + "PathSet": 18, + "Vector256": 19, + "UInt96": 20, + "UInt192": 21, + "UInt384": 22, + "UInt512": 23, + "Issue": 24, + "XChainBridge": 25, + "Transaction": 10001, + "LedgerEntry": 10002, + "Validation": 10003, + "Metadata": 10004 + }, + "LEDGER_ENTRY_TYPES": { + "Invalid": -1, + "AccountRoot": 97, + "DirectoryNode": 100, + "RippleState": 114, + "Ticket": 84, + "SignerList": 83, + "Offer": 111, + "Bridge": 105, + "LedgerHashes": 104, + "Amendments": 102, + "XChainOwnedClaimID": 113, + "XChainOwnedCreateAccountClaimID": 116, + "FeeSettings": 115, + "Escrow": 117, + "PayChannel": 120, + "Check": 67, + "DepositPreauth": 112, + "NegativeUNL": 78, + "NFTokenPage": 80, + "NFTokenOffer": 55, + "AMM": 121, + "Any": -3, + "Child": -2, + "Nickname": 110, + "Contract": 99, + "GeneratorMap": 103 + }, + "FIELDS": [ + [ + "Generic", + { + "nth": 0, + "isVLEncoded": false, + "isSerialized": false, + "isSigningField": false, + "type": "Unknown" + } + ], + [ + "Invalid", + { + "nth": -1, + "isVLEncoded": false, + "isSerialized": false, + "isSigningField": false, + "type": "Unknown" + } + ], + [ + "ObjectEndMarker", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "ArrayEndMarker", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "hash", + { + "nth": 257, + "isVLEncoded": false, + "isSerialized": false, + "isSigningField": false, + "type": "Hash256" + } + ], + [ + "index", + { + "nth": 258, + "isVLEncoded": false, + "isSerialized": false, + "isSigningField": false, + "type": "Hash256" + } + ], + [ + "taker_gets_funded", + { + "nth": 258, + "isVLEncoded": false, + "isSerialized": false, + "isSigningField": false, + "type": "Amount" + } + ], + [ + "taker_pays_funded", + { + "nth": 259, + "isVLEncoded": false, + "isSerialized": false, + "isSigningField": false, + "type": "Amount" + } + ], + [ + "LedgerEntry", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": false, + "isSigningField": true, + "type": "LedgerEntry" + } + ], + [ + "Transaction", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": false, + "isSigningField": true, + "type": "Transaction" + } + ], + [ + "Validation", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": false, + "isSigningField": true, + "type": "Validation" + } + ], + [ + "Metadata", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Metadata" + } + ], + [ + "CloseResolution", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt8" + } + ], + [ + "Method", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt8" + } + ], + [ + "TransactionResult", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt8" + } + ], + [ + "TickSize", + { + "nth": 16, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt8" + } + ], + [ + "UNLModifyDisabling", + { + "nth": 17, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt8" + } + ], + [ + "HookResult", + { + "nth": 18, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt8" + } + ], + [ + "WasLockingChainSend", + { + "nth": 19, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt8" + } + ], + [ + "LedgerEntryType", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt16" + } + ], + [ + "TransactionType", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt16" + } + ], + [ + "SignerWeight", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt16" + } + ], + [ + "TransferFee", + { + "nth": 4, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt16" + } + ], + [ + "TradingFee", + { + "nth": 5, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt16" + } + ], + [ + "DiscountedFee", + { + "nth": 6, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt16" + } + ], + [ + "Version", + { + "nth": 16, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt16" + } + ], + [ + "HookStateChangeCount", + { + "nth": 17, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt16" + } + ], + [ + "HookEmitCount", + { + "nth": 18, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt16" + } + ], + [ + "HookExecutionIndex", + { + "nth": 19, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt16" + } + ], + [ + "HookApiVersion", + { + "nth": 20, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt16" + } + ], + [ + "NetworkID", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "Flags", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "SourceTag", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "Sequence", + { + "nth": 4, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "PreviousTxnLgrSeq", + { + "nth": 5, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "LedgerSequence", + { + "nth": 6, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "CloseTime", + { + "nth": 7, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "ParentCloseTime", + { + "nth": 8, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "SigningTime", + { + "nth": 9, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "Expiration", + { + "nth": 10, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "TransferRate", + { + "nth": 11, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "WalletSize", + { + "nth": 12, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "OwnerCount", + { + "nth": 13, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "DestinationTag", + { + "nth": 14, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "HighQualityIn", + { + "nth": 16, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "HighQualityOut", + { + "nth": 17, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "LowQualityIn", + { + "nth": 18, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "LowQualityOut", + { + "nth": 19, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "QualityIn", + { + "nth": 20, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "QualityOut", + { + "nth": 21, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "StampEscrow", + { + "nth": 22, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "BondAmount", + { + "nth": 23, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "LoadFee", + { + "nth": 24, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "OfferSequence", + { + "nth": 25, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "FirstLedgerSequence", + { + "nth": 26, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "LastLedgerSequence", + { + "nth": 27, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "TransactionIndex", + { + "nth": 28, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "OperationLimit", + { + "nth": 29, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "ReferenceFeeUnits", + { + "nth": 30, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "ReserveBase", + { + "nth": 31, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "ReserveIncrement", + { + "nth": 32, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "SetFlag", + { + "nth": 33, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "ClearFlag", + { + "nth": 34, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "SignerQuorum", + { + "nth": 35, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "CancelAfter", + { + "nth": 36, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "FinishAfter", + { + "nth": 37, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "SignerListID", + { + "nth": 38, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "SettleDelay", + { + "nth": 39, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "TicketCount", + { + "nth": 40, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "TicketSequence", + { + "nth": 41, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "NFTokenTaxon", + { + "nth": 42, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "MintedNFTokens", + { + "nth": 43, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "BurnedNFTokens", + { + "nth": 44, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "HookStateCount", + { + "nth": 45, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "EmitGeneration", + { + "nth": 46, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "VoteWeight", + { + "nth": 48, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "FirstNFTokenSequence", + { + "nth": 50, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "IndexNext", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "IndexPrevious", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "BookNode", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "OwnerNode", + { + "nth": 4, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "BaseFee", + { + "nth": 5, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "ExchangeRate", + { + "nth": 6, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "LowNode", + { + "nth": 7, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "HighNode", + { + "nth": 8, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "DestinationNode", + { + "nth": 9, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "Cookie", + { + "nth": 10, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "ServerVersion", + { + "nth": 11, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "NFTokenOfferNode", + { + "nth": 12, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "EmitBurden", + { + "nth": 13, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "HookOn", + { + "nth": 16, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "HookInstructionCount", + { + "nth": 17, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "HookReturnCode", + { + "nth": 18, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "ReferenceCount", + { + "nth": 19, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "XChainClaimID", + { + "nth": 20, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "XChainAccountCreateCount", + { + "nth": 21, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "XChainAccountClaimCount", + { + "nth": 22, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "EmailHash", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash128" + } + ], + [ + "TakerPaysCurrency", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash160" + } + ], + [ + "TakerPaysIssuer", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash160" + } + ], + [ + "TakerGetsCurrency", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash160" + } + ], + [ + "TakerGetsIssuer", + { + "nth": 4, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash160" + } + ], + [ + "LedgerHash", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "ParentHash", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "TransactionHash", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "AccountHash", + { + "nth": 4, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "PreviousTxnID", + { + "nth": 5, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "LedgerIndex", + { + "nth": 6, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "WalletLocator", + { + "nth": 7, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "RootIndex", + { + "nth": 8, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "AccountTxnID", + { + "nth": 9, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "NFTokenID", + { + "nth": 10, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "EmitParentTxnID", + { + "nth": 11, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "EmitNonce", + { + "nth": 12, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "EmitHookHash", + { + "nth": 13, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "AMMID", + { + "nth": 14, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "BookDirectory", + { + "nth": 16, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "InvoiceID", + { + "nth": 17, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "Nickname", + { + "nth": 18, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "Amendment", + { + "nth": 19, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "Digest", + { + "nth": 21, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "Channel", + { + "nth": 22, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "ConsensusHash", + { + "nth": 23, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "CheckID", + { + "nth": 24, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "ValidatedHash", + { + "nth": 25, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "PreviousPageMin", + { + "nth": 26, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "NextPageMin", + { + "nth": 27, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "NFTokenBuyOffer", + { + "nth": 28, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "NFTokenSellOffer", + { + "nth": 29, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "HookStateKey", + { + "nth": 30, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "HookHash", + { + "nth": 31, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "HookNamespace", + { + "nth": 32, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "HookSetTxnID", + { + "nth": 33, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], + [ + "Amount", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "Balance", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "LimitAmount", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "TakerPays", + { + "nth": 4, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "TakerGets", + { + "nth": 5, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "LowLimit", + { + "nth": 6, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "HighLimit", + { + "nth": 7, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "Fee", + { + "nth": 8, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "SendMax", + { + "nth": 9, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "DeliverMin", + { + "nth": 10, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "Amount2", + { + "nth": 11, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "BidMin", + { + "nth": 12, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "BidMax", + { + "nth": 13, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "MinimumOffer", + { + "nth": 16, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "RippleEscrow", + { + "nth": 17, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "DeliveredAmount", + { + "nth": 18, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "NFTokenBrokerFee", + { + "nth": 19, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "BaseFeeDrops", + { + "nth": 22, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "ReserveBaseDrops", + { + "nth": 23, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "ReserveIncrementDrops", + { + "nth": 24, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "LPTokenOut", + { + "nth": 25, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "LPTokenIn", + { + "nth": 26, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "EPrice", + { + "nth": 27, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "Price", + { + "nth": 28, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "SignatureReward", + { + "nth": 29, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "MinAccountCreateAmount", + { + "nth": 30, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "LPTokenBalance", + { + "nth": 31, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "PublicKey", + { + "nth": 1, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "MessageKey", + { + "nth": 2, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "SigningPubKey", + { + "nth": 3, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "TxnSignature", + { + "nth": 4, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": false, + "type": "Blob" + } + ], + [ + "URI", + { + "nth": 5, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "Signature", + { + "nth": 6, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": false, + "type": "Blob" + } + ], + [ + "Domain", + { + "nth": 7, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "FundCode", + { + "nth": 8, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "RemoveCode", + { + "nth": 9, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "ExpireCode", + { + "nth": 10, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "CreateCode", + { + "nth": 11, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "MemoType", + { + "nth": 12, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "MemoData", + { + "nth": 13, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "MemoFormat", + { + "nth": 14, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "Fulfillment", + { + "nth": 16, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "Condition", + { + "nth": 17, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "MasterSignature", + { + "nth": 18, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": false, + "type": "Blob" + } + ], + [ + "UNLModifyValidator", + { + "nth": 19, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "ValidatorToDisable", + { + "nth": 20, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "ValidatorToReEnable", + { + "nth": 21, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "HookStateData", + { + "nth": 22, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "HookReturnString", + { + "nth": 23, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "HookParameterName", + { + "nth": 24, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "HookParameterValue", + { + "nth": 25, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "Account", + { + "nth": 1, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "Owner", + { + "nth": 2, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "Destination", + { + "nth": 3, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "Issuer", + { + "nth": 4, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "Authorize", + { + "nth": 5, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "Unauthorize", + { + "nth": 6, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "RegularKey", + { + "nth": 8, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "NFTokenMinter", + { + "nth": 9, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "EmitCallback", + { + "nth": 10, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "HookAccount", + { + "nth": 16, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "OtherChainSource", + { + "nth": 18, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "OtherChainDestination", + { + "nth": 19, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "AttestationSignerAccount", + { + "nth": 20, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "AttestationRewardAccount", + { + "nth": 21, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "LockingChainDoor", + { + "nth": 22, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "IssuingChainDoor", + { + "nth": 23, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "Indexes", + { + "nth": 1, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Vector256" + } + ], + [ + "Hashes", + { + "nth": 2, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Vector256" + } + ], + [ + "Amendments", + { + "nth": 3, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Vector256" + } + ], + [ + "NFTokenOffers", + { + "nth": 4, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Vector256" + } + ], + [ + "Paths", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "PathSet" + } + ], + [ + "LockingChainIssue", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Issue" + } + ], + [ + "IssuingChainIssue", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Issue" + } + ], + [ + "Asset", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Issue" + } + ], + [ + "Asset2", + { + "nth": 4, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Issue" + } + ], + [ + "XChainBridge", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "XChainBridge" + } + ], + [ + "TransactionMetaData", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "CreatedNode", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "DeletedNode", + { + "nth": 4, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "ModifiedNode", + { + "nth": 5, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "PreviousFields", + { + "nth": 6, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "FinalFields", + { + "nth": 7, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "NewFields", + { + "nth": 8, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "TemplateEntry", + { + "nth": 9, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "Memo", + { + "nth": 10, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "SignerEntry", + { + "nth": 11, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "NFToken", + { + "nth": 12, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "EmitDetails", + { + "nth": 13, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "Hook", + { + "nth": 14, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "Signer", + { + "nth": 16, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "Majority", + { + "nth": 18, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "DisabledValidator", + { + "nth": 19, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "EmittedTxn", + { + "nth": 20, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "HookExecution", + { + "nth": 21, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "HookDefinition", + { + "nth": 22, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "HookParameter", + { + "nth": 23, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "HookGrant", + { + "nth": 24, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "VoteEntry", + { + "nth": 25, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "AuctionSlot", + { + "nth": 26, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "AuthAccount", + { + "nth": 27, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "XChainClaimProofSig", + { + "nth": 28, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "XChainCreateAccountProofSig", + { + "nth": 29, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "XChainClaimAttestationCollectionElement", + { + "nth": 30, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "XChainCreateAccountAttestationCollectionElement", + { + "nth": 31, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "Signers", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": false, + "type": "STArray" + } + ], + [ + "SignerEntries", + { + "nth": 4, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "Template", + { + "nth": 5, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "Necessary", + { + "nth": 6, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "Sufficient", + { + "nth": 7, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "AffectedNodes", + { + "nth": 8, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "Memos", + { + "nth": 9, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "NFTokens", + { + "nth": 10, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "Hooks", + { + "nth": 11, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "VoteSlots", + { + "nth": 12, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "Majorities", + { + "nth": 16, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "DisabledValidators", + { + "nth": 17, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "HookExecutions", + { + "nth": 18, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "HookParameters", + { + "nth": 19, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "HookGrants", + { + "nth": 20, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "XChainClaimAttestations", + { + "nth": 21, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "XChainCreateAccountAttestations", + { + "nth": 22, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "AuthAccounts", + { + "nth": 25, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ] + ], + "TRANSACTION_RESULTS": { + "telLOCAL_ERROR": -399, + "telBAD_DOMAIN": -398, + "telBAD_PATH_COUNT": -397, + "telBAD_PUBLIC_KEY": -396, + "telFAILED_PROCESSING": -395, + "telINSUF_FEE_P": -394, + "telNO_DST_PARTIAL": -393, + "telCAN_NOT_QUEUE": -392, + "telCAN_NOT_QUEUE_BALANCE": -391, + "telCAN_NOT_QUEUE_BLOCKS": -390, + "telCAN_NOT_QUEUE_BLOCKED": -389, + "telCAN_NOT_QUEUE_FEE": -388, + "telCAN_NOT_QUEUE_FULL": -387, + "telWRONG_NETWORK": -386, + "telREQUIRES_NETWORK_ID": -385, + "telNETWORK_ID_MAKES_TX_NON_CANONICAL": -384, + + "temMALFORMED": -299, + "temBAD_AMOUNT": -298, + "temBAD_CURRENCY": -297, + "temBAD_EXPIRATION": -296, + "temBAD_FEE": -295, + "temBAD_ISSUER": -294, + "temBAD_LIMIT": -293, + "temBAD_OFFER": -292, + "temBAD_PATH": -291, + "temBAD_PATH_LOOP": -290, + "temBAD_REGKEY": -289, + "temBAD_SEND_XRP_LIMIT": -288, + "temBAD_SEND_XRP_MAX": -287, + "temBAD_SEND_XRP_NO_DIRECT": -286, + "temBAD_SEND_XRP_PARTIAL": -285, + "temBAD_SEND_XRP_PATHS": -284, + "temBAD_SEQUENCE": -283, + "temBAD_SIGNATURE": -282, + "temBAD_SRC_ACCOUNT": -281, + "temBAD_TRANSFER_RATE": -280, + "temDST_IS_SRC": -279, + "temDST_NEEDED": -278, + "temINVALID": -277, + "temINVALID_FLAG": -276, + "temREDUNDANT": -275, + "temRIPPLE_EMPTY": -274, + "temDISABLED": -273, + "temBAD_SIGNER": -272, + "temBAD_QUORUM": -271, + "temBAD_WEIGHT": -270, + "temBAD_TICK_SIZE": -269, + "temINVALID_ACCOUNT_ID": -268, + "temCANNOT_PREAUTH_SELF": -267, + "temINVALID_COUNT": -266, + "temUNCERTAIN": -265, + "temUNKNOWN": -264, + "temSEQ_AND_TICKET": -263, + "temBAD_NFTOKEN_TRANSFER_FEE": -262, + "temBAD_AMM_TOKENS": -261, + "temXCHAIN_EQUAL_DOOR_ACCOUNTS": -260, + "temXCHAIN_BAD_PROOF": -259, + "temXCHAIN_BRIDGE_BAD_ISSUES": -258, + "temXCHAIN_BRIDGE_NONDOOR_OWNER": -257, + "temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT": -256, + "temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT": -255, + + "tefFAILURE": -199, + "tefALREADY": -198, + "tefBAD_ADD_AUTH": -197, + "tefBAD_AUTH": -196, + "tefBAD_LEDGER": -195, + "tefCREATED": -194, + "tefEXCEPTION": -193, + "tefINTERNAL": -192, + "tefNO_AUTH_REQUIRED": -191, + "tefPAST_SEQ": -190, + "tefWRONG_PRIOR": -189, + "tefMASTER_DISABLED": -188, + "tefMAX_LEDGER": -187, + "tefBAD_SIGNATURE": -186, + "tefBAD_QUORUM": -185, + "tefNOT_MULTI_SIGNING": -184, + "tefBAD_AUTH_MASTER": -183, + "tefINVARIANT_FAILED": -182, + "tefTOO_BIG": -181, + "tefNO_TICKET": -180, + "tefNFTOKEN_IS_NOT_TRANSFERABLE": -179, + + "terRETRY": -99, + "terFUNDS_SPENT": -98, + "terINSUF_FEE_B": -97, + "terNO_ACCOUNT": -96, + "terNO_AUTH": -95, + "terNO_LINE": -94, + "terOWNERS": -93, + "terPRE_SEQ": -92, + "terLAST": -91, + "terNO_RIPPLE": -90, + "terQUEUED": -89, + "terPRE_TICKET": -88, + "terNO_AMM": -87, + "terSUBMITTED": -86, + + "tesSUCCESS": 0, + + "tecCLAIM": 100, + "tecPATH_PARTIAL": 101, + "tecUNFUNDED_ADD": 102, + "tecUNFUNDED_OFFER": 103, + "tecUNFUNDED_PAYMENT": 104, + "tecFAILED_PROCESSING": 105, + "tecDIR_FULL": 121, + "tecINSUF_RESERVE_LINE": 122, + "tecINSUF_RESERVE_OFFER": 123, + "tecNO_DST": 124, + "tecNO_DST_INSUF_XRP": 125, + "tecNO_LINE_INSUF_RESERVE": 126, + "tecNO_LINE_REDUNDANT": 127, + "tecPATH_DRY": 128, + "tecUNFUNDED": 129, + "tecNO_ALTERNATIVE_KEY": 130, + "tecNO_REGULAR_KEY": 131, + "tecOWNERS": 132, + "tecNO_ISSUER": 133, + "tecNO_AUTH": 134, + "tecNO_LINE": 135, + "tecINSUFF_FEE": 136, + "tecFROZEN": 137, + "tecNO_TARGET": 138, + "tecNO_PERMISSION": 139, + "tecNO_ENTRY": 140, + "tecINSUFFICIENT_RESERVE": 141, + "tecNEED_MASTER_KEY": 142, + "tecDST_TAG_NEEDED": 143, + "tecINTERNAL": 144, + "tecOVERSIZE": 145, + "tecCRYPTOCONDITION_ERROR": 146, + "tecINVARIANT_FAILED": 147, + "tecEXPIRED": 148, + "tecDUPLICATE": 149, + "tecKILLED": 150, + "tecHAS_OBLIGATIONS": 151, + "tecTOO_SOON": 152, + "tecHOOK_ERROR": 153, + "tecMAX_SEQUENCE_REACHED": 154, + "tecNO_SUITABLE_NFTOKEN_PAGE": 155, + "tecNFTOKEN_BUY_SELL_MISMATCH": 156, + "tecNFTOKEN_OFFER_TYPE_MISMATCH": 157, + "tecCANT_ACCEPT_OWN_NFTOKEN_OFFER": 158, + "tecINSUFFICIENT_FUNDS": 159, + "tecOBJECT_NOT_FOUND": 160, + "tecINSUFFICIENT_PAYMENT": 161, + "tecUNFUNDED_AMM": 162, + "tecAMM_BALANCE": 163, + "tecAMM_FAILED": 164, + "tecAMM_INVALID_TOKENS": 165, + "tecAMM_EMPTY": 166, + "tecAMM_NOT_EMPTY": 167, + "tecAMM_ACCOUNT": 168, + "tecINCOMPLETE": 169, + "tecXCHAIN_BAD_TRANSFER_ISSUE": 170, + "tecXCHAIN_NO_CLAIM_ID": 171, + "tecXCHAIN_BAD_CLAIM_ID": 172, + "tecXCHAIN_CLAIM_NO_QUORUM": 173, + "tecXCHAIN_PROOF_UNKNOWN_KEY": 174, + "tecXCHAIN_CREATE_ACCOUNT_NONXRP_ISSUE": 175, + "tecXCHAIN_WRONG_CHAIN": 176, + "tecXCHAIN_REWARD_MISMATCH": 177, + "tecXCHAIN_NO_SIGNERS_LIST": 178, + "tecXCHAIN_SENDING_ACCOUNT_MISMATCH": 179, + "tecXCHAIN_INSUFF_CREATE_AMOUNT": 180, + "tecXCHAIN_ACCOUNT_CREATE_PAST": 181, + "tecXCHAIN_ACCOUNT_CREATE_TOO_MANY": 182, + "tecXCHAIN_PAYMENT_FAILED": 183, + "tecXCHAIN_SELF_COMMIT": 184, + "tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR": 185, + "tecXCHAIN_CREATE_ACCOUNT_DISABLED": 186 + }, + "TRANSACTION_TYPES": { + "Invalid": -1, + "Payment": 0, + "EscrowCreate": 1, + "EscrowFinish": 2, + "AccountSet": 3, + "EscrowCancel": 4, + "SetRegularKey": 5, + "NickNameSet": 6, + "OfferCreate": 7, + "OfferCancel": 8, + "Contract": 9, + "TicketCreate": 10, + "TicketCancel": 11, + "SignerListSet": 12, + "PaymentChannelCreate": 13, + "PaymentChannelFund": 14, + "PaymentChannelClaim": 15, + "CheckCreate": 16, + "CheckCash": 17, + "CheckCancel": 18, + "DepositPreauth": 19, + "TrustSet": 20, + "AccountDelete": 21, + "SetHook": 22, + "NFTokenMint": 25, + "NFTokenBurn": 26, + "NFTokenCreateOffer": 27, + "NFTokenCancelOffer": 28, + "NFTokenAcceptOffer": 29, + "Clawback": 30, + "AMMCreate": 35, + "AMMDeposit": 36, + "AMMWithdraw": 37, + "AMMVote": 38, + "AMMBid": 39, + "AMMDelete": 40, + "XChainCreateClaimID": 41, + "XChainCommit": 42, + "XChainClaim": 43, + "XChainAccountCreateCommit": 44, + "XChainAddClaimAttestation": 45, + "XChainAddAccountCreateAttestation": 46, + "XChainModifyBridge": 47, + "XChainCreateBridge": 48, + "EnableAmendment": 100, + "SetFee": 101, + "UNLModify": 102 + } +} From 3dc47f639e4a53c713d16a75cf84972bbccfffae Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Sun, 22 Oct 2023 21:08:29 +0300 Subject: [PATCH 24/39] add AMM Response --- .../Json/Converters/LedgerObjectConverter.cs | 4 ++ .../Json/Converters/TransactionConverter.cs | 7 +++ Xrpl/Models/Ledger/LOAmm.cs | 2 +- Xrpl/Models/Transactions/AMMBid.cs | 40 ++++++++++++++--- Xrpl/Models/Transactions/AMMCreate.cs | 24 +++++++++- Xrpl/Models/Transactions/AMMDelete.cs | 33 +++++++++++--- Xrpl/Models/Transactions/AMMDeposit.cs | 45 ++++++++++++++++--- Xrpl/Models/Transactions/AMMVote.cs | 34 +++++++++++--- Xrpl/Models/Transactions/AMMWithdraw.cs | 28 ++++++++++++ 9 files changed, 194 insertions(+), 23 deletions(-) diff --git a/Xrpl/Client/Json/Converters/LedgerObjectConverter.cs b/Xrpl/Client/Json/Converters/LedgerObjectConverter.cs index a0d16bc..6a430f0 100644 --- a/Xrpl/Client/Json/Converters/LedgerObjectConverter.cs +++ b/Xrpl/Client/Json/Converters/LedgerObjectConverter.cs @@ -37,6 +37,7 @@ public static BaseLedgerEntry GetBaseRippleLO(LedgerEntryType type, object field //LedgerEntryType.NFTokenOffer => expr, LedgerEntryType.NFTokenPage => JsonConvert.DeserializeObject($"{field}"), LedgerEntryType.Ticket => JsonConvert.DeserializeObject($"{field}"), + LedgerEntryType.AMM => JsonConvert.DeserializeObject($"{field}"), //LedgerEntryType.Check => expr, //LedgerEntryType.DepositPreauth => expr, _ => throw new ArgumentOutOfRangeException() @@ -89,6 +90,8 @@ public BaseLedgerEntry Create(Type objectType, JObject jObject) return new LONFTokenPage(); case "LOTicket": return new LOTicket(); + case "LOAmm": + return new LOAmm(); } string ledgerEntryType = jObject.Property("LedgerEntryType")?.Value.ToString(); @@ -110,6 +113,7 @@ public BaseLedgerEntry Create(Type objectType, JObject jObject) "Ticket" => new LOTicket(), "Check" => new LOCheck(), "DepositPreauth" => new LODepositPreauth(), + "Amm" => new LOAmm(), _ => throw new Exception("Can't create ledger type" + ledgerEntryType) }; } diff --git a/Xrpl/Client/Json/Converters/TransactionConverter.cs b/Xrpl/Client/Json/Converters/TransactionConverter.cs index 857f80e..80dedc7 100644 --- a/Xrpl/Client/Json/Converters/TransactionConverter.cs +++ b/Xrpl/Client/Json/Converters/TransactionConverter.cs @@ -1,6 +1,7 @@ using System; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Xrpl.Models.Methods; using Xrpl.Models.Transactions; //https://xrpl.org/transaction-types.html @@ -61,6 +62,12 @@ public ITransactionResponseCommon Create(Type objectType, JObject jObject) "EnableAmendment" => new EnableAmendmentResponse(), "SetFee" => new SetFeeResponse(), "UNLModify" => new UNLModifyResponse(), + "AMMBid" => new AMMBidResponse(), + "AMMCreate" => new AMMCreateResponse(), + "AMMDelete" => new AMMDeleteResponse(), + "AMMDeposit" => new AMMDepositResponse(), + "AMMVote" => new AMMVoteResponse(), + "AMMWithdraw" => new AMMWithdrawResponse(), _ => throw new Exception("Can't create transaction type" + transactionType) }; } diff --git a/Xrpl/Models/Ledger/LOAmm.cs b/Xrpl/Models/Ledger/LOAmm.cs index 2319fbb..6fd3471 100644 --- a/Xrpl/Models/Ledger/LOAmm.cs +++ b/Xrpl/Models/Ledger/LOAmm.cs @@ -50,7 +50,7 @@ public LOAmm() /// /// A list of vote objects, representing votes on the pool's trading fee.. /// - public List VoteSlots { get; set; } + public List VoteSlots { get; set; } /// /// The ledger index of the current in-progress ledger, which was used when /// retrieving this information. diff --git a/Xrpl/Models/Transactions/AMMBid.cs b/Xrpl/Models/Transactions/AMMBid.cs index 8aa4142..c759151 100644 --- a/Xrpl/Models/Transactions/AMMBid.cs +++ b/Xrpl/Models/Transactions/AMMBid.cs @@ -2,11 +2,15 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; - +using Newtonsoft.Json; using Xrpl.Client.Exceptions; +using Xrpl.Client.Json.Converters; +using Xrpl.Models.Common; using Xrpl.Models.Ledger; using Xrpl.Models.Methods; +using static Xrpl.Models.Common.Common; + // https://github.com/XRPLF/xrpl.js/blob/amm/packages/xrpl/src/models/transactions/AMMBid.ts namespace Xrpl.Models.Transactions @@ -25,12 +29,16 @@ public AMMBid() } /// - public Xrpl.Models.Common.Currency Asset { get; set; } + [JsonConverter(typeof(IssuedCurrencyConverter))] + public IssuedCurrency Asset { get; set; } /// - public Xrpl.Models.Common.Currency Asset2 { get; set; } + [JsonConverter(typeof(IssuedCurrencyConverter))] + public IssuedCurrency Asset2 { get; set; } /// + [JsonConverter(typeof(CurrencyConverter))] public Xrpl.Models.Common.Currency? BidMin { get; set; } /// + [JsonConverter(typeof(CurrencyConverter))] public Xrpl.Models.Common.Currency? BidMax { get; set; } /// public List AuthAccounts { get; set; } @@ -45,11 +53,11 @@ public interface IAMMBid : ITransactionCommon /// /// Specifies one of the pool assets (XRP or token) of the AMM instance. /// - public Xrpl.Models.Common.Currency Asset { get; set; } + public IssuedCurrency Asset { get; set; } /// /// Specifies the other pool asset of the AMM instance. /// - public Xrpl.Models.Common.Currency Asset2 { get; set; } + public IssuedCurrency Asset2 { get; set; } /// /// This field represents the minimum price that the bidder wants to pay for the slot. /// It is specified in units of LPToken.If specified let BidMin be X and let @@ -70,6 +78,28 @@ public interface IAMMBid : ITransactionCommon public List AuthAccounts { get; set; } } + /// + public class AMMBidResponse : TransactionResponseCommon, IAMMBid + { + #region Implementation of IAMMBid + + /// + [JsonConverter(typeof(IssuedCurrencyConverter))] + public IssuedCurrency Asset { get; set; } + /// + [JsonConverter(typeof(IssuedCurrencyConverter))] + public IssuedCurrency Asset2 { get; set; } + /// + [JsonConverter(typeof(CurrencyConverter))] + public Currency? BidMin { get; set; } + /// + [JsonConverter(typeof(CurrencyConverter))] + public Currency? BidMax { get; set; } + /// + public List AuthAccounts { get; set; } + + #endregion + } public partial class Validation { private const int MAX_AUTH_ACCOUNTS = 4; diff --git a/Xrpl/Models/Transactions/AMMCreate.cs b/Xrpl/Models/Transactions/AMMCreate.cs index 7597a9f..7337629 100644 --- a/Xrpl/Models/Transactions/AMMCreate.cs +++ b/Xrpl/Models/Transactions/AMMCreate.cs @@ -1,10 +1,13 @@ #nullable enable using System.Collections.Generic; using System.Threading.Tasks; - +using Newtonsoft.Json; using Xrpl.BinaryCodec.Types; using Xrpl.Client.Exceptions; +using Xrpl.Client.Json.Converters; +using Xrpl.Models.Ledger; using Xrpl.Models.Methods; +using Currency = Xrpl.Models.Common.Currency; namespace Xrpl.Models.Transactions { @@ -21,8 +24,10 @@ public AMMCreate() } /// + [JsonConverter(typeof(CurrencyConverter))] public Xrpl.Models.Common.Currency Amount { get; set; } /// + [JsonConverter(typeof(CurrencyConverter))] public Xrpl.Models.Common.Currency Amount2 { get; set; } /// public uint TradingFee { get; set; } @@ -52,6 +57,23 @@ public interface IAMMCreate : ITransactionCommon public uint TradingFee { get; set; } } + /// + public class AMMCreateResponse : TransactionResponseCommon, IAMMCreate + { + #region Implementation of IAMMCreate + + /// + [JsonConverter(typeof(CurrencyConverter))] + public Currency Amount { get; set; } + /// + [JsonConverter(typeof(CurrencyConverter))] + public Currency Amount2 { get; set; } + /// + public uint TradingFee { get; set; } + + #endregion + } + public partial class Validation { public const uint AMM_MAX_TRADING_FEE = 1000; diff --git a/Xrpl/Models/Transactions/AMMDelete.cs b/Xrpl/Models/Transactions/AMMDelete.cs index 9b38bba..a2c005a 100644 --- a/Xrpl/Models/Transactions/AMMDelete.cs +++ b/Xrpl/Models/Transactions/AMMDelete.cs @@ -3,7 +3,13 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Newtonsoft.Json; using Xrpl.Client.Exceptions; +using Xrpl.Client.Json.Converters; +using Xrpl.Models.Common; +using Xrpl.Models.Ledger; + +using static Xrpl.Models.Common.Common; namespace Xrpl.Models.Transactions { @@ -25,11 +31,11 @@ public AMMDelete() } /// - public Xrpl.Models.Common.Currency Asset { get; set; } - /// - public Xrpl.Models.Common.Currency Asset2 { get; set; } + [JsonConverter(typeof(IssuedCurrencyConverter))] + public IssuedCurrency Asset { get; set; } /// - public uint TradingFee { get; set; } + [JsonConverter(typeof(IssuedCurrencyConverter))] + public IssuedCurrency Asset2 { get; set; } } /// /// Delete an empty Automated Market Maker (AMM) instance that could not be fully deleted automatically. @@ -46,11 +52,26 @@ public interface IAMMDelete : ITransactionCommon /// /// The definition for one of the assets in the AMM's pool. /// - public Xrpl.Models.Common.Currency Asset { get; set; } + public IssuedCurrency Asset { get; set; } /// /// The definition for the other asset in the AMM's pool. /// - public Xrpl.Models.Common.Currency Asset2 { get; set; } + public IssuedCurrency Asset2 { get; set; } + } + + /// + public class AMMDeleteResponse : TransactionResponseCommon, IAMMDelete + { + #region Implementation of IAMMDelete + + /// + [JsonConverter(typeof(IssuedCurrencyConverter))] + public IssuedCurrency Asset { get; set; } + /// + [JsonConverter(typeof(IssuedCurrencyConverter))] + public IssuedCurrency Asset2 { get; set; } + + #endregion } public partial class Validation diff --git a/Xrpl/Models/Transactions/AMMDeposit.cs b/Xrpl/Models/Transactions/AMMDeposit.cs index d6349bf..814e28d 100644 --- a/Xrpl/Models/Transactions/AMMDeposit.cs +++ b/Xrpl/Models/Transactions/AMMDeposit.cs @@ -3,10 +3,13 @@ using System.Linq; using System.Text; using System.Threading.Tasks; - +using Newtonsoft.Json; using static Xrpl.Models.Common.Common; using Xrpl.BinaryCodec.Types; using Xrpl.Client.Exceptions; +using Xrpl.Models.Ledger; +using Currency = Xrpl.Models.Common.Currency; +using Xrpl.Client.Json.Converters; namespace Xrpl.Models.Transactions { @@ -63,21 +66,27 @@ public AMMDeposit() TransactionType = TransactionType.AMMDeposit; } /// - public Xrpl.Models.Common.Currency Asset { get; set; } + [JsonConverter(typeof(IssuedCurrencyConverter))] + public IssuedCurrency Asset { get; set; } /// - public Xrpl.Models.Common.Currency Asset2 { get; set; } + [JsonConverter(typeof(IssuedCurrencyConverter))] + public IssuedCurrency Asset2 { get; set; } /// + [JsonConverter(typeof(CurrencyConverter))] public Xrpl.Models.Common.Currency? LPTokenOut { get; set; } /// + [JsonConverter(typeof(CurrencyConverter))] public Xrpl.Models.Common.Currency? Amount { get; set; } /// + [JsonConverter(typeof(CurrencyConverter))] public Xrpl.Models.Common.Currency? Amount2 { get; set; } /// + [JsonConverter(typeof(CurrencyConverter))] public Xrpl.Models.Common.Currency? EPrice { get; set; } } @@ -96,12 +105,12 @@ public interface IAMMDeposit : ITransactionCommon /// /// Specifies one of the pool assets (XRP or token) of the AMM instance. /// - public Xrpl.Models.Common.Currency Asset { get; set; } + public IssuedCurrency Asset { get; set; } /// /// Specifies the other pool asset of the AMM instance. /// - public Xrpl.Models.Common.Currency Asset2 { get; set; } + public IssuedCurrency Asset2 { get; set; } /// /// Specifies the amount of shares of the AMM instance pools that the trader wants to redeem or trade in. @@ -124,6 +133,32 @@ public interface IAMMDeposit : ITransactionCommon public Xrpl.Models.Common.Currency? EPrice { get; set; } } + /// + public class AMMDepositResponse : TransactionResponseCommon, IAMMDeposit + { + #region Implementation of IAMMDeposit + + /// + [JsonConverter(typeof(IssuedCurrencyConverter))] + public IssuedCurrency Asset { get; set; } + /// + [JsonConverter(typeof(IssuedCurrencyConverter))] + public IssuedCurrency Asset2 { get; set; } + /// + [JsonConverter(typeof(CurrencyConverter))] + public Currency LPTokenOut { get; set; } + /// + [JsonConverter(typeof(CurrencyConverter))] + public Currency Amount { get; set; } + /// + [JsonConverter(typeof(CurrencyConverter))] + public Currency Amount2 { get; set; } + /// + [JsonConverter(typeof(CurrencyConverter))] + public Currency EPrice { get; set; } + + #endregion + } public partial class Validation { diff --git a/Xrpl/Models/Transactions/AMMVote.cs b/Xrpl/Models/Transactions/AMMVote.cs index db6387c..e2b9f20 100644 --- a/Xrpl/Models/Transactions/AMMVote.cs +++ b/Xrpl/Models/Transactions/AMMVote.cs @@ -1,7 +1,11 @@ using System.Collections.Generic; using System.Threading.Tasks; - +using Newtonsoft.Json; using Xrpl.Client.Exceptions; +using Xrpl.Client.Json.Converters; +using Xrpl.Models.Common; + +using static Xrpl.Models.Common.Common; namespace Xrpl.Models.Transactions { @@ -12,9 +16,11 @@ public AMMVote() TransactionType = TransactionType.AMMVote; } /// - public Xrpl.Models.Common.Currency Asset { get; set; } + [JsonConverter(typeof(IssuedCurrencyConverter))] + public IssuedCurrency Asset { get; set; } /// - public Xrpl.Models.Common.Currency Asset2 { get; set; } + [JsonConverter(typeof(IssuedCurrencyConverter))] + public IssuedCurrency Asset2 { get; set; } /// public uint TradingFee { get; set; } } @@ -24,11 +30,11 @@ public interface IAMMVote : ITransactionCommon /// /// Specifies one of the pool assets (XRP or token) of the AMM instance. /// - public Xrpl.Models.Common.Currency Asset { get; set; } + public IssuedCurrency Asset { get; set; } /// /// Specifies the other pool asset of the AMM instance. /// - public Xrpl.Models.Common.Currency Asset2 { get; set; } + public IssuedCurrency Asset2 { get; set; } /// /// Specifies the fee, in basis point. /// Valid values for this field are between 0 and 1000 inclusive. @@ -37,6 +43,24 @@ public interface IAMMVote : ITransactionCommon /// public uint TradingFee { get; set; } } + + /// + public class AMMVoteResponse : TransactionResponseCommon, IAMMVote + { + #region Implementation of IAMMVote + + /// + [JsonConverter(typeof(IssuedCurrencyConverter))] + public IssuedCurrency Asset { get; set; } + /// + [JsonConverter(typeof(IssuedCurrencyConverter))] + public IssuedCurrency Asset2 { get; set; } + /// + public uint TradingFee { get; set; } + + #endregion + } + public partial class Validation { /// diff --git a/Xrpl/Models/Transactions/AMMWithdraw.cs b/Xrpl/Models/Transactions/AMMWithdraw.cs index f4937cc..6596349 100644 --- a/Xrpl/Models/Transactions/AMMWithdraw.cs +++ b/Xrpl/Models/Transactions/AMMWithdraw.cs @@ -138,6 +138,34 @@ public interface IAMMWithdraw : ITransactionCommon /// Currency EPrice { get; set; } } + + /// + public class AMMWithdrawResponse : TransactionResponseCommon, IAMMWithdraw + { + #region Implementation of IAMMWithdraw + + /// + [JsonConverter(typeof(IssuedCurrencyConverter))] + public IssuedCurrency Asset { get; set; } + /// + [JsonConverter(typeof(IssuedCurrencyConverter))] + public IssuedCurrency Asset2 { get; set; } + /// + [JsonConverter(typeof(CurrencyConverter))] + public Currency LPTokenIn { get; set; } + /// + [JsonConverter(typeof(CurrencyConverter))] + public Currency Amount { get; set; } + /// + [JsonConverter(typeof(CurrencyConverter))] + public Currency Amount2 { get; set; } + /// + [JsonConverter(typeof(CurrencyConverter))] + public Currency EPrice { get; set; } + + #endregion + } + public partial class Validation { From 00a79719c63430e46b882b79cf813aa611823f20 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Sun, 22 Oct 2023 22:12:59 +0300 Subject: [PATCH 25/39] add Clawback --- .../Json/Converters/LedgerObjectConverter.cs | 6 +- .../Json/Converters/TransactionConverter.cs | 1 + Xrpl/Models/Enums.cs | 4 + Xrpl/Models/Ledger/LONegativeUNL.cs | 2 +- Xrpl/Models/Transactions/ClawBack.cs | 88 +++++++++++++++++++ 5 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 Xrpl/Models/Transactions/ClawBack.cs diff --git a/Xrpl/Client/Json/Converters/LedgerObjectConverter.cs b/Xrpl/Client/Json/Converters/LedgerObjectConverter.cs index 6a430f0..52108a3 100644 --- a/Xrpl/Client/Json/Converters/LedgerObjectConverter.cs +++ b/Xrpl/Client/Json/Converters/LedgerObjectConverter.cs @@ -33,7 +33,7 @@ public static BaseLedgerEntry GetBaseRippleLO(LedgerEntryType type, object field LedgerEntryType.RippleState => JsonConvert.DeserializeObject($"{field}"), LedgerEntryType.SignerList => JsonConvert.DeserializeObject($"{field}"), LedgerEntryType.NFTokenOffer => JsonConvert.DeserializeObject($"{field}"), - //LedgerEntryType.NegativeUNL => expr, + LedgerEntryType.NegativeUNL => JsonConvert.DeserializeObject($"{field}"), //LedgerEntryType.NFTokenOffer => expr, LedgerEntryType.NFTokenPage => JsonConvert.DeserializeObject($"{field}"), LedgerEntryType.Ticket => JsonConvert.DeserializeObject($"{field}"), @@ -90,6 +90,8 @@ public BaseLedgerEntry Create(Type objectType, JObject jObject) return new LONFTokenPage(); case "LOTicket": return new LOTicket(); + case "LONegativeUNL": + return new LONegativeUNL(); case "LOAmm": return new LOAmm(); } @@ -107,7 +109,7 @@ public BaseLedgerEntry Create(Type objectType, JObject jObject) "PayChannel" => new LOPayChannel(), "RippleState" => new LORippleState(), "SignerList" => new LOSignerList(), - //"NegativeUNL" => new NegativeUNL(), + "NegativeUNL" => new LONegativeUNL(), "NFTokenOffer" => new LONFTokenOffer(), "NFTokenPage" => new LONFTokenPage(), "Ticket" => new LOTicket(), diff --git a/Xrpl/Client/Json/Converters/TransactionConverter.cs b/Xrpl/Client/Json/Converters/TransactionConverter.cs index 80dedc7..f0adb55 100644 --- a/Xrpl/Client/Json/Converters/TransactionConverter.cs +++ b/Xrpl/Client/Json/Converters/TransactionConverter.cs @@ -68,6 +68,7 @@ public ITransactionResponseCommon Create(Type objectType, JObject jObject) "AMMDeposit" => new AMMDepositResponse(), "AMMVote" => new AMMVoteResponse(), "AMMWithdraw" => new AMMWithdrawResponse(), + "Clawback" => new ClawBackResponse(), _ => throw new Exception("Can't create transaction type" + transactionType) }; } diff --git a/Xrpl/Models/Enums.cs b/Xrpl/Models/Enums.cs index 4ded38f..5e6ebee 100644 --- a/Xrpl/Models/Enums.cs +++ b/Xrpl/Models/Enums.cs @@ -96,6 +96,10 @@ public enum TransactionType /// of LPTokenIn. /// AMMWithdraw, + /// + /// The Clawback transaction is used by the token issuer to claw back issued tokens from a holder. + /// + Clawback, } /// /// Each ledger version's state data is a set of ledger objects, sometimes called ledger entries, diff --git a/Xrpl/Models/Ledger/LONegativeUNL.cs b/Xrpl/Models/Ledger/LONegativeUNL.cs index ba05d7a..f704ba7 100644 --- a/Xrpl/Models/Ledger/LONegativeUNL.cs +++ b/Xrpl/Models/Ledger/LONegativeUNL.cs @@ -16,7 +16,7 @@ public LONegativeUNL() /// /// A list of trusted validators that are currently disabled. /// - public List DisabledValidators { get; set; } + public List DisabledValidators { get; set; } /// /// The public key of a trusted validator that is scheduled to be disabled in the next flag ledger. /// diff --git a/Xrpl/Models/Transactions/ClawBack.cs b/Xrpl/Models/Transactions/ClawBack.cs new file mode 100644 index 0000000..edc347e --- /dev/null +++ b/Xrpl/Models/Transactions/ClawBack.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Xrpl.Client.Exceptions; +using static Xrpl.Models.Common.Common; +using Xrpl.Client.Json.Converters; +using Xrpl.Models.Common; +using Xrpl.Models.Ledger; +using Xrpl.BinaryCodec.Types; +using Currency = Xrpl.Models.Common.Currency; + +namespace Xrpl.Models.Transactions +{ + /// + /// The Clawback transaction is used by the token issuer to claw back issued tokens from a holder. + /// + public class ClawBack : TransactionCommon, IClawBack + { + public ClawBack() + { + TransactionType = TransactionType.Clawback; + } + + /// + [JsonConverter(typeof(CurrencyConverter))] + public Xrpl.Models.Common.Currency Amount { get; set; } + } + /// + /// ClawBack is used for submitting a vote for the trading fee of an AMM Instance. + /// Any XRPL account that holds LPToken for an AMM instance may submit this + /// transaction to vote for the trading fee for that instance. + /// + public interface IClawBack : ITransactionCommon + { + /// + /// The amount of currency to deliver, and it must be non-XRP. + /// The nested field names MUST be lower-case. The `issuer` field MUST be the holder's address, whom to be clawed back. + /// + public Xrpl.Models.Common.Currency Amount { get; set; } + } + + /// + public class ClawBackResponse : TransactionResponseCommon, IClawBack + { + #region Implementation of IClawBack + + /// + [JsonConverter(typeof(CurrencyConverter))] + public Currency Amount { get; set; } + + #endregion + } + public partial class Validation + { + /// + /// Verify the form and type of an ClawBack at runtime. + /// + /// An ClawBack Transaction. + /// + /// When the ClawBack is Malformed. + public static async Task ValidateClawBack(Dictionary tx) + { + await Common.ValidateBaseTransaction(tx); + + if (!tx.TryGetValue("Amount", out var Amount) || Amount is null) + { + throw new ValidationException("ClawBack: missing field Amount"); + } + + if (!Xrpl.Models.Transactions.Common.IsIssuedCurrency(Amount)) + { + throw new ValidationException("ClawBack: invalid Amount"); + } + + if (!tx.TryGetValue("Account", out var acc) || acc is null) + throw new ValidationException("ClawBack: invalid Account"); + var amount = JsonConvert.DeserializeObject($"{Amount}"); + if (amount.Issuer == acc) + { + throw new ValidationException("ClawBack: invalid holder Account"); + } + } + } + +} From d16a74bee7b0ba7e86bc5bbf17c0b642e66083d8 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Tue, 12 Dec 2023 17:05:04 +0300 Subject: [PATCH 26/39] comment autofill --- Xrpl/Sugar/Autofill.cs | 3 ++- Xrpl/Xrpl.csproj | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Xrpl/Sugar/Autofill.cs b/Xrpl/Sugar/Autofill.cs index 2883146..317dafc 100644 --- a/Xrpl/Sugar/Autofill.cs +++ b/Xrpl/Sugar/Autofill.cs @@ -66,7 +66,8 @@ public static async Task> Autofill(this IXrplClient } if (tt == "AccountDelete") { - promises.Add(client.CheckAccountDeleteBlockers(tx)); + //todo error here + //promises.Add(client.CheckAccountDeleteBlockers(tx)); } await Task.WhenAll(promises); string jsonData = JsonConvert.SerializeObject(tx); diff --git a/Xrpl/Xrpl.csproj b/Xrpl/Xrpl.csproj index 4c3ccdc..1a5c39e 100644 --- a/Xrpl/Xrpl.csproj +++ b/Xrpl/Xrpl.csproj @@ -28,7 +28,6 @@ - From 2d5788460dc33d65b2878c42926598b3d87c08d3 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Thu, 14 Dec 2023 05:56:17 +0300 Subject: [PATCH 27/39] add nftokenoffer owner parse --- Tests/Xrpl.Tests/Utils/ParseNFTokenID.cs | 12 ++++++++++++ Xrpl/Models/Methods/AccountTransactions.cs | 8 ++++++++ Xrpl/Utils/ParseNFTID.cs | 13 +++++++++++++ 3 files changed, 33 insertions(+) diff --git a/Tests/Xrpl.Tests/Utils/ParseNFTokenID.cs b/Tests/Xrpl.Tests/Utils/ParseNFTokenID.cs index 9757edb..e5ed001 100644 --- a/Tests/Xrpl.Tests/Utils/ParseNFTokenID.cs +++ b/Tests/Xrpl.Tests/Utils/ParseNFTokenID.cs @@ -43,5 +43,17 @@ public void TestUParseNFTOffer() response ); } + + [TestMethod] + public void TestUParseNFTOfferOwner() + { + string nftokenOfferId = "0003000002723E8A008169F1A3ECB8B2C313F3D1A0609B663F96D69A046DADA7"; + + string response = ParseNFTID.ParseNFTokenOfferOwner(nftokenOfferId); + Assert.AreEqual( + "rDADDYgduA1JbVU6FQnciXmCHpNHwxFew", + response + ); + } } } \ No newline at end of file diff --git a/Xrpl/Models/Methods/AccountTransactions.cs b/Xrpl/Models/Methods/AccountTransactions.cs index b8250d4..67a2c02 100644 --- a/Xrpl/Models/Methods/AccountTransactions.cs +++ b/Xrpl/Models/Methods/AccountTransactions.cs @@ -152,5 +152,13 @@ public AccountTransactionsRequest(string account) /// [JsonProperty("marker")] public object Marker { get; set; } + + /// + /// Optional) Clio Only Return only transactions of a specific type,
+ /// such as "Clawback", "AccountSet", "AccountDelete", et al. Case-insensitive.
+ /// Supports any transaction type except AMM* (See Transaction Types https://xrpl.org/transaction-types.html) + ///
+ [JsonProperty("tx_type")] + public string TxType { get; set; } } } diff --git a/Xrpl/Utils/ParseNFTID.cs b/Xrpl/Utils/ParseNFTID.cs index 9ced9e6..94fed7b 100644 --- a/Xrpl/Utils/ParseNFTID.cs +++ b/Xrpl/Utils/ParseNFTID.cs @@ -71,5 +71,18 @@ public static string ParseNFTOffer(string account, uint sequence) byte[] response = Sha512.Half(rv); return response.ToHex(); } + + public static string ParseNFTokenOfferOwner(this string nftokenOfferId) + { + const int expectedLength = 64; + if (nftokenOfferId.Length != expectedLength) + { + throw new XrplException($"Attempting to parse a nftokenID with length ${nftokenOfferId.Length}\n" + + $", but expected a token with length ${expectedLength}"); + } + string issuer = XrplCodec.EncodeAccountID(nftokenOfferId.Substring(8, 40).FromHexToBytes()); + return issuer; + } + } } From ba482b092848fe63fe1bc384541528af62ab8ad1 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Thu, 14 Dec 2023 10:48:34 +0300 Subject: [PATCH 28/39] remove wrong --- Tests/Xrpl.Tests/Utils/ParseNFTokenID.cs | 12 ------------ Xrpl/Utils/ParseNFTID.cs | 13 ------------- 2 files changed, 25 deletions(-) diff --git a/Tests/Xrpl.Tests/Utils/ParseNFTokenID.cs b/Tests/Xrpl.Tests/Utils/ParseNFTokenID.cs index e5ed001..9757edb 100644 --- a/Tests/Xrpl.Tests/Utils/ParseNFTokenID.cs +++ b/Tests/Xrpl.Tests/Utils/ParseNFTokenID.cs @@ -43,17 +43,5 @@ public void TestUParseNFTOffer() response ); } - - [TestMethod] - public void TestUParseNFTOfferOwner() - { - string nftokenOfferId = "0003000002723E8A008169F1A3ECB8B2C313F3D1A0609B663F96D69A046DADA7"; - - string response = ParseNFTID.ParseNFTokenOfferOwner(nftokenOfferId); - Assert.AreEqual( - "rDADDYgduA1JbVU6FQnciXmCHpNHwxFew", - response - ); - } } } \ No newline at end of file diff --git a/Xrpl/Utils/ParseNFTID.cs b/Xrpl/Utils/ParseNFTID.cs index 94fed7b..9ced9e6 100644 --- a/Xrpl/Utils/ParseNFTID.cs +++ b/Xrpl/Utils/ParseNFTID.cs @@ -71,18 +71,5 @@ public static string ParseNFTOffer(string account, uint sequence) byte[] response = Sha512.Half(rv); return response.ToHex(); } - - public static string ParseNFTokenOfferOwner(this string nftokenOfferId) - { - const int expectedLength = 64; - if (nftokenOfferId.Length != expectedLength) - { - throw new XrplException($"Attempting to parse a nftokenID with length ${nftokenOfferId.Length}\n" + - $", but expected a token with length ${expectedLength}"); - } - string issuer = XrplCodec.EncodeAccountID(nftokenOfferId.Substring(8, 40).FromHexToBytes()); - return issuer; - } - } } From 7b19b1c745c0d595dd89887d214de7f6275b6868 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Thu, 28 Dec 2023 17:47:44 +0300 Subject: [PATCH 29/39] add networkID to connection --- Base/Xrpl.BinaryCodec/Enums/Field.cs | 1 + Xrpl/Models/Transactions/Common.cs | 5 +++++ Xrpl/Models/Transactions/TxFormat.cs | 1 + 3 files changed, 7 insertions(+) diff --git a/Base/Xrpl.BinaryCodec/Enums/Field.cs b/Base/Xrpl.BinaryCodec/Enums/Field.cs index 48b63b1..f79e744 100644 --- a/Base/Xrpl.BinaryCodec/Enums/Field.cs +++ b/Base/Xrpl.BinaryCodec/Enums/Field.cs @@ -92,6 +92,7 @@ private bool IsVlEncodedType() public static readonly Uint16Field HookStateExecutionIndex = new Uint16Field(nameof(HookStateExecutionIndex), 19); public static readonly Uint16Field HookApiVersion = new Uint16Field(nameof(HookApiVersion), 20); + public static readonly Uint32Field NetworkID = new Uint32Field(nameof(NetworkID), 1); public static readonly Uint32Field Flags = new Uint32Field(nameof(Flags), 2); public static readonly Uint32Field SourceTag = new Uint32Field(nameof(SourceTag), 3); public static readonly Uint32Field Sequence = new Uint32Field(nameof(Sequence), 4); diff --git a/Xrpl/Models/Transactions/Common.cs b/Xrpl/Models/Transactions/Common.cs index 052bc40..fe206a9 100644 --- a/Xrpl/Models/Transactions/Common.cs +++ b/Xrpl/Models/Transactions/Common.cs @@ -256,6 +256,8 @@ public abstract class TransactionCommon : ITransactionCommon //todo rename to Ba // Fee = new Currency { Value = "10" }; //} + public uint? NetworkID { get; set; } + /// public string Account { get; set; } @@ -577,6 +579,7 @@ public class NodeInfo ///
public interface ITransactionCommon { + public uint? NetworkID { get; set; } /// /// This is a required field /// The unique address of the account that initiated the transaction. @@ -684,6 +687,8 @@ public interface ITransactionResponseCommon : IBaseTransactionResponse, ITransac [JsonConverter(typeof(TransactionConverter))] public abstract class TransactionResponseCommon : BaseTransactionResponse, ITransactionResponseCommon { + public uint? NetworkID { get; set; } + /// public string Account { get; set; } diff --git a/Xrpl/Models/Transactions/TxFormat.cs b/Xrpl/Models/Transactions/TxFormat.cs index ae35423..70aaf5c 100644 --- a/Xrpl/Models/Transactions/TxFormat.cs +++ b/Xrpl/Models/Transactions/TxFormat.cs @@ -26,6 +26,7 @@ public TxFormat() this[Field.SigningPubKey] = Requirement.Required; this[Field.TicketSequence] = Requirement.Optional; + this[Field.NetworkID] = Requirement.Optional; this[Field.Flags] = Requirement.Optional; this[Field.SourceTag] = Requirement.Optional; this[Field.PreviousTxnID] = Requirement.Optional; From 4260f3e61747798ee49e8d5dae4d53a48fc43254 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Fri, 29 Dec 2023 19:08:55 +0300 Subject: [PATCH 30/39] add network Id into server info & to connection options --- Xrpl/Client/IXrplClient.cs | 13 +++++++++++++ Xrpl/Models/Methods/ServerInfo.cs | 11 +++++++++++ 2 files changed, 24 insertions(+) diff --git a/Xrpl/Client/IXrplClient.cs b/Xrpl/Client/IXrplClient.cs index 6c0d51c..3dcf92f 100644 --- a/Xrpl/Client/IXrplClient.cs +++ b/Xrpl/Client/IXrplClient.cs @@ -42,6 +42,16 @@ public interface IXrplClient : IDisposable Connection connection { get; set; } double feeCushion { get; set; } string maxFeeXRP { get; set; } + uint? networkID { get; set; } + + /// + /// Set network id for transactions, required in network where Id > 1024 + /// + /// network id + public void SetNetworkId(uint? networkID) + { + this.networkID = networkID; + } //event OnError OnError; //event OnConnected OnConnected; @@ -288,6 +298,7 @@ public class XrplClient : IXrplClient public class ClientOptions : ConnectionOptions { + public uint? NetworkID { get; set; } public double? feeCushion { get; set; } public string? maxFeeXRP { get; set; } } @@ -295,6 +306,7 @@ public class ClientOptions : ConnectionOptions public Connection connection { get; set; } public double feeCushion { get; set; } public string maxFeeXRP { get; set; } + public uint? networkID { get; set; } //public event OnError OnError; //public event OnConnected OnConnected; @@ -320,6 +332,7 @@ public XrplClient(string server, ClientOptions? options = null) } feeCushion = options?.feeCushion ?? 1.2; maxFeeXRP = options?.maxFeeXRP ?? "2"; + networkID = options?.NetworkID; connection = new Connection(server, options); //connection.OnError += (e, em, m, d) => OnError?.Invoke(e, em, m, d); diff --git a/Xrpl/Models/Methods/ServerInfo.cs b/Xrpl/Models/Methods/ServerInfo.cs index 7546ad5..4d7fcc7 100644 --- a/Xrpl/Models/Methods/ServerInfo.cs +++ b/Xrpl/Models/Methods/ServerInfo.cs @@ -83,6 +83,17 @@ public class Info [JsonProperty("build_version")] public string BuildVersion { get; set; } + /// + /// he NetworkID field is a protection against "cross-chain" transaction replay attacks,
+ /// preventing the same transaction from being copied over
+ /// and executing on a parallel network that it wasn't intended for.
+ /// For compatibility with existing chains, the NetworkID field
+ /// must be omitted on any network with a Network ID of 1024 or less,
+ /// but must be included on any network with a Network ID of 1025 or greater. + ///
+ [JsonProperty("network_id")] + public uint? NetworkID { get; set; } + /// /// Range expression indicating the sequence numbers of the ledger versions the local rippled has in its database. /// From 96d7b96d52d1d9f1e96afa6a76a98edd9fd66b51 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Fri, 29 Dec 2023 19:09:31 +0300 Subject: [PATCH 31/39] add unknown type for new transaction types --- .../Json/Converters/TransactionConverter.cs | 24 ++++++++++++------- Xrpl/Models/Enums.cs | 4 ++++ Xrpl/Models/Transactions/Common.cs | 2 +- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Xrpl/Client/Json/Converters/TransactionConverter.cs b/Xrpl/Client/Json/Converters/TransactionConverter.cs index f0adb55..8b310e8 100644 --- a/Xrpl/Client/Json/Converters/TransactionConverter.cs +++ b/Xrpl/Client/Json/Converters/TransactionConverter.cs @@ -1,7 +1,8 @@ -using System; -using Newtonsoft.Json; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Xrpl.Models.Methods; + +using System; + using Xrpl.Models.Transactions; //https://xrpl.org/transaction-types.html @@ -26,13 +27,10 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s /// /// create /// - /// /// json object LedgerEntity /// - public ITransactionResponseCommon Create(Type objectType, JObject jObject) + public ITransactionResponseCommon Create(JObject jObject) { - var transactionType = jObject.Property("TransactionType"); - return jObject.Property("TransactionType")?.Value.ToString() switch { "AccountSet" => new AccountSetResponse(), @@ -69,10 +67,17 @@ public ITransactionResponseCommon Create(Type objectType, JObject jObject) "AMMVote" => new AMMVoteResponse(), "AMMWithdraw" => new AMMWithdrawResponse(), "Clawback" => new ClawBackResponse(), - _ => throw new Exception("Can't create transaction type" + transactionType) + //_ => throw new Exception("Can't create transaction type" + transactionType) + _ => SetUnknownType(jObject), }; } + static TransactionResponseCommon SetUnknownType(JObject jObject) + { + jObject.Property("TransactionType").Value = "Unknown"; + return new TransactionResponseCommon(); + } + /// read from json object /// json reader /// object type @@ -82,7 +87,8 @@ public ITransactionResponseCommon Create(Type objectType, JObject jObject) public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JObject jObject = JObject.Load(reader); - ITransactionResponseCommon transactionCommon = Create(objectType, jObject); + + ITransactionResponseCommon transactionCommon = Create(jObject); serializer.Populate(jObject.CreateReader(), transactionCommon); return transactionCommon; } diff --git a/Xrpl/Models/Enums.cs b/Xrpl/Models/Enums.cs index 5e6ebee..757eaff 100644 --- a/Xrpl/Models/Enums.cs +++ b/Xrpl/Models/Enums.cs @@ -100,6 +100,10 @@ public enum TransactionType /// The Clawback transaction is used by the token issuer to claw back issued tokens from a holder. ///
Clawback, + /// + /// Unknown tx Type. + /// + Unknown, } /// /// Each ledger version's state data is a set of ledger objects, sometimes called ledger entries, diff --git a/Xrpl/Models/Transactions/Common.cs b/Xrpl/Models/Transactions/Common.cs index fe206a9..0694e8c 100644 --- a/Xrpl/Models/Transactions/Common.cs +++ b/Xrpl/Models/Transactions/Common.cs @@ -685,7 +685,7 @@ public interface ITransactionResponseCommon : IBaseTransactionResponse, ITransac /// [JsonConverter(typeof(TransactionConverter))] - public abstract class TransactionResponseCommon : BaseTransactionResponse, ITransactionResponseCommon + public class TransactionResponseCommon : BaseTransactionResponse, ITransactionResponseCommon { public uint? NetworkID { get; set; } From 5b98a814ceb73546bc2672b09dd43a27d2a6102d Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Wed, 7 Feb 2024 21:44:02 +0300 Subject: [PATCH 32/39] add missed submit fields --- Xrpl/Models/Transactions/Submit.cs | 121 +++++++++++++++++------------ 1 file changed, 70 insertions(+), 51 deletions(-) diff --git a/Xrpl/Models/Transactions/Submit.cs b/Xrpl/Models/Transactions/Submit.cs index f94f811..3f6f1a7 100644 --- a/Xrpl/Models/Transactions/Submit.cs +++ b/Xrpl/Models/Transactions/Submit.cs @@ -1,57 +1,76 @@ using Newtonsoft.Json; + using Xrpl.Models.Methods; //https://github.com/XRPLF/xrpl.js/blob/b20c05c3680d80344006d20c44b4ae1c3b0ffcac/packages/xrpl/src/models/methods/submit.ts#L28 -namespace Xrpl.Models.Transactions +namespace Xrpl.Models.Transactions; + +/// +/// Response expected from a . +/// +public class Submit //todo rename to SubmitResponse extends BaseResponse { + [JsonProperty("Accepted")] + public bool Accepted { get; set; } + + [JsonProperty("applied")] + public bool Applied { get; set; } + + [JsonProperty("broadcast")] + public bool Broadcast { get; set; } + + [JsonProperty("open_ledger_cost")] + public string OpenLedgerCost { get; set; } + + /// + /// Text result code indicating the preliminary result of the transaction, for example `tesSUCCESS`. + /// + [JsonProperty("engine_result")] + public string EngineResult { get; set; } + + /// + /// Numeric version of the result code. + /// + [JsonProperty("engine_result_code")] + public int EngineResultCode { get; set; } + + /// + /// Human-readable explanation of the transaction's preliminary result. + /// + [JsonProperty("engine_result_message")] + public string EngineResultMessage { get; set; } + /// - /// Response expected from a . - /// - public class Submit //todo rename to SubmitResponse extends BaseResponse - { - /// - /// Text result code indicating the preliminary result of the transaction, for example `tesSUCCESS`. - /// - [JsonProperty("engine_result")] - public string EngineResult { get; set; } - - /// - /// Numeric version of the result code. - /// - [JsonProperty("engine_result_code")] - public int EngineResultCode { get; set; } - - /// - /// Human-readable explanation of the transaction's preliminary result. - /// - [JsonProperty("engine_result_message")] - public string EngineResultMessage { get; set; } - - /// - /// The complete transaction in hex string format. - /// - [JsonProperty("tx_blob")] - public string TxBlob { get; set; } - - /// - /// Next account sequence number. - /// - [JsonProperty("account_sequence_next")] - public uint? AccountSequenceNext { get; set; } - /// - /// The complete transaction in JSON format. - /// - [JsonProperty("tx_json")] - public dynamic TxJson { get; set; } - - //[JsonIgnore] - /// - /// The complete transaction. - /// - public ITransactionResponseCommon Transaction => JsonConvert.DeserializeObject(TxJson.ToString()); - - - //todo not found fields accepted: boolean, account_sequence_available: number, account_sequence_next: number, applied: boolean, broadcast: boolean - //kept: boolean, queued: boolean, open_ledger_cost: string, validated_ledger_index: number - } -} + /// The complete transaction in hex string format. + /// + [JsonProperty("tx_blob")] + public string TxBlob { get; set; } + + /// + /// Next account sequence number. + /// + [JsonProperty("account_sequence_next")] + public uint? AccountSequenceNext { get; set; } + + /// + /// Available account sequence number. + /// + [JsonProperty("account_sequence_available")] + public uint? AccountSequenceAvailable { get; set; } + + /// + /// The complete transaction in JSON format. + /// + [JsonProperty("tx_json")] + public dynamic TxJson { get; set; } + + //[JsonIgnore] + /// + /// The complete transaction. + /// + public ITransactionResponseCommon Transaction => JsonConvert.DeserializeObject(TxJson.ToString()); + + + //todo not found fields accepted: boolean, account_sequence_available: number, account_sequence_next: number, applied: boolean, broadcast: boolean + //kept: boolean, queued: boolean, open_ledger_cost: string, validated_ledger_index: number +} \ No newline at end of file From 8757c40de750931474cbbc0a2e3b5a531137d371 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Wed, 7 Feb 2024 21:44:59 +0300 Subject: [PATCH 33/39] fix amm fields --- Xrpl/Models/Ledger/LOAmm.cs | 5 +- Xrpl/Models/Methods/AMMInfo.cs | 213 ++++++++++++++++++--------------- 2 files changed, 120 insertions(+), 98 deletions(-) diff --git a/Xrpl/Models/Ledger/LOAmm.cs b/Xrpl/Models/Ledger/LOAmm.cs index 6fd3471..53ea104 100644 --- a/Xrpl/Models/Ledger/LOAmm.cs +++ b/Xrpl/Models/Ledger/LOAmm.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Newtonsoft.Json; using Xrpl.Client.Json.Converters; @@ -113,7 +114,7 @@ public class AuctionSlot /// The time when this slot expires, in seconds since the Ripple Epoch. ///
[JsonProperty("expiration")] - public uint Expiration { get; set; } + public DateTime? Expiration { get; set; } /// /// The amount the auction owner paid to win this slot, in LPTokens. /// diff --git a/Xrpl/Models/Methods/AMMInfo.cs b/Xrpl/Models/Methods/AMMInfo.cs index ae573af..0310775 100644 --- a/Xrpl/Models/Methods/AMMInfo.cs +++ b/Xrpl/Models/Methods/AMMInfo.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; + using Newtonsoft.Json; using Xrpl.Client.Json.Converters; @@ -7,106 +8,126 @@ using Xrpl.Models.Subscriptions; //https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/models/ledger/AMM.ts -namespace Xrpl.Models.Methods +namespace Xrpl.Models.Methods; + +/// +/// The `amm_info` command retrieves information about an AMM instance. +/// +/// Returns an . +public class AMMInfoRequest : BaseLedgerRequest { + public AMMInfoRequest() { Command = "amm_info"; } + + + /// + /// Show only LP Tokens held by this liquidity provider. + /// + [JsonProperty("account")] + public string? Account { get; set; } + + /// + /// The address of the AMM's special AccountRoot. (This is the issuer of the AMM's LP Tokens.) + /// + [JsonProperty("amm_account")] + public string? AmmAccount { get; set; } + /// - /// The `amm_info` command retrieves information about an AMM instance. + /// Specifies one of the pool assets (XRP or token) of the AMM instance.
+ /// Both asset and asset2 must be defined to specify an AMM instance. ///
- /// Returns an . - public class AMMInfoRequest : BaseLedgerRequest - { - public AMMInfoRequest() - { - Command = "amm_info"; - } - - [JsonProperty("amm_info")] - public string? AmmAccount { get; set; } - /// - /// Specifies one of the pool assets (XRP or token) of the AMM instance.
- /// Both asset and asset2 must be defined to specify an AMM instance. - ///
- [JsonProperty("asset")] - [JsonConverter(typeof(CurrencyConverter))] - public Currency Asset { get; set; } - /// - /// Specifies the other pool asset of the AMM instance.
- /// Both asset and asset2 must be defined to specify an AMM instance. - ///
- [JsonProperty("asset2")] - [JsonConverter(typeof(CurrencyConverter))] - public Currency Asset2 { get; set; } - } + [JsonProperty("asset"), JsonConverter(typeof(IssuedCurrencyConverter)),] + public Common.Common.IssuedCurrency Asset { get; set; } /// - /// Response expected from an . + /// Specifies the other pool asset of the AMM instance.
+ /// Both asset and asset2 must be defined to specify an AMM instance. ///
- public class AMMInfoResponse : BaseResponse - { - /// - /// The account that tracks the balance of LPTokens between the AMM instance via Trustline. - /// - [JsonProperty("auction_slot")] - public string Account { get; set; } - /// - /// Specifies one of the pool assets (XRP or token) of the AMM instance. - /// - [JsonProperty("amount")] - [JsonConverter(typeof(CurrencyConverter))] - public Currency Amount { get; set; } - /// - /// Specifies the other pool asset of the AMM instance. - /// - [JsonProperty("amount2")] - public Currency Amount2 { get; set; } - [JsonConverter(typeof(CurrencyConverter))] - [JsonProperty("asset_frozen")] - public bool? AssetFrozen { get; set; } - [JsonProperty("asset2_frozen")] - public bool? Asset2Frozen { get; set; } - /// - /// Details of the current owner of the auction slot. - /// - [JsonProperty("auction_slot")] - public AuctionSlot AuctionSlot { get; set; } - /// - /// The total outstanding balance of liquidity provider tokens from this AMM instance.
- /// The holders of these tokens can vote on the AMM's trading fee in proportion to their holdings, - /// or redeem the tokens for a share of the AMM's assets which grows with the trading fees collected. - ///
- [JsonProperty("lp_token")] - [JsonConverter(typeof(CurrencyConverter))] - public Currency LPTokenBalance { get; set; } - /// - /// Specifies the fee, in basis point, to be charged to the traders for the trades - /// executed against the AMM instance.
- /// Trading fee is a percentage of the trading volume.
- /// Valid values for this field are between 0 and 1000 inclusive.
- /// A value of 1 is equivalent to 1/10 bps or 0.001%, allowing trading fee - /// between 0% and 1%.
- /// This field is required. - ///
- [JsonProperty("trading_fee")] - public uint TradingFee { get; set; } - /// - /// Keeps a track of up to eight active votes for the instance. - /// - [JsonProperty("vote_slots")] - public List VoteSlots { get; set; } - /// - /// The ledger index of the ledger version that was used to generate this response. - /// - [JsonProperty("ledger_index")] - public int? LedgerIndex { get; set; } - /// - ///The identifying hash of the ledger that was used to generate this response. - /// - [JsonProperty("ledger_hash")] - public string? LedgerHash { get; set; } - /// - /// True if this data is from a validated ledger version;
- /// if omitted or set to false, this data is not final. - ///
- public bool? Validated { get; set; } - } + [JsonProperty("asset2"), JsonConverter(typeof(IssuedCurrencyConverter)),] + public Common.Common.IssuedCurrency Asset2 { get; set; } } + +/// +/// Response expected from an . +/// +public class AMMInfoResponse +{ + [JsonProperty("amm")] + public AMMInfo Amm { get; set; } +} + +public class AMMInfo +{ + /// + /// The account that tracks the balance of LPTokens between the AMM instance via Trustline. + /// + [JsonProperty("account")] + public string Account { get; set; } + + /// + /// Specifies one of the pool assets (XRP or token) of the AMM instance. + /// + [JsonProperty("amount"), JsonConverter(typeof(CurrencyConverter)),] + public Currency Amount { get; set; } + + /// + /// Specifies the other pool asset of the AMM instance. + /// + [JsonProperty("amount2"), JsonConverter(typeof(CurrencyConverter))] + public Currency Amount2 { get; set; } + + [JsonProperty("asset_frozen"),] + public bool? AssetFrozen { get; set; } + + [JsonProperty("asset2_frozen")] + public bool? Asset2Frozen { get; set; } + + /// + /// Details of the current owner of the auction slot. + /// + [JsonProperty("auction_slot")] + public AuctionSlot AuctionSlot { get; set; } + + /// + /// The total outstanding balance of liquidity provider tokens from this AMM instance.
+ /// The holders of these tokens can vote on the AMM's trading fee in proportion to their holdings, + /// or redeem the tokens for a share of the AMM's assets which grows with the trading fees collected. + ///
+ [JsonProperty("lp_token"), JsonConverter(typeof(CurrencyConverter)),] + public Currency LPTokenBalance { get; set; } + + /// + /// Specifies the fee, in basis point, to be charged to the traders for the trades + /// executed against the AMM instance.
+ /// Trading fee is a percentage of the trading volume.
+ /// Valid values for this field are between 0 and 1000 inclusive.
+ /// A value of 1 is equivalent to 1/10 bps or 0.001%, allowing trading fee + /// between 0% and 1%.
+ /// This field is required. + ///
+ [JsonProperty("trading_fee")] + public uint TradingFee { get; set; } + + /// + /// Keeps a track of up to eight active votes for the instance. + /// + [JsonProperty("vote_slots")] + public List VoteSlots { get; set; } + + /// + /// The ledger index of the ledger version that was used to generate this response. + /// + [JsonProperty("ledger_index")] + public int? LedgerIndex { get; set; } + + /// + ///The identifying hash of the ledger that was used to generate this response. + /// + [JsonProperty("ledger_hash")] + public string? LedgerHash { get; set; } + + /// + /// True if this data is from a validated ledger version;
+ /// if omitted or set to false, this data is not final. + ///
+ public bool? Validated { get; set; } +} \ No newline at end of file From d70194ab1a2ce1da74e6bef4b52fe8615e970807 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Wed, 7 Feb 2024 21:46:31 +0300 Subject: [PATCH 34/39] Add client network_id field to tx, add amm method --- Xrpl/Client/IXrplClient.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Xrpl/Client/IXrplClient.cs b/Xrpl/Client/IXrplClient.cs index 3dcf92f..0fa6d90 100644 --- a/Xrpl/Client/IXrplClient.cs +++ b/Xrpl/Client/IXrplClient.cs @@ -260,6 +260,13 @@ public void SetNetworkId(uint? networkID) #endregion + + /// + /// The amm_info method gets information about an Automated Market Maker (AMM) instance. + /// + /// An request. + /// An response. + Task AmmInfo(AMMInfoRequest request); /// /// The book_offers method retrieves a list of offers, also known as the order book , between two currencies /// @@ -401,6 +408,10 @@ public Task Submit(Dictionary tx, XrplWallet wallet) /// public Task Submit(ITransactionCommon tx, XrplWallet wallet) { + if (networkID is { } network) + { + tx.NetworkID = network; + } var json = tx.ToJson(); //var json = JsonConvert.SerializeObject(tx); Dictionary txJson = JsonConvert.DeserializeObject>(json); @@ -467,6 +478,12 @@ public Task AccountTransactions(AccountTransactionsRequest return this.GRequest(request); } + /// + public Task AmmInfo(AMMInfoRequest request) + { + return this.GRequest(request); + } + /// public Task BookOffers(BookOffersRequest request) { From 3fc5f33aa833d446c6950c12ecfb0a2e11d95e2a Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Wed, 27 Mar 2024 09:56:54 +0300 Subject: [PATCH 35/39] fix: amm tx response --- Xrpl/Models/Ledger/LOAmm.cs | 3 ++- Xrpl/Models/Methods/AMMInfo.cs | 36 +++++++++++++++++----------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/Xrpl/Models/Ledger/LOAmm.cs b/Xrpl/Models/Ledger/LOAmm.cs index 53ea104..49a564e 100644 --- a/Xrpl/Models/Ledger/LOAmm.cs +++ b/Xrpl/Models/Ledger/LOAmm.cs @@ -71,6 +71,7 @@ public interface IAuthAccount } public class AuthAccount : IAuthAccount { + [JsonProperty("account")] public string Account { get; set; } } @@ -103,7 +104,7 @@ public class AuctionSlot /// A list of at most 4 additional accounts that are authorized to trade at the discounted fee for this AMM instance. ///
[JsonProperty("auth_accounts")] - public List AuthAccounts { get; set; } + public List AuthAccounts { get; set; } /// /// The trading fee to be charged to the auction owner, in the same format as TradingFee.
/// By default this is 0, meaning that the auction owner can trade at no fee instead of the standard fee for this AMM. diff --git a/Xrpl/Models/Methods/AMMInfo.cs b/Xrpl/Models/Methods/AMMInfo.cs index 0310775..e7b547c 100644 --- a/Xrpl/Models/Methods/AMMInfo.cs +++ b/Xrpl/Models/Methods/AMMInfo.cs @@ -53,6 +53,24 @@ public class AMMInfoResponse { [JsonProperty("amm")] public AMMInfo Amm { get; set; } + /// + /// The ledger index of the ledger version that was used to generate this response. + /// + [JsonProperty("ledger_index")] + public int? LedgerIndex { get; set; } + + /// + ///The identifying hash of the ledger that was used to generate this response. + /// + [JsonProperty("ledger_hash")] + public string? LedgerHash { get; set; } + + /// + /// True if this data is from a validated ledger version;
+ /// if omitted or set to false, this data is not final. + ///
+ [JsonProperty("validated")] + public bool? Validated { get; set; } } public class AMMInfo @@ -112,22 +130,4 @@ public class AMMInfo ///
[JsonProperty("vote_slots")] public List VoteSlots { get; set; } - - /// - /// The ledger index of the ledger version that was used to generate this response. - /// - [JsonProperty("ledger_index")] - public int? LedgerIndex { get; set; } - - /// - ///The identifying hash of the ledger that was used to generate this response. - /// - [JsonProperty("ledger_hash")] - public string? LedgerHash { get; set; } - - /// - /// True if this data is from a validated ledger version;
- /// if omitted or set to false, this data is not final. - ///
- public bool? Validated { get; set; } } \ No newline at end of file From 4ac79a12273cba2fc7458a56cb22652112d6c2fc Mon Sep 17 00:00:00 2001 From: Aleksandr Platonenkov Date: Wed, 27 Mar 2024 09:57:23 +0300 Subject: [PATCH 36/39] feat: add LP token valid name converter --- Xrpl/Models/Common/Currency.cs | 251 ++++++++-------- Xrpl/Models/Methods/AccountLines.cs | 424 +++++++++++++++------------- 2 files changed, 364 insertions(+), 311 deletions(-) diff --git a/Xrpl/Models/Common/Currency.cs b/Xrpl/Models/Common/Currency.cs index 63782c9..383ebc6 100644 --- a/Xrpl/Models/Common/Currency.cs +++ b/Xrpl/Models/Common/Currency.cs @@ -9,148 +9,167 @@ //https://xrpl.org/currency-formats.html#currency-formats //https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/models/common/index.ts -namespace Xrpl.Models.Common +namespace Xrpl.Models.Common; + +/// +/// The XRP Ledger has two kinds of digital asset: XRP and tokens.
+/// Both types have high precision, although their formats are different +///
+public class Currency { /// - /// The XRP Ledger has two kinds of digital asset: XRP and tokens.
- /// Both types have high precision, although their formats are different + /// base constructor.
+ /// base currency code = XRP ///
- public class Currency - { - /// - /// base constructor.
- /// base currency code = XRP - ///
- public Currency() - { - CurrencyCode = "XRP"; - } + public Currency() { CurrencyCode = "XRP"; } + + /// + /// The standard format for currency codes is a three-character string such as USD.
+ /// This is intended for use with ISO 4217 Currency Codes
+ /// As a 160-bit hexadecimal string, such as "0158415500000000C1F76FF6ECB0BAC600000000".
+ /// The following characters are permitted:
+ /// all uppercase and lowercase letters, digits, as well as the symbols ? ! @ # $ % ^ * ( ) { } [ ] | and symbols ampersand, less, greater
+ /// Currency codes are case-sensitive. + ///
+ [JsonProperty(propertyName: "currency")] + public string CurrencyCode { get; set; } + + /// + /// Quoted decimal representation of the amount of the token.
+ /// This can include scientific notation, such as 1.23e11 meaning 123,000,000,000.
+ /// Both e and E may be used.
+ /// This can be negative when displaying balances, but negative values are disallowed in other contexts such as specifying how much to send. + ///
+ [JsonProperty(propertyName: "value")] + public string Value { get; set; } + + /// + /// Generally, the account that issues this token.
+ /// In special cases, this can refer to the account that holds the token instead. + ///
+ [JsonProperty(propertyName: "issuer")] + public string Issuer { get; set; } - /// - /// The standard format for currency codes is a three-character string such as USD.
- /// This is intended for use with ISO 4217 Currency Codes
- /// As a 160-bit hexadecimal string, such as "0158415500000000C1F76FF6ECB0BAC600000000".
- /// The following characters are permitted:
- /// all uppercase and lowercase letters, digits, as well as the symbols ? ! @ # $ % ^ * ( ) { } [ ] | and symbols ampersand, less, greater
- /// Currency codes are case-sensitive. - ///
- [JsonProperty("currency")] - public string CurrencyCode { get; set; } - /// - /// Quoted decimal representation of the amount of the token.
- /// This can include scientific notation, such as 1.23e11 meaning 123,000,000,000.
- /// Both e and E may be used.
- /// This can be negative when displaying balances, but negative values are disallowed in other contexts such as specifying how much to send. - ///
- [JsonProperty("value")] - public string Value { get; set; } - /// - /// Generally, the account that issues this token.
- /// In special cases, this can refer to the account that holds the token instead. - ///
- [JsonProperty("issuer")] - public string Issuer { get; set; } - /// - /// Readable currency name - /// - [JsonIgnore] - public string CurrencyValidName => CurrencyCode is { Length: > 0 } row - ? row.Length > 3 - ? IsHexCurrencyCode(row) - ? row.FromHexString().Trim('\0') - : row + /// + /// Readable currency name + /// + [JsonIgnore] + public string CurrencyValidName => CurrencyCode is { Length: > 0, } row + ? row.Length > 3 + ? IsHexCurrencyCode(row) + ? row.StartsWith(value: "03") ? $"LP {row[2..6]}.." + : row.FromHexString().Trim(trimChar: '\0') : row - : string.Empty; - /// - /// decimal currency amount (drops for XRP) - /// - [JsonIgnore] - public decimal ValueAsNumber + : row + : string.Empty; + + /// + /// decimal currency amount (drops for XRP) + /// + [JsonIgnore] + public decimal ValueAsNumber + { + get { - get + try + { + return string.IsNullOrWhiteSpace(Value) + ? 0 + : decimal.Parse( + Value, + NumberStyles.AllowLeadingSign + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | NumberStyles.AllowExponent + | NumberStyles.AllowDecimalPoint, + CultureInfo.InvariantCulture); + } + catch (Exception e) { try { - return string.IsNullOrWhiteSpace(Value) - ? 0 - : decimal.Parse( - Value, - NumberStyles.AllowLeadingSign - | (NumberStyles.AllowLeadingSign & NumberStyles.AllowDecimalPoint) - | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent) - | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) - | (NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) - | NumberStyles.AllowExponent - | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture); - - } - catch (Exception e) - { - try + var num = double.Parse( + Value, + (NumberStyles.Float & NumberStyles.AllowExponent) | NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint, + CultureInfo.InvariantCulture); + var valid = $"{num:#########e00}"; + if (valid.Contains(value: "e-")) { - var num = double.Parse(Value, (NumberStyles.Float & NumberStyles.AllowExponent) | NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture); - var valid = $"{num:#########e00}"; - if (valid.Contains("e-")) - return 0; - if (valid.Contains('-')) - return decimal.MinValue; - return decimal.MaxValue; + return 0; } - catch (Exception exception) + + if (valid.Contains(value: '-')) { - Console.WriteLine(exception); - throw; + return decimal.MinValue; } + + return decimal.MaxValue; + } + catch (Exception exception) + { + Console.WriteLine(exception); + throw; } } - - set => Value = value.ToString(CurrencyCode == "XRP" + } + set => Value = value.ToString( + CurrencyCode == "XRP" ? "G0" : "G15", - CultureInfo.InvariantCulture); + CultureInfo.InvariantCulture); + } + + /// + /// XRP token amount (non drops value) + /// + [JsonIgnore] + public decimal? ValueAsXrp + { + get + { + if (CurrencyCode != "XRP" || string.IsNullOrWhiteSpace(Value)) + { + return null; + } + + return ValueAsNumber / 1000000; } - /// - /// XRP token amount (non drops value) - /// - [JsonIgnore] - public decimal? ValueAsXrp + set { - get + if (value.HasValue) { - if (CurrencyCode != "XRP" || string.IsNullOrWhiteSpace(Value)) - return null; - return ValueAsNumber / 1000000; + CurrencyCode = "XRP"; + var val = value.Value * 1000000; + Value = val.ToString(format: "G0", CultureInfo.InvariantCulture); } - set + else { - if (value.HasValue) - { - CurrencyCode = "XRP"; - decimal val = value.Value * 1000000; - Value = val.ToString("G0", CultureInfo.InvariantCulture); - } - else - { - Value = "0"; - } + Value = "0"; } } - /// - /// check currency code for HEX - /// - /// currency code - /// - public static bool IsHexCurrencyCode(string code) => Regex.IsMatch(code, @"[0-9a-fA-F]{40}", RegexOptions.IgnoreCase); - #region Overrides of Object + } + + /// + /// check currency code for HEX + /// + /// currency code + /// + public static bool IsHexCurrencyCode(string code) { return Regex.IsMatch(code, pattern: @"[0-9a-fA-F]{40}", RegexOptions.IgnoreCase); } + + #region Overrides of Object - public override string ToString() => CurrencyValidName == "XRP" ? $"XRP: {ValueAsXrp:0.######}" : $"{CurrencyValidName}: {ValueAsNumber:0.###############}"; - public override bool Equals(object o) => o is Currency model && model.Issuer == Issuer && model.CurrencyCode == CurrencyCode; + public override string ToString() + { + return CurrencyValidName == "XRP" ? $"XRP: {ValueAsXrp:0.######}" : $"{CurrencyValidName}: {ValueAsNumber:0.###############}"; + } - public static bool operator ==(Currency c1, Currency c2) => c1.Equals(c2); + public override bool Equals(object o) { return o is Currency model && model.Issuer == Issuer && model.CurrencyCode == CurrencyCode; } - public static bool operator !=(Currency c1, Currency c2) => !c1.Equals(c2); + public static bool operator ==(Currency c1, Currency c2) { return c1.Equals(c2); } - #endregion + public static bool operator !=(Currency c1, Currency c2) { return !c1.Equals(c2); } - } -} + #endregion +} \ No newline at end of file diff --git a/Xrpl/Models/Methods/AccountLines.cs b/Xrpl/Models/Methods/AccountLines.cs index 2625e84..365a093 100644 --- a/Xrpl/Models/Methods/AccountLines.cs +++ b/Xrpl/Models/Methods/AccountLines.cs @@ -2,210 +2,244 @@ using System.Globalization; using Newtonsoft.Json; + using Xrpl.Client.Extensions; // https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/models/methods/accountLines.ts -namespace Xrpl.Models.Methods +namespace Xrpl.Models.Methods; + +/// +/// Response expected from an . +/// +public class AccountLines //todo rename to AccountLinesResponse { /// - /// Response expected from an . + /// Unique Address of the account this request corresponds to.
+ /// This is the "perspective account" for purpose of the trust lines. ///
- public class AccountLines //todo rename to AccountLinesResponse - { - /// - /// Unique Address of the account this request corresponds to.
- /// This is the "perspective account" for purpose of the trust lines. - ///
- [JsonProperty("account")] - public string Account { get; set; } - /// - /// Array of trust line objects.
- /// If the number of trust lines is large, only returns up to the limit at a time. - ///
- [JsonProperty("lines")] - public List TrustLines { get; set; } - /// - /// The ledger index of the current open ledger, which was used when retrieving this information. - /// - [JsonProperty("ledger_current_index")] - public uint? LedgerCurrentIndex { get; set; } - /// - /// The ledger index of the ledger version that was used when retrieving this data. - /// - [JsonProperty("ledger_index")] - public uint? LedgerIndex { get; set; } - /// - /// The identifying hash the ledger version that was used when retrieving this data. - /// - [JsonProperty("ledger_hash")] - public string LedgerHash { get; set; } - /// - /// Server-defined value indicating the response is paginated.
- /// Pass this to the next call to resume where this call left off.
- /// Omitted when there are No additional pages after this one. - ///
- [JsonProperty("marker")] - public object Marker { get; set; } - } + [JsonProperty(propertyName: "account")] + public string Account { get; set; } + /// - /// Trust line objects. + /// Array of trust line objects.
+ /// If the number of trust lines is large, only returns up to the limit at a time. ///
- public class TrustLine - { - /// - /// The unique Address of the counterparty to this trust line. - /// - [JsonProperty("account")] - public string Account { get; set; } - /// - /// Representation of the numeric balance currently held against this line.
- /// A positive balance means that the perspective account holds value;
- /// a negative Balance means that the perspective account owes value. - ///
- [JsonProperty("balance")] - public string Balance { get; set; } - /// - /// Representation of the numeric balance currently held against this line.
- /// A positive balance means that the perspective account holds value;
- /// a negative Balance means that the perspective account owes value. - ///
- [JsonIgnore] - public decimal BalanceAsNumber => decimal.Parse(Balance, NumberStyles.AllowLeadingSign - | (NumberStyles.AllowLeadingSign & NumberStyles.AllowDecimalPoint) - | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent) - | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) - | (NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) - | NumberStyles.AllowExponent - | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture); - - /// - /// A Currency Code identifying what currency this trust line can hold. - /// - [JsonProperty("currency")] - public string Currency { get; set; } - /// - /// Readable currency name - /// - [JsonIgnore] - public string CurrencyValidName => Currency is { Length: > 0 } row ? row.Length > 3 ? row.FromHexString().Trim('\0') : row : string.Empty; - /// - /// The maximum amount of currency that the issuer account is willing to owe the perspective account. - /// - [JsonProperty("limit")] - public string Limit { get; set; } - /// - /// The maximum amount of currency that the issuer account is willing to owe the perspective account. - /// - [JsonIgnore] - public double LimitAsNumber => double.Parse(Limit, NumberStyles.AllowLeadingSign - | (NumberStyles.AllowLeadingSign & NumberStyles.AllowDecimalPoint) - | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent) - | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) - | (NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) - | NumberStyles.AllowExponent - | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture); - /// - /// The maximum amount of currency that the issuer account is willing to owe the perspective account. - /// - [JsonProperty("limit_peer")] - public string LimitPeer { get; set; } - - [JsonIgnore] - public double LimitPeerAsNumber => double.Parse(LimitPeer, NumberStyles.AllowLeadingSign - | (NumberStyles.AllowLeadingSign & NumberStyles.AllowDecimalPoint) - | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent) - | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) - | (NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) - | NumberStyles.AllowExponent - | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture); - /// - /// Rate at which the account values incoming balances on this trust line, as a ratio of this value per 1 billion units.
- /// (For example, a value of 500 million represents a 0.5:1 ratio.)
- /// As a special case, 0 is treated as a 1:1 ratio. - ///
- [JsonProperty("quality_in")] - public uint QualityIn { get; set; } - /// - /// Rate at which the account values outgoing balances on this trust line, as a ratio of this value per 1 billion units.
- /// (For example, a value of 500 million represents a 0.5:1 ratio.)
- /// As a special case, 0 is treated as a 1:1 ratio. - ///
- [JsonProperty("quality_out")] - public uint QualityOut { get; set; } - /// - /// If true, this account has enabled the No Ripple flag for this trust line.
- /// If present and false, this account has disabled the No Ripple flag, but, - /// because the account also has the Default Ripple flag enabled, that is not considered the default state.
- /// If omitted, the account has the No Ripple flag disabled for this trust line and Default Ripple disabled. - ///
- [JsonProperty("no_ripple")] - public bool? NoRipple { get; set; } - /// - /// If true, the peer account has enabled the No Ripple flag for this trust line.
- /// If present and false, this account has disabled the No Ripple flag, but, - /// because the account also has the Default Ripple flag enabled, that is not considered the default state.
- /// If omitted, the account has the No Ripple flag disabled for this trust line and Default Ripple disabled. - ///
- [JsonProperty("no_ripple_peer")] - public bool? NoRipplePeer { get; set; } - /// - /// If true, this account has frozen this trust line. The default is false. - /// - [JsonProperty("freeze")] - public bool? Freeze { get; set; } - /// - /// If true, the peer account has frozen this trust line.
- /// The default is false. - ///
- [JsonProperty("freeze_peer")] - public bool? FreezePeer { get; set; } - - //todo not found fields - authorized?: boolean, peer_authorized?: boolean - } + [JsonProperty(propertyName: "lines")] + public List TrustLines { get; set; } + + /// + /// The ledger index of the current open ledger, which was used when retrieving this information. + /// + [JsonProperty(propertyName: "ledger_current_index")] + public uint? LedgerCurrentIndex { get; set; } + + /// + /// The ledger index of the ledger version that was used when retrieving this data. + /// + [JsonProperty(propertyName: "ledger_index")] + public uint? LedgerIndex { get; set; } + + /// + /// The identifying hash the ledger version that was used when retrieving this data. + /// + [JsonProperty(propertyName: "ledger_hash")] + public string LedgerHash { get; set; } + + /// + /// Server-defined value indicating the response is paginated.
+ /// Pass this to the next call to resume where this call left off.
+ /// Omitted when there are No additional pages after this one. + ///
+ [JsonProperty(propertyName: "marker")] + public object Marker { get; set; } +} + +/// +/// Trust line objects. +/// +public class TrustLine +{ + /// + /// The unique Address of the counterparty to this trust line. + /// + [JsonProperty(propertyName: "account")] + public string Account { get; set; } + + /// + /// Representation of the numeric balance currently held against this line.
+ /// A positive balance means that the perspective account holds value;
+ /// a negative Balance means that the perspective account owes value. + ///
+ [JsonProperty(propertyName: "balance")] + public string Balance { get; set; } + + /// + /// Representation of the numeric balance currently held against this line.
+ /// A positive balance means that the perspective account holds value;
+ /// a negative Balance means that the perspective account owes value. + ///
+ [JsonIgnore] + public decimal BalanceAsNumber => decimal.Parse( + Balance, + NumberStyles.AllowLeadingSign + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | NumberStyles.AllowExponent + | NumberStyles.AllowDecimalPoint, + CultureInfo.InvariantCulture); + + /// + /// A Currency Code identifying what currency this trust line can hold. + /// + [JsonProperty(propertyName: "currency")] + public string Currency { get; set; } + + /// + /// Readable currency name + /// + [JsonIgnore] + public string CurrencyValidName => Currency is { Length: > 0, } row + ? row.Length > 3 ? row.StartsWith(value: "03") ? $"LP {row[2..6]}.." : row.FromHexString().Trim(trimChar: '\0') : row + : string.Empty; + + /// + /// The maximum amount of currency that the issuer account is willing to owe the perspective account. + /// + [JsonProperty(propertyName: "limit")] + public string Limit { get; set; } + /// - /// The account_lines method returns information about an account's trust lines, - /// including balances in all non-XRP currencies and assets.
- /// All information retrieved is relative to a particular version of the ledger.
- /// Expects an . - ///
- /// - /// { - /// "id": 1, - /// "command": "account_lines", - /// "account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59" - /// } - /// - public class AccountLinesRequest : BaseLedgerRequest + /// The maximum amount of currency that the issuer account is willing to owe the perspective account. + ///
+ [JsonIgnore] + public double LimitAsNumber => double.Parse( + Limit, + NumberStyles.AllowLeadingSign + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | NumberStyles.AllowExponent + | NumberStyles.AllowDecimalPoint, + CultureInfo.InvariantCulture); + + /// + /// The maximum amount of currency that the issuer account is willing to owe the perspective account. + /// + [JsonProperty(propertyName: "limit_peer")] + public string LimitPeer { get; set; } + + [JsonIgnore] + public double LimitPeerAsNumber => double.Parse( + LimitPeer, + NumberStyles.AllowLeadingSign + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent) + | (NumberStyles.AllowLeadingSign & NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | (NumberStyles.AllowExponent & NumberStyles.AllowDecimalPoint) + | NumberStyles.AllowExponent + | NumberStyles.AllowDecimalPoint, + CultureInfo.InvariantCulture); + + /// + /// Rate at which the account values incoming balances on this trust line, as a ratio of this value per 1 billion units.
+ /// (For example, a value of 500 million represents a 0.5:1 ratio.)
+ /// As a special case, 0 is treated as a 1:1 ratio. + ///
+ [JsonProperty(propertyName: "quality_in")] + public uint QualityIn { get; set; } + + /// + /// Rate at which the account values outgoing balances on this trust line, as a ratio of this value per 1 billion units.
+ /// (For example, a value of 500 million represents a 0.5:1 ratio.)
+ /// As a special case, 0 is treated as a 1:1 ratio. + ///
+ [JsonProperty(propertyName: "quality_out")] + public uint QualityOut { get; set; } + + /// + /// If true, this account has enabled the No Ripple flag for this trust line.
+ /// If present and false, this account has disabled the No Ripple flag, but, + /// because the account also has the Default Ripple flag enabled, that is not considered the default state.
+ /// If omitted, the account has the No Ripple flag disabled for this trust line and Default Ripple disabled. + ///
+ [JsonProperty(propertyName: "no_ripple")] + public bool? NoRipple { get; set; } + + /// + /// If true, the peer account has enabled the No Ripple flag for this trust line.
+ /// If present and false, this account has disabled the No Ripple flag, but, + /// because the account also has the Default Ripple flag enabled, that is not considered the default state.
+ /// If omitted, the account has the No Ripple flag disabled for this trust line and Default Ripple disabled. + ///
+ [JsonProperty(propertyName: "no_ripple_peer")] + public bool? NoRipplePeer { get; set; } + + /// + /// If true, this account has frozen this trust line. The default is false. + /// + [JsonProperty(propertyName: "freeze")] + public bool? Freeze { get; set; } + + /// + /// If true, the peer account has frozen this trust line.
+ /// The default is false. + ///
+ [JsonProperty(propertyName: "freeze_peer")] + public bool? FreezePeer { get; set; } + + //todo not found fields - authorized?: boolean, peer_authorized?: boolean +} + +/// +/// The account_lines method returns information about an account's trust lines, +/// including balances in all non-XRP currencies and assets.
+/// All information retrieved is relative to a particular version of the ledger.
+/// Expects an . +///
+/// +/// { +/// "id": 1, +/// "command": "account_lines", +/// "account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59" +/// } +/// +public class AccountLinesRequest : BaseLedgerRequest +{ + public AccountLinesRequest(string account) { - public AccountLinesRequest(string account) - { - Account = account; - Command = "account_lines"; - } - /// - /// A unique identifier for the account, most commonly the account's Address. - /// - [JsonProperty("account")] - public string Account { get; set; } - /// - /// The Address of a second account. - /// If provided, show only lines of trust connecting the two accounts. - /// - [JsonProperty("peer")] - public string Peer { get; set; } - /// - /// Limit the number of trust lines to retrieve.
- /// The server is not required to honor this value.
- /// Must be within the inclusive range 10 to 400. - ///
- [JsonProperty("limit")] - public int? Limit { get; set; } = 10; - /// - /// Value from a previous paginated response.
- /// Resume retrieving data where that response left off. - ///
- [JsonProperty("marker")] - public object Marker { get; set; } + Account = account; + Command = "account_lines"; } -} + + /// + /// A unique identifier for the account, most commonly the account's Address. + /// + [JsonProperty(propertyName: "account")] + public string Account { get; set; } + + /// + /// The Address of a second account. + /// If provided, show only lines of trust connecting the two accounts. + /// + [JsonProperty(propertyName: "peer")] + public string Peer { get; set; } + + /// + /// Limit the number of trust lines to retrieve.
+ /// The server is not required to honor this value.
+ /// Must be within the inclusive range 10 to 400. + ///
+ [JsonProperty(propertyName: "limit")] + public int? Limit { get; set; } = 10; + + /// + /// Value from a previous paginated response.
+ /// Resume retrieving data where that response left off. + ///
+ [JsonProperty(propertyName: "marker")] + public object Marker { get; set; } +} \ No newline at end of file From 1cef2353499da13dfc605e99525df7cfe4639065 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Fri, 10 May 2024 12:49:25 +0200 Subject: [PATCH 37/39] fix merge --- Xrpl/Models/Common/Common.cs | 59 +++++++++ Xrpl/Models/Common/{Index.cs => Currency.cs} | 132 +------------------ 2 files changed, 61 insertions(+), 130 deletions(-) create mode 100644 Xrpl/Models/Common/Common.cs rename Xrpl/Models/Common/{Index.cs => Currency.cs} (63%) diff --git a/Xrpl/Models/Common/Common.cs b/Xrpl/Models/Common/Common.cs new file mode 100644 index 0000000..28b7c6d --- /dev/null +++ b/Xrpl/Models/Common/Common.cs @@ -0,0 +1,59 @@ +using Newtonsoft.Json; + +// https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/models/common/index.ts + +namespace Xrpl.Models.Common +{ + ///// + ///// Order book currency + ///// + //public class Currency + //{ + // /// + // /// Currency code + // /// + // [JsonProperty("currency")] + // public string Currency { get; set; } + // /// + // /// Currency Issuer + // /// + // [JsonProperty("issuer")] + // public string Issuer { get; set; } + //} + + /// common class + public class Common + { + /// is XRP currency + public class XRP + { + /// XRP currency code + [JsonProperty("currency")] + public string Currency = "XRP"; + } + + /// currency with issuer + public class IssuedCurrency + { + /// + /// currency code + /// + [JsonProperty("currency")] + public string Currency { get; set; } + + /// + /// currency issuer + /// + [JsonProperty("issuer")] + public string Issuer { get; set; } + } + + /// currency with amount and issuer + public class IssuedCurrencyAmount : IssuedCurrency + { + /// currency value + [JsonProperty("value")] + public string Value { get; set; } + } + } +} \ No newline at end of file diff --git a/Xrpl/Models/Common/Index.cs b/Xrpl/Models/Common/Currency.cs similarity index 63% rename from Xrpl/Models/Common/Index.cs rename to Xrpl/Models/Common/Currency.cs index 0131927..383ebc6 100644 --- a/Xrpl/Models/Common/Index.cs +++ b/Xrpl/Models/Common/Currency.cs @@ -6,9 +6,8 @@ using Xrpl.Client.Extensions; -//https://xrpl.org/ledger-header.html#ledger-index //https://xrpl.org/currency-formats.html#currency-formats -// https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/models/common/index.ts +//https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/models/common/index.ts namespace Xrpl.Models.Common; @@ -150,133 +149,6 @@ public decimal? ValueAsXrp Value = "0"; } } - #region Overrides of Object - - public override string ToString() => CurrencyValidName == "XRP" ? $"XRP: {ValueAsXrp:0.######}" : $"{CurrencyValidName}: {ValueAsNumber:0.###############}"; - public override bool Equals(object o) => o is Currency model && model.Issuer == Issuer && model.CurrencyCode == CurrencyCode; - - public static bool operator ==(Currency c1, Currency c2) => c1.Equals(c2); - - public static bool operator !=(Currency c1, Currency c2) => !c1.Equals(c2); - - #endregion - - } - - public class LedgerIndex - { - public LedgerIndex(uint index) - { - Index = index; - } - - public LedgerIndex(LedgerIndexType ledgerIndexType) - { - LedgerIndexType = ledgerIndexType; - } - - public uint? Index { get; set; } - /// - /// Index type
- /// validated
- /// closed
- /// current
- ///
- public LedgerIndexType LedgerIndexType { get; set; } - } - - /// common class - public class Common - { - public enum LedgerIndex - { - Validated, - Closed, - Current - } - - public enum AccountObjectType - { - Check, - DepositPreauth, - Escrow, - NftOffer, - Offer, - PaymentChannel, - SignerList, - Ticket, - State - } - - public class MemoEntry - { - public string MemoData { get; set; } - public string MemoType { get; set; } - public string MemoFormat { get; set; } - } - - public class ISigner - { - public SignerEntry Signer { get; set; } - } - - public class IssuedCurrencyAmount - { - [JsonProperty("currency")] - public string Currency { get; set; } - - [JsonProperty("value")] - public string Value { get; set; } - - [JsonProperty("issuer")] - public string Issuer { get; set; } - } - - public class IMemo - { - public MemoEntry Memo { get; set; } - } - - public enum StreamType - { - Consensus, - Ledger, - Manifests, - PeerStatus, - Transactions, - TransactionsProposed, - Server, - Validations - } - - public class PathStep - { - public string Account { get; set; } - public string Currency { get; set; } - public string Issuer { get; set; } - } - - public class Path - { - public List PathStep { get; set; } - } - - public class ResponseOnlyTxInfo - { - public int Date { get; set; } - public string Hash { get; set; } - public int LedgerIndex { get; set; } - } - - public class NFTOffer - { - public Amount Amount { get; set; } - public int Flags { get; set; } - public string NftOfferIndex { get; set; } - public string Owner { get; set; } - public string Destination { get; set; } - public int Expiration { get; set; } - } } /// @@ -300,4 +172,4 @@ public override string ToString() public static bool operator !=(Currency c1, Currency c2) { return !c1.Equals(c2); } #endregion -} +} \ No newline at end of file From 38d32cdc40e9ff490630ddf6e684b96d1a32e889 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Fri, 10 May 2024 12:52:41 +0200 Subject: [PATCH 38/39] fix bad merge --- Xrpl/Models/Transactions/TxFormat.cs | 56 +++++++++++++------------- Xrpl/Models/Transactions/Validation.cs | 16 -------- 2 files changed, 28 insertions(+), 44 deletions(-) diff --git a/Xrpl/Models/Transactions/TxFormat.cs b/Xrpl/Models/Transactions/TxFormat.cs index 444af33..b510215 100644 --- a/Xrpl/Models/Transactions/TxFormat.cs +++ b/Xrpl/Models/Transactions/TxFormat.cs @@ -94,13 +94,13 @@ internal static void Validate(StObject obj, Action onError) } } - public static Dictionary Formats; + public static Dictionary Formats; static TxFormat() { - Formats = new Dictionary + Formats = new Dictionary { - [BinaryCodec.Enums.TransactionType.Payment] = new TxFormat + [BinaryCodec.Types.TransactionType.Payment] = new TxFormat { [Field.Destination] = Requirement.Required, [Field.Amount] = Requirement.Required, @@ -110,7 +110,7 @@ static TxFormat() [Field.DestinationTag] = Requirement.Optional, [Field.DeliverMin] = Requirement.Optional }, - [BinaryCodec.Enums.TransactionType.EscrowCreate] = new TxFormat + [BinaryCodec.Types.TransactionType.EscrowCreate] = new TxFormat { [Field.Amount] = Requirement.Required, [Field.Destination] = Requirement.Required, @@ -119,14 +119,14 @@ static TxFormat() [Field.FinishAfter] = Requirement.Optional, [Field.DestinationTag] = Requirement.Optional, }, - [BinaryCodec.Enums.TransactionType.EscrowFinish] = new TxFormat + [BinaryCodec.Types.TransactionType.EscrowFinish] = new TxFormat { [Field.Owner] = Requirement.Required, [Field.OfferSequence] = Requirement.Required, [Field.Condition] = Requirement.Optional, [Field.Fulfillment] = Requirement.Optional }, - [BinaryCodec.Enums.TransactionType.AccountSet] = new TxFormat + [BinaryCodec.Types.TransactionType.AccountSet] = new TxFormat { [Field.EmailHash] = Requirement.Optional, [Field.WalletLocator] = Requirement.Optional, @@ -139,41 +139,41 @@ static TxFormat() [Field.TickSize] = Requirement.Optional, [Field.NFTokenMinter] = Requirement.Optional }, - [BinaryCodec.Enums.TransactionType.EscrowCancel] = new TxFormat + [BinaryCodec.Types.TransactionType.EscrowCancel] = new TxFormat { [Field.Owner] = Requirement.Required, [Field.OfferSequence] = Requirement.Required }, - [BinaryCodec.Enums.TransactionType.SetRegularKey] = new TxFormat + [BinaryCodec.Types.TransactionType.SetRegularKey] = new TxFormat { [Field.RegularKey] = Requirement.Optional }, // 6 - [BinaryCodec.Enums.TransactionType.OfferCreate] = new TxFormat + [BinaryCodec.Types.TransactionType.OfferCreate] = new TxFormat { [Field.TakerPays] = Requirement.Required, [Field.TakerGets] = Requirement.Required, [Field.Expiration] = Requirement.Optional, [Field.OfferSequence] = Requirement.Optional }, - [BinaryCodec.Enums.TransactionType.OfferCancel] = new TxFormat + [BinaryCodec.Types.TransactionType.OfferCancel] = new TxFormat { [Field.OfferSequence] = Requirement.Required }, // 9 - [BinaryCodec.Enums.TransactionType.TicketCreate] = new TxFormat + [BinaryCodec.Types.TransactionType.TicketCreate] = new TxFormat { [Field.Target] = Requirement.Optional, [Field.Expiration] = Requirement.Optional, [Field.TicketCount] = Requirement.Required }, // 11 - [BinaryCodec.Enums.TransactionType.SignerListSet] = new TxFormat + [BinaryCodec.Types.TransactionType.SignerListSet] = new TxFormat { [Field.SignerQuorum] = Requirement.Required, [Field.SignerEntries] = Requirement.Optional }, - [BinaryCodec.Enums.TransactionType.PaymentChannelCreate] = new TxFormat() + [BinaryCodec.Types.TransactionType.PaymentChannelCreate] = new TxFormat() { [Field.Destination] = Requirement.Required, [Field.Amount] = Requirement.Required, @@ -182,13 +182,13 @@ static TxFormat() [Field.CancelAfter] = Requirement.Optional, [Field.DestinationTag] = Requirement.Optional }, - [BinaryCodec.Enums.TransactionType.PaymentChannelFund] = new TxFormat() + [BinaryCodec.Types.TransactionType.PaymentChannelFund] = new TxFormat() { [Field.Channel] = Requirement.Required, [Field.Amount] = Requirement.Required, [Field.Expiration] = Requirement.Optional }, - [BinaryCodec.Enums.TransactionType.PaymentChannelClaim] = new TxFormat() + [BinaryCodec.Types.TransactionType.PaymentChannelClaim] = new TxFormat() { [Field.Channel] = Requirement.Required, [Field.Amount] = Requirement.Optional, @@ -196,7 +196,7 @@ static TxFormat() [Field.Signature] = Requirement.Optional, [Field.PublicKey] = Requirement.Optional }, - [BinaryCodec.Enums.TransactionType.CheckCreate] = new TxFormat() + [BinaryCodec.Types.TransactionType.CheckCreate] = new TxFormat() { [Field.Channel] = Requirement.Required, [Field.Amount] = Requirement.Optional, @@ -204,7 +204,7 @@ static TxFormat() [Field.Signature] = Requirement.Optional, [Field.PublicKey] = Requirement.Optional }, - [BinaryCodec.Enums.TransactionType.CheckCash] = new TxFormat() + [BinaryCodec.Types.TransactionType.CheckCash] = new TxFormat() { [Field.Channel] = Requirement.Required, [Field.Amount] = Requirement.Optional, @@ -212,7 +212,7 @@ static TxFormat() [Field.Signature] = Requirement.Optional, [Field.PublicKey] = Requirement.Optional }, - [BinaryCodec.Enums.TransactionType.CheckCancel] = new TxFormat() + [BinaryCodec.Types.TransactionType.CheckCancel] = new TxFormat() { [Field.Channel] = Requirement.Required, [Field.Amount] = Requirement.Optional, @@ -220,7 +220,7 @@ static TxFormat() [Field.Signature] = Requirement.Optional, [Field.PublicKey] = Requirement.Optional }, - [BinaryCodec.Enums.TransactionType.DepositPreauth] = new TxFormat() + [BinaryCodec.Types.TransactionType.DepositPreauth] = new TxFormat() { [Field.Channel] = Requirement.Required, [Field.Amount] = Requirement.Optional, @@ -228,31 +228,31 @@ static TxFormat() [Field.Signature] = Requirement.Optional, [Field.PublicKey] = Requirement.Optional }, - [BinaryCodec.Enums.TransactionType.TrustSet] = new TxFormat + [BinaryCodec.Types.TransactionType.TrustSet] = new TxFormat { [Field.LimitAmount] = Requirement.Optional, [Field.QualityIn] = Requirement.Optional, [Field.QualityOut] = Requirement.Optional }, - [BinaryCodec.Enums.TransactionType.AccountDelete] = new TxFormat + [BinaryCodec.Types.TransactionType.AccountDelete] = new TxFormat { [Field.Destination] = Requirement.Required, [Field.DestinationTag] = Requirement.Optional, }, - [BinaryCodec.Enums.TransactionType.NFTokenMint] = new TxFormat + [BinaryCodec.Types.TransactionType.NFTokenMint] = new TxFormat { [Field.NFTokenTaxon] = Requirement.Required, [Field.Issuer] = Requirement.Optional, [Field.TransferFee] = Requirement.Optional, [Field.URI] = Requirement.Optional }, - [BinaryCodec.Enums.TransactionType.NFTokenBurn] = new TxFormat + [BinaryCodec.Types.TransactionType.NFTokenBurn] = new TxFormat { [Field.NFTokenID] = Requirement.Required, [Field.Owner] = Requirement.Optional }, - [BinaryCodec.Enums.TransactionType.NFTokenCreateOffer] = new TxFormat + [BinaryCodec.Types.TransactionType.NFTokenCreateOffer] = new TxFormat { [Field.NFTokenID] = Requirement.Required, [Field.Amount] = Requirement.Required, @@ -260,18 +260,18 @@ static TxFormat() [Field.Destination] = Requirement.Optional, [Field.Expiration] = Requirement.Optional }, - [BinaryCodec.Enums.TransactionType.NFTokenCancelOffer] = new TxFormat + [BinaryCodec.Types.TransactionType.NFTokenCancelOffer] = new TxFormat { [Field.NFTokenOffers] = Requirement.Required }, - [BinaryCodec.Enums.TransactionType.NFTokenAcceptOffer] = new TxFormat + [BinaryCodec.Types.TransactionType.NFTokenAcceptOffer] = new TxFormat { //[Field.NFTokenID] = Requirement.Required, //no need this field [Field.NFTokenSellOffer] = Requirement.Optional, [Field.NFTokenBuyOffer] = Requirement.Optional, [Field.NFTokenBrokerFee] = Requirement.Optional, }, - [BinaryCodec.Enums.TransactionType.UNLModify] = new TxFormat + [BinaryCodec.Types.TransactionType.UNLModify] = new TxFormat { [Field.LedgerSequence] = Requirement.Optional, [Field.BaseFee] = Requirement.Required, @@ -333,4 +333,4 @@ public TxFormatValidationException(string msg) : base(msg) { } } -} +} \ No newline at end of file diff --git a/Xrpl/Models/Transactions/Validation.cs b/Xrpl/Models/Transactions/Validation.cs index a04986a..f51d695 100644 --- a/Xrpl/Models/Transactions/Validation.cs +++ b/Xrpl/Models/Transactions/Validation.cs @@ -142,22 +142,6 @@ public static async Task Validate(Dictionary tx) case "AMMWithdraw": await ValidateAMMWithdraw(tx); break; - - case "AMMBid": - await ValidateAMMBid(tx); - break; - case "AMMDeposit": - await ValidateAMMDeposit(tx); - break; - case "AMMCreate": - await ValidateAMMCreate(tx); - break; - case "AMMVote": - await ValidateAMMVote(tx); - break; - case "AMMWithdraw": - await ValidateAMMWithdraw(tx); - break; default: throw new ValidationException($"Invalid field TransactionType: {type}"); } From eaae0c20de5557e2c0552ab052ac55ce7856daf6 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Fri, 10 May 2024 12:55:09 +0200 Subject: [PATCH 39/39] fix bad merge --- Base/Xrpl.BinaryCodec/Enums/TransactionType.cs | 2 +- Xrpl/Models/Utils/Flags.cs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Base/Xrpl.BinaryCodec/Enums/TransactionType.cs b/Base/Xrpl.BinaryCodec/Enums/TransactionType.cs index 46111ca..d7a7d91 100644 --- a/Base/Xrpl.BinaryCodec/Enums/TransactionType.cs +++ b/Base/Xrpl.BinaryCodec/Enums/TransactionType.cs @@ -1,6 +1,6 @@ using Xrpl.BinaryCodec.Enums; -namespace Xrpl.BinaryCodec.Enums +namespace Xrpl.BinaryCodec.Types { public class TransactionType : SerializedEnumItem { diff --git a/Xrpl/Models/Utils/Flags.cs b/Xrpl/Models/Utils/Flags.cs index 622578f..3cb111a 100644 --- a/Xrpl/Models/Utils/Flags.cs +++ b/Xrpl/Models/Utils/Flags.cs @@ -55,8 +55,6 @@ public static void SetTransactionFlagsToNumber(Dictionary tx) "OfferCreate" => ConvertOfferCreateFlagsToNumber(Flags), "Payment" => ConvertPaymentTransactionFlagsToNumber(Flags), "PaymentChannelClaim" => ConvertPaymentChannelClaimFlagsToNumber(Flags), - "AMMDeposit" => ConvertAMMDepositFlagsToNumber(Flags), - "AMMWithdraw" => ConvertAMMWithdrawFlagsToNumber(Flags), //TransactionType.PaymentChannelCreate => expr, //TransactionType.PaymentChannelFund => expr, //TransactionType.SetRegularKey => expr,