diff --git a/.vscode/settings.json b/.vscode/settings.json index 484b83a52..d33c0bbf0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,9 +12,11 @@ "altnet", "asyncio", "binarycodec", + "isnumeric", "nftoken", "rippletest", "ripplex", - "xaddress" + "xaddress", + "xchain" ], } diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ccbc2246..a3000ac2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,12 +11,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Created function alias to `safe_sign_and_autofill_transaction` called `autofill_and_sign` to reflect order of operations - Created function alias to `submit_transaction` called `submit` - Created function alias to `safe_sign_and_submit_transaction` called `sign_and_submit` +- Support for cross-chain bridge proposal ### Changed: - `check_fee` now has a higher limit that is less likely to be hit - When connected to nft devnet or hooks v2 testnet generate_faucet_wallet now defaults to using the faucet instead of requiring specification - Deprecated `get_account_info`, `get_transaction_from_hash`, `get_account_payment_transactions` for direct requests - Private function `request_impl` has been renamed to `_request_impl`. Users should always use `request` over `request_impl`. +- Support for cross-chain bridge proposal +- New AMM API signature changes ### Fixed: - Properly type the instance functions of NestedModel @@ -29,8 +32,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Function to parse the final account balances from a transaction's metadata - Function to parse order book changes from a transaction's metadata - Support for Ed25519 seeds that don't use the `sEd` prefix +- Support for cross-chain bridge proposal +- Support for Automated Market Maker (AMM) transactions and requests as defined in XLS-30. - Add docs to`get_account_transactions` explaining how to allow pagination through all transaction history [#462] - Common field `ticket_sequence` to Transaction class +- Support for cross-chain bridge proposal ### Fixed: - Typing for factory classmethods on models diff --git a/pyproject.toml b/pyproject.toml index ca3296886..a9c40685a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "xrpl-py" -version = "1.7.0" +version = "1.9.0-beta.0" description = "A complete Python library for interacting with the XRP ledger" readme = "README.md" repository = "https://github.com/XRPLF/xrpl-py" diff --git a/tests/unit/core/binarycodec/fixtures/data/codec-fixtures.json b/tests/unit/core/binarycodec/fixtures/data/codec-fixtures.json index be2e637a3..2fde3cea0 100644 --- a/tests/unit/core/binarycodec/fixtures/data/codec-fixtures.json +++ b/tests/unit/core/binarycodec/fixtures/data/codec-fixtures.json @@ -4435,20 +4435,351 @@ } } ], - "transactions": [{ - "binary": "1200002200000000240000003E6140000002540BE40068400000000000000A7321034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E74473045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F17962646398114550FC62003E785DC231A1058A05E56E3F09CF4E68314D4CC8AB5B21D86A82C3E9E8D0ECF2404B77FECBA", - "json": { - "Account": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", - "Destination": "rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj", - "TransactionType": "Payment", - "TxnSignature": "3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639", - "SigningPubKey": "034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E", - "Amount": "10000000000", - "Fee": "10", - "Flags": 0, - "Sequence": 62 + "transactions": [ + { + "binary": "1200002200000000240000003E6140000002540BE40068400000000000000A7321034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E74473045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F17962646398114550FC62003E785DC231A1058A05E56E3F09CF4E68314D4CC8AB5B21D86A82C3E9E8D0ECF2404B77FECBA", + "json": { + "Account": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "Destination": "rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj", + "TransactionType": "Payment", + "TxnSignature": "3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639", + "SigningPubKey": "034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E", + "Amount": "10000000000", + "Fee": "10", + "Flags": 0, + "Sequence": 62 + } + }, + { + "binary": "1200282200000000240000000168400000000000000A601D40000000000003E8601E400000000000271073210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020744630440220101BCA4B5B5A37C6F44480F9A34752C9AA8B2CDF5AD47E3CB424DEDC21C06DB702206EEB257E82A89B1F46A0A2C7F070B0BD181D980FF86FE4269E369F6FC7A270918114B5F762798A53D543A014CAF8B297CFF8F2F937E8011914AF80285F637EE4AF3C20378F9DFB12511ACB8D27000000000000000000000000000000000000000014550FC62003E785DC231A1058A05E56E3F09CF4E60000000000000000000000000000000000000000", + "json": { + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "XChainBridge": { + "LockingChainDoor": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "LockingChainIssue": {"currency": "XRP"}, + "IssuingChainDoor": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "IssuingChainIssue": {"currency": "XRP"} + }, + "Fee": "10", + "Flags": 0, + "MinAccountCreateAmount": "10000", + "Sequence": 1, + "SignatureReward": "1000", + "SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020", + "TransactionType": "XChainCreateBridge", + "TxnSignature": "30440220101BCA4B5B5A37C6F44480F9A34752C9AA8B2CDF5AD47E3CB424DEDC21C06DB702206EEB257E82A89B1F46A0A2C7F070B0BD181D980FF86FE4269E369F6FC7A27091" + } + }, + { + "binary": "12002F2200000000240000000168400000000000000A601D40000000000003E8601E400000000000271073210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100D2CABC1B0E0635A8EE2E6554F6D474C49BC292C995C5C9F83179F4A60634B04C02205D1DB569D9593136F2FBEA7140010C8F46794D653AFDBEA8D30B8750BA4805E58114B5F762798A53D543A014CAF8B297CFF8F2F937E8011914AF80285F637EE4AF3C20378F9DFB12511ACB8D27000000000000000000000000000000000000000014550FC62003E785DC231A1058A05E56E3F09CF4E60000000000000000000000000000000000000000", + "json": { + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "XChainBridge": { + "LockingChainDoor": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "LockingChainIssue": {"currency": "XRP"}, + "IssuingChainDoor": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "IssuingChainIssue": {"currency": "XRP"} + }, + "Fee": "10", + "Flags": 0, + "MinAccountCreateAmount": "10000", + "Sequence": 1, + "SignatureReward": "1000", + "SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020", + "TransactionType": "XChainModifyBridge", + "TxnSignature": "3045022100D2CABC1B0E0635A8EE2E6554F6D474C49BC292C995C5C9F83179F4A60634B04C02205D1DB569D9593136F2FBEA7140010C8F46794D653AFDBEA8D30B8750BA4805E5" + } + }, + { + "binary": "1200292280000000240000000168400000000000000A601D400000000000271073210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020744630440220247B20A1B9C48E21A374CB9B3E1FE2A7C528151868DF8D307E9FBE15237E531A02207C20C092DDCC525E583EF4AB7CB91E862A6DED19426997D3F0A2C84E2BE8C5DD8114B5F762798A53D543A014CAF8B297CFF8F2F937E8801214AF80285F637EE4AF3C20378F9DFB12511ACB8D27011914AF80285F637EE4AF3C20378F9DFB12511ACB8D27000000000000000000000000000000000000000014550FC62003E785DC231A1058A05E56E3F09CF4E60000000000000000000000000000000000000000", + "json": { + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "XChainBridge": { + "LockingChainDoor": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "LockingChainIssue": {"currency": "XRP"}, + "IssuingChainDoor": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "IssuingChainIssue": {"currency": "XRP"} + }, + "Fee": "10", + "Flags": 2147483648, + "OtherChainSource": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "Sequence": 1, + "SignatureReward": "10000", + "SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020", + "TransactionType": "XChainCreateClaimID", + "TxnSignature": "30440220247B20A1B9C48E21A374CB9B3E1FE2A7C528151868DF8D307E9FBE15237E531A02207C20C092DDCC525E583EF4AB7CB91E862A6DED19426997D3F0A2C84E2BE8C5DD" + } + }, + { + "binary": "12002A228000000024000000013014000000000000000161400000000000271068400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074453043021F177323F0D93612C82A4393A99B23905A7E675753FD80C52997AFAB13F5F9D002203BFFAF457E90BDA65AABE8F8762BD96162FAD98A0C030CCD69B06EE9B12BBFFE8114B5F762798A53D543A014CAF8B297CFF8F2F937E8011914AF80285F637EE4AF3C20378F9DFB12511ACB8D27000000000000000000000000000000000000000014550FC62003E785DC231A1058A05E56E3F09CF4E60000000000000000000000000000000000000000", + "json": { + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "Amount": "10000", + "XChainBridge": { + "LockingChainDoor": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "LockingChainIssue": {"currency": "XRP"}, + "IssuingChainDoor": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "IssuingChainIssue": {"currency": "XRP"} + }, + "Fee": "10", + "Flags": 2147483648, + "Sequence": 1, + "SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020", + "TransactionType": "XChainCommit", + "TxnSignature": "3043021F177323F0D93612C82A4393A99B23905A7E675753FD80C52997AFAB13F5F9D002203BFFAF457E90BDA65AABE8F8762BD96162FAD98A0C030CCD69B06EE9B12BBFFE", + "XChainClaimID": "0000000000000001" + } + }, + { + "binary": "12002B228000000024000000013014000000000000000161400000000000271068400000000000000A73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020744630440220445F7469FDA401787D9EE8A9B6E24DFF81E94F4C09FD311D2C0A58FCC02C684A022029E2EF34A5EA35F50D5BB57AC6320AD3AE12C13C8D1379B255A486D72CED142E8114B5F762798A53D543A014CAF8B297CFF8F2F937E88314550FC62003E785DC231A1058A05E56E3F09CF4E6011914AF80285F637EE4AF3C20378F9DFB12511ACB8D27000000000000000000000000000000000000000014550FC62003E785DC231A1058A05E56E3F09CF4E60000000000000000000000000000000000000000", + "json": { + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "Amount": "10000", + "XChainBridge": { + "LockingChainDoor": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "LockingChainIssue": {"currency": "XRP"}, + "IssuingChainDoor": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "IssuingChainIssue": {"currency": "XRP"} + }, + "Destination": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "Fee": "10", + "Flags": 2147483648, + "Sequence": 1, + "SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020", + "TransactionType": "XChainClaim", + "TxnSignature": "30440220445F7469FDA401787D9EE8A9B6E24DFF81E94F4C09FD311D2C0A58FCC02C684A022029E2EF34A5EA35F50D5BB57AC6320AD3AE12C13C8D1379B255A486D72CED142E", + "XChainClaimID": "0000000000000001" + } + }, + { + "binary": "12002C228000000024000000016140000000000F424068400000000000000A601D400000000000271073210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD0207446304402202984DDE7F0B566F081F7953D7212BF031ACBF8860FE114102E9512C4C8768C77022070113F4630B1DC3045E4A98DDD648CEBC31B12774F7B44A1B8123CD2C9F5CF188114B5F762798A53D543A014CAF8B297CFF8F2F937E88314AF80285F637EE4AF3C20378F9DFB12511ACB8D27011914AF80285F637EE4AF3C20378F9DFB12511ACB8D27000000000000000000000000000000000000000014550FC62003E785DC231A1058A05E56E3F09CF4E60000000000000000000000000000000000000000", + "json": { + "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "XChainBridge": { + "LockingChainDoor": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "LockingChainIssue": {"currency": "XRP"}, + "IssuingChainDoor": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "IssuingChainIssue": {"currency": "XRP"} + }, + "Amount": "1000000", + "Fee": "10", + "Flags": 2147483648, + "Destination": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "Sequence": 1, + "SignatureReward": "10000", + "SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020", + "TransactionType": "XChainAccountCreateCommit", + "TxnSignature": "304402202984DDE7F0B566F081F7953D7212BF031ACBF8860FE114102E9512C4C8768C77022070113F4630B1DC3045E4A98DDD648CEBC31B12774F7B44A1B8123CD2C9F5CF18" + } + }, + { + "binary": "12002315000A220000000024000000026140000000000027106840000000000000016BD5838D7EA4C680000000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C7321ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D07440913E39EC2BA0E5BC4C5DF1222B1AE9E76758F2B8FFEF1F056076147BB0ADC8117CD0296360DA08B3D48BE9EFC8693C03A253E0D9F166C19CA8D936F9E61A1100811462D4D845D20B4F09CFEA8BB4C01063D99FC9673E", + "json": { + "Account": "rwr2UWxNwoBdysPSiDDraTQjAQKZEeZAcV", + "TransactionType": "AMMCreate", + "TxnSignature": "913E39EC2BA0E5BC4C5DF1222B1AE9E76758F2B8FFEF1F056076147BB0ADC8117CD0296360DA08B3D48BE9EFC8693C03A253E0D9F166C19CA8D936F9E61A1100", + "Amount": "10000", + "Amount2": { + "currency": "ETH", + "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9", + "value": "10000" + }, + "TradingFee": 10, + "Fee": "1", + "Flags": 0, + "Sequence": 2, + "SigningPubKey": "ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D0" + } + }, + { + "binary": "120024220001000024000000026840000000000000016014D5438D7EA4C68000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D074409EEE8CF88C668B955E7EEAB1B4A1B059EDF4F51B7F1546810F87E3E48B09237F015C651E37FB40A979E00EA21361D4E18D7A33DB7DD23070CEEAB2648AB3BB0D811462D4D845D20B4F09CFEA8BB4C01063D99FC9673E0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rwr2UWxNwoBdysPSiDDraTQjAQKZEeZAcV", + "TransactionType": "AMMDeposit", + "TxnSignature": "9EEE8CF88C668B955E7EEAB1B4A1B059EDF4F51B7F1546810F87E3E48B09237F015C651E37FB40A979E00EA21361D4E18D7A33DB7DD23070CEEAB2648AB3BB0D", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "LPTokenOut": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"}, + "Fee": "1", + "Flags": 65536, + "Sequence": 2, + "SigningPubKey": "ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D0" + } + }, + { + "binary": "120024220008000024000000026140000000000003E86840000000000000017321ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D07440BD18A6E2B10B451F61CFADC32B59A0243702DC5DAAE556D51CB9C79981D40C78101FFA9DE6163CFBDF6E7578DF02F2AE3B8A5AB60697E0746D65064D91E8F90A811462D4D845D20B4F09CFEA8BB4C01063D99FC9673E0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rwr2UWxNwoBdysPSiDDraTQjAQKZEeZAcV", + "TransactionType": "AMMDeposit", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Amount": "1000", + "Fee": "1", + "Flags": 524288, + "Sequence": 2, + "SigningPubKey": "ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D0", + "TxnSignature": "BD18A6E2B10B451F61CFADC32B59A0243702DC5DAAE556D51CB9C79981D40C78101FFA9DE6163CFBDF6E7578DF02F2AE3B8A5AB60697E0746D65064D91E8F90A" + } + }, + { + "binary": "120024220010000024000000026140000000000003E86840000000000000016BD511C37937E080000000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C7321ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D07440E0B1AE32A0F731BF0CEF0D019295BD7F35B22F11A5962F65FA99EE4D38993B14B53DB11C15E36D756E282812E9015D38A6F225940A157693F43F9B795C59950F811462D4D845D20B4F09CFEA8BB4C01063D99FC9673E0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rwr2UWxNwoBdysPSiDDraTQjAQKZEeZAcV", + "TransactionType": "AMMDeposit", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Amount": "1000", + "Amount2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9", "value": "500"}, + "Fee": "1", + "Flags": 1048576, + "Sequence": 2, + "SigningPubKey": "ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D0", + "TxnSignature": "E0B1AE32A0F731BF0CEF0D019295BD7F35B22F11A5962F65FA99EE4D38993B14B53DB11C15E36D756E282812E9015D38A6F225940A157693F43F9B795C59950F" + } + }, + { + "binary": "120024220020000024000000026140000000000003E86840000000000000016014D5438D7EA4C68000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D07440452BC59F9EE12C224EC983EFDF580F20C4A50E897105FD1FB13520D9753CFB02BD210599181574DF6AD0DB6A42C1EA48D9E48FC3D11B9008E4C76FBB163D5B00811462D4D845D20B4F09CFEA8BB4C01063D99FC9673E0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rwr2UWxNwoBdysPSiDDraTQjAQKZEeZAcV", + "TransactionType": "AMMDeposit", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Amount": "1000", + "LPTokenOut": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"}, + "Fee": "1", + "Flags": 2097152, + "Sequence": 2, + "SigningPubKey": "ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D0", + "TxnSignature": "452BC59F9EE12C224EC983EFDF580F20C4A50E897105FD1FB13520D9753CFB02BD210599181574DF6AD0DB6A42C1EA48D9E48FC3D11B9008E4C76FBB163D5B00" + } + }, + { + "binary": "120024220040000024000000026140000000000003E8684000000000000001601640000000000000197321ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D07440DD6685DC586FAA6AD2D50D785900122EB147D4AC09A55D7080267A9B38180F87CEC44B823359FC3F0AC0104D47B53FFC6B80415664C3C4582672420A0100F70C811462D4D845D20B4F09CFEA8BB4C01063D99FC9673E0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rwr2UWxNwoBdysPSiDDraTQjAQKZEeZAcV", + "TransactionType": "AMMDeposit", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Amount": "1000", + "EPrice": "25", + "Fee": "1", + "Flags": 4194304, + "Sequence": 2, + "SigningPubKey": "ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D0", + "TxnSignature": "DD6685DC586FAA6AD2D50D785900122EB147D4AC09A55D7080267A9B38180F87CEC44B823359FC3F0AC0104D47B53FFC6B80415664C3C4582672420A0100F70C" + } + }, + { + "binary": "120025220001000024000000026840000000000000016015D5438D7EA4C68000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D0744066944797E9F03808C9A00AAEFF786AD74FEB2E64B51A9601E89ABA820AAA15927C2E961A9CCA22C4B0D2A2B55E342BD6E297BD765B6F4D3FDCA578A3416BB505811462D4D845D20B4F09CFEA8BB4C01063D99FC9673E0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rwr2UWxNwoBdysPSiDDraTQjAQKZEeZAcV", + "TransactionType": "AMMWithdraw", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "LPTokenIn": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"}, + "Fee": "1", + "Flags": 65536, + "Sequence": 2, + "SigningPubKey": "ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D0", + "TxnSignature": "66944797E9F03808C9A00AAEFF786AD74FEB2E64B51A9601E89ABA820AAA15927C2E961A9CCA22C4B0D2A2B55E342BD6E297BD765B6F4D3FDCA578A3416BB505" + } + }, + { + "binary": "120025220008000024000000026140000000000003E86840000000000000017321ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D07440E30397CE7E99B13D35FFB5C66725B17F4F103675E10293C7B1D63C1BE3FA81B884BD3FBD31B52F6B811F99C5FBB5102D170EC379C268DF80DABF04E7F2DD4F0C811462D4D845D20B4F09CFEA8BB4C01063D99FC9673E0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rwr2UWxNwoBdysPSiDDraTQjAQKZEeZAcV", + "TransactionType": "AMMWithdraw", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Amount": "1000", + "Fee": "1", + "Flags": 524288, + "Sequence": 2, + "SigningPubKey": "ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D0", + "TxnSignature": "E30397CE7E99B13D35FFB5C66725B17F4F103675E10293C7B1D63C1BE3FA81B884BD3FBD31B52F6B811F99C5FBB5102D170EC379C268DF80DABF04E7F2DD4F0C" + } + }, + { + "binary": "120025220010000024000000026140000000000003E86840000000000000016BD511C37937E080000000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C7321ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D07440C0818312B269A4EF16C1C7EBBB74EFD1852A288BB214A714B8BE3B5F4B2F9CFDFF4F66C931B8434244A8016035B9EC9493B7CF5E0ACF4570A88DF808D79E4300811462D4D845D20B4F09CFEA8BB4C01063D99FC9673E0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rwr2UWxNwoBdysPSiDDraTQjAQKZEeZAcV", + "TransactionType": "AMMWithdraw", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Amount": "1000", + "Amount2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9", "value": "500"}, + "Fee": "1", + "Flags": 1048576, + "Sequence": 2, + "SigningPubKey": "ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D0", + "TxnSignature": "C0818312B269A4EF16C1C7EBBB74EFD1852A288BB214A714B8BE3B5F4B2F9CFDFF4F66C931B8434244A8016035B9EC9493B7CF5E0ACF4570A88DF808D79E4300" + } + }, + { + "binary": "120025220020000024000000026140000000000003E86840000000000000016015D5438D7EA4C68000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D0744073552B3DC7AE99DDF4E4FF0D60E6D0BE4688E3474D363603FA25DA6AD8BBA8F0E4E3EA82ADB2B57F5B9A6C379969E00095546DDA0E74FF3D0F0689351C2F8C06811462D4D845D20B4F09CFEA8BB4C01063D99FC9673E0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rwr2UWxNwoBdysPSiDDraTQjAQKZEeZAcV", + "TransactionType": "AMMWithdraw", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Amount": "1000", + "LPTokenIn": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "1000"}, + "Fee": "1", + "Flags": 2097152, + "Sequence": 2, + "SigningPubKey": "ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D0", + "TxnSignature": "73552B3DC7AE99DDF4E4FF0D60E6D0BE4688E3474D363603FA25DA6AD8BBA8F0E4E3EA82ADB2B57F5B9A6C379969E00095546DDA0E74FF3D0F0689351C2F8C06" + } + }, + { + "binary": "120025220040000024000000026140000000000003E8684000000000000001601640000000000000197321ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D0744023BAFE5BFE58E7BF0B02B5875983D007C10796C8E62A190BF688EBE5D8A104DAD2DE7EDE995FE2E494883FD8140F38E22E3376A2F49C50EFCAA00C7499A4690E811462D4D845D20B4F09CFEA8BB4C01063D99FC9673E0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rwr2UWxNwoBdysPSiDDraTQjAQKZEeZAcV", + "TransactionType": "AMMWithdraw", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "Amount": "1000", + "EPrice": "25", + "Fee": "1", + "Flags": 4194304, + "Sequence": 2, + "SigningPubKey": "ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D0", + "TxnSignature": "23BAFE5BFE58E7BF0B02B5875983D007C10796C8E62A190BF688EBE5D8A104DAD2DE7EDE995FE2E494883FD8140F38E22E3376A2F49C50EFCAA00C7499A4690E" + } + }, + { + "binary": "120027220000000024000000026840000000000000016CD4C8E1BC9BF04000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0486DD4CC6F3B40B6C000B3813FCAB4EE68B3D0D735D6849465A9113EE048B3813FCAB4EE68B3D0D735D6849465A9113EE0487321ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D074406B2A1548E6DC14681356C27CCBE7072CAB2AD8C72D0D7A045916FB0E0DBE6BF71A429CC519E9200172829D3EEF79100899D3A8710C1C3C1A2B664FD64086AD0A811462D4D845D20B4F09CFEA8BB4C01063D99FC9673EF01AE01C81149A91957F8F16BC57F3F200CD8C98375BF1791586E1F10318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rwr2UWxNwoBdysPSiDDraTQjAQKZEeZAcV", + "TransactionType": "AMMBid", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "AuthAccounts": [{"AuthAccount": {"Account": "rEaHTti4HZsMBpxTAF4ncWxkcdqDh1h6P7"}}], + "BidMax": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "35"}, + "BidMin": {"currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", "value": "25"}, + "Fee": "1", + "Flags": 0, + "Sequence": 2, + "SigningPubKey": "ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D0", + "TxnSignature": "6B2A1548E6DC14681356C27CCBE7072CAB2AD8C72D0D7A045916FB0E0DBE6BF71A429CC519E9200172829D3EEF79100899D3A8710C1C3C1A2B664FD64086AD0A" + } + }, + { + "binary": "1200261500EA220000000024000000026840000000000000017321ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D0744072767CF9A0F5E9C9DA6BBB6E84905B0ECDF122D3E2D730843EFD377521E8E73664AD809D0A54E8C75CD1735ACB64E310BB49FDED10913FA150B8C006D4ACEC00811462D4D845D20B4F09CFEA8BB4C01063D99FC9673E0318000000000000000000000000000000000000000004180000000000000000000000004554480000000000FBEF9A3A2B814E807745FA3D9C32FFD155FA2E8C", + "json": { + "Account": "rwr2UWxNwoBdysPSiDDraTQjAQKZEeZAcV", + "TransactionType": "AMMVote", + "Asset": {"currency": "XRP"}, + "Asset2": {"currency": "ETH", "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9"}, + "TradingFee": 234, + "Fee": "1", + "Flags": 0, + "Sequence": 2, + "SigningPubKey": "ED8A00C1D29E762266576408B08D583B987673550655F930635678B436D5CDF7D0", + "TxnSignature": "72767CF9A0F5E9C9DA6BBB6E84905B0ECDF122D3E2D730843EFD377521E8E73664AD809D0A54E8C75CD1735ACB64E310BB49FDED10913FA150B8C006D4ACEC00" + } } - }], + ], "ledgerData": [{ "binary": "01E91435016340767BF1C4A3EACEB081770D8ADE216C85445DD6FB002C6B5A2930F2DECE006DA18150CB18F6DD33F6F0990754C962A7CCE62F332FF9C13939B03B864117F0BDA86B6E9B4F873B5C3E520634D343EF5D9D9A4246643D64DAD278BA95DC0EAC6EB5350CF970D521276CDE21276CE60A00", "json": { diff --git a/tests/unit/core/binarycodec/test_main.py b/tests/unit/core/binarycodec/test_main.py index 0e345d6b3..33ac55048 100644 --- a/tests/unit/core/binarycodec/test_main.py +++ b/tests/unit/core/binarycodec/test_main.py @@ -331,14 +331,14 @@ def test_xaddress_xaddr_and_matching_source_tag(self): class TestMainFixtures(TestCase): - maxDiff = 1000 + maxDiff = None def _check_binary_and_json(self, test): test_binary = test["binary"] test_json = test["json"] with self.subTest(test_binary=test_binary, test_json=test_json): - self.assertEqual(encode(test_json), test_binary) self.assertEqual(decode(test_binary), test_json) + self.assertEqual(encode(test_json), test_binary) def _check_xaddress_jsons(self, test): x_json = test["xjson"] @@ -390,14 +390,32 @@ def test_single_signing(self): ) self.assertEqual(encode_for_signing(signing_json), expected) - def test_claim(self): + def test_native_claim(self): channel = "43904CBFCDCEC530B4037871F86EE90BF799DF8D2E0EA564BC8A3F332E4F5FB1" amount = "1000" json = {"amount": amount, "channel": channel} expected = ( - "434C4D0043904CBFCDCEC530B4037871F86EE90BF799DF8D2E0EA564BC8A3F332E" - "4F5FB100000000000003E8" + "434C4D00" + "43904CBFCDCEC530B4037871F86EE90BF799DF8D2E0EA564BC8A3F332E4F5FB1" + "00000000000003E8" + ) + self.assertEqual(encode_for_signing_claim(json), expected) + + def test_ic_claim(self): + channel = "43904CBFCDCEC530B4037871F86EE90BF799DF8D2E0EA564BC8A3F332E4F5FB1" + amount = { + "issuer": "rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN", + "currency": "USD", + "value": "10", + } + json = {"amount": amount, "channel": channel} + + expected = ( + "434C4D00" + "43904CBFCDCEC530B4037871F86EE90BF799DF8D2E0EA564BC8A3F332E4F5FB1" + "D4C38D7EA4C680000000000000000000000000005553440000000000C0A5ABEF" + "242802EFED4B041E8F2D4A8CC86AE3D1" ) self.assertEqual(encode_for_signing_claim(json), expected) diff --git a/tests/unit/models/requests/test_amm_info.py b/tests/unit/models/requests/test_amm_info.py new file mode 100644 index 000000000..0f21394e2 --- /dev/null +++ b/tests/unit/models/requests/test_amm_info.py @@ -0,0 +1,16 @@ +from unittest import TestCase + +from xrpl.models.currencies.issue import Issue +from xrpl.models.requests import AMMInfo + +_ASSET = Issue(currency="XRP") +_ASSET_2 = Issue(currency="USD", issuer="rN6zcSynkRnf8zcgTVrRL8K7r4ovE7J4Zj") + + +class TestAMMInfo(TestCase): + def test_asset_asset2(self): + request = AMMInfo( + asset=_ASSET, + asset2=_ASSET_2, + ) + self.assertTrue(request.is_valid()) diff --git a/tests/unit/models/test_base_model.py b/tests/unit/models/test_base_model.py index ae5c9bff1..21e66a0cf 100644 --- a/tests/unit/models/test_base_model.py +++ b/tests/unit/models/test_base_model.py @@ -4,6 +4,7 @@ from xrpl.models import XRPLModelException from xrpl.models.amounts import IssuedCurrencyAmount +from xrpl.models.currencies import XRP, Issue from xrpl.models.requests import ( AccountChannels, BookOffers, @@ -16,6 +17,12 @@ SubmitOnly, ) from xrpl.models.transactions import ( + AMMBid, + AMMCreate, + AMMDeposit, + AMMVote, + AMMWithdraw, + AuthAccount, CheckCreate, Memo, Payment, @@ -24,8 +31,10 @@ SignerListSet, TrustSet, TrustSetFlag, + XChainClaim, ) from xrpl.models.transactions.transaction import Transaction +from xrpl.models.xchain_bridge import XChainBridge currency = "BTC" value = "100" @@ -597,3 +606,422 @@ def test_to_xrpl_signer(self): ], } self.assertEqual(tx.to_xrpl(), expected) + + def test_to_from_xrpl_xchain(self): + tx_json = { + "Account": account, + "Amount": value, + "XChainBridge": { + "LockingChainDoor": "rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + "LockingChainIssue": {"currency": "XRP"}, + "IssuingChainDoor": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + "IssuingChainIssue": {"currency": "XRP"}, + }, + "Destination": destination, + "TransactionType": "XChainClaim", + "Flags": 0, + "SigningPubKey": "", + "XChainClaimID": 1, + } + tx_obj = XChainClaim( + account=account, + amount=value, + xchain_bridge=XChainBridge( + locking_chain_door="rGzx83BVoqTYbGn7tiVAnFw7cbxjin13jL", + locking_chain_issue=XRP(), + issuing_chain_door="r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", + issuing_chain_issue=XRP(), + ), + destination=destination, + xchain_claim_id=1, + ) + self.assertEqual(tx_obj.to_xrpl(), tx_json) + self.assertEqual(Transaction.from_xrpl(tx_json), tx_obj) + + def test_to_xrpl_amm_create(self): + tx = AMMCreate( + account="r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + amount="1000", + amount2=IssuedCurrencyAmount( + currency="USD", + issuer="rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9", + value="1000", + ), + trading_fee=12, + ) + expected = { + "Account": "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + "Amount": "1000", + "Amount2": { + "currency": "USD", + "issuer": "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9", + "value": "1000", + }, + "TransactionType": "AMMCreate", + "SigningPubKey": "", + "TradingFee": 12, + "Flags": 0, + } + self.assertEqual(tx.to_xrpl(), expected) + + def test_to_xrpl_amm_deposit_lptoken(self): + tx = AMMDeposit( + account="r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + sequence=1337, + asset=Issue(currency="XRP"), + asset2=Issue(currency="ETH", issuer="rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW"), + lp_token_out=IssuedCurrencyAmount( + currency="B3813FCAB4EE68B3D0D735D6849465A9113EE048", + issuer="rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", + value="1000", + ), + ) + expected = { + "Asset": {"currency": "XRP"}, + "Asset2": { + "currency": "ETH", + "issuer": "rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW", + }, + "Account": "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + "LPTokenOut": { + "currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", + "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", + "value": "1000", + }, + "TransactionType": "AMMDeposit", + "Sequence": 1337, + "SigningPubKey": "", + "Flags": 0, + } + self.assertEqual(tx.to_xrpl(), expected) + + def test_to_xrpl_amm_deposit_amount(self): + tx = AMMDeposit( + account="r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + sequence=1337, + asset=Issue(currency="XRP"), + asset2=Issue(currency="ETH", issuer="rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW"), + amount="1000", + ) + expected = { + "Asset": {"currency": "XRP"}, + "Asset2": { + "currency": "ETH", + "issuer": "rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW", + }, + "Account": "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + "Amount": "1000", + "TransactionType": "AMMDeposit", + "Sequence": 1337, + "SigningPubKey": "", + "Flags": 0, + } + self.assertEqual(tx.to_xrpl(), expected) + + def test_to_xrpl_amm_deposit_amount_amount2(self): + tx = AMMDeposit( + account="r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + sequence=1337, + asset=Issue(currency="XRP"), + asset2=Issue(currency="ETH", issuer="rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW"), + amount="1000", + amount2="500", + ) + expected = { + "Asset": {"currency": "XRP"}, + "Asset2": { + "currency": "ETH", + "issuer": "rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW", + }, + "Account": "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + "Amount": "1000", + "Amount2": "500", + "TransactionType": "AMMDeposit", + "Sequence": 1337, + "SigningPubKey": "", + "Flags": 0, + } + self.assertEqual(tx.to_xrpl(), expected) + + def test_to_xrpl_amm_deposit_amount_lptoken(self): + tx = AMMDeposit( + account="r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + sequence=1337, + asset=Issue(currency="XRP"), + asset2=Issue(currency="ETH", issuer="rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW"), + amount="1000", + lp_token_out=IssuedCurrencyAmount( + currency="B3813FCAB4EE68B3D0D735D6849465A9113EE048", + issuer="rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", + value="500", + ), + ) + expected = { + "Asset": {"currency": "XRP"}, + "Asset2": { + "currency": "ETH", + "issuer": "rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW", + }, + "Account": "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + "Amount": "1000", + "LPTokenOut": { + "currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", + "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", + "value": "500", + }, + "TransactionType": "AMMDeposit", + "Sequence": 1337, + "SigningPubKey": "", + "Flags": 0, + } + self.assertEqual(tx.to_xrpl(), expected) + + def test_to_xrpl_amm_deposit_amount_eprice(self): + tx = AMMDeposit( + account="r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + sequence=1337, + asset=Issue(currency="XRP"), + asset2=Issue(currency="ETH", issuer="rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW"), + amount="1000", + e_price="25", + ) + expected = { + "Asset": {"currency": "XRP"}, + "Asset2": { + "currency": "ETH", + "issuer": "rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW", + }, + "Account": "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + "Amount": "1000", + "EPrice": "25", + "TransactionType": "AMMDeposit", + "Sequence": 1337, + "SigningPubKey": "", + "Flags": 0, + } + self.assertEqual(tx.to_xrpl(), expected) + + def test_to_xrpl_amm_withdraw_lptoken(self): + tx = AMMWithdraw( + account="r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + sequence=1337, + asset=Issue(currency="XRP"), + asset2=Issue(currency="ETH", issuer="rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW"), + lp_token_in=IssuedCurrencyAmount( + currency="B3813FCAB4EE68B3D0D735D6849465A9113EE048", + issuer="rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", + value="1000", + ), + ) + expected = { + "Asset": {"currency": "XRP"}, + "Asset2": { + "currency": "ETH", + "issuer": "rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW", + }, + "Account": "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + "LPTokenIn": { + "currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", + "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", + "value": "1000", + }, + "TransactionType": "AMMWithdraw", + "Sequence": 1337, + "SigningPubKey": "", + "Flags": 0, + } + self.assertEqual(tx.to_xrpl(), expected) + + def test_to_xrpl_amm_withdraw_amount(self): + tx = AMMWithdraw( + account="r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + sequence=1337, + asset=Issue(currency="XRP"), + asset2=Issue(currency="ETH", issuer="rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW"), + amount="1000", + ) + expected = { + "Asset": {"currency": "XRP"}, + "Asset2": { + "currency": "ETH", + "issuer": "rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW", + }, + "Account": "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + "Amount": "1000", + "TransactionType": "AMMWithdraw", + "Sequence": 1337, + "SigningPubKey": "", + "Flags": 0, + } + self.assertEqual(tx.to_xrpl(), expected) + + def test_to_xrpl_amm_withdraw_amount_amount2(self): + tx = AMMWithdraw( + account="r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + sequence=1337, + asset=Issue(currency="XRP"), + asset2=Issue(currency="ETH", issuer="rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW"), + amount="1000", + amount2="500", + ) + expected = { + "Asset": {"currency": "XRP"}, + "Asset2": { + "currency": "ETH", + "issuer": "rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW", + }, + "Account": "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + "Amount": "1000", + "Amount2": "500", + "TransactionType": "AMMWithdraw", + "Sequence": 1337, + "SigningPubKey": "", + "Flags": 0, + } + self.assertEqual(tx.to_xrpl(), expected) + + def test_to_xrpl_amm_withdraw_amount_lptoken(self): + tx = AMMWithdraw( + account="r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + sequence=1337, + asset=Issue(currency="XRP"), + asset2=Issue(currency="ETH", issuer="rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW"), + amount="1000", + lp_token_in=IssuedCurrencyAmount( + currency="B3813FCAB4EE68B3D0D735D6849465A9113EE048", + issuer="rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", + value="500", + ), + ) + expected = { + "Asset": {"currency": "XRP"}, + "Asset2": { + "currency": "ETH", + "issuer": "rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW", + }, + "Account": "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + "Amount": "1000", + "LPTokenIn": { + "currency": "B3813FCAB4EE68B3D0D735D6849465A9113EE048", + "issuer": "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg", + "value": "500", + }, + "TransactionType": "AMMWithdraw", + "Sequence": 1337, + "SigningPubKey": "", + "Flags": 0, + } + self.assertEqual(tx.to_xrpl(), expected) + + def test_to_xrpl_amm_withdraw_amount_eprice(self): + tx = AMMWithdraw( + account="r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + sequence=1337, + asset=Issue(currency="XRP"), + asset2=Issue(currency="ETH", issuer="rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW"), + amount="1000", + e_price="25", + ) + expected = { + "Asset": {"currency": "XRP"}, + "Asset2": { + "currency": "ETH", + "issuer": "rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW", + }, + "Account": "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + "Amount": "1000", + "EPrice": "25", + "TransactionType": "AMMWithdraw", + "Sequence": 1337, + "SigningPubKey": "", + "Flags": 0, + } + self.assertEqual(tx.to_xrpl(), expected) + + def test_to_xrpl_amm_vote(self): + tx = AMMVote( + account="r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + asset=Issue(currency="XRP"), + asset2=Issue(currency="ETH", issuer="rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW"), + trading_fee=234, + ) + expected = { + "Account": "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + "Asset": {"currency": "XRP"}, + "Asset2": { + "currency": "ETH", + "issuer": "rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW", + }, + "TradingFee": 234, + "TransactionType": "AMMVote", + "SigningPubKey": "", + "Flags": 0, + } + self.assertEqual(tx.to_xrpl(), expected) + + def test_to_xrpl_amm_bid(self): + tx = AMMBid( + account="r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + asset=Issue(currency="XRP"), + asset2=Issue(currency="ETH", issuer="rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW"), + bid_min=IssuedCurrencyAmount( + currency="5475B6C930B7BDD81CDA8FBA5CED962B11218E5A", + issuer="r3628pXjRqfw5zfwGfhSusjZTvE3BoxEBw", + value="25", + ), + bid_max=IssuedCurrencyAmount( + currency="5475B6C930B7BDD81CDA8FBA5CED962B11218E5A", + issuer="r3628pXjRqfw5zfwGfhSusjZTvE3BoxEBw", + value="35", + ), + auth_accounts=[ + AuthAccount(account="rNZdsTBP5tH1M6GHC6bTreHAp6ouP8iZSh"), + AuthAccount(account="rfpFv97Dwu89FTyUwPjtpZBbuZxTqqgTmH"), + AuthAccount(account="rzzYHPGb8Pa64oqxCzmuffm122bitq3Vb"), + AuthAccount(account="rhwxHxaHok86fe4LykBom1jSJ3RYQJs1h4"), + ], + ) + expected = { + "Account": "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", + "Asset": {"currency": "XRP"}, + "Asset2": { + "currency": "ETH", + "issuer": "rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW", + }, + "BidMin": { + "currency": "5475B6C930B7BDD81CDA8FBA5CED962B11218E5A", + "issuer": "r3628pXjRqfw5zfwGfhSusjZTvE3BoxEBw", + "value": "25", + }, + "BidMax": { + "currency": "5475B6C930B7BDD81CDA8FBA5CED962B11218E5A", + "issuer": "r3628pXjRqfw5zfwGfhSusjZTvE3BoxEBw", + "value": "35", + }, + "AuthAccounts": [ + { + "AuthAccount": { + "Account": "rNZdsTBP5tH1M6GHC6bTreHAp6ouP8iZSh", + } + }, + { + "AuthAccount": { + "Account": "rfpFv97Dwu89FTyUwPjtpZBbuZxTqqgTmH", + } + }, + { + "AuthAccount": { + "Account": "rzzYHPGb8Pa64oqxCzmuffm122bitq3Vb", + } + }, + { + "AuthAccount": { + "Account": "rhwxHxaHok86fe4LykBom1jSJ3RYQJs1h4", + } + }, + ], + "TransactionType": "AMMBid", + "SigningPubKey": "", + "Flags": 0, + } + self.assertEqual(tx.to_xrpl(), expected) diff --git a/tests/unit/models/transactions/test_amm_bid.py b/tests/unit/models/transactions/test_amm_bid.py new file mode 100644 index 000000000..d34654ec3 --- /dev/null +++ b/tests/unit/models/transactions/test_amm_bid.py @@ -0,0 +1,66 @@ +from unittest import TestCase + +from xrpl.models.amounts import IssuedCurrencyAmount +from xrpl.models.currencies.issue import Issue +from xrpl.models.exceptions import XRPLModelException +from xrpl.models.transactions import AMMBid, AuthAccount + +_ACCOUNT = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ" +_ASSET = Issue(currency="XRP") +_ASSET2 = Issue(currency="ETH", issuer="rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW") +_AUTH_ACCOUNTS = [ + AuthAccount( + account="rNZdsTBP5tH1M6GHC6bTreHAp6ouP8iZSh", + ), + AuthAccount( + account="rfpFv97Dwu89FTyUwPjtpZBbuZxTqqgTmH", + ), + AuthAccount( + account="rzzYHPGb8Pa64oqxCzmuffm122bitq3Vb", + ), + AuthAccount( + account="rhwxHxaHok86fe4LykBom1jSJ3RYQJs1h4", + ), +] +_LPTOKEN_CURRENCY = "5475B6C930B7BDD81CDA8FBA5CED962B11218E5A" +_LPTOKEN_ISSUER = "r3628pXjRqfw5zfwGfhSusjZTvE3BoxEBw" + + +class TestAMMBid(TestCase): + def test_tx_valid(self): + tx = AMMBid( + account=_ACCOUNT, + asset=_ASSET, + asset2=_ASSET2, + bid_min=IssuedCurrencyAmount( + currency=_LPTOKEN_CURRENCY, + issuer=_LPTOKEN_ISSUER, + value="25", + ), + bid_max=IssuedCurrencyAmount( + currency=_LPTOKEN_CURRENCY, + issuer=_LPTOKEN_ISSUER, + value="35", + ), + auth_accounts=_AUTH_ACCOUNTS, + ) + self.assertTrue(tx.is_valid()) + + def test_auth_accounts_length_error(self): + auth_accounts = _AUTH_ACCOUNTS.copy() + auth_accounts.append( + AuthAccount( + account="r3X6noRsvaLapAKCG78zAtWcbhB3sggS1s", + ), + ) + with self.assertRaises(XRPLModelException) as error: + AMMBid( + account=_ACCOUNT, + asset=_ASSET, + asset2=_ASSET2, + auth_accounts=auth_accounts, + ) + self.assertEqual( + error.exception.args[0], + "{'auth_accounts': 'Length must not be greater than 4'}", + ) diff --git a/tests/unit/models/transactions/test_amm_create.py b/tests/unit/models/transactions/test_amm_create.py new file mode 100644 index 000000000..f8b3d392a --- /dev/null +++ b/tests/unit/models/transactions/test_amm_create.py @@ -0,0 +1,52 @@ +from sys import maxsize +from unittest import TestCase + +from xrpl.models.amounts import IssuedCurrencyAmount +from xrpl.models.exceptions import XRPLModelException +from xrpl.models.transactions import AMMCreate + +_ACCOUNT = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ" +_IOU_ISSUER = "rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9" + + +class TestAMMCreate(TestCase): + def test_tx_is_valid(self): + tx = AMMCreate( + account=_ACCOUNT, + amount="1000", + amount2=IssuedCurrencyAmount( + currency="USD", issuer=_IOU_ISSUER, value="1000" + ), + trading_fee=12, + ) + self.assertTrue(tx.is_valid()) + + def test_trading_fee_too_high(self): + with self.assertRaises(XRPLModelException) as error: + AMMCreate( + account=_ACCOUNT, + amount="1000", + amount2=IssuedCurrencyAmount( + currency="USD", issuer=_IOU_ISSUER, value="1000" + ), + trading_fee=maxsize, + ) + self.assertEqual( + error.exception.args[0], + "{'trading_fee': 'Must be between 0 and 1000'}", + ) + + def test_trading_fee_negative_number(self): + with self.assertRaises(XRPLModelException) as error: + AMMCreate( + account=_ACCOUNT, + amount="1000", + amount2=IssuedCurrencyAmount( + currency="USD", issuer=_IOU_ISSUER, value="1000" + ), + trading_fee=-1, + ) + self.assertEqual( + error.exception.args[0], + "{'trading_fee': 'Must be between 0 and 1000'}", + ) diff --git a/tests/unit/models/transactions/test_amm_deposit.py b/tests/unit/models/transactions/test_amm_deposit.py new file mode 100644 index 000000000..63c39851d --- /dev/null +++ b/tests/unit/models/transactions/test_amm_deposit.py @@ -0,0 +1,127 @@ +from unittest import TestCase + +from xrpl.models.amounts import IssuedCurrencyAmount +from xrpl.models.currencies.issue import Issue +from xrpl.models.exceptions import XRPLModelException +from xrpl.models.transactions import AMMDeposit +from xrpl.models.transactions.amm_deposit import AMMDepositFlag + +_ACCOUNT = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ" +_ASSET = Issue(currency="XRP") +_ASSET2 = Issue(currency="ETH", issuer="rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW") +_AMOUNT = "1000" +_LPTOKEN_CURRENCY = "B3813FCAB4EE68B3D0D735D6849465A9113EE048" +_LPTOKEN_ISSUER = "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg" + + +class TestAMMDeposit(TestCase): + def test_tx_valid_xrpl_lptokenout(self): + tx = AMMDeposit( + account=_ACCOUNT, + sequence=1337, + asset=_ASSET, + asset2=_ASSET2, + lp_token_out=IssuedCurrencyAmount( + currency=_LPTOKEN_CURRENCY, + issuer=_LPTOKEN_ISSUER, + value=_AMOUNT, + ), + flags=AMMDepositFlag.TF_LP_TOKEN, + ) + self.assertTrue(tx.is_valid()) + + def test_tx_valid_amount(self): + tx = AMMDeposit( + account=_ACCOUNT, + sequence=1337, + asset=_ASSET, + asset2=_ASSET2, + amount=_AMOUNT, + flags=AMMDepositFlag.TF_SINGLE_ASSET, + ) + self.assertTrue(tx.is_valid()) + + def test_tx_valid_amount_amount2(self): + tx = AMMDeposit( + account=_ACCOUNT, + sequence=1337, + asset=_ASSET, + asset2=_ASSET2, + amount=_AMOUNT, + amount2=IssuedCurrencyAmount( + currency=_ASSET2.currency, issuer=_ASSET2.issuer, value="500" + ), + flags=AMMDepositFlag.TF_TWO_ASSET, + ) + self.assertTrue(tx.is_valid()) + + def test_tx_valid_amount_lptokenout(self): + tx = AMMDeposit( + account=_ACCOUNT, + sequence=1337, + asset=_ASSET, + asset2=_ASSET2, + amount=_AMOUNT, + lp_token_out=IssuedCurrencyAmount( + currency=_LPTOKEN_CURRENCY, + issuer=_LPTOKEN_ISSUER, + value="500", + ), + flags=AMMDepositFlag.TF_ONE_ASSET_LP_TOKEN, + ) + self.assertTrue(tx.is_valid()) + + def test_tx_valid_amount_eprice(self): + tx = AMMDeposit( + account=_ACCOUNT, + sequence=1337, + asset=_ASSET, + asset2=_ASSET2, + amount=_AMOUNT, + e_price="25", + flags=AMMDepositFlag.TF_LIMIT_LP_TOKEN, + ) + self.assertTrue(tx.is_valid()) + + def test_undefined_amount_undefined_lptokenout_invalid_combo(self): + with self.assertRaises(XRPLModelException) as error: + AMMDeposit( + account=_ACCOUNT, + sequence=1337, + asset=_ASSET, + asset2=_ASSET2, + ) + self.assertEqual( + error.exception.args[0], + "{'AMMDeposit': 'Must set at least `lp_token_out` or `amount`'}", + ) + + def test_undefined_amount_defined_amount2_invalid_combo(self): + with self.assertRaises(XRPLModelException) as error: + AMMDeposit( + account=_ACCOUNT, + sequence=1337, + asset=_ASSET, + asset2=_ASSET2, + amount2=IssuedCurrencyAmount( + currency=_ASSET2.currency, issuer=_ASSET2.issuer, value="500" + ), + ) + self.assertEqual( + error.exception.args[0], + "{'AMMDeposit': 'Must set `amount` with `amount2`'}", + ) + + def test_undefined_amount_defined_eprice_invalid_combo(self): + with self.assertRaises(XRPLModelException) as error: + AMMDeposit( + account=_ACCOUNT, + sequence=1337, + asset=_ASSET, + asset2=_ASSET2, + e_price="25", + ) + self.assertEqual( + error.exception.args[0], + "{'AMMDeposit': 'Must set `amount` with `e_price`'}", + ) diff --git a/tests/unit/models/transactions/test_amm_vote.py b/tests/unit/models/transactions/test_amm_vote.py new file mode 100644 index 000000000..b882b0b0f --- /dev/null +++ b/tests/unit/models/transactions/test_amm_vote.py @@ -0,0 +1,48 @@ +from sys import maxsize +from unittest import TestCase + +from xrpl.models.currencies.issue import Issue +from xrpl.models.exceptions import XRPLModelException +from xrpl.models.transactions import AMMVote + +_ACCOUNT = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ" +_ASSET = Issue(currency="XRP") +_ASSET2 = Issue(currency="ETH", issuer="rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW") +_TRADING_FEE = 234 + + +class TestAMMVote(TestCase): + def test_tx_valid(self): + tx = AMMVote( + account=_ACCOUNT, + asset=_ASSET, + asset2=_ASSET2, + trading_fee=_TRADING_FEE, + ) + self.assertTrue(tx.is_valid()) + + def test_trading_fee_too_high(self): + with self.assertRaises(XRPLModelException) as error: + AMMVote( + account=_ACCOUNT, + asset=_ASSET, + asset2=_ASSET2, + trading_fee=maxsize, + ) + self.assertEqual( + error.exception.args[0], + "{'trading_fee': 'Must be between 0 and 1000'}", + ) + + def test_trading_fee_negative_number(self): + with self.assertRaises(XRPLModelException) as error: + AMMVote( + account=_ACCOUNT, + asset=_ASSET, + asset2=_ASSET2, + trading_fee=-1, + ) + self.assertEqual( + error.exception.args[0], + "{'trading_fee': 'Must be between 0 and 1000'}", + ) diff --git a/tests/unit/models/transactions/test_amm_withdraw.py b/tests/unit/models/transactions/test_amm_withdraw.py new file mode 100644 index 000000000..9129233a2 --- /dev/null +++ b/tests/unit/models/transactions/test_amm_withdraw.py @@ -0,0 +1,135 @@ +from unittest import TestCase + +from xrpl.models.amounts import IssuedCurrencyAmount +from xrpl.models.currencies.issue import Issue +from xrpl.models.exceptions import XRPLModelException +from xrpl.models.transactions import AMMWithdraw +from xrpl.models.transactions.amm_withdraw import AMMWithdrawFlag + +_ACCOUNT = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ" +_ASSET = Issue(currency="XRP") +_ASSET2 = Issue(currency="ETH", issuer="rpGtkFRXhgVaBzC5XCR7gyE2AZN5SN3SEW") +_AMOUNT = "1000" +_LPTOKEN_CURRENCY = "B3813FCAB4EE68B3D0D735D6849465A9113EE048" +_LPTOKEN_ISSUER = "rH438jEAzTs5PYtV6CHZqpDpwCKQmPW9Cg" + + +class TestAMMWithdraw(TestCase): + def test_tx_valid_lptokenin(self): + tx = AMMWithdraw( + account=_ACCOUNT, + sequence=1337, + asset=_ASSET, + asset2=_ASSET2, + lp_token_in=IssuedCurrencyAmount( + currency=_LPTOKEN_CURRENCY, + issuer=_LPTOKEN_ISSUER, + value=_AMOUNT, + ), + flags=AMMWithdrawFlag.TF_LP_TOKEN, + ) + self.assertTrue(tx.is_valid()) + + def test_tx_valid_amount(self): + tx = AMMWithdraw( + account=_ACCOUNT, + sequence=1337, + asset=_ASSET, + asset2=_ASSET2, + amount=_AMOUNT, + flags=AMMWithdrawFlag.TF_SINGLE_ASSET, + ) + self.assertTrue(tx.is_valid()) + + def test_tx_valid_amount_amount2(self): + tx = AMMWithdraw( + account=_ACCOUNT, + sequence=1337, + asset=_ASSET, + asset2=_ASSET2, + amount=_AMOUNT, + amount2=IssuedCurrencyAmount( + currency=_ASSET2.currency, issuer=_ASSET2.issuer, value="500" + ), + flags=AMMWithdrawFlag.TF_TWO_ASSET, + ) + self.assertTrue(tx.is_valid()) + + def test_tx_valid_amount_lptokenin(self): + tx = AMMWithdraw( + account=_ACCOUNT, + sequence=1337, + asset=_ASSET, + asset2=_ASSET2, + amount=_AMOUNT, + lp_token_in=IssuedCurrencyAmount( + currency=_LPTOKEN_CURRENCY, + issuer=_LPTOKEN_ISSUER, + value="500", + ), + flags=AMMWithdrawFlag.TF_ONE_ASSET_LP_TOKEN, + ) + self.assertTrue(tx.is_valid()) + + def test_tx_valid_amount_eprice(self): + tx = AMMWithdraw( + account=_ACCOUNT, + sequence=1337, + asset=_ASSET, + asset2=_ASSET2, + amount=_AMOUNT, + e_price="25", + flags=AMMWithdrawFlag.TF_LIMIT_LP_TOKEN, + ) + self.assertTrue(tx.is_valid()) + + def test_tx_valid_one_asset_withdraw_all(self): + tx = AMMWithdraw( + account=_ACCOUNT, + sequence=1337, + asset=_ASSET, + asset2=_ASSET2, + amount=_AMOUNT, + flags=AMMWithdrawFlag.TF_ONE_ASSET_WITHDRAW_ALL, + ) + self.assertTrue(tx.is_valid()) + + def test_tx_valid_withdraw_all(self): + tx = AMMWithdraw( + account=_ACCOUNT, + sequence=1337, + asset=_ASSET, + asset2=_ASSET2, + flags=AMMWithdrawFlag.TF_WITHDRAW_ALL, + ) + self.assertTrue(tx.is_valid()) + + def test_undefined_amount_defined_amount2_invalid_combo(self): + with self.assertRaises(XRPLModelException) as error: + AMMWithdraw( + account=_ACCOUNT, + sequence=1337, + asset=_ASSET, + asset2=_ASSET2, + amount2=IssuedCurrencyAmount( + currency=_ASSET2.currency, issuer=_ASSET2.issuer, value="500" + ), + ) + self.assertEqual( + error.exception.args[0], + "{'AMMWithdraw': 'Must set `amount` with `amount2`'}", + ) + + def test_undefined_amount_defined_eprice_invalid_combo(self): + with self.assertRaises(XRPLModelException) as error: + AMMWithdraw( + account=_ACCOUNT, + sequence=1337, + asset=_ASSET, + asset2=_ASSET2, + e_price="25", + ) + self.assertEqual( + error.exception.args[0], + "{'AMMWithdraw': 'Must set `amount` with `e_price`'}", + ) diff --git a/tests/unit/models/transactions/test_xchain_account_create_commit.py b/tests/unit/models/transactions/test_xchain_account_create_commit.py new file mode 100644 index 000000000..26651610c --- /dev/null +++ b/tests/unit/models/transactions/test_xchain_account_create_commit.py @@ -0,0 +1,61 @@ +from unittest import TestCase + +from xrpl.models import ( + XRP, + IssuedCurrency, + XChainAccountCreateCommit, + XChainBridge, + XRPLModelException, +) + +_ACCOUNT = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ" +_ACCOUNT2 = "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo" + +_ISSUER = "rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf" + +_GENESIS = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh" + +_XRP_BRIDGE = XChainBridge( + locking_chain_door=_ACCOUNT, + locking_chain_issue=XRP(), + issuing_chain_door=_GENESIS, + issuing_chain_issue=XRP(), +) + +_IOU_BRIDGE = XChainBridge( + locking_chain_door=_ACCOUNT, + locking_chain_issue=IssuedCurrency(currency="USD", issuer=_ISSUER), + issuing_chain_door=_ACCOUNT2, + issuing_chain_issue=IssuedCurrency(currency="USD", issuer=_ACCOUNT2), +) + + +class TestXChainAccountCreateCommit(TestCase): + def test_successful(self): + XChainAccountCreateCommit( + account=_ACCOUNT, + xchain_bridge=_XRP_BRIDGE, + signature_reward="200", + destination=_ACCOUNT2, + amount="1000000", + ) + + def test_bad_signature_reward(self): + with self.assertRaises(XRPLModelException): + XChainAccountCreateCommit( + account=_ACCOUNT, + xchain_bridge=_XRP_BRIDGE, + signature_reward="hello", + destination=_ACCOUNT2, + amount="1000000", + ) + + def test_bad_amount(self): + with self.assertRaises(XRPLModelException): + XChainAccountCreateCommit( + account=_ACCOUNT, + xchain_bridge=_XRP_BRIDGE, + signature_reward="200", + destination=_ACCOUNT2, + amount="hello", + ) diff --git a/tests/unit/models/transactions/test_xchain_claim.py b/tests/unit/models/transactions/test_xchain_claim.py new file mode 100644 index 000000000..82f9e690d --- /dev/null +++ b/tests/unit/models/transactions/test_xchain_claim.py @@ -0,0 +1,108 @@ +from unittest import TestCase + +from xrpl.models import ( + XRP, + IssuedCurrency, + IssuedCurrencyAmount, + XChainBridge, + XChainClaim, + XRPLModelException, +) + +_ACCOUNT = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ" +_ACCOUNT2 = "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo" +_FEE = "0.00001" +_SEQUENCE = 19048 + +_ISSUER = "rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf" +_GENESIS = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh" + +_DESTINATION = "rJrRMgiRgrU6hDF4pgu5DXQdWyPbY35ErN" +_CLAIM_ID = 3 +_XRP_AMOUNT = "123456789" +_IOU_AMOUNT = IssuedCurrencyAmount(currency="USD", issuer=_ISSUER, value="123") + +_XRP_BRIDGE = XChainBridge( + locking_chain_door=_ACCOUNT, + locking_chain_issue=XRP(), + issuing_chain_door=_GENESIS, + issuing_chain_issue=XRP(), +) + +_IOU_BRIDGE = XChainBridge( + locking_chain_door=_ACCOUNT, + locking_chain_issue=IssuedCurrency(currency="USD", issuer=_ISSUER), + issuing_chain_door=_ACCOUNT2, + issuing_chain_issue=IssuedCurrency(currency="USD", issuer=_ACCOUNT2), +) + + +class TestXChainClaim(TestCase): + def test_successful_claim_xrp(self): + XChainClaim( + account=_ACCOUNT, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=_XRP_BRIDGE, + xchain_claim_id=_CLAIM_ID, + destination=_DESTINATION, + amount=_XRP_AMOUNT, + ) + + def test_successful_claim_iou(self): + XChainClaim( + account=_ACCOUNT, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=_IOU_BRIDGE, + xchain_claim_id=_CLAIM_ID, + destination=_DESTINATION, + amount=_IOU_AMOUNT, + ) + + def test_successful_claim_destination_tag(self): + XChainClaim( + account=_ACCOUNT, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=_XRP_BRIDGE, + xchain_claim_id=_CLAIM_ID, + destination=_DESTINATION, + destination_tag="12345", + amount=_XRP_AMOUNT, + ) + + def test_successful_claim_str_claim_id(self): + XChainClaim( + account=_ACCOUNT, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=_XRP_BRIDGE, + xchain_claim_id=str(_CLAIM_ID), + destination=_DESTINATION, + amount=_XRP_AMOUNT, + ) + + def test_xrp_bridge_iou_amount(self): + with self.assertRaises(XRPLModelException): + XChainClaim( + account=_ACCOUNT, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=_XRP_BRIDGE, + xchain_claim_id=_CLAIM_ID, + destination=_DESTINATION, + amount=_IOU_AMOUNT, + ) + + def test_iou_bridge_xrp_amount(self): + with self.assertRaises(XRPLModelException): + XChainClaim( + account=_ACCOUNT, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=_IOU_BRIDGE, + xchain_claim_id=_CLAIM_ID, + destination=_DESTINATION, + amount=_XRP_AMOUNT, + ) diff --git a/tests/unit/models/transactions/test_xchain_create_bridge.py b/tests/unit/models/transactions/test_xchain_create_bridge.py new file mode 100644 index 000000000..47e3f49c7 --- /dev/null +++ b/tests/unit/models/transactions/test_xchain_create_bridge.py @@ -0,0 +1,166 @@ +from unittest import TestCase + +from xrpl.models import ( + XRP, + IssuedCurrency, + XChainBridge, + XChainCreateBridge, + XRPLModelException, +) + +_ACCOUNT = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ" +_ACCOUNT2 = "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo" +_FEE = "0.00001" +_SEQUENCE = 19048 + +_ISSUER = "rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf" + +_GENESIS = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh" + + +class TestXChainCreateBridge(TestCase): + def test_successful_xrp_xrp_bridge(self): + bridge = XChainBridge( + locking_chain_door=_ACCOUNT, + locking_chain_issue=XRP(), + issuing_chain_door=_GENESIS, + issuing_chain_issue=XRP(), + ) + XChainCreateBridge( + account=_ACCOUNT, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=bridge, + signature_reward="200", + min_account_create_amount="1000000", + ) + + def test_successful_iou_iou_bridge(self): + bridge = XChainBridge( + locking_chain_door=_ACCOUNT, + locking_chain_issue=IssuedCurrency(currency="USD", issuer=_ISSUER), + issuing_chain_door=_ACCOUNT2, + issuing_chain_issue=IssuedCurrency(currency="USD", issuer=_ACCOUNT2), + ) + XChainCreateBridge( + account=_ACCOUNT, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=bridge, + signature_reward="200", + ) + + def test_same_door_accounts(self): + bridge = XChainBridge( + locking_chain_door=_ACCOUNT, + locking_chain_issue=IssuedCurrency(currency="USD", issuer=_ISSUER), + issuing_chain_door=_ACCOUNT, + issuing_chain_issue=IssuedCurrency(currency="USD", issuer=_ACCOUNT), + ) + with self.assertRaises(XRPLModelException): + XChainCreateBridge( + account=_ACCOUNT, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=bridge, + signature_reward="200", + ) + + def test_xrp_iou_bridge(self): + bridge = XChainBridge( + locking_chain_door=_ACCOUNT, + locking_chain_issue=XRP(), + issuing_chain_door=_ACCOUNT, + issuing_chain_issue=IssuedCurrency(currency="USD", issuer=_ACCOUNT), + ) + with self.assertRaises(XRPLModelException): + XChainCreateBridge( + account=_ACCOUNT, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=bridge, + signature_reward="200", + ) + + def test_iou_xrp_bridge(self): + bridge = XChainBridge( + locking_chain_door=_ACCOUNT, + locking_chain_issue=IssuedCurrency(currency="USD", issuer=_ISSUER), + issuing_chain_door=_ACCOUNT, + issuing_chain_issue=XRP(), + ) + with self.assertRaises(XRPLModelException): + XChainCreateBridge( + account=_ACCOUNT, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=bridge, + signature_reward="200", + ) + + def test_account_not_in_bridge(self): + bridge = XChainBridge( + locking_chain_door=_ACCOUNT, + locking_chain_issue=XRP(), + issuing_chain_door=_ACCOUNT2, + issuing_chain_issue=XRP(), + ) + with self.assertRaises(XRPLModelException): + XChainCreateBridge( + account=_GENESIS, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=bridge, + signature_reward="200", + ) + + def test_iou_iou_min_account_create_amount(self): + bridge = XChainBridge( + locking_chain_door=_ACCOUNT, + locking_chain_issue=IssuedCurrency(currency="USD", issuer=_ISSUER), + issuing_chain_door=_ACCOUNT2, + issuing_chain_issue=IssuedCurrency(currency="USD", issuer=_ACCOUNT2), + ) + with self.assertRaises(XRPLModelException): + XChainCreateBridge( + account=_ACCOUNT, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=bridge, + signature_reward="200", + min_account_create_amount="1000000", + ) + + def test_invalid_signature_reward(self): + bridge = XChainBridge( + locking_chain_door=_ACCOUNT, + locking_chain_issue=XRP(), + issuing_chain_door=_GENESIS, + issuing_chain_issue=XRP(), + ) + with self.assertRaises(XRPLModelException): + XChainCreateBridge( + account=_ACCOUNT, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=bridge, + signature_reward="hello", + min_account_create_amount="1000000", + ) + + def test_invalid_min_account_create_amount(self): + bridge = XChainBridge( + locking_chain_door=_ACCOUNT, + locking_chain_issue=XRP(), + issuing_chain_door=_GENESIS, + issuing_chain_issue=XRP(), + ) + with self.assertRaises(XRPLModelException): + XChainCreateBridge( + account=_ACCOUNT, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=bridge, + signature_reward="-200", + min_account_create_amount="hello", + ) diff --git a/tests/unit/models/transactions/test_xchain_create_claim_id.py b/tests/unit/models/transactions/test_xchain_create_claim_id.py new file mode 100644 index 000000000..589646b23 --- /dev/null +++ b/tests/unit/models/transactions/test_xchain_create_claim_id.py @@ -0,0 +1,47 @@ +from unittest import TestCase + +from xrpl.models import XRP, XChainBridge, XChainCreateClaimID, XRPLModelException + +_ACCOUNT = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ" +_ACCOUNT2 = "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo" + +_ISSUER = "rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf" +_GENESIS = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh" + +_SOURCE = "rJrRMgiRgrU6hDF4pgu5DXQdWyPbY35ErN" +_SIGNATURE_REWARD = "200" + +_XRP_BRIDGE = XChainBridge( + locking_chain_door=_ACCOUNT, + locking_chain_issue=XRP(), + issuing_chain_door=_GENESIS, + issuing_chain_issue=XRP(), +) + + +class TestXChainCreateClaimID(TestCase): + def test_successful(self): + XChainCreateClaimID( + account=_ACCOUNT, + xchain_bridge=_XRP_BRIDGE, + signature_reward=_SIGNATURE_REWARD, + other_chain_source=_SOURCE, + ) + + def test_bad_signature_reward(self): + with self.assertRaises(XRPLModelException): + XChainCreateClaimID( + account=_ACCOUNT, + xchain_bridge=_XRP_BRIDGE, + signature_reward="hello", + other_chain_source=_SOURCE, + ) + + def test_bad_other_chain_source(self): + with self.assertRaises(XRPLModelException): + XChainCreateClaimID( + account=_ACCOUNT, + xchain_bridge=_XRP_BRIDGE, + signature_reward=_SIGNATURE_REWARD, + other_chain_source="hello", + ) diff --git a/tests/unit/models/transactions/test_xchain_modify_bridge.py b/tests/unit/models/transactions/test_xchain_modify_bridge.py new file mode 100644 index 000000000..63177024b --- /dev/null +++ b/tests/unit/models/transactions/test_xchain_modify_bridge.py @@ -0,0 +1,113 @@ +from unittest import TestCase + +from xrpl.models import ( + XRP, + IssuedCurrency, + XChainBridge, + XChainModifyBridge, + XRPLModelException, +) + +_ACCOUNT = "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ" +_ACCOUNT2 = "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo" +_FEE = "0.00001" +_SEQUENCE = 19048 + +_ISSUER = "rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf" + +_GENESIS = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh" + +_XRP_BRIDGE = XChainBridge( + locking_chain_door=_ACCOUNT, + locking_chain_issue=XRP(), + issuing_chain_door=_GENESIS, + issuing_chain_issue=XRP(), +) + +_IOU_BRIDGE = XChainBridge( + locking_chain_door=_ACCOUNT, + locking_chain_issue=IssuedCurrency(currency="USD", issuer=_ISSUER), + issuing_chain_door=_ACCOUNT2, + issuing_chain_issue=IssuedCurrency(currency="USD", issuer=_ACCOUNT2), +) + + +class TestXChainModifyBridge(TestCase): + def test_successful_modify_bridge(self): + XChainModifyBridge( + account=_ACCOUNT, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=_XRP_BRIDGE, + signature_reward="200", + min_account_create_amount="1000000", + ) + + def test_successful_modify_bridge_only_signature_reward(self): + XChainModifyBridge( + account=_ACCOUNT, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=_IOU_BRIDGE, + signature_reward="200", + ) + + def test_successful_modify_bridge_only_min_account_create_amount(self): + XChainModifyBridge( + account=_ACCOUNT, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=_XRP_BRIDGE, + min_account_create_amount="1000000", + ) + + def test_modify_bridge_empty(self): + with self.assertRaises(XRPLModelException): + XChainModifyBridge( + account=_ACCOUNT, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=_IOU_BRIDGE, + ) + + def test_account_not_in_bridge(self): + with self.assertRaises(XRPLModelException): + XChainModifyBridge( + account=_ACCOUNT2, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=_XRP_BRIDGE, + signature_reward="200", + ) + + def test_iou_iou_min_account_create_amount(self): + with self.assertRaises(XRPLModelException): + XChainModifyBridge( + account=_ACCOUNT, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=_IOU_BRIDGE, + min_account_create_amount="1000000", + ) + + def test_invalid_signature_reward(self): + with self.assertRaises(XRPLModelException): + XChainModifyBridge( + account=_ACCOUNT, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=_XRP_BRIDGE, + signature_reward="hello", + min_account_create_amount="1000000", + ) + + def test_invalid_min_account_create_amount(self): + with self.assertRaises(XRPLModelException): + XChainModifyBridge( + account=_ACCOUNT, + fee=_FEE, + sequence=_SEQUENCE, + xchain_bridge=_XRP_BRIDGE, + signature_reward="200", + min_account_create_amount="hello", + ) diff --git a/xrpl/core/binarycodec/definitions/definitions.json b/xrpl/core/binarycodec/definitions/definitions.json index 317e1feb7..770ae578a 100644 --- a/xrpl/core/binarycodec/definitions/definitions.json +++ b/xrpl/core/binarycodec/definitions/definitions.json @@ -21,6 +21,8 @@ "UInt192": 21, "UInt384": 22, "UInt512": 23, + "Issue": 24, + "XChainBridge": 25, "Transaction": 10001, "LedgerEntry": 10002, "Validation": 10003, @@ -34,8 +36,11 @@ "Ticket": 84, "SignerList": 83, "Offer": 111, + "Bridge": 105, "LedgerHashes": 104, "Amendments": 102, + "XChainClaimID": 113, + "XChainCreateAccountClaimID": 116, "FeeSettings": 115, "Escrow": 117, "PayChannel": 120, @@ -44,6 +49,7 @@ "NegativeUNL": 78, "NFTokenPage": 80, "NFTokenOffer": 55, + "AMM": 121, "Any": -3, "Child": -2, "Nickname": 110, @@ -231,6 +237,16 @@ "type": "UInt8" } ], + [ + "WasLockingChainSend", + { + "nth": 19, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt8" + } + ], [ "LedgerEntryType", { @@ -271,6 +287,16 @@ "type": "UInt16" } ], + [ + "TradingFee", + { + "nth": 5, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt16" + } + ], [ "Version", { @@ -761,6 +787,36 @@ "type": "UInt32" } ], + [ + "VoteWeight", + { + "nth": 47, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "DiscountedFee", + { + "nth": 48, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], + [ + "LockCount", + { + "nth": 49, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], [ "IndexNext", { @@ -931,6 +987,36 @@ "type": "UInt64" } ], + [ + "XChainClaimID", + { + "nth": 20, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "XChainAccountCreateCount", + { + "nth": 21, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "XChainAccountClaimCount", + { + "nth": 22, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], [ "EmailHash", { @@ -1111,6 +1197,16 @@ "type": "Hash256" } ], + [ + "AMMID", + { + "nth": 14, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash256" + } + ], [ "BookDirectory", { @@ -1381,6 +1477,36 @@ "type": "Amount" } ], + [ + "Amount2", + { + "nth": 11, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "BidMin", + { + "nth": 12, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "BidMax", + { + "nth": 13, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], [ "MinimumOffer", { @@ -1421,6 +1547,96 @@ "type": "Amount" } ], + [ + "LPTokenOut", + { + "nth": 20, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "LPTokenIn", + { + "nth": 21, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "EPrice", + { + "nth": 22, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "Price", + { + "nth": 23, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "LPTokenBalance", + { + "nth": 24, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "XChainFee", + { + "nth": 28, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "SignatureReward", + { + "nth": 29, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "MinAccountCreateAmount", + { + "nth": 30, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], + [ + "LockedBalance", + { + "nth": 31, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Amount" + } + ], [ "PublicKey", { @@ -1751,6 +1967,16 @@ "type": "AccountID" } ], + [ + "AMMAccount", + { + "nth": 11, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], [ "HookAccount", { @@ -1761,6 +1987,66 @@ "type": "AccountID" } ], + [ + "OtherChainSource", + { + "nth": 18, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "OtherChainDestination", + { + "nth": 19, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "AttestationSignerAccount", + { + "nth": 20, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "AttestationRewardAccount", + { + "nth": 21, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "LockingChainDoor", + { + "nth": 22, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], + [ + "IssuingChainDoor", + { + "nth": 23, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], [ "Indexes", { @@ -1811,6 +2097,56 @@ "type": "PathSet" } ], + [ + "LockingChainIssue", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Issue" + } + ], + [ + "Asset", + { + "nth": 3, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Issue" + } + ], + [ + "IssuingChainIssue", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Issue" + } + ], + [ + "Asset2", + { + "nth": 4, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Issue" + } + ], + [ + "XChainBridge", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "XChainBridge" + } + ], [ "TransactionMetaData", { @@ -2021,6 +2357,106 @@ "type": "STObject" } ], + [ + "VoteEntry", + { + "nth": 25, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "AuctionSlot", + { + "nth": 27, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "AuthAccount", + { + "nth": 28, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "AMMToken", + { + "nth": 29, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "Token1", + { + "nth": 30, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "Token2", + { + "nth": 31, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "XChainClaimProofSig", + { + "nth": 32, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "XChainCreateAccountProofSig", + { + "nth": 33, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "XChainClaimAttestationBatchElement", + { + "nth": 34, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], + [ + "XChainCreateAccountAttestationBatchElement", + { + "nth": 35, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], [ "Signers", { @@ -2111,6 +2547,16 @@ "type": "STArray" } ], + [ + "VoteSlots", + { + "nth": 14, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], [ "Majorities", { @@ -2160,6 +2606,56 @@ "isSigningField": true, "type": "STArray" } + ], + [ + "XChainClaimAttestationBatch", + { + "nth": 21, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "XChainCreateAccountAttestationBatch", + { + "nth": 22, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "XChainClaimAttestations", + { + "nth": 23, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "XChainCreateAccountAttestations", + { + "nth": 24, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], + [ + "AuthAccounts", + { + "nth": 26, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } ] ], "TRANSACTION_RESULTS": { @@ -2215,6 +2711,14 @@ "temUNKNOWN": -264, "temSEQ_AND_TICKET": -263, "temBAD_NFTOKEN_TRANSFER_FEE": -262, + "temAMM_BAD_TOKENS": -261, + "temEQUAL_DOOR_ACCOUNTS": -259, + "temBAD_XCHAIN_PROOF": -258, + "temSIDECHAIN_BAD_ISSUES": -257, + "temSIDECHAIN_NONDOOR_OWNER": -256, + "temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT": -255, + "temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT": -254, + "temXCHAIN_TOO_MANY_ATTESTATIONS": -253, "tefFAILURE": -199, "tefALREADY": -198, @@ -2250,6 +2754,7 @@ "terNO_RIPPLE": -90, "terQUEUED": -89, "terPRE_TICKET": -88, + "terNO_AMM": -87, "tesSUCCESS": 0, @@ -2298,7 +2803,31 @@ "tecCANT_ACCEPT_OWN_NFTOKEN_OFFER": 158, "tecINSUFFICIENT_FUNDS": 159, "tecOBJECT_NOT_FOUND": 160, - "tecINSUFFICIENT_PAYMENT": 161 + "tecINSUFFICIENT_PAYMENT": 161, + "tecAMM_UNFUNDED": 162, + "tecAMM_BALANCE": 163, + "tecAMM_FAILED_DEPOSIT": 164, + "tecAMM_FAILED_WITHDRAW": 165, + "tecAMM_INVALID_TOKENS": 166, + "tecAMM_FAILED_BID": 167, + "tecAMM_FAILED_VOTE": 168, + "tecBAD_XCHAIN_TRANSFER_ISSUE": 171, + "tecXCHAIN_NO_CLAIM_ID": 172, + "tecXCHAIN_BAD_CLAIM_ID": 173, + "tecXCHAIN_CLAIM_NO_QUORUM": 174, + "tecXCHAIN_PROOF_UNKNOWN_KEY": 175, + "tecXCHAIN_CREATE_ACCOUNT_NONXRP_ISSUE": 176, + "tecXCHAIN_WRONG_CHAIN": 177, + "tecXCHAIN_REWARD_MISMATCH": 178, + "tecXCHAIN_NO_SIGNERS_LIST": 179, + "tecXCHAIN_SENDING_ACCOUNT_MISMATCH": 180, + "tecXCHAIN_INSUFF_CREATE_AMOUNT": 181, + "tecXCHAIN_ACCOUNT_CREATE_PAST": 182, + "tecXCHAIN_ACCOUNT_CREATE_TOO_MANY": 183, + "tecXCHAIN_PAYMENT_FAILED": 184, + "tecXCHAIN_SELF_COMMIT": 185, + "tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR": 186, + "tecPRECISION_LOSS": 187 }, "TRANSACTION_TYPES": { "Invalid": -1, @@ -2330,6 +2859,19 @@ "NFTokenCreateOffer": 27, "NFTokenCancelOffer": 28, "NFTokenAcceptOffer": 29, + "AMMCreate": 35, + "AMMDeposit": 36, + "AMMWithdraw": 37, + "AMMVote": 38, + "AMMBid": 39, + "XChainCreateBridge": 40, + "XChainCreateClaimID": 41, + "XChainCommit": 42, + "XChainClaim": 43, + "XChainAccountCreateCommit": 44, + "XChainAddClaimAttestation": 45, + "XChainAddAccountCreateAttestation": 46, + "XChainModifyBridge": 47, "EnableAmendment": 100, "SetFee": 101, "UNLModify": 102 diff --git a/xrpl/core/binarycodec/definitions/field_header.py b/xrpl/core/binarycodec/definitions/field_header.py index 3a9b05fcb..a2ff0bc4f 100644 --- a/xrpl/core/binarycodec/definitions/field_header.py +++ b/xrpl/core/binarycodec/definitions/field_header.py @@ -48,3 +48,7 @@ def __bytes__(self: FieldHeader) -> bytes: header += [0, self.type_code, self.field_code] return bytes(header) + + def __repr__(self: FieldHeader) -> str: + """Print a string representation of a FieldHeader (for debugging).""" + return f"FieldHeader({self.type_code}, {self.field_code})" diff --git a/xrpl/core/binarycodec/main.py b/xrpl/core/binarycodec/main.py index a2b5cec86..e20b864cd 100644 --- a/xrpl/core/binarycodec/main.py +++ b/xrpl/core/binarycodec/main.py @@ -3,12 +3,13 @@ decoding them. """ -from typing import Any, Dict, Optional, cast +from typing import Any, Dict, Optional, Union, cast from typing_extensions import Final from xrpl.core.binarycodec.binary_wrappers.binary_parser import BinaryParser from xrpl.core.binarycodec.types.account_id import AccountID +from xrpl.core.binarycodec.types.amount import Amount from xrpl.core.binarycodec.types.hash256 import Hash256 from xrpl.core.binarycodec.types.st_object import STObject from xrpl.core.binarycodec.types.uint64 import UInt64 @@ -67,7 +68,11 @@ def encode_for_signing_claim(json: Dict[str, Any]) -> str: """ prefix = _PAYMENT_CHANNEL_CLAIM_PREFIX channel = Hash256.from_value(json["channel"]) - amount = UInt64.from_value(int(json["amount"])) + + if isinstance(json["amount"], str): + amount: Union[Amount, UInt64] = UInt64.from_value(int(json["amount"])) + else: + amount = Amount.from_value(json["amount"]) buffer = prefix + bytes(channel) + bytes(amount) return buffer.hex().upper() diff --git a/xrpl/core/binarycodec/types/__init__.py b/xrpl/core/binarycodec/types/__init__.py index 9511fc29b..e9d31a608 100644 --- a/xrpl/core/binarycodec/types/__init__.py +++ b/xrpl/core/binarycodec/types/__init__.py @@ -7,6 +7,7 @@ from xrpl.core.binarycodec.types.hash128 import Hash128 from xrpl.core.binarycodec.types.hash160 import Hash160 from xrpl.core.binarycodec.types.hash256 import Hash256 +from xrpl.core.binarycodec.types.issue import Issue from xrpl.core.binarycodec.types.path_set import PathSet from xrpl.core.binarycodec.types.st_array import STArray from xrpl.core.binarycodec.types.st_object import STObject @@ -16,6 +17,7 @@ from xrpl.core.binarycodec.types.uint32 import UInt32 from xrpl.core.binarycodec.types.uint64 import UInt64 from xrpl.core.binarycodec.types.vector256 import Vector256 +from xrpl.core.binarycodec.types.xchain_bridge import XChainBridge __all__ = [ "AccountID", @@ -26,6 +28,7 @@ "Hash128", "Hash160", "Hash256", + "Issue", "PathSet", "STObject", "STArray", @@ -35,4 +38,5 @@ "UInt32", "UInt64", "Vector256", + "XChainBridge", ] diff --git a/xrpl/core/binarycodec/types/issue.py b/xrpl/core/binarycodec/types/issue.py new file mode 100644 index 000000000..1c5b5029a --- /dev/null +++ b/xrpl/core/binarycodec/types/issue.py @@ -0,0 +1,94 @@ +"""Codec for serializing and deserializing issued currency fields.""" + +from __future__ import annotations + +from typing import Any, Dict, Optional, Type, Union + +from xrpl.core.binarycodec.binary_wrappers.binary_parser import BinaryParser +from xrpl.core.binarycodec.exceptions import XRPLBinaryCodecException +from xrpl.core.binarycodec.types.account_id import AccountID +from xrpl.core.binarycodec.types.currency import Currency +from xrpl.core.binarycodec.types.serialized_type import SerializedType +from xrpl.models.currencies import XRP as XRPModel +from xrpl.models.currencies import IssuedCurrency as IssuedCurrencyModel + + +class Issue(SerializedType): + """Codec for serializing and deserializing issued currency fields.""" + + def __init__(self: Issue, buffer: bytes) -> None: + """ + Construct an Issue from given bytes. + + Args: + buffer: The byte buffer that will be used to store the serialized + encoding of this field. + """ + super().__init__(buffer) + + @classmethod + def from_value(cls: Type[Issue], value: Dict[str, str]) -> Issue: + """ + Construct an Issue object from a string or dictionary representation + of an issued currency. + + Args: + value: The dictionary to construct an Issue object from. + + Returns: + An Issue object constructed from value. + + Raises: + XRPLBinaryCodecException: If the Issue representation is invalid. + """ + if XRPModel.is_dict_of_model(value): + currency_bytes = bytes(Currency.from_value(value["currency"])) + return cls(currency_bytes) + + if IssuedCurrencyModel.is_dict_of_model(value): + currency_bytes = bytes(Currency.from_value(value["currency"])) + issuer_bytes = bytes(AccountID.from_value(value["issuer"])) + return cls(currency_bytes + issuer_bytes) + + raise XRPLBinaryCodecException( + "Invalid type to construct an Issue: expected str or dict," + f" received {value.__class__.__name__}." + ) + + @classmethod + def from_parser( + cls: Type[Issue], + parser: BinaryParser, + length_hint: Optional[int] = None, + ) -> Issue: + """ + Construct an Issue object from an existing BinaryParser. + + Args: + parser: The parser to construct the Issue object from. + length_hint: The number of bytes to consume from the parser. + + Returns: + The Issue object constructed from a parser. + """ + currency = Currency.from_parser(parser) + if currency.to_json() == "XRP": + return cls(bytes(currency)) + + issuer = parser.read(20) # the length in bytes of an account ID + return cls(bytes(currency) + issuer) + + def to_json(self: Issue) -> Union[str, Dict[Any, Any]]: + """ + Returns the JSON representation of an issued currency. + + Returns: + The JSON representation of an Issue. + """ + parser = BinaryParser(str(self)) + currency: Union[str, Dict[Any, Any]] = Currency.from_parser(parser).to_json() + if currency == "XRP": + return {"currency": currency} + + issuer = AccountID.from_parser(parser) + return {"currency": currency, "issuer": issuer.to_json()} diff --git a/xrpl/core/binarycodec/types/issued_currency.py b/xrpl/core/binarycodec/types/issued_currency.py new file mode 100644 index 000000000..9427a6fad --- /dev/null +++ b/xrpl/core/binarycodec/types/issued_currency.py @@ -0,0 +1,90 @@ +"""Codec for serializing and deserializing issued currency fields.""" + +from __future__ import annotations + +from typing import Any, Dict, Optional, Type, Union + +from xrpl.core.binarycodec.binary_wrappers.binary_parser import BinaryParser +from xrpl.core.binarycodec.exceptions import XRPLBinaryCodecException +from xrpl.core.binarycodec.types.account_id import AccountID +from xrpl.core.binarycodec.types.currency import Currency +from xrpl.core.binarycodec.types.serialized_type import SerializedType +from xrpl.models.currencies import IssuedCurrency as IssuedCurrencyModel + + +class IssuedCurrency(SerializedType): + """Codec for serializing and deserializing issued currency fields.""" + + def __init__(self: IssuedCurrency, buffer: bytes) -> None: + """Construct an IssuedCurrency from given bytes.""" + super().__init__(buffer) + + @classmethod + def from_value( + cls: Type[IssuedCurrency], value: Union[str, Dict[str, str]] + ) -> IssuedCurrency: + """ + Construct an IssuedCurrency object from a string or dictionary representation + of an issued currency. + + Args: + value: The dictionary to construct an IssuedCurrency object from. + + Returns: + An IssuedCurrency object constructed from value. + + Raises: + XRPLBinaryCodecException: If the IssuedCurrency representation is invalid. + """ + if isinstance(value, str): + if value != "XRP": + raise XRPLBinaryCodecException(f"{value} is an illegal currency") + return cls(bytes(Currency.from_value(value))) + + if IssuedCurrencyModel.is_dict_of_model(value): + currency_bytes = bytes(Currency.from_value(value["currency"])) + issuer_bytes = bytes(AccountID.from_value(value["issuer"])) + return cls(currency_bytes + issuer_bytes) + + raise XRPLBinaryCodecException( + "Invalid type to construct an IssuedCurrency: expected str or dict," + f" received {value.__class__.__name__}." + ) + + @classmethod + def from_parser( + cls: Type[IssuedCurrency], + parser: BinaryParser, + length_hint: Optional[int] = None, + ) -> IssuedCurrency: + """ + Construct an IssuedCurrency object from an existing BinaryParser. + + Args: + parser: The parser to construct the IssuedCurrency object from. + length_hint: The number of bytes to consume from the parser. + + Returns: + The IssuedCurrency object constructed from a parser. + """ + currency = Currency.from_parser(parser) + if currency.to_json() == "XRP": + return cls(bytes(currency)) + + issuer = parser.read(20) # the length in bytes of an account ID + return cls(bytes(currency) + issuer) + + def to_json(self: IssuedCurrency) -> Union[str, Dict[Any, Any]]: + """ + Returns the JSON representation of an issued currency. + + Returns: + The JSON representation of an IssuedCurrency. + """ + parser = BinaryParser(str(self)) + currency: Union[str, Dict[Any, Any]] = Currency.from_parser(parser).to_json() + if currency == "XRP": + return currency + + issuer = AccountID.from_parser(parser) + return {"currency": currency, "issuer": issuer.to_json()} diff --git a/xrpl/core/binarycodec/types/xchain_bridge.py b/xrpl/core/binarycodec/types/xchain_bridge.py new file mode 100644 index 000000000..227b5d414 --- /dev/null +++ b/xrpl/core/binarycodec/types/xchain_bridge.py @@ -0,0 +1,98 @@ +"""Codec for serializing and deserializing bridge fields.""" +from __future__ import annotations + +from typing import Any, Dict, List, Optional, Tuple, Type, Union + +from xrpl.core.binarycodec.binary_wrappers.binary_parser import BinaryParser +from xrpl.core.binarycodec.exceptions import XRPLBinaryCodecException +from xrpl.core.binarycodec.types.account_id import AccountID +from xrpl.core.binarycodec.types.issue import Issue +from xrpl.core.binarycodec.types.serialized_type import SerializedType + +_TYPE_ORDER: List[Tuple[str, Type[SerializedType]]] = [ + ("LockingChainDoor", AccountID), + ("LockingChainIssue", Issue), + ("IssuingChainDoor", AccountID), + ("IssuingChainIssue", Issue), +] + +_TYPE_KEYS = {type[0] for type in _TYPE_ORDER} + + +class XChainBridge(SerializedType): + """Codec for serializing and deserializing bridge fields.""" + + def __init__(self: XChainBridge, buffer: bytes) -> None: + """Construct a XChainBridge from given bytes.""" + super().__init__(buffer) + + @classmethod + def from_value( + cls: Type[XChainBridge], value: Union[str, Dict[str, str]] + ) -> XChainBridge: + """ + Construct a XChainBridge object from a dictionary representation of a bridge. + + Args: + value: The dictionary to construct a XChainBridge object from. + + Returns: + A XChainBridge object constructed from value. + + Raises: + XRPLBinaryCodecException: If the XChainBridge representation is invalid. + """ + if isinstance(value, dict) and set(value.keys()) == _TYPE_KEYS: + buffer = b"" + for (name, object_type) in _TYPE_ORDER: + obj = object_type.from_value(value[name]) + if object_type == AccountID: + buffer += bytes.fromhex("14") # AccountID length (I think) + buffer += bytes(obj) + return cls(buffer) + + raise XRPLBinaryCodecException( + "Invalid type to construct a XChainBridge: expected dict," + f" received {value.__class__.__name__}." + ) + + @classmethod + def from_parser( + cls: Type[XChainBridge], parser: BinaryParser, length_hint: Optional[int] = None + ) -> XChainBridge: + """ + Construct a XChainBridge object from an existing BinaryParser. + + Args: + parser: The parser to construct the XChainBridge object from. + length_hint: The number of bytes to consume from the parser. + + Returns: + The XChainBridge object constructed from a parser. + """ + buffer = b"" + + for (name, object_type) in _TYPE_ORDER: + if object_type == AccountID: + parser.skip(1) + buffer += bytes.fromhex("14") + obj = object_type.from_parser(parser, length_hint) + buffer += bytes(obj) + + return cls(buffer) + + def to_json(self: XChainBridge) -> Union[str, Dict[Any, Any]]: + """ + Returns the JSON representation of a bridge. + + Returns: + The JSON representation of a XChainBridge. + """ + parser = BinaryParser(str(self)) + return_json = {} + for (name, object_type) in _TYPE_ORDER: + if object_type == AccountID: + parser.skip(1) + obj = object_type.from_parser(parser, None) + return_json[name] = obj.to_json() + return return_json diff --git a/xrpl/models/__init__.py b/xrpl/models/__init__.py index 059e5f355..8d7d4e63a 100644 --- a/xrpl/models/__init__.py +++ b/xrpl/models/__init__.py @@ -1,6 +1,7 @@ """Top-level exports for the models package.""" from xrpl.models import amounts, currencies, requests, transactions from xrpl.models.amounts import * # noqa: F401, F403 +from xrpl.models.auth_account import AuthAccount from xrpl.models.currencies import * # noqa: F401, F403 from xrpl.models.exceptions import XRPLModelException from xrpl.models.path import Path, PathStep @@ -8,11 +9,13 @@ from xrpl.models.response import Response from xrpl.models.transactions import * # noqa: F401, F403 from xrpl.models.transactions.pseudo_transactions import * # noqa: F401, F403 +from xrpl.models.xchain_bridge import XChainBridge __all__ = [ "XRPLModelException", "amounts", *amounts.__all__, + "AuthAccount", "currencies", *currencies.__all__, "requests", @@ -23,4 +26,5 @@ "Path", "PathStep", "Response", + "XChainBridge", ] diff --git a/xrpl/models/auth_account.py b/xrpl/models/auth_account.py new file mode 100644 index 000000000..b9182cbed --- /dev/null +++ b/xrpl/models/auth_account.py @@ -0,0 +1,70 @@ +"""Model used in AMMBid transaction.""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Dict, Type + +from xrpl.models.base_model import BaseModel +from xrpl.models.required import REQUIRED +from xrpl.models.utils import require_kwargs_on_init + + +@require_kwargs_on_init +@dataclass(frozen=True) +class AuthAccount(BaseModel): + """Represents one entry in a list of AuthAccounts used in AMMBid transaction.""" + + account: str = REQUIRED # type: ignore + """ + This field is required. + + :meta hide-value: + """ + + @classmethod + def is_dict_of_model(cls: Type[AuthAccount], dictionary: Dict[str, Any]) -> bool: + """ + Returns True if the input dictionary was derived by the `to_dict` + method of an instance of this class. In other words, True if this is + a dictionary representation of an instance of this class. + + NOTE: does not account for model inheritance, IE will only return True + if dictionary represents an instance of this class, but not if + dictionary represents an instance of a subclass of this class. + + Args: + dictionary: The dictionary to check. + + Returns: + True if dictionary is a dict representation of an instance of this + class. + """ + return ( + isinstance(dictionary, dict) + and "auth_account" in dictionary + and super().is_dict_of_model(dictionary["auth_account"]) + ) + + @classmethod + def from_dict(cls: Type[AuthAccount], value: Dict[str, Any]) -> AuthAccount: + """ + Construct a new AuthAccount from a dictionary of parameters. + + Args: + value: The value to construct the AuthAccount from. + + Returns: + A new AuthAccount object, constructed using the given parameters. + """ + if len(value) == 1 and "auth_account" in value: + return super(AuthAccount, cls).from_dict(value["auth_account"]) + return super(AuthAccount, cls).from_dict(value) + + def to_dict(self: AuthAccount) -> Dict[str, Any]: + """ + Returns the dictionary representation of a AuthAccount. + + Returns: + The dictionary representation of a AuthAccount. + """ + return {"auth_account": super().to_dict()} diff --git a/xrpl/models/base_model.py b/xrpl/models/base_model.py index 829113f6f..8c94bf1ed 100644 --- a/xrpl/models/base_model.py +++ b/xrpl/models/base_model.py @@ -9,7 +9,7 @@ from enum import Enum from typing import Any, Dict, List, Pattern, Type, TypeVar, Union, cast, get_type_hints -from typing_extensions import Final, get_args, get_origin +from typing_extensions import Final, Literal, get_args, get_origin from xrpl.models.exceptions import XRPLModelException from xrpl.models.required import REQUIRED @@ -32,8 +32,18 @@ _CAMEL_TO_SNAKE_CASE_REGEX: Final[Pattern[str]] = re.compile( f"(?:{_CAMEL_CASE_LEADING_LOWER}|{_CAMEL_CASE_ABBREVIATION}|{_CAMEL_CASE_TYPICAL})" ) -# used for converting special substrings inside CamelCase fields -SPECIAL_CAMELCASE_STRINGS = ["NFToken"] + +# This is used to make exceptions when converting dictionary keys to xrpl JSON +# keys. We snake case keys, but some keys are abbreviations. +ABBREVIATIONS: Final[Dict[str, str]] = { + "amm": "AMM", + "id": "ID", + "lp": "LP", + "nftoken": "NFToken", + "unl": "UNL", + "uri": "URI", + "xchain": "XChain", +} BM = TypeVar("BM", bound="BaseModel") # any type inherited from BaseModel @@ -46,7 +56,7 @@ def _key_to_json(field: str) -> str: 3. 'URI' becomes 'uri' """ # convert all special CamelCase substrings to capitalized strings - for spec_str in SPECIAL_CAMELCASE_STRINGS: + for spec_str in ABBREVIATIONS.values(): if spec_str in field: field = field.replace(spec_str, spec_str.capitalize()) @@ -167,6 +177,10 @@ def _from_dict_single_param( # expected an object, received the correct object return param_value + if get_origin(param_type) == Literal: + if param_value in get_args(param_type): + return param_value + if ( isinstance(param_type, type) and issubclass(param_type, Enum) diff --git a/xrpl/models/currencies/__init__.py b/xrpl/models/currencies/__init__.py index 256b2b203..f883d153c 100644 --- a/xrpl/models/currencies/__init__.py +++ b/xrpl/models/currencies/__init__.py @@ -4,11 +4,13 @@ formats are different. """ from xrpl.models.currencies.currency import Currency +from xrpl.models.currencies.issue import Issue from xrpl.models.currencies.issued_currency import IssuedCurrency from xrpl.models.currencies.xrp import XRP __all__ = [ "Currency", + "Issue", "IssuedCurrency", "XRP", ] diff --git a/xrpl/models/currencies/issue.py b/xrpl/models/currencies/issue.py new file mode 100644 index 000000000..93fb4c41d --- /dev/null +++ b/xrpl/models/currencies/issue.py @@ -0,0 +1,49 @@ +""" +Specifies an Issue. +This format is used for AMM and sidechain requests. +""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Dict, Optional + +from xrpl.constants import HEX_CURRENCY_REGEX, ISO_CURRENCY_REGEX +from xrpl.models.base_model import BaseModel +from xrpl.models.required import REQUIRED +from xrpl.models.utils import require_kwargs_on_init + + +def _is_valid_currency(candidate: str) -> bool: + return bool( + ISO_CURRENCY_REGEX.fullmatch(candidate) + or HEX_CURRENCY_REGEX.fullmatch(candidate) + ) + + +@require_kwargs_on_init +@dataclass(frozen=True) +class Issue(BaseModel): + """ + Specifies an Issue. + This format is used for AMM and sidechain requests. + """ + + currency: str = REQUIRED # type: ignore + """ + This field is required. + + :meta hide-value: + """ + + issuer: Optional[str] = None + """ + The issuer of the currency. None if XRP is currency. + """ + + def _get_errors(self: Issue) -> Dict[str, str]: + errors = super()._get_errors() + if self.issuer is not None and self.currency.upper() == "XRP": + errors["currency"] = "Currency must not be XRP when issuer is set" + elif not _is_valid_currency(self.currency): + errors["currency"] = f"Invalid currency {self.currency}" + return errors diff --git a/xrpl/models/flags.py b/xrpl/models/flags.py index 78f4de22d..8cf3c86a3 100644 --- a/xrpl/models/flags.py +++ b/xrpl/models/flags.py @@ -21,6 +21,22 @@ "asf_require_auth": 0x00000002, "asf_require_dest": 0x00000001, }, + "AMMDeposit": { + "tf_lp_token": 0x00010000, + "tf_single_asset": 0x00080000, + "tf_two_asset": 0x00100000, + "tf_one_asset_lp_token": 0x00200000, + "tf_limit_lp_token": 0x00400000, + }, + "AMMWithdraw": { + "tf_lp_token": 0x00010000, + "tf_withdraw_all": 0x00020000, + "tf_one_asset_withdraw_all": 0x00040000, + "tf_single_asset": 0x00080000, + "tf_two_asset": 0x00100000, + "tf_one_asset_lp_token": 0x00200000, + "tf_limit_lp_token": 0x00400000, + }, "NFTokenCreateOffer": { "tf_sell_token": 0x00000001, }, diff --git a/xrpl/models/requests/__init__.py b/xrpl/models/requests/__init__.py index ae575b383..adde2698f 100644 --- a/xrpl/models/requests/__init__.py +++ b/xrpl/models/requests/__init__.py @@ -1,4 +1,5 @@ """Request models.""" +from xrpl.models.auth_account import AuthAccount from xrpl.models.path import PathStep from xrpl.models.requests.account_channels import AccountChannels from xrpl.models.requests.account_currencies import AccountCurrencies @@ -8,6 +9,7 @@ from xrpl.models.requests.account_objects import AccountObjects, AccountObjectType from xrpl.models.requests.account_offers import AccountOffers from xrpl.models.requests.account_tx import AccountTx +from xrpl.models.requests.amm_info import AMMInfo from xrpl.models.requests.book_offers import BookOffers from xrpl.models.requests.channel_authorize import ChannelAuthorize from xrpl.models.requests.channel_verify import ChannelVerify @@ -52,6 +54,8 @@ "AccountObjectType", "AccountOffers", "AccountTx", + "AMMInfo", + "AuthAccount", "BookOffers", "ChannelAuthorize", "ChannelVerify", diff --git a/xrpl/models/requests/account_objects.py b/xrpl/models/requests/account_objects.py index b2a116c87..1d7b2e7c8 100644 --- a/xrpl/models/requests/account_objects.py +++ b/xrpl/models/requests/account_objects.py @@ -21,12 +21,14 @@ class AccountObjectType(str, Enum): CHECK = "check" DEPOSIT_PREAUTH = "deposit_preauth" ESCROW = "escrow" + NFT_OFFER = "nft_offer" OFFER = "offer" PAYMENT_CHANNEL = "payment_channel" SIGNER_LIST = "signer_list" STATE = "state" TICKET = "ticket" - NFT_OFFER = "nft_offer" + XCHAIN_CREATE_ACCOUNT_CLAIM_ID = "xchain_create_account_claim_id" + XCHAIN_CLAIM_ID = "xchain_claim_id" @require_kwargs_on_init diff --git a/xrpl/models/requests/amm_info.py b/xrpl/models/requests/amm_info.py new file mode 100644 index 000000000..2ac28d321 --- /dev/null +++ b/xrpl/models/requests/amm_info.py @@ -0,0 +1,36 @@ +"""This request retrieves information about an AMM instance.""" +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Dict + +from xrpl.models.currencies.issue import Issue +from xrpl.models.requests.request import Request, RequestMethod +from xrpl.models.required import REQUIRED +from xrpl.models.utils import require_kwargs_on_init + + +@require_kwargs_on_init +@dataclass(frozen=True) +class AMMInfo(Request): + """ + This request retrieves information about an AMM instance. + + Must provide Asset and Asset2 params. + """ + + asset: Issue = REQUIRED # type: ignore + """ + Specifies one of the pool assets (XRP or token) of the AMM instance. + """ + + asset2: Issue = REQUIRED # type: ignore + """ + Specifies the other pool asset of the AMM instance. + """ + + method: RequestMethod = field(default=RequestMethod.AMM_INFO, init=False) + + def _get_errors(self: AMMInfo) -> Dict[str, str]: + errors = super()._get_errors() + return errors diff --git a/xrpl/models/requests/channel_authorize.py b/xrpl/models/requests/channel_authorize.py index 61a50ce36..77364dd53 100644 --- a/xrpl/models/requests/channel_authorize.py +++ b/xrpl/models/requests/channel_authorize.py @@ -1,6 +1,6 @@ """ The channel_authorize method creates a signature that can -be used to redeem a specific amount of XRP from a payment channel. +be used to redeem a specific amount from a payment channel. Warning: Do not send secret keys to untrusted servers or through unsecured network connections. (This includes the secret, seed, seed_hex, or passphrase fields of this @@ -18,6 +18,7 @@ from typing import Dict, Optional from xrpl.constants import CryptoAlgorithm +from xrpl.models.amounts import Amount from xrpl.models.requests.request import Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.utils import require_kwargs_on_init @@ -28,7 +29,7 @@ class ChannelAuthorize(Request): """ The channel_authorize method creates a signature that can - be used to redeem a specific amount of XRP from a payment channel. + be used to redeem a specific amount from a payment channel. Warning: Do not send secret keys to untrusted servers or through unsecured network connections. (This includes the secret, seed, seed_hex, or passphrase fields of @@ -50,7 +51,7 @@ class ChannelAuthorize(Request): :meta hide-value: """ - amount: str = REQUIRED # type: ignore + amount: Amount = REQUIRED # type: ignore """ This field is required. diff --git a/xrpl/models/requests/channel_verify.py b/xrpl/models/requests/channel_verify.py index a88eb64d3..c15143aef 100644 --- a/xrpl/models/requests/channel_verify.py +++ b/xrpl/models/requests/channel_verify.py @@ -1,10 +1,11 @@ """ The channel_verify method checks the validity of a -signature that can be used to redeem a specific amount of -XRP from a payment channel. +signature that can be used to redeem a specific amount +from a payment channel. """ from dataclasses import dataclass, field +from xrpl.models.amounts import Amount from xrpl.models.requests.request import Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.utils import require_kwargs_on_init @@ -15,8 +16,8 @@ class ChannelVerify(Request): """ The channel_verify method checks the validity of a - signature that can be used to redeem a specific amount of - XRP from a payment channel. + signature that can be used to redeem a specific amount + from a payment channel. """ method: RequestMethod = field(default=RequestMethod.CHANNEL_VERIFY, init=False) @@ -27,7 +28,7 @@ class ChannelVerify(Request): :meta hide-value: """ - amount: str = REQUIRED # type: ignore + amount: Amount = REQUIRED # type: ignore """ This field is required. diff --git a/xrpl/models/requests/ledger_entry.py b/xrpl/models/requests/ledger_entry.py index b409e2cbe..3c9ba7eed 100644 --- a/xrpl/models/requests/ledger_entry.py +++ b/xrpl/models/requests/ledger_entry.py @@ -11,6 +11,7 @@ from typing import Dict, List, Optional, Union from xrpl.models.base_model import BaseModel +from xrpl.models.currencies import Currency from xrpl.models.requests.request import Request, RequestMethod from xrpl.models.required import REQUIRED from xrpl.models.utils import require_kwargs_on_init @@ -112,7 +113,7 @@ class Offer(BaseModel): @require_kwargs_on_init @dataclass(frozen=True) class RippleState(BaseModel): - """Required fields for requesting a RippleState.""" + """Required fields for requesting a RippleState if not querying by object ID.""" accounts: List[str] = REQUIRED # type: ignore """ @@ -132,10 +133,7 @@ class RippleState(BaseModel): @require_kwargs_on_init @dataclass(frozen=True) class Ticket(BaseModel): - """ - Required fields for requesting a Ticket, if not querying by - object ID. - """ + """Required fields for requesting a Ticket if not querying by object ID.""" owner: str = REQUIRED # type: ignore """ @@ -152,6 +150,33 @@ class Ticket(BaseModel): """ +@require_kwargs_on_init +@dataclass(frozen=True) +class XChainClaimID(BaseModel): + """Required fields for requesting an XChainClaimID if not querying by object ID.""" + + locking_chain_door: str + locking_chain_issue: Currency + issuing_chain_door: str + issuing_chain_issue: Currency + xchain_claim_id: Union[int, str] + + +@require_kwargs_on_init +@dataclass(frozen=True) +class XChainCreateAccountClaimID(BaseModel): + """ + Required fields for requesting an XChainCreateAccountClaimID if not querying by + object ID. + """ + + locking_chain_door: str + locking_chain_issue: Currency + issuing_chain_door: str + issuing_chain_issue: Currency + xchain_create_account_claim_id: Union[int, str] + + @require_kwargs_on_init @dataclass(frozen=True) class LedgerEntry(Request): @@ -174,6 +199,12 @@ class LedgerEntry(Request): payment_channel: Optional[str] = None ripple_state: Optional[RippleState] = None ticket: Optional[Union[str, Ticket]] = None + bridge_account: Optional[str] = None + xchain_claim_id: Optional[Union[str, XChainClaimID]] = None + xchain_create_account_claim_id: Optional[ + Union[str, XChainCreateAccountClaimID] + ] = None + binary: bool = False ledger_hash: Optional[str] = None ledger_index: Optional[Union[str, int]] = None diff --git a/xrpl/models/requests/request.py b/xrpl/models/requests/request.py index 325ddd682..57324a473 100644 --- a/xrpl/models/requests/request.py +++ b/xrpl/models/requests/request.py @@ -76,6 +76,9 @@ class RequestMethod(str, Enum): # sidechain methods FEDERATOR_INFO = "federator_info" + # amm methods + AMM_INFO = "amm_info" + # generic unknown/unsupported request # (there is no XRPL analog, this model is specific to xrpl-py) GENERIC_REQUEST = "zzgeneric_request" diff --git a/xrpl/models/transactions/__init__.py b/xrpl/models/transactions/__init__.py index cd040e508..3a6a8112a 100644 --- a/xrpl/models/transactions/__init__.py +++ b/xrpl/models/transactions/__init__.py @@ -9,6 +9,19 @@ AccountSetFlag, AccountSetFlagInterface, ) +from xrpl.models.transactions.amm_bid import AMMBid, AuthAccount +from xrpl.models.transactions.amm_create import AMMCreate +from xrpl.models.transactions.amm_deposit import ( + AMMDeposit, + AMMDepositFlag, + AMMDepositFlagInterface, +) +from xrpl.models.transactions.amm_vote import AMMVote +from xrpl.models.transactions.amm_withdraw import ( + AMMWithdraw, + AMMWithdrawFlag, + AMMWithdrawFlagInterface, +) from xrpl.models.transactions.check_cancel import CheckCancel from xrpl.models.transactions.check_cash import CheckCash from xrpl.models.transactions.check_create import CheckCreate @@ -53,12 +66,40 @@ TrustSetFlag, TrustSetFlagInterface, ) +from xrpl.models.transactions.xchain_account_create_commit import ( + XChainAccountCreateCommit, +) +from xrpl.models.transactions.xchain_add_account_create_attestation import ( + XChainAddAccountCreateAttestation, +) +from xrpl.models.transactions.xchain_add_claim_attestation import ( + XChainAddClaimAttestation, +) +from xrpl.models.transactions.xchain_claim import XChainClaim +from xrpl.models.transactions.xchain_commit import XChainCommit +from xrpl.models.transactions.xchain_create_bridge import XChainCreateBridge +from xrpl.models.transactions.xchain_create_claim_id import XChainCreateClaimID +from xrpl.models.transactions.xchain_modify_bridge import ( + XChainModifyBridge, + XChainModifyBridgeFlag, + XChainModifyBridgeFlagInterface, +) __all__ = [ "AccountDelete", "AccountSet", "AccountSetFlag", "AccountSetFlagInterface", + "AMMBid", + "AMMCreate", + "AMMDeposit", + "AMMDepositFlag", + "AMMDepositFlagInterface", + "AMMVote", + "AMMWithdraw", + "AMMWithdrawFlag", + "AMMWithdrawFlagInterface", + "AuthAccount", "CheckCancel", "CheckCash", "CheckCreate", @@ -98,4 +139,14 @@ "TrustSet", "TrustSetFlag", "TrustSetFlagInterface", + "XChainAccountCreateCommit", + "XChainAddAccountCreateAttestation", + "XChainAddClaimAttestation", + "XChainClaim", + "XChainCommit", + "XChainCreateBridge", + "XChainCreateClaimID", + "XChainModifyBridge", + "XChainModifyBridgeFlag", + "XChainModifyBridgeFlagInterface", ] diff --git a/xrpl/models/transactions/amm_bid.py b/xrpl/models/transactions/amm_bid.py new file mode 100644 index 000000000..f4b942c8d --- /dev/null +++ b/xrpl/models/transactions/amm_bid.py @@ -0,0 +1,83 @@ +"""Model for AMMBid transaction type.""" +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Dict, List, Optional + +from typing_extensions import Final + +from xrpl.models.amounts import Amount +from xrpl.models.auth_account import AuthAccount +from xrpl.models.currencies.issue import Issue +from xrpl.models.required import REQUIRED +from xrpl.models.transactions.transaction import Transaction +from xrpl.models.transactions.types import TransactionType +from xrpl.models.utils import require_kwargs_on_init + +_MAX_AUTH_ACCOUNTS: Final[int] = 4 + + +@require_kwargs_on_init +@dataclass(frozen=True) +class AMMBid(Transaction): + """ + AMMBid is used to place a bid for the auction slot of obtaining trading advantages + of an AMM instance. + + An AMM instance auctions off the trading advantages to users (arbitrageurs) at a + discounted TradingFee for a 24 hour slot. + """ + + asset: Issue = REQUIRED # type: ignore + """ + Specifies one of the pool assets (XRP or token) of the AMM instance. + """ + + asset2: Issue = REQUIRED # type: ignore + """ + Specifies the other pool asset of the AMM instance. + """ + + bid_min: Optional[Amount] = None + """ + 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 MinSlotPrice be X and let + the slot-price computed by price scheduling algorithm be Y, then bidder always pays + the max(X, Y). + """ + + bid_max: Optional[Amount] = None + """ + This field represents the maximum price that the bidder wants to pay for the slot. + It is specified in units of LPToken. + """ + + auth_accounts: Optional[List[AuthAccount]] = None + """ + 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. + """ + + transaction_type: TransactionType = field( + default=TransactionType.AMM_BID, + init=False, + ) + + def _get_errors(self: AMMBid) -> Dict[str, str]: + return { + key: value + for key, value in { + **super()._get_errors(), + "auth_accounts": self._get_auth_accounts_error(), + }.items() + if value is not None + } + + def _get_auth_accounts_error(self: AMMBid) -> Optional[str]: + if ( + self.auth_accounts is not None + and len(self.auth_accounts) > _MAX_AUTH_ACCOUNTS + ): + return f"Length must not be greater than {_MAX_AUTH_ACCOUNTS}" + return None diff --git a/xrpl/models/transactions/amm_create.py b/xrpl/models/transactions/amm_create.py new file mode 100644 index 000000000..212be06df --- /dev/null +++ b/xrpl/models/transactions/amm_create.py @@ -0,0 +1,66 @@ +"""Model for AMMCreate transaction type.""" +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Dict, Optional + +from typing_extensions import Final + +from xrpl.models.amounts import Amount +from xrpl.models.required import REQUIRED +from xrpl.models.transactions.transaction import Transaction +from xrpl.models.transactions.types import TransactionType +from xrpl.models.utils import require_kwargs_on_init + +AMM_MAX_TRADING_FEE: Final[int] = 1000 + + +@require_kwargs_on_init +@dataclass(frozen=True) +class AMMCreate(Transaction): + """ + 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. + """ + + amount: Amount = REQUIRED # type: ignore + """ + Specifies one of the pool assets (XRP or token) of the AMM instance. + This field is required. + """ + + amount2: Amount = REQUIRED # type: ignore + """ + Specifies the other pool asset of the AMM instance. This field is required. + """ + + trading_fee: int = REQUIRED # type: ignore + """ + 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. + """ + + transaction_type: TransactionType = field( + default=TransactionType.AMM_CREATE, + init=False, + ) + + def _get_errors(self: AMMCreate) -> Dict[str, str]: + return { + key: value + for key, value in { + **super()._get_errors(), + "trading_fee": self._get_trading_fee_error(), + }.items() + if value is not None + } + + def _get_trading_fee_error(self: AMMCreate) -> Optional[str]: + if self.trading_fee < 0 or self.trading_fee > AMM_MAX_TRADING_FEE: + return f"Must be between 0 and {AMM_MAX_TRADING_FEE}" + return None diff --git a/xrpl/models/transactions/amm_deposit.py b/xrpl/models/transactions/amm_deposit.py new file mode 100644 index 000000000..57d6c5c88 --- /dev/null +++ b/xrpl/models/transactions/amm_deposit.py @@ -0,0 +1,104 @@ +"""Model for AMMDeposit transaction type.""" +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import Enum +from typing import Dict, Optional + +from xrpl.models.amounts import Amount, IssuedCurrencyAmount +from xrpl.models.currencies.issue import Issue +from xrpl.models.flags import FlagInterface +from xrpl.models.required import REQUIRED +from xrpl.models.transactions.transaction import Transaction +from xrpl.models.transactions.types import TransactionType +from xrpl.models.utils import require_kwargs_on_init + + +class AMMDepositFlag(int, Enum): + """ + Transactions of the AMMDeposit type support additional values in the Flags field. + This enum represents those options. + """ + + TF_LP_TOKEN = 0x00010000 + TF_SINGLE_ASSET = 0x00080000 + TF_TWO_ASSET = 0x00100000 + TF_ONE_ASSET_LP_TOKEN = 0x00200000 + TF_LIMIT_LP_TOKEN = 0x00400000 + + +class AMMDepositFlagInterface(FlagInterface): + """ + Transactions of the AMMDeposit type support additional values in the Flags field. + This TypedDict represents those options. + """ + + TF_LP_TOKEN: bool + TF_SINGLE_ASSET: bool + TF_TWO_ASSET: bool + TF_ONE_ASSET_LP_TOKEN: bool + TF_LIMIT_LP_TOKEN: bool + + +@require_kwargs_on_init +@dataclass(frozen=True) +class AMMDeposit(Transaction): + """ + 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 LPToken. + + The following are the recommended valid combinations: + - LPTokenOut + - Amount + - Amount and Amount2 + - Amount and LPTokenOut + - Amount and EPrice + """ + + asset: Issue = REQUIRED # type: ignore + """ + Specifies one of the pool assets (XRP or token) of the AMM instance. + """ + + asset2: Issue = REQUIRED # type: ignore + """ + Specifies the other pool asset of the AMM instance. + """ + + lp_token_out: Optional[IssuedCurrencyAmount] = None + """ + Specifies the amount of shares of the AMM instance pools that the trader + wants to redeem or trade in. + """ + + amount: Optional[Amount] = None + """ + Specifies one of the pool assets (XRP or token) of the AMM instance to + deposit more of its value. + """ + + amount2: Optional[Amount] = None + """ + Specifies the other pool asset of the AMM instance to deposit more of its + value. + """ + + e_price: Optional[Amount] = None + """ + Specifies the maximum effective-price that LPToken can be traded out. + """ + + transaction_type: TransactionType = field( + default=TransactionType.AMM_DEPOSIT, + init=False, + ) + + def _get_errors(self: AMMDeposit) -> Dict[str, str]: + errors = super()._get_errors() + if self.amount2 is not None and self.amount is None: + errors["AMMDeposit"] = "Must set `amount` with `amount2`" + elif self.e_price is not None and self.amount is None: + errors["AMMDeposit"] = "Must set `amount` with `e_price`" + elif self.lp_token_out is None and self.amount is None: + errors["AMMDeposit"] = "Must set at least `lp_token_out` or `amount`" + return errors diff --git a/xrpl/models/transactions/amm_vote.py b/xrpl/models/transactions/amm_vote.py new file mode 100644 index 000000000..3c095801e --- /dev/null +++ b/xrpl/models/transactions/amm_vote.py @@ -0,0 +1,61 @@ +"""Model for AMMVote transaction type.""" +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Dict, Optional + +from xrpl.models.currencies.issue import Issue +from xrpl.models.required import REQUIRED +from xrpl.models.transactions.amm_create import AMM_MAX_TRADING_FEE +from xrpl.models.transactions.transaction import Transaction +from xrpl.models.transactions.types import TransactionType +from xrpl.models.utils import require_kwargs_on_init + + +@require_kwargs_on_init +@dataclass(frozen=True) +class AMMVote(Transaction): + """ + AMMVote 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. + """ + + asset: Issue = REQUIRED # type: ignore + """ + Specifies one of the pool assets (XRP or token) of the AMM instance. + """ + + asset2: Issue = REQUIRED # type: ignore + """ + Specifies the other pool asset of the AMM instance. + """ + + trading_fee: int = REQUIRED # type: ignore + """ + 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. + """ + + transaction_type: TransactionType = field( + default=TransactionType.AMM_VOTE, + init=False, + ) + + def _get_errors(self: AMMVote) -> Dict[str, str]: + return { + key: value + for key, value in { + **super()._get_errors(), + "trading_fee": self._get_trading_fee_error(), + }.items() + if value is not None + } + + def _get_trading_fee_error(self: AMMVote) -> Optional[str]: + if self.trading_fee < 0 or self.trading_fee > AMM_MAX_TRADING_FEE: + return f"Must be between 0 and {AMM_MAX_TRADING_FEE}" + return None diff --git a/xrpl/models/transactions/amm_withdraw.py b/xrpl/models/transactions/amm_withdraw.py new file mode 100644 index 000000000..610e2c602 --- /dev/null +++ b/xrpl/models/transactions/amm_withdraw.py @@ -0,0 +1,108 @@ +"""Model for AMMWithdraw transaction type.""" +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import Enum +from typing import Dict, Optional + +from xrpl.models.amounts import Amount, IssuedCurrencyAmount +from xrpl.models.currencies.issue import Issue +from xrpl.models.flags import FlagInterface +from xrpl.models.required import REQUIRED +from xrpl.models.transactions.transaction import Transaction +from xrpl.models.transactions.types import TransactionType +from xrpl.models.utils import require_kwargs_on_init + + +class AMMWithdrawFlag(int, Enum): + """ + Transactions of the AMMWithdraw type support additional values in the Flags field. + This enum represents those options. + """ + + TF_LP_TOKEN = 0x00010000 + TF_WITHDRAW_ALL = 0x00020000 + TF_ONE_ASSET_WITHDRAW_ALL = 0x00040000 + TF_SINGLE_ASSET = 0x00080000 + TF_TWO_ASSET = 0x00100000 + TF_ONE_ASSET_LP_TOKEN = 0x00200000 + TF_LIMIT_LP_TOKEN = 0x00400000 + + +class AMMWithdrawFlagInterface(FlagInterface): + """ + Transactions of the AMMWithdraw type support additional values in the Flags field. + This TypedDict represents those options. + """ + + TF_LP_TOKEN: bool + TF_WITHDRAW_ALL: bool + TF_ONE_ASSET_WITHDRAW_ALL: bool + TF_SINGLE_ASSET: bool + TF_TWO_ASSET: bool + TF_ONE_ASSET_LP_TOKEN: bool + TF_LIMIT_LP_TOKEN: bool + + +@require_kwargs_on_init +@dataclass(frozen=True) +class AMMWithdraw(Transaction): + """ + 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 LPToken. + + The following are the recommended valid combinations: + - LPTokenIn + - Amount + - Amount and Amount2 + - Amount and LPTokenIn + - Amount and EPrice + """ + + asset: Issue = REQUIRED # type: ignore + """ + Specifies one of the pool assets (XRP or token) of the AMM instance. + """ + + asset2: Issue = REQUIRED # type: ignore + """ + Specifies the other pool asset of the AMM instance. + """ + + lp_token_in: Optional[IssuedCurrencyAmount] = None + """ + Specifies the amount of shares of the AMM instance pools that the trader + wants to redeem or trade in. + """ + + amount: Optional[Amount] = None + """ + 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. + """ + + amount2: Optional[Amount] = None + """ + Specifies the other pool asset that the trader wants to remove. + """ + + e_price: Optional[Amount] = None + """ + Specifies the effective-price of the token out after successful execution of + the transaction. + """ + + transaction_type: TransactionType = field( + default=TransactionType.AMM_WITHDRAW, + init=False, + ) + + def _get_errors(self: AMMWithdraw) -> Dict[str, str]: + errors = super()._get_errors() + if self.amount2 is not None and self.amount is None: + errors["AMMWithdraw"] = "Must set `amount` with `amount2`" + elif self.e_price is not None and self.amount is None: + errors["AMMWithdraw"] = "Must set `amount` with `e_price`" + return errors diff --git a/xrpl/models/transactions/escrow_cancel.py b/xrpl/models/transactions/escrow_cancel.py index a6b36e2c6..f6ae87385 100644 --- a/xrpl/models/transactions/escrow_cancel.py +++ b/xrpl/models/transactions/escrow_cancel.py @@ -13,7 +13,7 @@ class EscrowCancel(Transaction): """ Represents an `EscrowCancel `_ - transaction, which returns escrowed XRP to the sender after the Escrow has + transaction, which returns escrowed amount to the sender after the Escrow has expired. """ diff --git a/xrpl/models/transactions/escrow_create.py b/xrpl/models/transactions/escrow_create.py index b1852aa31..330d1d3fe 100644 --- a/xrpl/models/transactions/escrow_create.py +++ b/xrpl/models/transactions/escrow_create.py @@ -16,20 +16,21 @@ class EscrowCreate(Transaction): """ Represents an `EscrowCreate `_ - transaction, which locks up XRP until a specific time or condition is met. + transaction, which locks up amount until a specific time or condition is met. """ amount: Amount = REQUIRED # type: ignore """ - Amount of XRP, in drops, to deduct from the sender's balance and set - aside in escrow. This field is required. + Amount to deduct from the sender's balance and escrow. Once escrowed, the + amount can either go to the Destination address (after the FinishAfter time) + or returned to the sender (after the CancelAfter time). This field is required. :meta hide-value: """ destination: str = REQUIRED # type: ignore """ - The address that should receive the escrowed XRP when the time or + The address that should receive the escrowed amount when the time or condition is met. This field is required. :meta hide-value: @@ -51,7 +52,7 @@ class EscrowCreate(Transaction): finish_after: Optional[int] = None """ - The time, in seconds since the Ripple Epoch, when the escrowed XRP can + The time, in seconds since the Ripple Epoch, when the escrowed amount can be released to the recipient. This value is immutable; the funds cannot move until this time is reached. """ diff --git a/xrpl/models/transactions/payment_channel_claim.py b/xrpl/models/transactions/payment_channel_claim.py index a79e649c5..80fd530e6 100644 --- a/xrpl/models/transactions/payment_channel_claim.py +++ b/xrpl/models/transactions/payment_channel_claim.py @@ -3,6 +3,7 @@ from enum import Enum from typing import Optional +from xrpl.models.amounts import Amount from xrpl.models.flags import FlagInterface from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction @@ -30,14 +31,14 @@ class PaymentChannelClaimFlag(int, Enum): """ Request to close the channel. Only the channel source and destination addresses can use this flag. This flag closes the channel immediately if it has no more - XRP allocated to it after processing the current claim, or if the destination + funds allocated to it after processing the current claim, or if the destination address uses it. If the source address uses this flag when the channel still - holds XRP, this schedules the channel to close after `SettleDelay` seconds have + holds a value, this schedules the channel to close after `SettleDelay` seconds have passed. (Specifically, this sets the `Expiration` of the channel to the close time of the previous ledger plus the channel's `SettleDelay` time, unless the channel already has an earlier `Expiration` time.) If the destination address - uses this flag when the channel still holds XRP, any XRP that remains after - processing the claim is returned to the source address. + uses this flag when the channel still holds an amount, any amount that remains + after processing the claim is returned to the source address. """ @@ -59,7 +60,7 @@ class PaymentChannelClaimFlagInterface(FlagInterface): class PaymentChannelClaim(Transaction): """ Represents a `PaymentChannelClaim `_ - transaction, which claims XRP from a `payment channel + transaction, which claims an amount from a `payment channel `_, adjusts channel's expiration, or both. This transaction can be used differently depending on the transaction sender's role in the specified channel. @@ -73,16 +74,19 @@ class PaymentChannelClaim(Transaction): :meta hide-value: """ - balance: Optional[str] = None + balance: Optional[Amount] = None """ - The cumulative amount of XRP to have delivered through this channel after - processing this claim. Required unless closing the channel. + Total amount delivered by this channel after processing this claim. Required + to deliver amount. Must be more than the total amount delivered by the channel + so far, but not greater than the Amount of the signed claim. Must be provided + except when closing the channel. """ - amount: Optional[str] = None + amount: Optional[Amount] = None """ - The cumulative amount of XRP that has been authorized to deliver by the - attached claim signature. Required unless closing the channel. + The amount authorized by the Signature. This must match the amount in the signed + message. This is the cumulative amount that can be dispensed by the channel, + including amounts previously redeemed. Required unless closing the channel. """ signature: Optional[str] = None diff --git a/xrpl/models/transactions/payment_channel_create.py b/xrpl/models/transactions/payment_channel_create.py index 78cff1c7d..17f0ad6d6 100644 --- a/xrpl/models/transactions/payment_channel_create.py +++ b/xrpl/models/transactions/payment_channel_create.py @@ -16,21 +16,23 @@ class PaymentChannelCreate(Transaction): Represents a `PaymentChannelCreate `_ transaction, which creates a `payment channel `_ and funds it with - XRP. The sender of this transaction is the "source address" of the payment + an amount. The sender of this transaction is the "source address" of the payment channel. """ amount: Amount = REQUIRED # type: ignore """ - The amount of XRP, in drops, to set aside in this channel. This field is - required. + Amount to deduct from the sender's balance and set aside in this channel. + While the channel is open, the amount can only go to the Destination address. + When the channel closes, any unclaimed amount is returned to the source + address's balance. This field is required. :meta hide-value: """ destination: str = REQUIRED # type: ignore """ - The account that can receive XRP from this channel, also known as the + The account that can receive amounts from this channel, also known as the "destination address" of the channel. Cannot be the same as the sender. This field is required. diff --git a/xrpl/models/transactions/payment_channel_fund.py b/xrpl/models/transactions/payment_channel_fund.py index 5ee9e48ff..9c6d6f2cc 100644 --- a/xrpl/models/transactions/payment_channel_fund.py +++ b/xrpl/models/transactions/payment_channel_fund.py @@ -2,6 +2,7 @@ from dataclasses import dataclass, field from typing import Optional +from xrpl.models.amounts import Amount from xrpl.models.required import REQUIRED from xrpl.models.transactions.transaction import Transaction from xrpl.models.transactions.types import TransactionType @@ -13,7 +14,7 @@ class PaymentChannelFund(Transaction): """ Represents a `PaymentChannelFund `_ - transaction, adds additional XRP to an open `payment channel + transaction, adds additional amount to an open `payment channel `_, and optionally updates the expiration time of the channel. Only the source address of the channel can use this transaction. @@ -27,10 +28,9 @@ class PaymentChannelFund(Transaction): :meta hide-value: """ - amount: str = REQUIRED # type: ignore + amount: Amount = REQUIRED # type: ignore """ - The amount of XRP, in drops, to add to the channel. This field is - required. + Amount to add to the channel. Must be a positive amount. This field is required. :meta hide-value: """ diff --git a/xrpl/models/transactions/transaction.py b/xrpl/models/transactions/transaction.py index 29fb557b6..f77b7d8ab 100644 --- a/xrpl/models/transactions/transaction.py +++ b/xrpl/models/transactions/transaction.py @@ -9,7 +9,7 @@ from xrpl.core.binarycodec import encode from xrpl.models.amounts import IssuedCurrencyAmount -from xrpl.models.base_model import BaseModel +from xrpl.models.base_model import ABBREVIATIONS, BaseModel from xrpl.models.exceptions import XRPLModelException from xrpl.models.flags import check_false_flag_definition, interface_to_flag_list from xrpl.models.nested_model import NestedModel @@ -20,14 +20,12 @@ from xrpl.models.utils import require_kwargs_on_init _TRANSACTION_HASH_PREFIX: Final[int] = 0x54584E00 -# This is used to make exceptions when converting dictionary keys to xrpl JSON -# keys. We snake case keys, but some keys are abbreviations. -_ABBREVIATIONS: Final[Dict[str, str]] = { - "unl": "UNL", - "id": "ID", - "uri": "URI", - "nftoken": "NFToken", -} + +# special cases that should not be snake cased and only contain primitive members +_LOWER_CASE_MODELS: List[Type[BaseModel]] = [ + IssuedCurrencyAmount, + PathStep, +] def transaction_json_to_binary_codec_form( @@ -56,20 +54,20 @@ def _key_to_tx_json(key: str) -> str: 1. 'transaction_type' becomes 'TransactionType' 2. 'URI' becomes 'uri' - Known abbreviations (example 2 above) need to be enumerated in _ABBREVIATIONS. + Known abbreviations (example 2 above) need to be enumerated in ABBREVIATIONS. """ return "".join( [ - _ABBREVIATIONS[word] if word in _ABBREVIATIONS else word.capitalize() + ABBREVIATIONS[word] if word in ABBREVIATIONS else word.capitalize() for word in key.split("_") ] ) def _value_to_tx_json(value: XRPL_VALUE_TYPE) -> XRPL_VALUE_TYPE: - # IssuedCurrencyAmount and PathStep are special cases and should not be snake cased - # and only contain primitive members - if IssuedCurrencyAmount.is_dict_of_model(value) or PathStep.is_dict_of_model(value): + if isinstance(value, dict) and "auth_account" in value: + return _auth_account_value_to_tx_json(value) + if any([model.is_dict_of_model(value) for model in _LOWER_CASE_MODELS]): return value if isinstance(value, dict): return transaction_json_to_binary_codec_form(value) @@ -78,6 +76,13 @@ def _value_to_tx_json(value: XRPL_VALUE_TYPE) -> XRPL_VALUE_TYPE: return value +def _auth_account_value_to_tx_json(value: Dict[str, Any]) -> Dict[str, Any]: + return { + _key_to_tx_json(key): transaction_json_to_binary_codec_form(val) + for (key, val) in value.items() + } + + @require_kwargs_on_init @dataclass(frozen=True) class Memo(NestedModel): diff --git a/xrpl/models/transactions/types/transaction_type.py b/xrpl/models/transactions/types/transaction_type.py index 3b8a0c809..8470d6f22 100644 --- a/xrpl/models/transactions/types/transaction_type.py +++ b/xrpl/models/transactions/types/transaction_type.py @@ -8,6 +8,11 @@ class TransactionType(str, Enum): ACCOUNT_DELETE = "AccountDelete" ACCOUNT_SET = "AccountSet" + AMM_BID = "AMMBid" + AMM_CREATE = "AMMCreate" + AMM_DEPOSIT = "AMMDeposit" + AMM_VOTE = "AMMVote" + AMM_WITHDRAW = "AMMWithdraw" CHECK_CANCEL = "CheckCancel" CHECK_CASH = "CheckCash" CHECK_CREATE = "CheckCreate" @@ -30,3 +35,11 @@ class TransactionType(str, Enum): SIGNER_LIST_SET = "SignerListSet" TICKET_CREATE = "TicketCreate" TRUST_SET = "TrustSet" + XCHAIN_ACCOUNT_CREATE_COMMIT = "XChainAccountCreateCommit" + XCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION = "XChainAddAccountCreateAttestation" + XCHAIN_ADD_CLAIM_ATTESTATION = "XChainAddClaimAttestation" + XCHAIN_CLAIM = "XChainClaim" + XCHAIN_COMMIT = "XChainCommit" + XCHAIN_CREATE_BRIDGE = "XChainCreateBridge" + XCHAIN_CREATE_CLAIM_ID = "XChainCreateClaimID" + XCHAIN_MODIFY_BRIDGE = "XChainModifyBridge" diff --git a/xrpl/models/transactions/xchain_account_create_commit.py b/xrpl/models/transactions/xchain_account_create_commit.py new file mode 100644 index 000000000..865adc383 --- /dev/null +++ b/xrpl/models/transactions/xchain_account_create_commit.py @@ -0,0 +1,42 @@ +"""Model for a XChainAccountCreateCommit transaction type.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Dict + +from xrpl.models.required import REQUIRED +from xrpl.models.transactions.transaction import Transaction +from xrpl.models.transactions.types import TransactionType +from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.xchain_bridge import XChainBridge + + +@require_kwargs_on_init +@dataclass(frozen=True) +class XChainAccountCreateCommit(Transaction): + """Represents a XChainAccountCreateCommit transaction.""" + + xchain_bridge: XChainBridge = REQUIRED # type: ignore + + signature_reward: str = REQUIRED # type: ignore + + destination: str = REQUIRED # type: ignore + + amount: str = REQUIRED # type: ignore + + transaction_type: TransactionType = field( + default=TransactionType.XCHAIN_ACCOUNT_CREATE_COMMIT, + init=False, + ) + + def _get_errors(self: XChainAccountCreateCommit) -> Dict[str, str]: + errors = super()._get_errors() + + if self.signature_reward is not None and not self.signature_reward.isnumeric(): + errors["signature_reward"] = "`signature_reward` must be numeric." + + if self.amount is not None and not self.amount.isnumeric(): + errors["amount"] = "`amount` must be numeric." + + return errors diff --git a/xrpl/models/transactions/xchain_add_account_create_attestation.py b/xrpl/models/transactions/xchain_add_account_create_attestation.py new file mode 100644 index 000000000..6c3196a77 --- /dev/null +++ b/xrpl/models/transactions/xchain_add_account_create_attestation.py @@ -0,0 +1,48 @@ +"""Model for a XChainAddAccountCreateAttestation transaction type.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Union + +from typing_extensions import Literal + +from xrpl.models.amounts import Amount +from xrpl.models.required import REQUIRED +from xrpl.models.transactions.transaction import Transaction +from xrpl.models.transactions.types import TransactionType +from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.xchain_bridge import XChainBridge + + +@require_kwargs_on_init +@dataclass(frozen=True) +class XChainAddAccountCreateAttestation(Transaction): + """Represents a XChainAddAccountCreateAttestation transaction.""" + + xchain_bridge: XChainBridge = REQUIRED # type: ignore + + public_key: str = REQUIRED # type: ignore + + signature: str = REQUIRED # type: ignore + + other_chain_source: str = REQUIRED # type: ignore + + amount: Amount = REQUIRED # type: ignore + + attestation_reward_account: str = REQUIRED # type: ignore + + attestation_signer_account: str = REQUIRED # type: ignore + + was_locking_chain_send: Union[Literal[0], Literal[1]] = REQUIRED # type: ignore + + xchain_account_create_count: str = REQUIRED # type: ignore + + destination: str = REQUIRED # type: ignore + + signature_reward: Amount = REQUIRED # type: ignore + + transaction_type: TransactionType = field( + default=TransactionType.XCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, + init=False, + ) diff --git a/xrpl/models/transactions/xchain_add_claim_attestation.py b/xrpl/models/transactions/xchain_add_claim_attestation.py new file mode 100644 index 000000000..bb30700f6 --- /dev/null +++ b/xrpl/models/transactions/xchain_add_claim_attestation.py @@ -0,0 +1,46 @@ +"""Model for a XChainAddClaimAttestation transaction type.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Optional, Union + +from typing_extensions import Literal + +from xrpl.models.amounts import Amount +from xrpl.models.required import REQUIRED +from xrpl.models.transactions.transaction import Transaction +from xrpl.models.transactions.types import TransactionType +from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.xchain_bridge import XChainBridge + + +@require_kwargs_on_init +@dataclass(frozen=True) +class XChainAddClaimAttestation(Transaction): + """Represents a XChainAddClaimAttestation transaction.""" + + xchain_bridge: XChainBridge = REQUIRED # type: ignore + + public_key: str = REQUIRED # type: ignore + + signature: str = REQUIRED # type: ignore + + other_chain_source: str = REQUIRED # type: ignore + + amount: Amount = REQUIRED # type: ignore + + attestation_reward_account: str = REQUIRED # type: ignore + + attestation_signer_account: str = REQUIRED # type: ignore + + was_locking_chain_send: Union[Literal[0], Literal[1]] = REQUIRED # type: ignore + + xchain_claim_id: str = REQUIRED # type: ignore + + destination: Optional[str] = None + + transaction_type: TransactionType = field( + default=TransactionType.XCHAIN_ADD_CLAIM_ATTESTATION, + init=False, + ) diff --git a/xrpl/models/transactions/xchain_claim.py b/xrpl/models/transactions/xchain_claim.py new file mode 100644 index 000000000..ff8808a6d --- /dev/null +++ b/xrpl/models/transactions/xchain_claim.py @@ -0,0 +1,50 @@ +"""Model for a XChainClaim transaction type.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Dict, Optional, Union + +from xrpl.models.amounts import Amount +from xrpl.models.currencies import XRP +from xrpl.models.required import REQUIRED +from xrpl.models.transactions.transaction import Transaction +from xrpl.models.transactions.types import TransactionType +from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.xchain_bridge import XChainBridge + + +@require_kwargs_on_init +@dataclass(frozen=True) +class XChainClaim(Transaction): + """Represents a XChainClaim transaction.""" + + xchain_bridge: XChainBridge = REQUIRED # type: ignore + + xchain_claim_id: Union[int, str] = REQUIRED # type: ignore + + destination: str = REQUIRED # type: ignore + + destination_tag: Optional[int] = None + + amount: Amount = REQUIRED # type: ignore + + transaction_type: TransactionType = field( + default=TransactionType.XCHAIN_CLAIM, + init=False, + ) + + def _get_errors(self: XChainClaim) -> Dict[str, str]: + errors = super()._get_errors() + + bridge = self.xchain_bridge + currency = XRP() if isinstance(self.amount, str) else self.amount.to_currency() + if ( + currency != bridge.locking_chain_issue + and currency != bridge.issuing_chain_issue + ): + errors[ + "amount" + ] = "Amount must match either locking chain issue or issuing chain issue." + + return errors diff --git a/xrpl/models/transactions/xchain_commit.py b/xrpl/models/transactions/xchain_commit.py new file mode 100644 index 000000000..7d23ca870 --- /dev/null +++ b/xrpl/models/transactions/xchain_commit.py @@ -0,0 +1,30 @@ +"""Model for a XChainCommit transaction type.""" + +from dataclasses import dataclass, field +from typing import Optional, Union + +from xrpl.models.amounts import Amount +from xrpl.models.required import REQUIRED +from xrpl.models.transactions.transaction import Transaction +from xrpl.models.transactions.types import TransactionType +from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.xchain_bridge import XChainBridge + + +@require_kwargs_on_init +@dataclass(frozen=True) +class XChainCommit(Transaction): + """Represents a XChainCommit transaction.""" + + xchain_bridge: XChainBridge = REQUIRED # type: ignore + + xchain_claim_id: Union[int, str] = REQUIRED # type: ignore + + amount: Amount = REQUIRED # type: ignore + + other_chain_destination: Optional[str] = None + + transaction_type: TransactionType = field( + default=TransactionType.XCHAIN_COMMIT, + init=False, + ) diff --git a/xrpl/models/transactions/xchain_create_bridge.py b/xrpl/models/transactions/xchain_create_bridge.py new file mode 100644 index 000000000..c89d668d9 --- /dev/null +++ b/xrpl/models/transactions/xchain_create_bridge.py @@ -0,0 +1,71 @@ +"""Model for a XChainCreateBridge transaction type.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Dict, Optional + +from xrpl.models.currencies import XRP +from xrpl.models.required import REQUIRED +from xrpl.models.transactions.transaction import Transaction +from xrpl.models.transactions.types import TransactionType +from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.xchain_bridge import XChainBridge + + +@require_kwargs_on_init +@dataclass(frozen=True) +class XChainCreateBridge(Transaction): + """Represents a XChainCreateBridge transaction.""" + + xchain_bridge: XChainBridge = REQUIRED # type: ignore + + signature_reward: str = REQUIRED # type: ignore + + min_account_create_amount: Optional[str] = None + + transaction_type: TransactionType = field( + default=TransactionType.XCHAIN_CREATE_BRIDGE, + init=False, + ) + + def _get_errors(self: XChainCreateBridge) -> Dict[str, str]: + errors = super()._get_errors() + + bridge = self.xchain_bridge + + if bridge.locking_chain_door == bridge.issuing_chain_door: + errors[ + "xchain_bridge" + ] = "Cannot have the same door accounts on the locking and issuing chain." + + if self.account not in [bridge.locking_chain_door, bridge.issuing_chain_door]: + errors[ + "account" + ] = "Account must be either locking chain door or issuing chain door." + + if (bridge.locking_chain_issue == XRP()) != ( + bridge.issuing_chain_issue == XRP() + ): + errors["issue"] = "Bridge must be XRP-XRP or IOU-IOU." + + if ( + self.min_account_create_amount is not None + and bridge.locking_chain_issue != XRP() + ): + errors[ + "min_account_create_amount" + ] = "Cannot have MinAccountCreateAmount if bridge is IOU-IOU." + + if self.signature_reward is not None and not self.signature_reward.isnumeric(): + errors["signature_reward"] = "signature_reward must be numeric." + + if ( + self.min_account_create_amount is not None + and not self.min_account_create_amount.isnumeric() + ): + errors[ + "min_account_create_amount_value" + ] = "min_account_create_amount must be numeric." + + return errors diff --git a/xrpl/models/transactions/xchain_create_claim_id.py b/xrpl/models/transactions/xchain_create_claim_id.py new file mode 100644 index 000000000..859b3947c --- /dev/null +++ b/xrpl/models/transactions/xchain_create_claim_id.py @@ -0,0 +1,43 @@ +"""Model for a XChainCreateClaimID transaction type.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Dict + +from xrpl.core.addresscodec import is_valid_classic_address +from xrpl.models.required import REQUIRED +from xrpl.models.transactions.transaction import Transaction +from xrpl.models.transactions.types import TransactionType +from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.xchain_bridge import XChainBridge + + +@require_kwargs_on_init +@dataclass(frozen=True) +class XChainCreateClaimID(Transaction): + """Represents a XChainCreateClaimID transaction.""" + + xchain_bridge: XChainBridge = REQUIRED # type: ignore + + signature_reward: str = REQUIRED # type: ignore + + other_chain_source: str = REQUIRED # type: ignore + + transaction_type: TransactionType = field( + default=TransactionType.XCHAIN_CREATE_CLAIM_ID, + init=False, + ) + + def _get_errors(self: XChainCreateClaimID) -> Dict[str, str]: + errors = super()._get_errors() + + if self.signature_reward is not None and not self.signature_reward.isnumeric(): + errors["signature_reward"] = "`signature_reward` must be numeric." + + if not is_valid_classic_address(self.other_chain_source): + errors[ + "other_chain_source" + ] = "`other_chain_source` must be a valid XRPL address." + + return errors diff --git a/xrpl/models/transactions/xchain_modify_bridge.py b/xrpl/models/transactions/xchain_modify_bridge.py new file mode 100644 index 000000000..ae8decc30 --- /dev/null +++ b/xrpl/models/transactions/xchain_modify_bridge.py @@ -0,0 +1,86 @@ +"""Model for a XChainModifyBridge transaction type.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import Enum +from typing import Dict, Optional + +from xrpl.models.currencies import XRP +from xrpl.models.flags import FlagInterface +from xrpl.models.required import REQUIRED +from xrpl.models.transactions.transaction import Transaction +from xrpl.models.transactions.types import TransactionType +from xrpl.models.utils import require_kwargs_on_init +from xrpl.models.xchain_bridge import XChainBridge + + +class XChainModifyBridgeFlag(int, Enum): + """ + Transactions of the XChainModifyBridge type support additional values in the Flags + field. This enum represents those options. + """ + + TF_CLEAR_ACCOUNT_CREATE_AMOUNT = 0x00010000 + + +class XChainModifyBridgeFlagInterface(FlagInterface): + """ + Transactions of the XChainModifyBridge type support additional values in the Flags + field. This TypedDict represents those options. + """ + + TF_CLEAR_ACCOUNT_CREATE_AMOUNT: bool + + +@require_kwargs_on_init +@dataclass(frozen=True) +class XChainModifyBridge(Transaction): + """Represents a XChainModifyBridge transaction.""" + + xchain_bridge: XChainBridge = REQUIRED # type: ignore + + signature_reward: Optional[str] = None + + min_account_create_amount: Optional[str] = None + + transaction_type: TransactionType = field( + default=TransactionType.XCHAIN_MODIFY_BRIDGE, + init=False, + ) + + def _get_errors(self: XChainModifyBridge) -> Dict[str, str]: + errors = super()._get_errors() + + bridge = self.xchain_bridge + + if self.signature_reward is None and self.min_account_create_amount is None: + errors[ + "xchain_modify_bridge" + ] = "Must either change signature_reward or min_account_create_amount." + + if self.account not in [bridge.locking_chain_door, bridge.issuing_chain_door]: + errors[ + "account" + ] = "Account must be either locking chain door or issuing chain door." + + if self.signature_reward is not None and not self.signature_reward.isnumeric(): + errors["signature_reward"] = "`signature_reward` must be numeric." + + if ( + self.min_account_create_amount is not None + and bridge.locking_chain_issue != XRP() + ): + errors[ + "min_account_create_amount" + ] = "Cannot have MinAccountCreateAmount if bridge is IOU-IOU." + + if ( + self.min_account_create_amount is not None + and not self.min_account_create_amount.isnumeric() + ): + errors[ + "min_account_create_amount_value" + ] = "`min_account_create_amount` must be numeric." + + return errors diff --git a/xrpl/models/xchain_bridge.py b/xrpl/models/xchain_bridge.py new file mode 100644 index 000000000..fd1d158d9 --- /dev/null +++ b/xrpl/models/xchain_bridge.py @@ -0,0 +1,20 @@ +"""A XChainBridge represents a cross-chain bridge.""" + +from __future__ import annotations + +from dataclasses import dataclass + +from xrpl.models.base_model import BaseModel +from xrpl.models.currencies import Currency +from xrpl.models.utils import require_kwargs_on_init + + +@require_kwargs_on_init +@dataclass(frozen=True) +class XChainBridge(BaseModel): + """A XChainBridge represents a cross-chain bridge.""" + + locking_chain_door: str + locking_chain_issue: Currency + issuing_chain_door: str + issuing_chain_issue: Currency diff --git a/xrpl/wallet/main.py b/xrpl/wallet/main.py index 29c42dabc..44b0bc6b0 100644 --- a/xrpl/wallet/main.py +++ b/xrpl/wallet/main.py @@ -39,6 +39,7 @@ def __init__( """ pk, sk = derive_keypair(self.seed, algorithm=algorithm) + self.algorithm = algorithm self.public_key = pk """ The public key that is used to identify this wallet's signatures, as