From 27659c02962f08ee97d7a1d8c5aeda7110531fc8 Mon Sep 17 00:00:00 2001 From: Aleksandr <44946855+Platonenkov@users.noreply.github.com> Date: Fri, 10 May 2024 13:19:03 +0300 Subject: [PATCH] NegativeUNL, AMMInfo (#68) --- .ci-config/rippled.cfg | 121 +++++------ .github/workflows/dotnet.test.yml | 11 +- .../Binary/BinarySerializer.cs | 1 + Base/Xrpl.BinaryCodec/Binary/BytesList.cs | 9 + .../{Types => Enums}/EngineResult.cs | 27 ++- Base/Xrpl.BinaryCodec/Enums/Field.cs | 29 ++- Base/Xrpl.BinaryCodec/Enums/FieldType.cs | 5 +- Base/Xrpl.BinaryCodec/Enums/FromJson.cs | 1 + Base/Xrpl.BinaryCodec/Enums/FromParser.cs | 1 + Base/Xrpl.BinaryCodec/Enums/IssueField.cs | 12 ++ .../{Types => Enums}/LedgerEntryType.cs | 2 +- .../Enums/SerializedEnumItem.cs | 1 + .../{Types => Enums}/TransactionType.cs | 9 +- Base/Xrpl.BinaryCodec/Exceptions.cs | 21 ++ Base/Xrpl.BinaryCodec/ISerializedType.cs | 36 ---- Base/Xrpl.BinaryCodec/ShaMapTree/ShaMap.cs | 1 + Base/Xrpl.BinaryCodec/Types/Currency.cs | 19 -- .../Xrpl.BinaryCodec/Types/ISerializedType.cs | 124 +++++++++++ .../Types/InvalidJsonException.cs | 25 --- Base/Xrpl.BinaryCodec/Types/Issue.cs | 106 +++++++++ Base/Xrpl.BinaryCodec/Types/UnissuedAmount.cs | 28 --- Tests/Xrpl.Tests/Models/TestAMMBid.cs | 165 ++++++++++++++ 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 ++++++++++++++ Tests/Xrpl.Tests/Models/TestAccountSet.cs | 3 +- Xrpl/Models/Common/Common.cs | 60 ------ Xrpl/Models/Common/{Currency.cs => Index.cs} | 137 +++++++++++- Xrpl/Models/Common/LedgerIndex.cs | 31 --- Xrpl/Models/Enums.cs | 31 ++- Xrpl/Models/Ledger/LOAccountRoot.cs | 1 + Xrpl/Models/Ledger/LOAmm.cs | 132 ++++++++++++ Xrpl/Models/Ledger/LONegativeUNL.cs | 40 ++++ Xrpl/Models/Methods/AMMInfo.cs | 112 ++++++++++ Xrpl/Models/Transactions/AMMBid.cs | 149 +++++++++++++ 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 | 203 ++++++++++++++++++ Xrpl/Models/Transactions/Common.cs | 18 ++ Xrpl/Models/Transactions/TxFormat.cs | 54 ++--- Xrpl/Models/Transactions/Validation.cs | 27 ++- Xrpl/Models/Utils/Flags.cs | 14 ++ 45 files changed, 2403 insertions(+), 313 deletions(-) rename Base/Xrpl.BinaryCodec/{Types => Enums}/EngineResult.cs (86%) create mode 100644 Base/Xrpl.BinaryCodec/Enums/IssueField.cs rename Base/Xrpl.BinaryCodec/{Types => Enums}/LedgerEntryType.cs (98%) rename Base/Xrpl.BinaryCodec/{Types => Enums}/TransactionType.cs (93%) 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 delete mode 100644 Xrpl/Models/Common/Common.cs rename Xrpl/Models/Common/{Currency.cs => Index.cs} (52%) delete mode 100644 Xrpl/Models/Common/LedgerIndex.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/.ci-config/rippled.cfg b/.ci-config/rippled.cfg index 5c219c65..09bbe09f 100644 --- a/.ci-config/rippled.cfg +++ b/.ci-config/rippled.cfg @@ -105,67 +105,68 @@ validators.txt # Note: The version of rippled you use this config with must have an implementation for the amendments you attempt to enable or it will crash. # If you need the version of rippled to be more up to date, you may need to make a comment on this repo: https://github.com/WietseWind/docker-rippled -[amendments] +[features] # Devnet amendments as of June 28th, 2023 -B4E4F5D2D6FB84DF7399960A732309C9FD530EAE5941838160042833625A6076 NegativeUNL -DF8B4536989BDACE3F934F29423848B9F1D76D09BE6A1FCFE7E7F06AA26ABEAD fixRemoveNFTokenAutoTrustLine -3C43D9A973AA4443EF3FC38E42DD306160FBFFDAB901CD8BAA15D09F2597EB87 NonFungibleTokensV1 -98DECF327BF79997AEC178323AD51A830E457BFC6D454DAF3E46E5EC42DC619F CheckCashMakesTrustLine -B6B3EEDC0267AB50491FDC450A398AF30DBCD977CECED8BEF2499CAB5DAC19E2 fixRmSmallIncreasedQOffers -452F5906C46D46F407883344BFDD90E672B672C5E9943DB4891E3A34FEEEB9DB fixSTAmountCanonicalize -AF8DF7465C338AE64B1E937D6C8DA138C0D63AD5134A68792BBBE1F63356C422 FlowSortStrands -955DF3FA5891195A9DAEFA1DDC6BB244B545DDE1BAA84CBB25D5F12A8DA68A0C TicketBatch -B4D44CC3111ADD964E846FC57760C8B50FFCD5A82C86A72756F6B058DDDF96AD fix1201 -89308AF3B8B10B7192C4E613E1D2E4D9BA64B2EE2D5232402AE82A6A7220D953 fixQualityUpperBound -3012E8230864E95A58C60FD61430D7E1B4D3353195F2981DC12B0C7C0950FFAC FlowCross -DC9CA96AEA1DCF83E527D1AFC916EFAF5D27388ECA4060A88817C1238CAEE0BF EnforceInvariants -B9E739B8296B4A1BB29BE990B17D66E21B62A300A909F25AC55C22D6C72E1F9D fix1523 -1F4AFA8FA1BC8827AD4C0F682C03A8B671DCDF6B5C4DE36D44243A684103EF88 HardenedValidations -3CBC5C4E630A1B82380295CDA84B32B49DD066602E74E39B85EF64137FA65194 DepositPreauth -586480873651E106F1D6339B0C4A8945BA705A777F3F4524626FF1FC07EFE41D MultiSignReserve -58BE9B5968C4DA7C59BA900961828B113E5490699B21877DEF9A31E9D0FE5D5F fix1623 -42426C4D4F1009EE67080A9B7965B44656D7714D104A72F9B4369F97ABF044EE FeeEscalation -08DE7D96082187F6E6578530258C77FAABABE4C20474BDB82F04B021F1A68647 PayChan -67A34F2CF55BFC0F93AACD5B281413176FEE195269FA6D95219A2DF738671172 fix1513 -00C1FC4A53E60AB02C864641002B3172F38677E29C26C5406685179B37E1EDAC RequireFullyCanonicalSig -CA7C02118BA27599528543DFE77BA6838D1B0F43B447D4D7F53523CE6A0E9AC2 fix1543 -532651B4FD58DF8922A49BA101AB3E996E5BFBF95A913B3E392504863E63B164 TickSize -25BA44241B3BD880770BFA4DA21C7180576831855368CBEC6A3154FDE4A7676E fix1781 -8F81B066ED20DAECA20DF57187767685EEF3980B228E0667A650BAF24426D3B4 fixCheckThreading -5D08145F0A4983F23AFFFF514E83FAD355C5ABFBB6CAB76FB5BC8519FF5F33BE fix1515 -1562511F573A19AE9BD103B5D6B9E01B3B46805AEC5D3C4805C902B514399146 CryptoConditions -1D3463A5891F9E589C5AE839FFAC4A917CE96197098A1EF22304E1BC5B98A454 fix1528 -621A0B264970359869E3C0363A899909AAB7A887C8B73519E4ECF952D33258A8 fixPayChanRecipientOwnerDir -CC5ABAE4F3EC92E94A59B1908C2BE82D2228B6485C00AFF8F22DF930D89C194E SortedDirectories -FBD513F1B893AC765B78F250E6FFA6A11B573209D1842ADC787C850696741288 fix1578 -7117E2EC2DBF119CA55181D69819F1999ECEE1A0225A7FD2B9ED47940968479C fix1571 -4F46DF03559967AC60F2EB272FEFE3928A7594A45FF774B87A7E540DB0F8F068 fixAmendmentMajorityCalc -2CD5286D8D687E98B41102BDD797198E81EA41DF7BD104E6561FEB104EFF2561 fixTakerDryOfferRemoval -C4483A1896170C66C098DEA5B0E024309C60DC960DE5F01CD7AF986AA3D9AD37 fixMasterKeyAsRegularKey -740352F2412A9909880C23A559FCECEDA3BE2126FED62FC7660D628A06927F11 Flow -07D43DCE529B15A10827E5E04943B496762F9A88E3268269D69C44BE49E21104 Escrow -6781F8368C4771B83E8B821D88F580202BCB4228075297B19E4FDC5233F1EFDC TrustSetAuth -30CD365592B8EE40489BA01AE2F7555CAC9C983145871DC82A42A31CF5BAE7D9 DeletableAccounts -F64E1EABBE79D55B3BB82020516CEC2C582A98A6BFE20FBE9BB6A0D233418064 DepositAuth -E2E6F2866106419B88C50045ACE96368558C345566AC8F2BDF5A5B5587F0E6FA fix1368 -6C92211186613F9647A89DFFBAB8F94C99D4C7E956D495270789128569177DA1 fix1512 -42EEA5E28A97824821D4EF97081FE36A54E9593C6E4F20CBAE098C69D2E072DC fix1373 -4C97EBA926031A7CF7D7B36FDE3ED66DDA5421192D63DE53FFB46E43B9DC8373 MultiSign -157D2D480E006395B76F948E3E07A45A05FE10230D88A7993C71F97AE4B1F2D1 Checks -32A122F1352A4C7B3A6D790362CC34749C5E57FCE896377BFDC6CCD14F6CD627 NonFungibleTokensV1_1 +NegativeUNL +fixRemoveNFTokenAutoTrustLine +NonFungibleTokensV1 +CheckCashMakesTrustLine +fixRmSmallIncreasedQOffers +fixSTAmountCanonicalize +FlowSortStrands +TicketBatch +fix1201 +fixQualityUpperBound +FlowCross +EnforceInvariants +fix1523 +HardenedValidations +DepositPreauth +MultiSignReserve +fix1623 +FeeEscalation +PayChan +fix1513 +RequireFullyCanonicalSig +fix1543 +TickSize +fix1781 +fixCheckThreading +fix1515 +CryptoConditions +fix1528 +fixPayChanRecipientOwnerDir +SortedDirectories +fix1578 +fix1571 +fixAmendmentMajorityCalc +fixTakerDryOfferRemoval +fixMasterKeyAsRegularKey +Flow +Escrow +TrustSetAuth +DeletableAccounts +DepositAuth +fix1368 +fix1512 +fix1373 +MultiSign +Checks +NonFungibleTokensV1_1 # 1.10.0 Amendments -47C3002ABA31628447E8E9A8B315FAA935CE30183F9A9B86845E469CA2CDC3DF DisallowIncoming -73761231F7F3D94EC3D8C63D91BDD0D89045C6F71B917D1925C01253515A6669 fixNonFungibleTokensV1_2 -F1ED6B4A411D8B872E65B9DCB4C8B100375B0DD3D62D07192E011D6D7F339013 fixTrustLinesToSelf -2E2FB9CF8A44EB80F4694D38AADAE9B8B7ADAFD2F092E10068E61C98C4F092B0 fixUniversalNumber -75A7E01C505DD5A179DFE3E000A9B6F1EDDEB55A12F95579A23E15B15DC8BE5A ImmediateOfferKilled -93E516234E35E08CA689FA33A6D38E103881F8DCB53023F728C307AA89D515A7 XRPFees +DisallowIncoming +fixNonFungibleTokensV1_2 +fixTrustLinesToSelf +fixUniversalNumber +ImmediateOfferKilled +XRPFees # 1.11.0 Amendments -B2A4DB846F0891BF2C76AB2F2ACC8F5B4EC64437135C6E56F3F859DE5FFD5856 ExpandedSignerList -# 1.12.0-b1 Amendments -56B241D7A43D40354D02A9DC4C8DF5C7A1F930D92A9035C4E12291B3CA3E1C2B featureClawback -27CD95EE8E1E5A537FF2F89B6CEB7C622E78E9374EBD7DCBEDFAE21CD6F16E0A fixReducedOffersV1 +ExpandedSignerList # 1.12.0 Amendments -8CC0774A3BF66D1D22E76BBDA8E8A232E6B6313834301B3B23E8601196AE6455 AMM -# Added August 9th, 2023 -AE35ABDEFBDE520372B31C957020B34A7A4A9DC3115A69803A44016477C84D6E fixNFTokenRemint \ No newline at end of file +AMM +Clawback +fixReducedOffersV1 +fixNFTokenRemint +# 2.0.0 Amendments +XChainBridge +DID \ No newline at end of file diff --git a/.github/workflows/dotnet.test.yml b/.github/workflows/dotnet.test.yml index 529466a3..f08eae98 100644 --- a/.github/workflows/dotnet.test.yml +++ b/.github/workflows/dotnet.test.yml @@ -3,6 +3,9 @@ name: .NET CI +env: + RIPPLED_DOCKER_IMAGE: rippleci/rippled:2.0.0-b4 + on: push: branches: [ main ] @@ -49,8 +52,8 @@ jobs: - uses: actions/checkout@v3 - name: Run docker in background - run: | - docker run --detach --rm --name rippled-service -p 6006:6006 --volume "${{ github.workspace }}/.ci-config/":"/config/" --health-cmd="wget localhost:6006 || exit 1" --health-interval=5s --health-retries=10 --health-timeout=2s --env "ENV_ARGS=-a --start" --env GITHUB_ACTIONS=true --env CI=true xrpllabsofficial/xrpld:1.12.0 + run: | + docker run --detach --rm --name rippled-service -p 6006:6006 --volume "${{ github.workspace }}/.ci-config/":"/opt/ripple/etc/" --health-cmd="wget localhost:6006 || exit 1" --health-interval=5s --health-retries=10 --health-timeout=2s --env GITHUB_ACTIONS=true --env CI=true ${{ env.RIPPLED_DOCKER_IMAGE }} /opt/ripple/bin/rippled -a --conf /opt/ripple/etc/rippled.cfg - name: Use .NET "${{ matrix.dotnet }}" uses: actions/setup-dotnet@v3 @@ -67,5 +70,5 @@ jobs: PORT: ${{ job.services.rippled.ports['6006'] }} - name: Stop docker container - if: always() - run: docker stop rippled-service \ No newline at end of file + if: always() + run: docker stop rippled-service \ No newline at end of file diff --git a/Base/Xrpl.BinaryCodec/Binary/BinarySerializer.cs b/Base/Xrpl.BinaryCodec/Binary/BinarySerializer.cs index 625bf1b8..2ccec0be 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 037903e4..5e5c3109 100644 --- a/Base/Xrpl.BinaryCodec/Binary/BytesList.cs +++ b/Base/Xrpl.BinaryCodec/Binary/BytesList.cs @@ -49,6 +49,15 @@ public byte[] Bytes() AddBytes(bytes, 0); return bytes; } + /// Get all bytes + /// 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/Types/EngineResult.cs b/Base/Xrpl.BinaryCodec/Enums/EngineResult.cs similarity index 86% rename from Base/Xrpl.BinaryCodec/Types/EngineResult.cs rename to Base/Xrpl.BinaryCodec/Enums/EngineResult.cs index 37b9e7eb..8a3dde12 100644 --- a/Base/Xrpl.BinaryCodec/Types/EngineResult.cs +++ b/Base/Xrpl.BinaryCodec/Enums/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/Enums/Field.cs b/Base/Xrpl.BinaryCodec/Enums/Field.cs index 27fc6d48..e17fe111 100644 --- a/Base/Xrpl.BinaryCodec/Enums/Field.cs +++ b/Base/Xrpl.BinaryCodec/Enums/Field.cs @@ -1,4 +1,5 @@ using System; +using Xrpl.BinaryCodec.Types; namespace Xrpl.BinaryCodec.Enums { @@ -85,6 +86,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 +141,9 @@ 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,11 +212,19 @@ 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 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 BlobField PublicKey = new BlobField(nameof(PublicKey), 1); public static readonly BlobField MessageKey = new BlobField(nameof(MessageKey), 2); @@ -247,6 +260,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 +271,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,8 +295,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 StArrayField Signers = new StArrayField(nameof(Signers), 3, isSigningField: false); + 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); public static readonly StArrayField Necessary = new StArrayField(nameof(Necessary), 6); @@ -288,11 +307,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 491f9e22..7c459284 100644 --- a/Base/Xrpl.BinaryCodec/Enums/FieldType.cs +++ b/Base/Xrpl.BinaryCodec/Enums/FieldType.cs @@ -1,4 +1,6 @@ -namespace Xrpl.BinaryCodec.Enums +using Xrpl.BinaryCodec.Types; + +namespace Xrpl.BinaryCodec.Enums { public class FieldType : EnumItem { @@ -24,6 +26,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 e62eb3cb..9b0cecbb 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 2fab4299..b5ddaaa0 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 00000000..6c0f98f1 --- /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/Types/LedgerEntryType.cs b/Base/Xrpl.BinaryCodec/Enums/LedgerEntryType.cs similarity index 98% rename from Base/Xrpl.BinaryCodec/Types/LedgerEntryType.cs rename to Base/Xrpl.BinaryCodec/Enums/LedgerEntryType.cs index 5ae599f3..834d80c9 100644 --- a/Base/Xrpl.BinaryCodec/Types/LedgerEntryType.cs +++ b/Base/Xrpl.BinaryCodec/Enums/LedgerEntryType.cs @@ -3,7 +3,7 @@ //todo not found doc -namespace Xrpl.BinaryCodec.Types +namespace Xrpl.BinaryCodec.Enums { public class LedgerEntryType : SerializedEnumItem { diff --git a/Base/Xrpl.BinaryCodec/Enums/SerializedEnumItem.cs b/Base/Xrpl.BinaryCodec/Enums/SerializedEnumItem.cs index e2c5f0d0..f3c30b92 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/Types/TransactionType.cs b/Base/Xrpl.BinaryCodec/Enums/TransactionType.cs similarity index 93% rename from Base/Xrpl.BinaryCodec/Types/TransactionType.cs rename to Base/Xrpl.BinaryCodec/Enums/TransactionType.cs index f9d1c6c1..25428afd 100644 --- a/Base/Xrpl.BinaryCodec/Types/TransactionType.cs +++ b/Base/Xrpl.BinaryCodec/Enums/TransactionType.cs @@ -1,6 +1,6 @@ using Xrpl.BinaryCodec.Enums; -namespace Xrpl.BinaryCodec.Types +namespace Xrpl.BinaryCodec.Enums { public class TransactionType : SerializedEnumItem { @@ -80,6 +80,13 @@ 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); + // ... /// /// This system-generated transaction type is used to update the status of the various amendments.
diff --git a/Base/Xrpl.BinaryCodec/Exceptions.cs b/Base/Xrpl.BinaryCodec/Exceptions.cs index 8f56aa03..f3c60c04 100644 --- a/Base/Xrpl.BinaryCodec/Exceptions.cs +++ b/Base/Xrpl.BinaryCodec/Exceptions.cs @@ -8,4 +8,25 @@ 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 1a3c0140..00000000 --- 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 b00b93b9..c57e461e 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 dcf42064..45dc1926 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/ISerializedType.cs b/Base/Xrpl.BinaryCodec/Types/ISerializedType.cs new file mode 100644 index 00000000..6b542db3 --- /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 f6cedf64..00000000 --- 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 00000000..fe0d47b9 --- /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/UnissuedAmount.cs b/Base/Xrpl.BinaryCodec/Types/UnissuedAmount.cs deleted file mode 100644 index cc907dd0..00000000 --- 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 00000000..1f3e8f3f --- /dev/null +++ b/Tests/Xrpl.Tests/Models/TestAMMBid.cs @@ -0,0 +1,165 @@ + + +// 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"); + 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" } + }} + } + }; + } + } +} + diff --git a/Tests/Xrpl.Tests/Models/TestAMMCreate.cs b/Tests/Xrpl.Tests/Models/TestAMMCreate.cs new file mode 100644 index 00000000..74105987 --- /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 00000000..56a0dfe0 --- /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 00000000..044afbc0 --- /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", 12u}, + {"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 00000000..cae73161 --- /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/Tests/Xrpl.Tests/Models/TestAccountSet.cs b/Tests/Xrpl.Tests/Models/TestAccountSet.cs index 46eb78ee..71ffeb41 100644 --- a/Tests/Xrpl.Tests/Models/TestAccountSet.cs +++ b/Tests/Xrpl.Tests/Models/TestAccountSet.cs @@ -3,10 +3,11 @@ // https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/test/models/accountSet.ts using Microsoft.VisualStudio.TestTools.UnitTesting; + using System.Collections.Generic; using System.Threading.Tasks; + using Xrpl.Client.Exceptions; -using Xrpl.Models.Transaction; using Xrpl.Models.Transactions; namespace XrplTests.Xrpl.Models diff --git a/Xrpl/Models/Common/Common.cs b/Xrpl/Models/Common/Common.cs deleted file mode 100644 index 6a783b8c..00000000 --- a/Xrpl/Models/Common/Common.cs +++ /dev/null @@ -1,60 +0,0 @@ -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; } - } - } -} - diff --git a/Xrpl/Models/Common/Currency.cs b/Xrpl/Models/Common/Index.cs similarity index 52% rename from Xrpl/Models/Common/Currency.cs rename to Xrpl/Models/Common/Index.cs index 11c2e053..b5a3e74a 100644 --- a/Xrpl/Models/Common/Currency.cs +++ b/Xrpl/Models/Common/Index.cs @@ -1,10 +1,13 @@ -using Newtonsoft.Json; - +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://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 { @@ -100,5 +103,133 @@ 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 + + } + + 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; } + } } } + diff --git a/Xrpl/Models/Common/LedgerIndex.cs b/Xrpl/Models/Common/LedgerIndex.cs deleted file mode 100644 index df8c34f4..00000000 --- a/Xrpl/Models/Common/LedgerIndex.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Xrpl.Models.Ledger; - -//https://xrpl.org/ledger-header.html#ledger-index -//https://github.com/XRPLF/xrpl.js/blob/76b73e16a97e1a371261b462ee1a24f1c01dbb0c/packages/xrpl/src/models/common/index.ts - - -namespace Xrpl.Models.Common -{ - - 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; } - } -} diff --git a/Xrpl/Models/Enums.cs b/Xrpl/Models/Enums.cs index 5f3d777d..74a6ab86 100644 --- a/Xrpl/Models/Enums.cs +++ b/Xrpl/Models/Enums.cs @@ -55,7 +55,33 @@ public enum TransactionType /// Set aside one or more sequence numbers as Tickets. TicketCreate, /// Add or modify a trust line. - TrustSet + TrustSet, + /// 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, @@ -129,7 +155,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/LOAccountRoot.cs b/Xrpl/Models/Ledger/LOAccountRoot.cs index 2797de2d..40e6113a 100644 --- a/Xrpl/Models/Ledger/LOAccountRoot.cs +++ b/Xrpl/Models/Ledger/LOAccountRoot.cs @@ -3,6 +3,7 @@ using System; using Xrpl.Client.Json.Converters; using Xrpl.Models.Common; +using Xrpl.Models.Methods; // https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/models/ledger/AccountRoot.ts diff --git a/Xrpl/Models/Ledger/LOAmm.cs b/Xrpl/Models/Ledger/LOAmm.cs new file mode 100644 index 00000000..afb010d6 --- /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 Common.Currency Asset { get; set; } + /// + /// Specifies the other pool asset of the AMM instance. + /// + [JsonConverter(typeof(CurrencyConverter))] + public Common.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 Common.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; } + /// + /// Keeps a track of up to eight active votes for the instance. + /// + 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 00000000..ba05d7a2 --- /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 00000000..ae573af2 --- /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 00000000..189dfb6c --- /dev/null +++ b/Xrpl/Models/Transactions/AMMBid.cs @@ -0,0 +1,149 @@ +#nullable enable +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 async Task 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 00000000..7597a9f1 --- /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 00000000..9b38bbac --- /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 00000000..d6349bff --- /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 00000000..db6387c1 --- /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 00000000..5a2318d4 --- /dev/null +++ b/Xrpl/Models/Transactions/AMMWithdraw.cs @@ -0,0 +1,203 @@ +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 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 + + /// + public Xrpl.Models.Common.Currency Asset { get; set; } + + /// + public Xrpl.Models.Common.Currency Asset2 { get; set; } + + /// + public Xrpl.Models.Common.Currency LPTokenIn { get; set; } + + /// + public Amount Amount { get; set; } + + /// + public Amount Amount2 { get; set; } + + /// + public Amount 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. + /// + Xrpl.Models.Common.Currency Asset { get; set; } + + /// + /// Specifies the other pool asset of the AMM instance. + /// + 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. + /// + Xrpl.Models.Common.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. + /// + Amount Amount { get; set; } + + /// + /// Specifies the other pool asset that the trader wants to remove. + /// + Amount Amount2 { get; set; } + + /// + /// Specifies the effective-price of the token out after successful execution of + /// the transaction. + /// + Amount 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 35f938f6..56e7d836 100644 --- a/Xrpl/Models/Transactions/Common.cs +++ b/Xrpl/Models/Transactions/Common.cs @@ -124,6 +124,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 6719d997..9849710b 100644 --- a/Xrpl/Models/Transactions/TxFormat.cs +++ b/Xrpl/Models/Transactions/TxFormat.cs @@ -92,13 +92,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.Types.TransactionType.Payment] = new TxFormat + [BinaryCodec.Enums.TransactionType.Payment] = new TxFormat { [Field.Destination] = Requirement.Required, [Field.Amount] = Requirement.Required, @@ -108,7 +108,7 @@ static TxFormat() [Field.DestinationTag] = Requirement.Optional, [Field.DeliverMin] = Requirement.Optional }, - [BinaryCodec.Types.TransactionType.EscrowCreate] = new TxFormat + [BinaryCodec.Enums.TransactionType.EscrowCreate] = new TxFormat { [Field.Amount] = Requirement.Required, [Field.Destination] = Requirement.Required, @@ -117,14 +117,14 @@ static TxFormat() [Field.FinishAfter] = Requirement.Optional, [Field.DestinationTag] = Requirement.Optional, }, - [BinaryCodec.Types.TransactionType.EscrowFinish] = new TxFormat + [BinaryCodec.Enums.TransactionType.EscrowFinish] = new TxFormat { [Field.Owner] = Requirement.Required, [Field.OfferSequence] = Requirement.Required, [Field.Condition] = Requirement.Optional, [Field.Fulfillment] = Requirement.Optional }, - [BinaryCodec.Types.TransactionType.AccountSet] = new TxFormat + [BinaryCodec.Enums.TransactionType.AccountSet] = new TxFormat { [Field.EmailHash] = Requirement.Optional, [Field.WalletLocator] = Requirement.Optional, @@ -136,40 +136,40 @@ static TxFormat() [Field.ClearFlag] = Requirement.Optional, [Field.TickSize] = Requirement.Optional }, - [BinaryCodec.Types.TransactionType.EscrowCancel] = new TxFormat + [BinaryCodec.Enums.TransactionType.EscrowCancel] = new TxFormat { [Field.Owner] = Requirement.Required, [Field.OfferSequence] = Requirement.Required }, - [BinaryCodec.Types.TransactionType.SetRegularKey] = new TxFormat + [BinaryCodec.Enums.TransactionType.SetRegularKey] = new TxFormat { [Field.RegularKey] = Requirement.Optional }, // 6 - [BinaryCodec.Types.TransactionType.OfferCreate] = new TxFormat + [BinaryCodec.Enums.TransactionType.OfferCreate] = new TxFormat { [Field.TakerPays] = Requirement.Required, [Field.TakerGets] = Requirement.Required, [Field.Expiration] = Requirement.Optional, [Field.OfferSequence] = Requirement.Optional }, - [BinaryCodec.Types.TransactionType.OfferCancel] = new TxFormat + [BinaryCodec.Enums.TransactionType.OfferCancel] = new TxFormat { [Field.OfferSequence] = Requirement.Required }, // 9 - [BinaryCodec.Types.TransactionType.TicketCreate] = new TxFormat + [BinaryCodec.Enums.TransactionType.TicketCreate] = new TxFormat { [Field.Target] = Requirement.Optional, [Field.Expiration] = Requirement.Optional }, // 11 - [BinaryCodec.Types.TransactionType.SignerListSet] = new TxFormat + [BinaryCodec.Enums.TransactionType.SignerListSet] = new TxFormat { [Field.SignerQuorum] = Requirement.Required, [Field.SignerEntries] = Requirement.Optional }, - [BinaryCodec.Types.TransactionType.PaymentChannelCreate] = new TxFormat() + [BinaryCodec.Enums.TransactionType.PaymentChannelCreate] = new TxFormat() { [Field.Destination] = Requirement.Required, [Field.Amount] = Requirement.Required, @@ -178,13 +178,13 @@ static TxFormat() [Field.CancelAfter] = Requirement.Optional, [Field.DestinationTag] = Requirement.Optional }, - [BinaryCodec.Types.TransactionType.PaymentChannelFund] = new TxFormat() + [BinaryCodec.Enums.TransactionType.PaymentChannelFund] = new TxFormat() { [Field.Channel] = Requirement.Required, [Field.Amount] = Requirement.Required, [Field.Expiration] = Requirement.Optional }, - [BinaryCodec.Types.TransactionType.PaymentChannelClaim] = new TxFormat() + [BinaryCodec.Enums.TransactionType.PaymentChannelClaim] = new TxFormat() { [Field.Channel] = Requirement.Required, [Field.Amount] = Requirement.Optional, @@ -192,7 +192,7 @@ static TxFormat() [Field.Signature] = Requirement.Optional, [Field.PublicKey] = Requirement.Optional }, - [BinaryCodec.Types.TransactionType.CheckCreate] = new TxFormat() + [BinaryCodec.Enums.TransactionType.CheckCreate] = new TxFormat() { [Field.Channel] = Requirement.Required, [Field.Amount] = Requirement.Optional, @@ -200,7 +200,7 @@ static TxFormat() [Field.Signature] = Requirement.Optional, [Field.PublicKey] = Requirement.Optional }, - [BinaryCodec.Types.TransactionType.CheckCash] = new TxFormat() + [BinaryCodec.Enums.TransactionType.CheckCash] = new TxFormat() { [Field.Channel] = Requirement.Required, [Field.Amount] = Requirement.Optional, @@ -208,7 +208,7 @@ static TxFormat() [Field.Signature] = Requirement.Optional, [Field.PublicKey] = Requirement.Optional }, - [BinaryCodec.Types.TransactionType.CheckCancel] = new TxFormat() + [BinaryCodec.Enums.TransactionType.CheckCancel] = new TxFormat() { [Field.Channel] = Requirement.Required, [Field.Amount] = Requirement.Optional, @@ -216,7 +216,7 @@ static TxFormat() [Field.Signature] = Requirement.Optional, [Field.PublicKey] = Requirement.Optional }, - [BinaryCodec.Types.TransactionType.DepositPreauth] = new TxFormat() + [BinaryCodec.Enums.TransactionType.DepositPreauth] = new TxFormat() { [Field.Channel] = Requirement.Required, [Field.Amount] = Requirement.Optional, @@ -224,30 +224,30 @@ static TxFormat() [Field.Signature] = Requirement.Optional, [Field.PublicKey] = Requirement.Optional }, - [BinaryCodec.Types.TransactionType.TrustSet] = new TxFormat + [BinaryCodec.Enums.TransactionType.TrustSet] = new TxFormat { [Field.LimitAmount] = Requirement.Optional, [Field.QualityIn] = Requirement.Optional, [Field.QualityOut] = Requirement.Optional }, - [BinaryCodec.Types.TransactionType.AccountDelete] = new TxFormat + [BinaryCodec.Enums.TransactionType.AccountDelete] = new TxFormat { [Field.Destination] = Requirement.Required, [Field.DestinationTag] = Requirement.Optional, }, - [BinaryCodec.Types.TransactionType.NFTokenMint] = new TxFormat + [BinaryCodec.Enums.TransactionType.NFTokenMint] = new TxFormat { [Field.NFTokenTaxon] = Requirement.Required, [Field.Issuer] = Requirement.Optional, [Field.TransferFee] = Requirement.Optional, [Field.URI] = Requirement.Optional }, - [BinaryCodec.Types.TransactionType.NFTokenBurn] = new TxFormat + [BinaryCodec.Enums.TransactionType.NFTokenBurn] = new TxFormat { [Field.NFTokenID] = Requirement.Required }, - [BinaryCodec.Types.TransactionType.NFTokenCreateOffer] = new TxFormat + [BinaryCodec.Enums.TransactionType.NFTokenCreateOffer] = new TxFormat { [Field.NFTokenID] = Requirement.Required, [Field.Amount] = Requirement.Required, @@ -255,15 +255,15 @@ static TxFormat() [Field.Destination] = Requirement.Optional, [Field.Expiration] = Requirement.Optional }, - [BinaryCodec.Types.TransactionType.NFTokenCancelOffer] = new TxFormat + [BinaryCodec.Enums.TransactionType.NFTokenCancelOffer] = new TxFormat { [Field.NFTokenOffers] = Requirement.Required }, - [BinaryCodec.Types.TransactionType.NFTokenAcceptOffer] = new TxFormat + [BinaryCodec.Enums.TransactionType.NFTokenAcceptOffer] = new TxFormat { [Field.NFTokenID] = Requirement.Required }, - [BinaryCodec.Types.TransactionType.UNLModify] = new TxFormat + [BinaryCodec.Enums.TransactionType.UNLModify] = new TxFormat { [Field.LedgerSequence] = Requirement.Optional, [Field.BaseFee] = Requirement.Required, diff --git a/Xrpl/Models/Transactions/Validation.cs b/Xrpl/Models/Transactions/Validation.cs index 81df0f24..1e84206b 100644 --- a/Xrpl/Models/Transactions/Validation.cs +++ b/Xrpl/Models/Transactions/Validation.cs @@ -1,14 +1,7 @@ -using Newtonsoft.Json; - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Collections.Generic; using System.Threading.Tasks; using Xrpl.Client.Exceptions; -using Xrpl.Client.Json.Converters; -using Xrpl.Models.Subscriptions; using Xrpl.Models.Utils; namespace Xrpl.Models.Transactions @@ -131,6 +124,24 @@ public static async Task Validate(Dictionary tx) case "TrustSet": await ValidateTrustSet(tx); break; + case "AMMBid": + await ValidateAMMBid(tx); + break; + case "AMMDeposit": + await ValidateAMMDeposit(tx); + break; + case "AMMCreate": + await ValidateAMMCreate(tx); + break; + case "AMMDelete": + await ValidateAMMDelete(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 741ab2b8..2ddcbf97 100644 --- a/Xrpl/Models/Utils/Flags.cs +++ b/Xrpl/Models/Utils/Flags.cs @@ -55,6 +55,8 @@ 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, @@ -67,6 +69,18 @@ public static void SetTransactionFlagsToNumber(Dictionary tx) } } + 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)