diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 459fb7b..38b9268 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -37,12 +37,10 @@ jobs: with: dotnet-version: ${{ env.DOTNET_VERSION }} - - name: Set release outputs + - name: Set release params id: release-params run: | - VERSION_TAG=$(git describe --tags --abbrev=0) - RELEASE_NAME="cscli-$VERSION_TAG-${{ matrix.target }}" - echo "::set-output name=release_name::$RELEASE_NAME" + echo "::set-output name=release_name::cscli-$(git describe --tags --abbrev=0)-${{ matrix.target }}" - name: Build shell: bash diff --git a/README.md b/README.md index 7f0264d..c5e18ae 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,19 @@ # cscli ## Goals -A cross-platform CLI for building and interacting with [Cardano](https://developers.cardano.org/) wallet primitives (i.e. recovery-phrases, keys, addresses and transactions) -using .NET native types built on top of [CardanoSharp](https://github.com/CardanoSharp/cardanosharp-wallet). +A lightweight cross-platform CLI for [Cardano](https://developers.cardano.org/) using .NET native types and cryptographic libraries built on top of [CardanoSharp](https://github.com/CardanoSharp/cardanosharp-wallet) and [Koios](https://api.koios.rest/). It supports the following features out of the box: + - Building and serialising wallet primitives (i.e. recovery-phrases, keys, addresses and transactions) + - Live querying of accounts, addresses, transactions and native assets across both Testnet and Mainnet networks + - Submitting transactions to the Testnet or Mainnet network + - Cryptographic and encoding transformations (blake2b, bech32, etc.) +Why would you use `cscli` in addition to `cardano-cli`, `cardano-address`, `cardano-wallet` and a host of other tools? ### Advantages - - Easy recovery-phrase (aka mnemonic) based key and address derivation for [Hierarchical Deterministic (HD) wallets](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki), perfect for cold key/address/tx management and storage + - Simple installation and powerful commands with **no dependencies** on a local full node or other tools/sdks + - Easy recovery-phrase (aka mnemonic) based key and address derivation for [Hierarchical Deterministic (HD) wallets](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki), perfect for offline management - [Passphrase](https://vault12.com/securemycrypto/crypto-security-basics/what-is-a-passphrase/passphrases-increase-your-protection-and-your-risk) support for additional root key security - - International multi-language support for [recovery-phrases](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) - - Generates compatible outputs for `cardano-cli` and `cardano-addresses` without any additional dependencies + - [International multi-language](https://github.com/CardanoSharp/cardanosharp-wallet/tree/main/CardanoSharp.Wallet/Words) support for [recovery-phrases](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) + - Generates compatible outputs for `cardano-cli`, `cardano-address` and `cardano-wallet` ## Installation @@ -37,13 +42,13 @@ dotnet restore dotnet build --no-restore -c Release dotnet test --no-build -c Release dotnet publish --no-build Src/ConsoleTool/CsCli.ConsoleTool.csproj -c Release -o release --nologo -.\release\CsCli.ConsoleTool.exe +.\release\CsCli.ConsoleTool.exe wallet recovery-phrase generate ``` Or directly building and running with `dotnet run` ```console cd Src/ConsoleTool -dotnet run --version +dotnet run wallet recovery-phrase generate ``` Or build, test and install the global tool based on local source @@ -52,7 +57,7 @@ dotnet restore dotnet build --no-restore dotnet test --no-build dotnet pack --no-build Src/ConsoleTool/CsCli.ConsoleTool.csproj -o nupkg -c Release -dotnet tool install --global --add-source ./nupkg cscli --version 0.0.6-local-branch.1 +dotnet tool install --global --add-source ./nupkg cscli --version 0.1.0-local-branch.1 ``` @@ -63,35 +68,47 @@ dotnet tool install --global --add-source ./nupkg cscli --version 0.0.6-local-br ### Overview and Help ```console $ cscli --help -cscli v0.0.6 -A cross-platform tool for building and interacting with Cardano wallet primitives (i.e. recovery-phrases, keys, addresses and transactions). -Please see https://github.com/CardanoSharp/cscli from more detailed documentation. +cscli v0.1.0 +A lightweight cross-platform tool for building and serialising Cardano wallet entities (i.e. recovery-phrases, keys, addresses and transactions), querying the chain and submitting transactions to the testnet or mainnet networks. Please refer to https://github.com/CardanoSharp/cscli for further documentation. USAGE: cscli (OPTION | COMMAND) -Available options: +Options: -v, --version Show the cscli version -h, --help Show this help text -Available commands: +Wallet commands: wallet recovery-phrase generate --size [--language ] wallet key root derive --recovery-phrase "" [--language ] [--passphrase ""] - wallet key stake derive --recovery-phrase "" [--language ] [--passphrase ""] [--account-index ] [--address-index ] [--verification-key-file ] [--signing-key-file ] - wallet key payment derive --recovery-phrase "" [--language ] [--passphrase ""] [--account-index ] [--address-index ] [--verification-key-file ] [--signing-key-file ] - wallet key policy derive --recovery-phrase "" [--language ] [--passphrase ""] [--policy-index ] [--verification-key-file ] [--signing-key-file ] - wallet address stake derive --recovery-phrase "" --network-type [--language ] [--passphrase ""] [--account-index ] [--address-index ] - wallet address payment derive --recovery-phrase "" --network-type --payment-address-type [--language ] [--passphrase ""] [--account-index ] [--address-index ] [--stake-account-index ] [--stake-address-index ] - bech32 encode --value "" --prefix "" - bech32 decode --value "" - blake2b hash --value "" --length + wallet key stake derive --recovery-phrase "" [--account-index ] [--address-index ] + wallet key payment derive --recovery-phrase "" [--account-index ] [--address-index ] + wallet key policy derive --recovery-phrase "" [--policy-index ] + wallet address stake derive --recovery-phrase "" --network [--account-index ] [--address-index ] + wallet address payment derive --recovery-phrase "" --network --payment-address-type [--account-index ] [--address-index ] [--stake-account-index ] [--stake-address-index ] + +Query commands: + query tip --network + query protocol-parameters --network + query info account --network [--stake-address ][--address ] + query asset account --network --stake-address + query info address --network --address + query info transaction --network --tx-id + +Transaction Commands: + transaction submit --network --cbor-hex + +Encoding/Cryptography Commands: + bech32 encode --value --prefix + bech32 decode --value + blake2b hash --value [--length ] Arguments: ::= 9 | 12 | 15 | 18 | 21 | 24(default) ::= english(default)|chinesesimplified|chinesetraditional|french|italian|japanese|korean|spanish|czech|portuguese ::= 0(default) | 1 | .. | 2147483647 - ::= testnet | mainnet + ::= testnet | mainnet ::= enterprise | base - ::= 160 | 224 | 256 | 512 + ::= 160 | 224(default) | 256 | 512 ``` ### Generate Recovery Phrase @@ -100,6 +117,15 @@ $ cscli wallet recovery-phrase generate | tee phrase.prv more enjoy seminar food bench online render dry essence indoor crazy page eight fragile mango zoo burger exhibit crouch drop rocket property alter uphold ``` +
+ Generating a recovery phrase in Spanish + +```console +$ cscli wallet recovery-phrase generate --language spanish | tee phrase.es.prv +solución aborto víspera puma molino ático ética feroz hacer orador salero baba carbón lonja texto sanción sobre pasar iris masa vacuna diseño pez playa +``` +
+ ### Derive Root Key ```console $ cscli wallet key root derive --recovery-phrase "$(cat phrase.prv)" | tee root.xsk @@ -139,6 +165,26 @@ $ cat pay_0_0.vkey } ``` +
+ Payment Key from Spanish recovery-phrase with custom passphrase + +```console +$ cscli wallet key payment derive --language spanish --recovery-phrase "$(cat phrase.es.prv)" --passphrase "/\\/\\`/ |\\|4/\\/\\3 !5 02`//\\/\\4|\\||)!45, | ### Derive Stake Key ```console @@ -177,7 +223,7 @@ $ cat stake_0_0.vkey ### Derive Stake/Reward Address ```console -$ cscli wallet address stake derive --recovery-phrase "$(cat phrase.prv)" --network-tag mainnet | tee stake_0_0.addr +$ cscli wallet address stake derive --recovery-phrase "$(cat phrase.prv)" --network mainnet | tee stake_0_0.addr stake1u9wqktpz964g6jaemt5wr5tspy9cqxpdkw98d022d85kxxc2n2yxj ``` @@ -185,38 +231,55 @@ stake1u9wqktpz964g6jaemt5wr5tspy9cqxpdkw98d022d85kxxc2n2yxj Stake Address with Custom Indexes ```console -$ cscli wallet address stake derive --recovery-phrase "$(cat phrase.prv)" --network-tag mainnet --account-index 1 --address-index 7 | tee stake_1_7.addr +$ cscli wallet address stake derive --recovery-phrase "$(cat phrase.prv)" --network mainnet --account-index 1 --address-index 7 | tee stake_1_7.addr stake1u87phtdn9shvp39c44elyfdduuqg7wz072vs0vjvc20hvaqym7xan ```
+
+ Stake Address from Spanish recovery-phrase with custom passphrase + +```console +$ cscli wallet address stake derive --language spanish --recovery-phrase "$(cat phrase.es.prv)" --passphrase "/\\/\\`/ |\\|4/\\/\\3 !5 02`//\\/\\4|\\||)!45, | ### Derive Payment Enterprise Address ```console -$ cscli wallet address payment derive --recovery-phrase "$(cat phrase.prv)" --payment-address-type enterprise --network-tag mainnet | tee pay_0_0.addr +$ cscli wallet address payment derive --recovery-phrase "$(cat phrase.prv)" --payment-address-type enterprise --network mainnet | tee pay_0_0.addr addr1vy5zuhh9685fup86syuzmu3e6eengzv8t46mfqxg086cvqqrukl6w ```
Payment Enterprise Address with Custom Indexes ```console -$ cscli wallet address payment derive --recovery-phrase "$(cat phrase.prv)" --payment-address-type enterprise --network-tag mainnet --account-index 1387 --address-index 12 | tee pay_1387_12.addr +$ cscli wallet address payment derive --recovery-phrase "$(cat phrase.prv)" --payment-address-type enterprise --network mainnet --account-index 1387 --address-index 12 | tee pay_1387_12.addr addr1vy3y89nnzdqs4fmqv49fmpqw24hjheen3ce7tch082hh6xcc8pzd9 ``` +
### Derive Payment Base Address ```console -$ cscli wallet address payment derive --recovery-phrase "$(cat phrase.prv)" --payment-address-type base --network-tag mainnet | tee pay_0_0_0_0.addr +$ cscli wallet address payment derive --recovery-phrase "$(cat phrase.prv)" --payment-address-type base --network mainnet | tee pay_0_0_0_0.addr addr1qy5zuhh9685fup86syuzmu3e6eengzv8t46mfqxg086cvqzupvkzyt42349mnkhgu8ghqzgtsqvzmvu2w675560fvvdspma4ht ```
Payment Base Address with Custom Indexes ```console -$ cscli wallet address payment derive --recovery-phrase "$(cat phrase.prv)" --payment-address-type base --network-tag mainnet --account-index 1387 --address-index 12 --stake-account-index 968 --stake-address-index 83106 | tee pay_1387_12_968_83106.addr +$ cscli wallet address payment derive --recovery-phrase "$(cat phrase.prv)" --payment-address-type base --network mainnet --account-index 1387 --address-index 12 --stake-account-index 968 --stake-address-index 83106 | tee pay_1387_12_968_83106.addr addr1qy3y89nnzdqs4fmqv49fmpqw24hjheen3ce7tch082hh6x7nwwgg06dngunf9ea4rd7mu9084sd3km6z56rqd7e04ylslhzn9h ```
+
+ Payment Base Address from Spanish recovery-phrase with custom passphrase + +```console +$ cscli wallet address payment derive --language spanish --recovery-phrase "$(cat phrase.es.prv)" --passphrase "/\\/\\`/ |\\|4/\\/\\3 !5 02`//\\/\\4|\\||)!45, | ### Derive Policy Key ```console @@ -252,6 +315,126 @@ $ cat policy_0.vkey ```
+### Submit Transaction +```console +$ cscli transaction submit --network testnet --cbor-hex 84a600818258207f1d24706e65b3eaef608d6ba5adf8b2bf69254bbd1e1532fa7c601a1d6aca3d000d8001828258390058b5a28ad45b3e2e5ea96d46e071a12d4d8cb5498cf0285051b3c30297660614ab2241b986bb1222f0373a359509a79610ac1210ad11e3031a05f5e10082581d60f3a76db98805ebfb391d8a7fa176e0a4da4d20955c47a5d35936353c1a35a23dbb021a0002ab45031a03831a6f0e80a1008182582047a69a1a41541c00a1e62ab8d78c1870e4f04c0507530b90c7dfde2a144d0cfa58406f50cd131250768a3b707e5eb5797e1dc519157e8c7ac27a72ac472fb546bc4604d3b51b2460e4517e28aea5fd0d19ddf8d95d9bf223e59f0306db0a7794d40af5f6 +5c9f1456a2f7cdf30c12d569ede3f298b377115a63dc0cef791e692dbe4be26b +``` + +### Query Tip +```console +$ cscli query tip --network mainnet +{ + "hash": "ae5514780cb47a920cd219a4635a54c9ce517a89c65889325c1fd6d166cfdcaa", + "epoch_no": 339, + "abs_slot": 61499734, + "epoch_slot": 414934, + "block_no": 7271096, + "block_time": "2022-05-20T17:00:25" +} +``` + +### Query Protocol Parameters +```console +$ cscli query protocol-parameters --network mainnet +{ + "epoch_no": 339, + "min_fee_a": 44, + "min_fee_b": 155381, + ... + "coins_per_utxo_word": 34482 +} +``` + +### Query Account Info +```console +$ cscli query info account --network mainnet --stake-address stake1uyrx65wjqjgeeksd8hptmcgl5jfyrqkfq0xe8xlp367kphsckq250 +[ + { + "status": "registered", + "delegated_pool": "pool14wk2m2af7y4gk5uzlsmsunn7d9ppldvcxxa5an9r5ywek8330fg", + "total_balance": "1126364036992", + "utxo": "1120067931255", + "rewards": "90668729339", + "withdrawals": "84372623602", + "rewards_available": "6296105737", + "reserves": "0", + "treasury": "0" + } +] +``` +
+ Query Account Info of Payment Address (requires base address) + +```console +$ cscli query info account --network mainnet --address addr1q9r4307pqxq93fh554yvfssha46atz7h8waha568d8ddvnktwkkz3tg57qd9knlsfyhlgjuxpyxhl09u2w8f4l20hk2q7dt678 +[ + { + "status": "registered", + "delegated_pool": "pool1ddg6t2h9kj6lqlec4ncjs945lzj43m3ggrgdhf5sgzhtygpkznz", + "total_balance": "7031456885", + "utxo": "7019386794", + "rewards": "56497309", + "withdrawals": "44427218", + "rewards_available": "12070091", + "reserves": "0", + "treasury": "0" + } +] +``` +
+ +### Query Account Asset +```console +$ cscli query asset account --network testnet --stake-address $(cat stake_0_0.es.addr) +[ + { + "asset_policy": "540f107c7a3df20d2111a41c3bc407cce3e63c10c8dd673d51a02c22", + "asset_name": "COND1", + "quantity": "1" + } +] +``` +
+ Query Account Asset of Payment Address (requires base address) + +```console +$ cscli query asset account --network testnet --address $(cat pay_0_0_0_0.es.addr) +[ + { + "asset_policy": "540f107c7a3df20d2111a41c3bc407cce3e63c10c8dd673d51a02c22", + "asset_name": "COND1", + "quantity": "1" + } +] +``` +
+ +### Query Address Info +```console +$ cscli query info address --network testnet --address $(cat pay_0_0_0_0.es.addr) +{ + "balance": "1001344798", + "stake_address": "stake_test1uztkvps54v3yrwvxhvfz9uph8g6e2zd8jcg2cyss45g7xqclj4scq", + "utxo_set": [ ... ] +} +``` + +### Query Transaction Info +```console +$ cscli query info transaction --network testnet --txid 4fe73db7e345f6853ade214b0779d5db51f9a4b5e296198d3cb84b7b707e7d34 +[ + { + "tx_hash": "4fe73db7e345f6853ade214b0779d5db51f9a4b5e296198d3cb84b7b707e7d34", + "block_hash": "e96c400f303d2f30f7b49761b1c541b5a29b43ddb28268a1f179b2877f828aad", + ... + "inputs": [ ... ], + "outputs": [ ... ], + ... + } +] +``` + ### Bech32 Decode ```console $ cscli bech32 decode --value "$(cat pay_0_0.addr)" @@ -266,9 +449,9 @@ addr1vy5zuhh9685fup86syuzmu3e6eengzv8t46mfqxg086cvqqrukl6w ### Blake2b Hash ```console -$ cscli blake2b hash --length 224 --value 1872bc5ecc95b419de3f72544a6656ceb9a813755544618bb6b4dcc230ed9721 -9df9179beb0ce89f84025e02ae11c18b3003e7690149caa662fafd01 +$ cscli blake2b hash --length 224 --value de9503426759fa18624657f5bcc932f38220ec9eceb262907caf2d198b6e0faa +282e5ee5d1e89e04fa81382df239d6733409875d75b480c879f58600 ``` ## Contributing -Please see [CONTRIBUTING.md](./CONTRIBUTING.md) \ No newline at end of file +Please see [CONTRIBUTING.md](./CONTRIBUTING.md) \ No newline at end of file diff --git a/Src/ConsoleTool/BackendGateway.cs b/Src/ConsoleTool/BackendGateway.cs new file mode 100644 index 0000000..67f21ef --- /dev/null +++ b/Src/ConsoleTool/BackendGateway.cs @@ -0,0 +1,18 @@ +using CardanoSharp.Wallet.Enums; +using Refit; + +namespace Cscli.ConsoleTool.Koios +{ + public static class BackendGateway + { + public static T GetBackendClient(NetworkType networkType) => + RestService.For(GetBaseUrlForNetwork(networkType)); + + private static string GetBaseUrlForNetwork(NetworkType networkType) => networkType switch + { + NetworkType.Mainnet => "https://api.koios.rest/api/v0", + NetworkType.Testnet => "https://testnet.koios.rest/api/v0", + _ => throw new ArgumentException($"{nameof(networkType)} {networkType} is invalid", nameof(networkType)) + }; + } +} diff --git a/Src/ConsoleTool/CardanoNodeTypes.cs b/Src/ConsoleTool/CardanoNodeTypes.cs deleted file mode 100644 index 70990f5..0000000 --- a/Src/ConsoleTool/CardanoNodeTypes.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Cscli.ConsoleTool; - -public record TextEnvelope(string? Type, string? Description, string? CborHex); \ No newline at end of file diff --git a/Src/ConsoleTool/CommandParser.cs b/Src/ConsoleTool/CommandParser.cs index ab6ae56..2c2f8c1 100644 --- a/Src/ConsoleTool/CommandParser.cs +++ b/Src/ConsoleTool/CommandParser.cs @@ -1,4 +1,7 @@ -using Cscli.ConsoleTool.Commands; +using Cscli.ConsoleTool.Crypto; +using Cscli.ConsoleTool.Query; +using Cscli.ConsoleTool.Transaction; +using Cscli.ConsoleTool.Wallet; using Microsoft.Extensions.Configuration; using System.Text; @@ -41,9 +44,9 @@ private static ICommand ParseCommands(string intent, string[] args) => args[0] switch { "wallet" => ParseWalletCommands(intent, args), - //"query" => ParseQueryCommands(intent, args), // TODO: query via Blockfrost/Koios integration - //"tx" => ParseTxCommands(intent, args), // TODO: Easier Tx creation and submission via Blockfrost/Koios integration - "bech32" or "blake2b" => ParseEncodingHashingCommands(intent, args), + "query" => ParseQueryCommands(intent, args), + "transaction" => ParseTransactionCommands(intent, args), + "bech32" or "blake2b" => ParseCryptoCommands(intent, args), _ => new ShowInvalidArgumentCommand(intent) }; @@ -60,7 +63,26 @@ private static ICommand ParseWalletCommands(string intent, string[] args) => _ => new ShowInvalidArgumentCommand(intent) }; - private static ICommand ParseEncodingHashingCommands(string intent, string[] args) => + private static ICommand ParseQueryCommands(string intent, string[] args) => + intent switch + { + "query tip" => BuildCommand(args), + "query protocol-parameters" => BuildCommand(args), + "query info account" => BuildCommand(args), + "query asset account" => BuildCommand(args), + "query info address" => BuildCommand(args), + "query info transaction" => BuildCommand(args), + _ => new ShowInvalidArgumentCommand(intent) + }; + + private static ICommand ParseTransactionCommands(string intent, string[] args) => + intent switch + { + "transaction submit" => BuildCommand(args), + _ => new ShowInvalidArgumentCommand(intent) + }; + + private static ICommand ParseCryptoCommands(string intent, string[] args) => intent switch { "bech32 encode" => BuildCommand(args), @@ -69,7 +91,6 @@ private static ICommand ParseEncodingHashingCommands(string intent, string[] arg _ => new ShowInvalidArgumentCommand(intent) }; - private static ICommand BuildCommand( string[] args) where T : ICommand diff --git a/Src/ConsoleTool/Constants.cs b/Src/ConsoleTool/Constants.cs index fde3a58..217a573 100644 --- a/Src/ConsoleTool/Constants.cs +++ b/Src/ConsoleTool/Constants.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +using System.Text.Encodings.Web; +using System.Text.Json; namespace Cscli.ConsoleTool; @@ -31,7 +32,8 @@ public static class Constants public static readonly JsonSerializerOptions SerialiserOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - WriteIndented = true + WriteIndented = true, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; // Commandline args switch mappings public static Dictionary SwitchMappings => new() @@ -45,7 +47,9 @@ public static class Constants { "--stake-account-index", "stakeAccountIndex" }, { "--stake-address-index", "stakeAddressIndex" }, { "--payment-address-type", "paymentAddressType" }, - { "--network-tag", "networkTag" }, + { "--stake-address", "stakeAddress" }, + { "--cbor-hex", "cborHex" }, + { "--tx-id", "txId" }, //{ "--output-format", "outputFormat" }, }; } \ No newline at end of file diff --git a/Src/ConsoleTool/Commands/DecodeBech32Command.cs b/Src/ConsoleTool/Crypto/DecodeBech32Command.cs similarity index 96% rename from Src/ConsoleTool/Commands/DecodeBech32Command.cs rename to Src/ConsoleTool/Crypto/DecodeBech32Command.cs index a71169e..87ae257 100644 --- a/Src/ConsoleTool/Commands/DecodeBech32Command.cs +++ b/Src/ConsoleTool/Crypto/DecodeBech32Command.cs @@ -1,7 +1,7 @@ using CardanoSharp.Wallet.Encoding; using CardanoSharp.Wallet.Extensions; -namespace Cscli.ConsoleTool.Commands; +namespace Cscli.ConsoleTool.Crypto; public class DecodeBech32Command : ICommand { diff --git a/Src/ConsoleTool/Commands/EncodeBech32Command.cs b/Src/ConsoleTool/Crypto/EncodeBech32Command.cs similarity index 96% rename from Src/ConsoleTool/Commands/EncodeBech32Command.cs rename to Src/ConsoleTool/Crypto/EncodeBech32Command.cs index fa5e32d..9cfddec 100644 --- a/Src/ConsoleTool/Commands/EncodeBech32Command.cs +++ b/Src/ConsoleTool/Crypto/EncodeBech32Command.cs @@ -1,6 +1,6 @@ using CardanoSharp.Wallet.Encoding; -namespace Cscli.ConsoleTool.Commands; +namespace Cscli.ConsoleTool.Crypto; public class EncodeBech32Command : ICommand { diff --git a/Src/ConsoleTool/Commands/HashBlake2bCommand.cs b/Src/ConsoleTool/Crypto/HashBlake2bCommand.cs similarity index 97% rename from Src/ConsoleTool/Commands/HashBlake2bCommand.cs rename to Src/ConsoleTool/Crypto/HashBlake2bCommand.cs index 30c0f89..3baace8 100644 --- a/Src/ConsoleTool/Commands/HashBlake2bCommand.cs +++ b/Src/ConsoleTool/Crypto/HashBlake2bCommand.cs @@ -2,7 +2,7 @@ using CardanoSharp.Wallet.Extensions; using System.Collections.Immutable; -namespace Cscli.ConsoleTool.Commands; +namespace Cscli.ConsoleTool.Crypto; public class HashBlake2bCommand : ICommand { diff --git a/Src/ConsoleTool/Cscli.ConsoleTool.csproj b/Src/ConsoleTool/Cscli.ConsoleTool.csproj index d600944..1b5d9a8 100644 --- a/Src/ConsoleTool/Cscli.ConsoleTool.csproj +++ b/Src/ConsoleTool/Cscli.ConsoleTool.csproj @@ -31,12 +31,13 @@ - + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Src/ConsoleTool/DomainTypes.cs b/Src/ConsoleTool/DomainTypes.cs new file mode 100644 index 0000000..66019b8 --- /dev/null +++ b/Src/ConsoleTool/DomainTypes.cs @@ -0,0 +1,15 @@ +namespace Cscli.ConsoleTool; + +public record struct Utxo(string TxHash, int OutputIndex, TokenBundle TokenBundle); + +public record struct TokenBundle(long LovelaceValue, NativeAssetValue[] NativeAssets); + +public record struct NativeAssetValue(string PolicyId, string AssetNameHex, long Quantity); + +public record WalletInfo(AccountInfo[] Accounts); + +public record AccountInfo(string StakeAddress, string PaymentAddress, Utxo[] Utxos); + +public record AddressInfo(string PaymentAddress, string? StakeAddress, Utxo[] Utxos); + +public record TextEnvelope(string? Type, string? Description, string? CborHex); \ No newline at end of file diff --git a/Src/ConsoleTool/Properties/launchSettings.json b/Src/ConsoleTool/Properties/launchSettings.json index 9f54038..03acf8f 100644 --- a/Src/ConsoleTool/Properties/launchSettings.json +++ b/Src/ConsoleTool/Properties/launchSettings.json @@ -1,8 +1,7 @@ { "profiles": { "Cscli.ConsoleTool": { - "commandName": "Project", - "commandLineArgs": "bech32 encode --value 00e3f81c986990cfd80283944858ae50c2d82f6a79d330ce7014e0b7fd9df9179beb0ce89f84025e02ae11c18b3003e7690149caa662fafd01 --prefix addr_test" + "commandName": "Project" } } } \ No newline at end of file diff --git a/Src/ConsoleTool/Query/QueryAccountAssetCommand.cs b/Src/ConsoleTool/Query/QueryAccountAssetCommand.cs new file mode 100644 index 0000000..f94236c --- /dev/null +++ b/Src/ConsoleTool/Query/QueryAccountAssetCommand.cs @@ -0,0 +1,107 @@ +using CardanoSharp.Koios.Sdk; +using CardanoSharp.Wallet; +using CardanoSharp.Wallet.Encoding; +using CardanoSharp.Wallet.Enums; +using CardanoSharp.Wallet.Models.Addresses; +using Cscli.ConsoleTool.Koios; +using System.Text.Json; +using static Cscli.ConsoleTool.Constants; + +namespace Cscli.ConsoleTool.Query; + +public class QueryAccountAssetCommand : ICommand +{ + public string StakeAddress { get; init; } = string.Empty; + public string Address { get; init; } = string.Empty; + public string? Network { get; init; } + + public async ValueTask ExecuteAsync(CancellationToken ct) + { + var (isValid, networkType, stakeAddress, errors) = Validate(); + if (!isValid || stakeAddress == null) + { + return CommandResult.FailureInvalidOptions( + string.Join(Environment.NewLine, errors)); + } + + var accountClient = BackendGateway.GetBackendClient(networkType); + try + { + var assets = await accountClient.GetStakeAssets(stakeAddress.ToString()).ConfigureAwait(false); + var json = JsonSerializer.Serialize(assets, SerialiserOptions); + return CommandResult.Success(json); + } + catch (Exception ex) + { + return CommandResult.FailureUnhandledException("Unexpected error", ex); + } + } + + private ( + bool isValid, + NetworkType derivedNetworkType, + Address? derivedStakeAddress, + IReadOnlyCollection validationErrors) Validate() + { + var validationErrors = new List(); + if (!Enum.TryParse(Network, ignoreCase: true, out var networkType)) + { + validationErrors.Add( + $"Invalid option --network must be either testnet or mainnet"); + } + var stakeAddressArgumentExists = !string.IsNullOrWhiteSpace(StakeAddress); + var paymentAddressArgumentExists = !string.IsNullOrWhiteSpace(Address); + if (!stakeAddressArgumentExists && !paymentAddressArgumentExists + || stakeAddressArgumentExists && paymentAddressArgumentExists) + { + validationErrors.Add( + "Invalid option, one of either --stake-address or --address is required"); + return (!validationErrors.Any(), networkType, null, validationErrors); + } + if (stakeAddressArgumentExists) + { + if (!Bech32.IsValid(StakeAddress)) + { + validationErrors.Add( + $"Invalid option --stake-address {StakeAddress} is invalid"); + } + else + { + var stakeAddress = new Address(StakeAddress); + if (stakeAddress.AddressType != AddressType.Reward || stakeAddress.NetworkType != networkType) + { + validationErrors.Add( + $"Invalid option --stake-address {StakeAddress} is invalid for {Network}"); + } + else + { + return (!validationErrors.Any(), networkType, stakeAddress, validationErrors); + } + } + } + if (paymentAddressArgumentExists) + { + if (!Bech32.IsValid(Address)) + { + validationErrors.Add( + $"Invalid option --address {Address} is not a base address with attached staking credentials"); + } + else + { + var addressService = new AddressService(); + var address = new Address(Address); + if (address.AddressType != AddressType.Base || address.NetworkType != networkType) + { + validationErrors.Add( + $"Invalid option --address {Address} is not a base address with attached staking credentials for {Network}"); + } + else + { + var stakeAddress = addressService.ExtractRewardAddress(address); + return (!validationErrors.Any(), networkType, stakeAddress, validationErrors); + } + } + } + return (!validationErrors.Any(), networkType, null, validationErrors); + } +} diff --git a/Src/ConsoleTool/Query/QueryAccountInfoCommand.cs b/Src/ConsoleTool/Query/QueryAccountInfoCommand.cs new file mode 100644 index 0000000..ccddb10 --- /dev/null +++ b/Src/ConsoleTool/Query/QueryAccountInfoCommand.cs @@ -0,0 +1,107 @@ +using CardanoSharp.Koios.Sdk; +using CardanoSharp.Wallet; +using CardanoSharp.Wallet.Encoding; +using CardanoSharp.Wallet.Enums; +using CardanoSharp.Wallet.Models.Addresses; +using Cscli.ConsoleTool.Koios; +using System.Text.Json; +using static Cscli.ConsoleTool.Constants; + +namespace Cscli.ConsoleTool.Query; + +public class QueryAccountInfoCommand : ICommand +{ + public string StakeAddress { get; init; } = string.Empty; + public string Address { get; init; } = string.Empty; + public string? Network { get; init; } + + public async ValueTask ExecuteAsync(CancellationToken ct) + { + var (isValid, networkType, stakeAddress, errors) = Validate(); + if (!isValid || stakeAddress == null) + { + return CommandResult.FailureInvalidOptions( + string.Join(Environment.NewLine, errors)); + } + + var accountClient = BackendGateway.GetBackendClient(networkType); + try + { + var accountInfo = await accountClient.GetStakeInformation(stakeAddress.ToString()).ConfigureAwait(false); + var json = JsonSerializer.Serialize(accountInfo, SerialiserOptions); + return CommandResult.Success(json); + } + catch (Exception ex) + { + return CommandResult.FailureUnhandledException("Unexpected error", ex); + } + } + + private ( + bool isValid, + NetworkType derivedNetworkType, + Address? derivedStakeAddress, + IReadOnlyCollection validationErrors) Validate() + { + var validationErrors = new List(); + if (!Enum.TryParse(Network, ignoreCase: true, out var networkType)) + { + validationErrors.Add( + $"Invalid option --network must be either testnet or mainnet"); + } + var stakeAddressArgumentExists = !string.IsNullOrWhiteSpace(StakeAddress); + var paymentAddressArgumentExists = !string.IsNullOrWhiteSpace(Address); + if (!stakeAddressArgumentExists && !paymentAddressArgumentExists + || stakeAddressArgumentExists && paymentAddressArgumentExists) + { + validationErrors.Add( + "Invalid option, one of either --stake-address or --address is required"); + return (!validationErrors.Any(), networkType, null, validationErrors); + } + if (stakeAddressArgumentExists) + { + if (!Bech32.IsValid(StakeAddress)) + { + validationErrors.Add( + $"Invalid option --stake-address {StakeAddress} is invalid"); + } + else + { + var stakeAddress = new Address(StakeAddress); + if (stakeAddress.AddressType != AddressType.Reward || stakeAddress.NetworkType != networkType) + { + validationErrors.Add( + $"Invalid option --stake-address {StakeAddress} is invalid for {Network}"); + } + else + { + return (!validationErrors.Any(), networkType, stakeAddress, validationErrors); + } + } + } + if (paymentAddressArgumentExists) + { + if (!Bech32.IsValid(Address)) + { + validationErrors.Add( + $"Invalid option --address {Address} is not a base address with attached staking credentials"); + } + else + { + var addressService = new AddressService(); + var address = new Address(Address); + if (address.AddressType != AddressType.Base || address.NetworkType != networkType) + { + validationErrors.Add( + $"Invalid option --address {Address} is not a base address with attached staking credentials for {Network}"); + } + else + { + var stakeAddress = addressService.ExtractRewardAddress(address); + return (!validationErrors.Any(), networkType, stakeAddress, validationErrors); + } + } + } + return (!validationErrors.Any(), networkType, null, validationErrors); + } +} diff --git a/Src/ConsoleTool/Query/QueryAddressInfoCommand.cs b/Src/ConsoleTool/Query/QueryAddressInfoCommand.cs new file mode 100644 index 0000000..b208317 --- /dev/null +++ b/Src/ConsoleTool/Query/QueryAddressInfoCommand.cs @@ -0,0 +1,64 @@ +using CardanoSharp.Koios.Sdk; +using CardanoSharp.Wallet.Encoding; +using CardanoSharp.Wallet.Enums; +using CardanoSharp.Wallet.Models.Addresses; +using Cscli.ConsoleTool.Koios; +using System.Text.Json; +using static Cscli.ConsoleTool.Constants; + +namespace Cscli.ConsoleTool.Query; + +public class QueryAddressInfoCommand : ICommand +{ + public string? Address { get; init; } + public string? Network { get; init; } + + public async ValueTask ExecuteAsync(CancellationToken ct) + { + var (isValid, networkType, errors) = Validate(); + if (!isValid) + { + return CommandResult.FailureInvalidOptions( + string.Join(Environment.NewLine, errors)); + } + + var addressClient = BackendGateway.GetBackendClient(networkType); + try + { +#pragma warning disable CS8604 // Possible null reference warning suppressed because of validation above + var addressInfo = await addressClient.GetAddressInformation(Address).ConfigureAwait(false); +#pragma warning restore CS8604 + var json = JsonSerializer.Serialize(addressInfo, SerialiserOptions); + return CommandResult.Success(json); + } + catch (Exception ex) + { + return CommandResult.FailureUnhandledException("Unexpected error", ex); + } + } + + private ( + bool isValid, + NetworkType derivedNetworkType, + IReadOnlyCollection validationErrors) Validate() + { + var validationErrors = new List(); + if (!Enum.TryParse(Network, ignoreCase: true, out var networkType)) + { + validationErrors.Add( + $"Invalid option --network must be either testnet or mainnet"); + } + if (string.IsNullOrWhiteSpace(Address)) + { + validationErrors.Add( + $"Invalid option --address is required"); + } + var address = new Address(Address); + if (!Bech32.IsValid(Address) || address.NetworkType != networkType) + { + validationErrors.Add( + $"Invalid option --address {Address} is invalid for {Network}"); + } + return (!validationErrors.Any(), networkType, validationErrors); + } +} diff --git a/Src/ConsoleTool/Query/QueryProtocolParametersCommand.cs b/Src/ConsoleTool/Query/QueryProtocolParametersCommand.cs new file mode 100644 index 0000000..363ff7c --- /dev/null +++ b/Src/ConsoleTool/Query/QueryProtocolParametersCommand.cs @@ -0,0 +1,33 @@ +using CardanoSharp.Koios.Sdk; +using CardanoSharp.Wallet.Enums; +using Cscli.ConsoleTool.Koios; +using System.Text.Json; +using static Cscli.ConsoleTool.Constants; + +namespace Cscli.ConsoleTool.Query; + +public class QueryProtocolParametersCommand : ICommand +{ + public string? Network { get; init; } + + public async ValueTask ExecuteAsync(CancellationToken ct) + { + if (!Enum.TryParse(Network, ignoreCase: true, out var networkType)) + { + return CommandResult.FailureInvalidOptions( + $"Invalid option --network must be either testnet or mainnet"); + } + + var epochClient = BackendGateway.GetBackendClient(networkType); + try + { + var protocolParams = await epochClient.GetProtocolParameters(null, limit:1).ConfigureAwait(false); + var json = JsonSerializer.Serialize(protocolParams.First(), SerialiserOptions); + return CommandResult.Success(json); + } + catch (Exception ex) + { + return CommandResult.FailureUnhandledException("Unexpected error", ex); + } + } +} diff --git a/Src/ConsoleTool/Query/QueryTipCommand.cs b/Src/ConsoleTool/Query/QueryTipCommand.cs new file mode 100644 index 0000000..64d6d07 --- /dev/null +++ b/Src/ConsoleTool/Query/QueryTipCommand.cs @@ -0,0 +1,33 @@ +using CardanoSharp.Koios.Sdk; +using CardanoSharp.Wallet.Enums; +using Cscli.ConsoleTool.Koios; +using System.Text.Json; +using static Cscli.ConsoleTool.Constants; + +namespace Cscli.ConsoleTool.Query; + +public class QueryTipCommand : ICommand +{ + public string? Network { get; init; } + + public async ValueTask ExecuteAsync(CancellationToken ct) + { + if (!Enum.TryParse(Network, ignoreCase: true, out var networkType)) + { + return CommandResult.FailureInvalidOptions( + $"Invalid option --network must be either testnet or mainnet"); + } + + var networkClient = BackendGateway.GetBackendClient(networkType); + try + { + var chainTip = await networkClient.GetChainTip().ConfigureAwait(false); + var json = JsonSerializer.Serialize(chainTip.First(), SerialiserOptions); + return CommandResult.Success(json); + } + catch (Exception ex) + { + return CommandResult.FailureUnhandledException("Unexpected error", ex); + } + } +} diff --git a/Src/ConsoleTool/Query/QueryTransactionInfoCommand.cs b/Src/ConsoleTool/Query/QueryTransactionInfoCommand.cs new file mode 100644 index 0000000..a548bb7 --- /dev/null +++ b/Src/ConsoleTool/Query/QueryTransactionInfoCommand.cs @@ -0,0 +1,59 @@ +using CardanoSharp.Koios.Sdk; +using CardanoSharp.Wallet.Encoding; +using CardanoSharp.Wallet.Enums; +using CardanoSharp.Wallet.Models.Addresses; +using Cscli.ConsoleTool.Koios; +using System.Text.Json; +using static Cscli.ConsoleTool.Constants; + +namespace Cscli.ConsoleTool.Query; + +public class QueryTransactionInfoCommand : ICommand +{ + public string? TxId { get; init; } + public string? Network { get; init; } + + public async ValueTask ExecuteAsync(CancellationToken ct) + { + var (isValid, networkType, errors) = Validate(); + if (!isValid) + { + return CommandResult.FailureInvalidOptions( + string.Join(Environment.NewLine, errors)); + } + + var transactionClient = BackendGateway.GetBackendClient(networkType); + try + { +#pragma warning disable CS8604 // Possible null reference warning suppressed because of validation above + var txInfo = await transactionClient.GetTransactionInformation( + new GetTransactionRequest { TxHashes = new List{ TxId } }).ConfigureAwait(false); +#pragma warning restore CS8604 + var json = JsonSerializer.Serialize(txInfo, SerialiserOptions); + return CommandResult.Success(json); + } + catch (Exception ex) + { + return CommandResult.FailureUnhandledException("Unexpected error", ex); + } + } + + private ( + bool isValid, + NetworkType derivedNetworkType, + IReadOnlyCollection validationErrors) Validate() + { + var validationErrors = new List(); + if (!Enum.TryParse(Network, ignoreCase: true, out var networkType)) + { + validationErrors.Add( + $"Invalid option --network must be either testnet or mainnet"); + } + if (string.IsNullOrWhiteSpace(TxId)) + { + validationErrors.Add( + $"Invalid option --tx-id is required"); + } + return (!validationErrors.Any(), networkType, validationErrors); + } +} diff --git a/Src/ConsoleTool/Commands/ShowBaseHelpCommand.cs b/Src/ConsoleTool/ShowBaseHelpCommand.cs similarity index 56% rename from Src/ConsoleTool/Commands/ShowBaseHelpCommand.cs rename to Src/ConsoleTool/ShowBaseHelpCommand.cs index 434b5f1..51c10da 100644 --- a/Src/ConsoleTool/Commands/ShowBaseHelpCommand.cs +++ b/Src/ConsoleTool/ShowBaseHelpCommand.cs @@ -1,6 +1,6 @@ using System.Reflection; -namespace Cscli.ConsoleTool.Commands; +namespace Cscli.ConsoleTool; public class ShowBaseHelpCommand : ICommand { @@ -10,32 +10,44 @@ public ValueTask ExecuteAsync(CancellationToken ct) .GetCustomAttribute()? .Version; var helpText = $@"cscli v{versionString} -A cross-platform tool for building and interacting with Cardano's wallet primitives (i.e. recovery-phrases, keys, addresses and transactions). -Please see https://github.com/CardanoSharp/cscli from more detailed documentation. +A lightweight cross-platform tool for generating/serialising Cardano wallet primitives (i.e. recovery-phrases, keys, addresses and transactions), querying the chain and submitting transactions to the testnet or mainnet networks. Please refer to https://github.com/CardanoSharp/cscli for further documentation. USAGE: cscli (OPTION | COMMAND) -Available options: +Options: -v, --version Show the cscli version -h, --help Show this help text -Available commands: +Wallet commands: wallet recovery-phrase generate --size [--language ] wallet key root derive --recovery-phrase """" [--language ] [--passphrase """"] wallet key stake derive --recovery-phrase """" [--language ] [--passphrase """"] [--account-index ] [--address-index ] [--verification-key-file ] [--signing-key-file ] wallet key payment derive --recovery-phrase """" [--language ] [--passphrase """"] [--account-index ] [--address-index ] [--verification-key-file ] [--signing-key-file ] wallet key policy derive --recovery-phrase """" [--language ] [--passphrase """"] [--policy-index ] [--verification-key-file ] [--signing-key-file ] - wallet address stake derive --recovery-phrase """" --network-type [--language ] [--passphrase """"] [--account-index ] [--address-index ] - wallet address payment derive --recovery-phrase """" --network-type --payment-address-type [--language ] [--passphrase """"] [--account-index ] [--address-index ] [--stake-account-index ] [--stake-address-index ] - bech32 encode --value """" --prefix """" - bech32 decode --value """" - blake2b hash --value """" --length + wallet address stake derive --recovery-phrase """" --network [--language ] [--passphrase """"] [--account-index ] [--address-index ] + wallet address payment derive --recovery-phrase """" --network --payment-address-type [--language ] [--passphrase """"] [--account-index ] [--address-index ] [--stake-account-index ] [--stake-address-index ] + +Query commands: + query tip --network + query protocol-parameters --network + query info account --network [--stake-address ][--address ] + query asset account --network --stake-address + query info address --network --address + query info transaction --network --tx-id + +Transaction Commands: + transaction submit --network --cbor-hex + +Encoding/Crypto Commands: + bech32 encode --value --prefix + bech32 decode --value + blake2b hash --value --length Arguments: ::= 9 | 12 | 15 | 18 | 21 | 24(default) ::= english(default)|chinesesimplified|chinesetraditional|french|italian|japanese|korean|spanish|czech|portuguese ::= 0(default) | 1 | .. | 2147483647 - ::= testnet | mainnet + ::= testnet | mainnet ::= enterprise | base ::= 160 | 224 | 256 | 512 "; diff --git a/Src/ConsoleTool/Commands/ShowInvalidArgumentCommand.cs b/Src/ConsoleTool/ShowInvalidArgumentCommand.cs similarity index 91% rename from Src/ConsoleTool/Commands/ShowInvalidArgumentCommand.cs rename to Src/ConsoleTool/ShowInvalidArgumentCommand.cs index a4e91ec..929c0e1 100644 --- a/Src/ConsoleTool/Commands/ShowInvalidArgumentCommand.cs +++ b/Src/ConsoleTool/ShowInvalidArgumentCommand.cs @@ -1,4 +1,4 @@ -namespace Cscli.ConsoleTool.Commands; +namespace Cscli.ConsoleTool; public class ShowInvalidArgumentCommand : ICommand { diff --git a/Src/ConsoleTool/Commands/ShowVersionCommand.cs b/Src/ConsoleTool/ShowVersionCommand.cs similarity index 94% rename from Src/ConsoleTool/Commands/ShowVersionCommand.cs rename to Src/ConsoleTool/ShowVersionCommand.cs index 0c39dcb..942355b 100644 --- a/Src/ConsoleTool/Commands/ShowVersionCommand.cs +++ b/Src/ConsoleTool/ShowVersionCommand.cs @@ -1,6 +1,6 @@ using System.Reflection; -namespace Cscli.ConsoleTool.Commands; +namespace Cscli.ConsoleTool; public class ShowVersionCommand : ICommand { diff --git a/Src/ConsoleTool/Transaction/SubmitTransactionCommand.cs b/Src/ConsoleTool/Transaction/SubmitTransactionCommand.cs new file mode 100644 index 0000000..53c6062 --- /dev/null +++ b/Src/ConsoleTool/Transaction/SubmitTransactionCommand.cs @@ -0,0 +1,66 @@ +using CardanoSharp.Koios.Sdk; +using CardanoSharp.Wallet.Enums; +using Cscli.ConsoleTool.Koios; + +namespace Cscli.ConsoleTool.Transaction; + +public class SubmitTransactionCommand : ICommand +{ + public string? CborHex { get; init; } + public string? Network { get; init; } + + public async ValueTask ExecuteAsync(CancellationToken ct) + { + var (isValid, networkType, txCborBytes, errors) = Validate(); + if (!isValid) + { + return CommandResult.FailureInvalidOptions( + string.Join(Environment.NewLine, errors)); + } + + var txClient = BackendGateway.GetBackendClient(networkType); + try + { + using var stream = new MemoryStream(txCborBytes); + var txHash = await txClient.Submit(stream).ConfigureAwait(false); + return CommandResult.Success(txHash); + } + catch (Exception ex) + { + return CommandResult.FailureUnhandledException("Unexpected error", ex); + } + } + + private ( + bool isValid, + NetworkType derivedNetworkType, + byte[] txCborBytes, + IReadOnlyCollection validationErrors) Validate() + { + var validationErrors = new List(); + if (!Enum.TryParse(Network, ignoreCase: true, out var networkType)) + { + validationErrors.Add( + $"Invalid option --network must be either testnet or mainnet"); + } + if (string.IsNullOrWhiteSpace(CborHex)) + { + validationErrors.Add( + $"Invalid option --cbor-hex is required"); + } + else + { + try + { + var txCborBytes = Convert.FromHexString(CborHex); + return (!validationErrors.Any(), networkType, txCborBytes, validationErrors); + } + catch (FormatException) + { + validationErrors.Add( + $"Invalid option --cbor-hex {CborHex} is not in hexadecimal format"); + } + } + return (!validationErrors.Any(), networkType, Array.Empty(), validationErrors); + } +} diff --git a/Src/ConsoleTool/Commands/DerivePaymentAddressCommand.cs b/Src/ConsoleTool/Wallet/DerivePaymentAddressCommand.cs similarity index 94% rename from Src/ConsoleTool/Commands/DerivePaymentAddressCommand.cs rename to Src/ConsoleTool/Wallet/DerivePaymentAddressCommand.cs index 8538319..8aa4881 100644 --- a/Src/ConsoleTool/Commands/DerivePaymentAddressCommand.cs +++ b/Src/ConsoleTool/Wallet/DerivePaymentAddressCommand.cs @@ -5,7 +5,7 @@ using CardanoSharp.Wallet.Models.Keys; using static Cscli.ConsoleTool.Constants; -namespace Cscli.ConsoleTool.Commands; +namespace Cscli.ConsoleTool.Wallet; public class DerivePaymentAddressCommand : ICommand { @@ -17,7 +17,7 @@ public class DerivePaymentAddressCommand : ICommand public int AddressIndex { get; init; } = 0; public int StakeAccountIndex { get; init; } = 0; public int StakeAddressIndex { get; init; } = 0; - public string? NetworkTag { get; init; } + public string? Network { get; init; } public ValueTask ExecuteAsync(CancellationToken ct) { @@ -66,8 +66,8 @@ private Address GetPaymentAddress( private ( bool isValid, - WordLists wordList, - AddressType addressType, + WordLists derivedWordList, + AddressType derivedAddressType, NetworkType derivedNetworkType, IReadOnlyCollection validationErrors) Validate() { @@ -114,10 +114,10 @@ private Address GetPaymentAddress( validationErrors.Add( $"Invalid option --stake-address-index must be between 0 and {MaxDerivationPathIndex}"); } - if (!Enum.TryParse(NetworkTag, ignoreCase: true, out var networkType)) + if (!Enum.TryParse(Network, ignoreCase: true, out var networkType)) { validationErrors.Add( - $"Invalid option --network-tag must be either testnet or mainnet"); + $"Invalid option --network must be either testnet or mainnet"); } return (!validationErrors.Any(), wordlist, paymentAddressType, networkType, validationErrors); } diff --git a/Src/ConsoleTool/Commands/DerivePaymentKeyCommand.cs b/Src/ConsoleTool/Wallet/DerivePaymentKeyCommand.cs similarity index 99% rename from Src/ConsoleTool/Commands/DerivePaymentKeyCommand.cs rename to Src/ConsoleTool/Wallet/DerivePaymentKeyCommand.cs index 5f30e48..4f60698 100644 --- a/Src/ConsoleTool/Commands/DerivePaymentKeyCommand.cs +++ b/Src/ConsoleTool/Wallet/DerivePaymentKeyCommand.cs @@ -5,7 +5,7 @@ using System.Text.Json; using static Cscli.ConsoleTool.Constants; -namespace Cscli.ConsoleTool.Commands; +namespace Cscli.ConsoleTool.Wallet; public class DerivePaymentKeyCommand : ICommand { diff --git a/Src/ConsoleTool/Commands/DerivePolicyKeyCommand.cs b/Src/ConsoleTool/Wallet/DerivePolicyKeyCommand.cs similarity index 99% rename from Src/ConsoleTool/Commands/DerivePolicyKeyCommand.cs rename to Src/ConsoleTool/Wallet/DerivePolicyKeyCommand.cs index 11bb8cc..dd9ebd5 100644 --- a/Src/ConsoleTool/Commands/DerivePolicyKeyCommand.cs +++ b/Src/ConsoleTool/Wallet/DerivePolicyKeyCommand.cs @@ -5,7 +5,7 @@ using System.Text.Json; using static Cscli.ConsoleTool.Constants; -namespace Cscli.ConsoleTool.Commands; +namespace Cscli.ConsoleTool.Wallet; // See https://cips.cardano.org/cips/cip1855/ public class DerivePolicyKeyCommand : ICommand diff --git a/Src/ConsoleTool/Commands/DeriveRootKeyCommand.cs b/Src/ConsoleTool/Wallet/DeriveRootKeyCommand.cs similarity index 98% rename from Src/ConsoleTool/Commands/DeriveRootKeyCommand.cs rename to Src/ConsoleTool/Wallet/DeriveRootKeyCommand.cs index 37f972f..469ead2 100644 --- a/Src/ConsoleTool/Commands/DeriveRootKeyCommand.cs +++ b/Src/ConsoleTool/Wallet/DeriveRootKeyCommand.cs @@ -4,7 +4,7 @@ using CardanoSharp.Wallet.Extensions.Models; using static Cscli.ConsoleTool.Constants; -namespace Cscli.ConsoleTool.Commands; +namespace Cscli.ConsoleTool.Wallet; public class DeriveRootKeyCommand : ICommand { diff --git a/Src/ConsoleTool/Commands/DeriveStakeAddressCommand.cs b/Src/ConsoleTool/Wallet/DeriveStakeAddressCommand.cs similarity index 88% rename from Src/ConsoleTool/Commands/DeriveStakeAddressCommand.cs rename to Src/ConsoleTool/Wallet/DeriveStakeAddressCommand.cs index a253238..2811d99 100644 --- a/Src/ConsoleTool/Commands/DeriveStakeAddressCommand.cs +++ b/Src/ConsoleTool/Wallet/DeriveStakeAddressCommand.cs @@ -3,7 +3,7 @@ using CardanoSharp.Wallet.Extensions.Models; using static Cscli.ConsoleTool.Constants; -namespace Cscli.ConsoleTool.Commands; +namespace Cscli.ConsoleTool.Wallet; public class DeriveStakeAddressCommand : ICommand { @@ -12,11 +12,11 @@ public class DeriveStakeAddressCommand : ICommand public string Passphrase { get; init; } = string.Empty; public int AccountIndex { get; init; } = 0; public int AddressIndex { get; init; } = 0; - public string? NetworkTag { get; init; } + public string? Network { get; init; } public ValueTask ExecuteAsync(CancellationToken ct) { - var (isValid, derivedWorldList, network, errors) = Validate(); + var (isValid, wordList, network, errors) = Validate(); if (!isValid) { return ValueTask.FromResult( @@ -27,7 +27,7 @@ public ValueTask ExecuteAsync(CancellationToken ct) var addressService = new AddressService(); try { - var rootPrvKey = mnemonicService.Restore(Mnemonic, derivedWorldList) + var rootPrvKey = mnemonicService.Restore(Mnemonic, wordList) .GetRootKey(Passphrase); var stakeVkey = rootPrvKey.Derive($"m/1852'/1815'/{AccountIndex}'/2/{AddressIndex}") .GetPublicKey(false); @@ -78,10 +78,10 @@ public ValueTask ExecuteAsync(CancellationToken ct) validationErrors.Add( $"Invalid option --address-index must be between 0 and {MaxDerivationPathIndex}"); } - if (!Enum.TryParse(NetworkTag, ignoreCase: true, out var networkType)) + if (!Enum.TryParse(Network, ignoreCase: true, out var networkType)) { validationErrors.Add( - $"Invalid option --network-tag must be either testnet or mainnet"); + $"Invalid option --network must be either testnet or mainnet"); } return (!validationErrors.Any(), wordlist, networkType, validationErrors); } diff --git a/Src/ConsoleTool/Commands/DeriveStakeKeyCommand.cs b/Src/ConsoleTool/Wallet/DeriveStakeKeyCommand.cs similarity index 99% rename from Src/ConsoleTool/Commands/DeriveStakeKeyCommand.cs rename to Src/ConsoleTool/Wallet/DeriveStakeKeyCommand.cs index dca70b2..c49c974 100644 --- a/Src/ConsoleTool/Commands/DeriveStakeKeyCommand.cs +++ b/Src/ConsoleTool/Wallet/DeriveStakeKeyCommand.cs @@ -5,7 +5,7 @@ using System.Text.Json; using static Cscli.ConsoleTool.Constants; -namespace Cscli.ConsoleTool.Commands; +namespace Cscli.ConsoleTool.Wallet; public class DeriveStakeKeyCommand : ICommand { diff --git a/Src/ConsoleTool/Commands/GenerateMnemonicCommand.cs b/Src/ConsoleTool/Wallet/GenerateMnemonicCommand.cs similarity index 97% rename from Src/ConsoleTool/Commands/GenerateMnemonicCommand.cs rename to Src/ConsoleTool/Wallet/GenerateMnemonicCommand.cs index 34fcb10..ea410e5 100644 --- a/Src/ConsoleTool/Commands/GenerateMnemonicCommand.cs +++ b/Src/ConsoleTool/Wallet/GenerateMnemonicCommand.cs @@ -2,7 +2,7 @@ using CardanoSharp.Wallet.Enums; using static Cscli.ConsoleTool.Constants; -namespace Cscli.ConsoleTool.Commands; +namespace Cscli.ConsoleTool.Wallet; public class GenerateMnemonicCommand : ICommand { diff --git a/Tests/ConsoleTool.UnitTests/CommandParserShould.cs b/Tests/ConsoleTool.UnitTests/CommandParserShould.cs index e9d682d..ce2b9ec 100644 --- a/Tests/ConsoleTool.UnitTests/CommandParserShould.cs +++ b/Tests/ConsoleTool.UnitTests/CommandParserShould.cs @@ -1,4 +1,7 @@ -using Cscli.ConsoleTool.Commands; +using Cscli.ConsoleTool.Crypto; +using Cscli.ConsoleTool.Query; +using Cscli.ConsoleTool.Transaction; +using Cscli.ConsoleTool.Wallet; using FluentAssertions; using Xunit; @@ -170,17 +173,17 @@ public void ParseArgs_Correctly_To_DeriveStakeKeyCommand_When_Options_Are_Valid( [Theory] [InlineData( - "wallet address stake derive --recovery-phrase {MNEMONIC} --network-tag Testnet", + "wallet address stake derive --recovery-phrase {MNEMONIC} --network Testnet", "rabbit fence domain dirt burden bone entry genre twelve obey dwarf icon fabric tattoo chalk monster deputy tomato gun toy garment portion gun ribbon", Constants.DefaultMnemonicLanguage, "", 0, 0, "Testnet")] [InlineData( - "wallet address stake derive --recovery-phrase {MNEMONIC} --passphrase helloworld --language Spanish --network-tag Mainnet --account-index 101 --address-index 980", + "wallet address stake derive --recovery-phrase {MNEMONIC} --passphrase helloworld --language Spanish --network mainnet --account-index 101 --address-index 980", "vena pomo bolero papel colina paleta regalo alma dibujo examen lindo programa venir bozal elogio tacto romper observar bono separar refrán ecuador clase reducir", "Spanish", - "helloworld", 101, 980, "Mainnet")] + "helloworld", 101, 980, "mainnet")] [InlineData( - "wallet address stake derive --recovery-phrase {MNEMONIC} --passphrase 0ZYM4ND14Z --language Japanese --network-tag Mainnet --account-index 2147483647 --address-index 2147483647", + "wallet address stake derive --recovery-phrase {MNEMONIC} --passphrase 0ZYM4ND14Z --language Japanese --network Mainnet --account-index 2147483647 --address-index 2147483647", "みすい よけい ちしりょう つみき たいせつ たりる せんぱい れんぞく めいぶつ あじわう はらう ちぬき やぶる ひろう にんい うらぐち おやゆび きんじょ きりん たいおう ねいる みつかる うよく かあつ", "Japanese", "0ZYM4ND14Z", 2147483647, 2147483647, "Mainnet")] @@ -197,17 +200,17 @@ public void ParseArgs_Correctly_To_DeriveStakeAddressCommand_When_Options_Are_Va stakeAddressCommand.Passphrase.Should().Be(expectedPassPhrase); stakeAddressCommand.AccountIndex.Should().Be(expectedAccountIndex); stakeAddressCommand.AddressIndex.Should().Be(expectedAddressIndex); - stakeAddressCommand.NetworkTag.Should().Be(expectedNetworkTag); + stakeAddressCommand.Network.Should().Be(expectedNetworkTag); } [Theory] [InlineData( - "wallet address payment derive --recovery-phrase {MNEMONIC} --network-tag Testnet --payment-address-type Enterprise", + "wallet address payment derive --recovery-phrase {MNEMONIC} --network testnet --payment-address-type Enterprise", "slight aspect potato wealth two lazy ill try kick visit chunk cloth snap follow now sun curve quality cousin sister decrease help stadium enact", Constants.DefaultMnemonicLanguage, - "", 0, 0, 0, 0, "Testnet", "Enterprise")] + "", 0, 0, 0, 0, "testnet", "Enterprise")] [InlineData( - "wallet address payment derive --language Portugese --recovery-phrase {MNEMONIC} --network-tag Mainnet --payment-address-type Base --account-index 256 --address-index 512 --stake-account-index 88 --stake-address-index 29 --passphrase 0ZYM4ND14Z", + "wallet address payment derive --language Portugese --recovery-phrase {MNEMONIC} --network Mainnet --payment-address-type Base --account-index 256 --address-index 512 --stake-account-index 88 --stake-address-index 29 --passphrase 0ZYM4ND14Z", "sadio sombrio prato selvagem enrugar fugir depois braveza acolhida javali enviado alfinete emenda mexer legado goela vedar refogar pivete afrontar tracejar materno vespa chapada", "Portugese", "0ZYM4ND14Z", 256, 512, 88, 29, "Mainnet", "Base")] public void ParseArgs_Correctly_To_DerivePaymentAddressCommand_When_Options_Are_Valid( @@ -228,7 +231,7 @@ public void ParseArgs_Correctly_To_DerivePaymentAddressCommand_When_Options_Are_ paymentAddressCommand.AddressIndex.Should().Be(expectedAddressIndex); paymentAddressCommand.StakeAccountIndex.Should().Be(expectedStakeAccountIndex); paymentAddressCommand.StakeAddressIndex.Should().Be(expectedStakeAddressIndex); - paymentAddressCommand.NetworkTag.Should().Be(expectedNetworkTag); + paymentAddressCommand.Network.Should().Be(expectedNetworkTag); paymentAddressCommand.PaymentAddressType.Should().Be(expectedPaymentAddressType); } @@ -278,6 +281,94 @@ public void ParseArgs_Correctly_To_HashBlake2bCommand_When_Options_Are_Valid(str hashBlake2bCommand.Length.Should().Be(expectedLength); } + [Theory] + [InlineData("query tip --network testnet", "testnet")] + [InlineData("query tip --network mainnet", "mainnet")] + public void ParseArgs_Correctly_To_QueryTipCommand_When_Options_Are_Valid(string args, string expectedNetwork) + { + var command = CommandParser.ParseArgsToCommand(args.Split(' ')); + + var queryTipCommand = (QueryTipCommand)command; + queryTipCommand.Should().BeOfType(); + queryTipCommand.Network.Should().Be(expectedNetwork); + } + + [Theory] + [InlineData("query protocol-parameters --network testnet", "testnet")] + [InlineData("query protocol-parameters --network mainnet", "mainnet")] + public void ParseArgs_Correctly_To_QueryProtocolParametersCommand_When_Options_Are_Valid(string args, string expectedNetwork) + { + var command = CommandParser.ParseArgsToCommand(args.Split(' ')); + + var queryProtocolParametersCommand = (QueryProtocolParametersCommand)command; + queryProtocolParametersCommand.Should().BeOfType(); + queryProtocolParametersCommand.Network.Should().Be(expectedNetwork); + } + + [Theory] + [InlineData("query asset account --network testnet --stake-address stake_test1uzdyuk9ts8eguyzn6s64hwy8phzkhqf76zfwznwfpaw94dgmj3zcx", "testnet", "stake_test1uzdyuk9ts8eguyzn6s64hwy8phzkhqf76zfwznwfpaw94dgmj3zcx")] + [InlineData("query asset account --network mainnet --stake-address stake1uxwlj9umavxw38uyqf0q9ts3cx9nqql8dyq5nj4xvta06qg8t55dn", "mainnet", "stake1uxwlj9umavxw38uyqf0q9ts3cx9nqql8dyq5nj4xvta06qg8t55dn")] + public void ParseArgs_Correctly_To_QueryAccountAssetCommand_When_Options_Are_Valid(string args, string expectedNetwork, string expectedStakeAddress) + { + var command = CommandParser.ParseArgsToCommand(args.Split(' ')); + + var queryAccountAssetCommand = (QueryAccountAssetCommand)command; + queryAccountAssetCommand.Should().BeOfType(); + queryAccountAssetCommand.Network.Should().Be(expectedNetwork); + queryAccountAssetCommand.StakeAddress.Should().Be(expectedStakeAddress); + } + + [Theory] + [InlineData("query info account --network testnet --stake-address stake_test1uzdyuk9ts8eguyzn6s64hwy8phzkhqf76zfwznwfpaw94dgmj3zcx", "testnet", "stake_test1uzdyuk9ts8eguyzn6s64hwy8phzkhqf76zfwznwfpaw94dgmj3zcx")] + [InlineData("query info account --network mainnet --stake-address stake1uxwlj9umavxw38uyqf0q9ts3cx9nqql8dyq5nj4xvta06qg8t55dn", "mainnet", "stake1uxwlj9umavxw38uyqf0q9ts3cx9nqql8dyq5nj4xvta06qg8t55dn")] + public void ParseArgs_Correctly_To_QueryAccountInfoCommand_When_Options_Are_Valid(string args, string expectedNetwork, string expectedStakeAddress) + { + var command = CommandParser.ParseArgsToCommand(args.Split(' ')); + + var queryAccountInfoCommand = (QueryAccountInfoCommand)command; + queryAccountInfoCommand.Should().BeOfType(); + queryAccountInfoCommand.Network.Should().Be(expectedNetwork); + queryAccountInfoCommand.StakeAddress.Should().Be(expectedStakeAddress); + } + + [Theory] + [InlineData("query info address --network testnet --address addr_test1qr3ls8ycdxgvlkqzsw2ysk9w2rpdstm208fnpnnsznst0lvalyteh6cvaz0cgqj7q2hprsvtxqp7w6gpf892vch6l5qs6ug90f", "testnet", "addr_test1qr3ls8ycdxgvlkqzsw2ysk9w2rpdstm208fnpnnsznst0lvalyteh6cvaz0cgqj7q2hprsvtxqp7w6gpf892vch6l5qs6ug90f")] + [InlineData("query info address --network mainnet --address addr1vy5zuhh9685fup86syuzmu3e6eengzv8t46mfqxg086cvqqrukl6w", "mainnet", "addr1vy5zuhh9685fup86syuzmu3e6eengzv8t46mfqxg086cvqqrukl6w")] + public void ParseArgs_Correctly_To_QueryAddressInfoCommand_When_Options_Are_Valid(string args, string expectedNetwork, string expectedAddress) + { + var command = CommandParser.ParseArgsToCommand(args.Split(' ')); + + var queryAddressInfoCommand = (QueryAddressInfoCommand)command; + queryAddressInfoCommand.Should().BeOfType(); + queryAddressInfoCommand.Network.Should().Be(expectedNetwork); + queryAddressInfoCommand.Address.Should().Be(expectedAddress); + } + + [Theory] + [InlineData("query info transaction --network testnet --tx-id 18d10520a11acfa8ceb8e13b2560747cf4f357cfaac1dc83b35a26c5dc61a2e3", "testnet", "18d10520a11acfa8ceb8e13b2560747cf4f357cfaac1dc83b35a26c5dc61a2e3")] + [InlineData("query info transaction --network mainnet --tx-id 421734e5c8e5be24d788da2defeb9005516c20eb7d3ddef2140e836d20282a2a", "mainnet", "421734e5c8e5be24d788da2defeb9005516c20eb7d3ddef2140e836d20282a2a")] + public void ParseArgs_Correctly_To_QueryTransactionInfoCommand_When_Options_Are_Valid(string args, string expectedNetwork, string expectedTxId) + { + var command = CommandParser.ParseArgsToCommand(args.Split(' ')); + + var queryTransactionInfoCommand = (QueryTransactionInfoCommand)command; + queryTransactionInfoCommand.Should().BeOfType(); + queryTransactionInfoCommand.Network.Should().Be(expectedNetwork); + queryTransactionInfoCommand.TxId.Should().Be(expectedTxId); + } + + [Theory] + [InlineData("transaction submit --network testnet --cbor-hex 9df9179beb0ce89f84025e02ae11c18b3003e7690149caa662fafd01", "testnet", "9df9179beb0ce89f84025e02ae11c18b3003e7690149caa662fafd01")] + [InlineData("transaction submit --network mainnet --cbor-hex 61282e5ee5d1e89e04fa81382df239d6733409875d75b480c879f58600", "mainnet", "61282e5ee5d1e89e04fa81382df239d6733409875d75b480c879f58600")] + public void ParseArgs_Correctly_SubmitTransactionCommand_When_Options_Are_Valid(string args, string expectedNetwork, string expectedCborHex) + { + var command = CommandParser.ParseArgsToCommand(args.Split(' ')); + + var submitTransactionCommand = (SubmitTransactionCommand)command; + submitTransactionCommand.Should().BeOfType(); + submitTransactionCommand.Network.Should().Be(expectedNetwork); + submitTransactionCommand.CborHex.Should().Be(expectedCborHex); + } private static string[] GenerateArgs(string flatArgs, string expectedMnemonic) { diff --git a/Tests/ConsoleTool.UnitTests/DecodeBech32CommandShould.cs b/Tests/ConsoleTool.UnitTests/DecodeBech32CommandShould.cs index 76b18b2..ea5ba72 100644 --- a/Tests/ConsoleTool.UnitTests/DecodeBech32CommandShould.cs +++ b/Tests/ConsoleTool.UnitTests/DecodeBech32CommandShould.cs @@ -1,4 +1,4 @@ -using Cscli.ConsoleTool.Commands; +using Cscli.ConsoleTool.Crypto; using FluentAssertions; using Xunit; diff --git a/Tests/ConsoleTool.UnitTests/DerivePaymentAddressCommandShould.cs b/Tests/ConsoleTool.UnitTests/DerivePaymentAddressCommandShould.cs index 61cc38e..e349cd6 100644 --- a/Tests/ConsoleTool.UnitTests/DerivePaymentAddressCommandShould.cs +++ b/Tests/ConsoleTool.UnitTests/DerivePaymentAddressCommandShould.cs @@ -1,4 +1,4 @@ -using Cscli.ConsoleTool.Commands; +using Cscli.ConsoleTool.Wallet; using FluentAssertions; using Xunit; @@ -24,13 +24,13 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Network { Mnemonic = "rapid limit bicycle embrace speak column spoil casino become evolve unknown worry letter team laptop unknown false elbow bench analyst dilemma engage pulse plug", PaymentAddressType = paymentAddressType, - NetworkTag = networkTag + Network = networkTag }; var executionResult = await command.ExecuteAsync(CancellationToken.None); executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); - executionResult.Result.Should().Be("Invalid option --network-tag must be either testnet or mainnet"); + executionResult.Result.Should().Be("Invalid option --network must be either testnet or mainnet"); } [Theory] @@ -57,7 +57,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Type_Is { Mnemonic = "rapid limit bicycle embrace speak column spoil casino become evolve unknown worry letter team laptop unknown false elbow bench analyst dilemma engage pulse plug", PaymentAddressType = paymentAddressType, - NetworkTag = networkTag + Network = networkTag }; var executionResult = await command.ExecuteAsync(CancellationToken.None); @@ -90,7 +90,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Mnemoni { Mnemonic = mnemonic, Language = language, - NetworkTag = networkTag, + Network = networkTag, PaymentAddressType = "Enterprise", }; @@ -124,7 +124,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Mnemoni { Mnemonic = mnemonic, Language = language, - NetworkTag = networkTag, + Network = networkTag, PaymentAddressType = "Base", }; @@ -170,7 +170,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Mnemoni { Mnemonic = mnemonic, Language = language, - NetworkTag = networkTag, + Network = networkTag, PaymentAddressType = "Enterprise", }; @@ -216,7 +216,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Mnemoni { Mnemonic = mnemonic, Language = language, - NetworkTag = networkTag, + Network = networkTag, PaymentAddressType = "Base", }; @@ -244,7 +244,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Languag { Mnemonic = mnemonic, Language = language, - NetworkTag = networkTag, + Network = networkTag, PaymentAddressType = "Enterprise", }; @@ -272,7 +272,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Languag { Mnemonic = mnemonic, Language = language, - NetworkTag = networkTag, + Network = networkTag, PaymentAddressType = "Base", }; @@ -302,7 +302,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Words_A { Mnemonic = mnemonic, Language = language, - NetworkTag = networkTag, + Network = networkTag, PaymentAddressType = "Enterprise", }; @@ -332,7 +332,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Words_A { Mnemonic = mnemonic, Language = language, - NetworkTag = networkTag, + Network = networkTag, PaymentAddressType = "Base", }; @@ -361,7 +361,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Account Mnemonic = mnemonic, Language = language, AccountIndex = accountIndex, - NetworkTag = networkTag, + Network = networkTag, PaymentAddressType = "Enterprise", }; @@ -390,7 +390,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Account Mnemonic = mnemonic, Language = language, AccountIndex = accountIndex, - NetworkTag = networkTag, + Network = networkTag, PaymentAddressType = "Base", }; @@ -418,7 +418,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Address { Mnemonic = mnemonic, Language = language, - NetworkTag = networkTag, + Network = networkTag, AddressIndex = addressIndex, PaymentAddressType = "Enterprise", }; @@ -447,7 +447,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Address { Mnemonic = mnemonic, Language = language, - NetworkTag = networkTag, + Network = networkTag, AddressIndex = addressIndex, PaymentAddressType = "Base", }; @@ -477,7 +477,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Stake_A Mnemonic = mnemonic, Language = language, StakeAccountIndex = accountIndex, - NetworkTag = networkTag, + Network = networkTag, PaymentAddressType = "Enterprise", }; @@ -506,7 +506,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Stake_A Mnemonic = mnemonic, Language = language, StakeAccountIndex = accountIndex, - NetworkTag = networkTag, + Network = networkTag, PaymentAddressType = "Base", }; @@ -534,7 +534,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Stake_A { Mnemonic = mnemonic, Language = language, - NetworkTag = networkTag, + Network = networkTag, StakeAddressIndex = addressIndex, PaymentAddressType = "Enterprise", }; @@ -563,7 +563,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Stake_A { Mnemonic = mnemonic, Language = language, - NetworkTag = networkTag, + Network = networkTag, StakeAddressIndex = addressIndex, PaymentAddressType = "Base", }; @@ -589,13 +589,13 @@ public async Task Derive_Correct_Bech32_Extended_Payment_Enterprise_Address_Defa var command = new DerivePaymentAddressCommand() { Mnemonic = mnemonic, - NetworkTag = networkTag, + Network = networkTag, PaymentAddressType = "Enterprise" }; var englishSpecificCommand = new DerivePaymentAddressCommand() { Mnemonic = mnemonic, - NetworkTag = networkTag, + Network = networkTag, PaymentAddressType = "Enterprise", Language = "English", }; @@ -623,13 +623,13 @@ public async Task Derive_Correct_Bech32_Extended_Payment_Base_Address_Defaulting var command = new DerivePaymentAddressCommand() { Mnemonic = mnemonic, - NetworkTag = networkTag, + Network = networkTag, PaymentAddressType = "Base" }; var englishSpecificCommand = new DerivePaymentAddressCommand() { Mnemonic = mnemonic, - NetworkTag = networkTag, + Network = networkTag, PaymentAddressType = "Base", Language = "English", }; @@ -670,7 +670,7 @@ public async Task Derive_Correct_Bech32_Extended_Payment_Enterprise_Address_When Language = language, Passphrase = passPhrase, PaymentAddressType = "Enterprise", - NetworkTag = networkTag, + Network = networkTag, AccountIndex = accountIndex, AddressIndex = addressIndex, }; @@ -725,7 +725,7 @@ public async Task Derive_Correct_Bech32_Extended_Payment_Base_Address_When_All_P Language = language, Passphrase = passPhrase, PaymentAddressType = "Base", - NetworkTag = networkTag, + Network = networkTag, AccountIndex = accountIndex, AddressIndex = addressIndex, StakeAccountIndex = stakeAccountIndex, diff --git a/Tests/ConsoleTool.UnitTests/DerivePaymentKeyCommandShould.cs b/Tests/ConsoleTool.UnitTests/DerivePaymentKeyCommandShould.cs index a10e762..9d99e5f 100644 --- a/Tests/ConsoleTool.UnitTests/DerivePaymentKeyCommandShould.cs +++ b/Tests/ConsoleTool.UnitTests/DerivePaymentKeyCommandShould.cs @@ -1,4 +1,4 @@ -using Cscli.ConsoleTool.Commands; +using Cscli.ConsoleTool.Wallet; using FluentAssertions; using Xunit; diff --git a/Tests/ConsoleTool.UnitTests/DerivePolicyKeyCommandShould.cs b/Tests/ConsoleTool.UnitTests/DerivePolicyKeyCommandShould.cs index 57211bf..017f4d4 100644 --- a/Tests/ConsoleTool.UnitTests/DerivePolicyKeyCommandShould.cs +++ b/Tests/ConsoleTool.UnitTests/DerivePolicyKeyCommandShould.cs @@ -1,4 +1,4 @@ -using Cscli.ConsoleTool.Commands; +using Cscli.ConsoleTool.Wallet; using FluentAssertions; using Xunit; diff --git a/Tests/ConsoleTool.UnitTests/DeriveRootKeyCommandShould.cs b/Tests/ConsoleTool.UnitTests/DeriveRootKeyCommandShould.cs index 265f72b..7cd2266 100644 --- a/Tests/ConsoleTool.UnitTests/DeriveRootKeyCommandShould.cs +++ b/Tests/ConsoleTool.UnitTests/DeriveRootKeyCommandShould.cs @@ -1,4 +1,4 @@ -using Cscli.ConsoleTool.Commands; +using Cscli.ConsoleTool.Wallet; using FluentAssertions; using Xunit; diff --git a/Tests/ConsoleTool.UnitTests/DeriveStakeAddressCommandShould.cs b/Tests/ConsoleTool.UnitTests/DeriveStakeAddressCommandShould.cs index 58cb0cc..394584c 100644 --- a/Tests/ConsoleTool.UnitTests/DeriveStakeAddressCommandShould.cs +++ b/Tests/ConsoleTool.UnitTests/DeriveStakeAddressCommandShould.cs @@ -1,4 +1,4 @@ -using Cscli.ConsoleTool.Commands; +using Cscli.ConsoleTool.Wallet; using FluentAssertions; using Xunit; @@ -20,13 +20,13 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Mnemoni var command = new DeriveStakeAddressCommand() { Mnemonic = "rapid limit bicycle embrace speak column spoil casino become evolve unknown worry letter team laptop unknown false elbow bench analyst dilemma engage pulse plug", - NetworkTag = networkTag + Network = networkTag }; var executionResult = await command.ExecuteAsync(CancellationToken.None); executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); - executionResult.Result.Should().Be("Invalid option --network-tag must be either testnet or mainnet"); + executionResult.Result.Should().Be("Invalid option --network must be either testnet or mainnet"); } [Theory] @@ -53,7 +53,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Mnemoni { Mnemonic = mnemonic, Language = language, - NetworkTag = networkTag + Network = networkTag }; var executionResult = await command.ExecuteAsync(CancellationToken.None); @@ -98,7 +98,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Mnemoni { Mnemonic = mnemonic, Language = language, - NetworkTag = networkTag + Network = networkTag }; var executionResult = await command.ExecuteAsync(CancellationToken.None); @@ -125,7 +125,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Languag { Mnemonic = mnemonic, Language = language, - NetworkTag = networkTag + Network = networkTag }; var executionResult = await command.ExecuteAsync(CancellationToken.None); @@ -154,7 +154,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Words_A { Mnemonic = mnemonic, Language = language, - NetworkTag = networkTag + Network = networkTag }; var executionResult = await command.ExecuteAsync(CancellationToken.None); @@ -182,7 +182,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Account Mnemonic = mnemonic, Language = language, AccountIndex = accountIndex, - NetworkTag = networkTag, + Network = networkTag, }; var executionResult = await command.ExecuteAsync(CancellationToken.None); @@ -209,7 +209,7 @@ public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Address { Mnemonic = mnemonic, Language = language, - NetworkTag = networkTag, + Network = networkTag, AddressIndex = addressIndex }; @@ -238,12 +238,12 @@ public async Task Derive_Correct_Bech32_Extended_Stake_Address_Defaulting_To_Eng var command = new DeriveStakeAddressCommand() { Mnemonic = mnemonic, - NetworkTag = networkTag, + Network = networkTag, }; var englishSpecificCommand = new DeriveStakeAddressCommand() { Mnemonic = mnemonic, - NetworkTag = networkTag, + Network = networkTag, Language = "English" }; @@ -287,13 +287,13 @@ public async Task Derive_Correct_Bech32_Extended_Payment_Signing_Key_Defaulting_ { Language = language, Mnemonic = mnemonic, - NetworkTag = networkTag, + Network = networkTag, }; var englishSpecificCommand = new DeriveStakeAddressCommand() { Language = language, Mnemonic = mnemonic, - NetworkTag = networkTag, + Network = networkTag, AccountIndex = 0, AddressIndex = 0, }; @@ -356,14 +356,14 @@ public async Task Derive_Correct_Bech32_Stake_Address_Defaulting_To_Empty_Passph { Mnemonic = mnemonic, Language = language, - NetworkTag = networkTag, + Network = networkTag, }; var emptyPassSpecificCommand = new DeriveStakeAddressCommand() { Mnemonic = mnemonic, Language = language, Passphrase = "", - NetworkTag = networkTag, + Network = networkTag, }; var executionResult = await command.ExecuteAsync(CancellationToken.None); @@ -407,7 +407,7 @@ public async Task Derive_Correct_Bech32_Stake_Address_When_Passphrase_And_Langua Mnemonic = mnemonic, Language = language, Passphrase = passPhrase, - NetworkTag = networkTag + Network = networkTag }; var executionResult = await command.ExecuteAsync(CancellationToken.None); @@ -456,7 +456,7 @@ public async Task Derive_Correct_Bech32_Extended_Stake_Address_When_Account_And_ { Mnemonic = mnemonic, Language = language, - NetworkTag = networkTag, + Network = networkTag, AccountIndex = accountIndex, AddressIndex = addressIndex, }; diff --git a/Tests/ConsoleTool.UnitTests/DeriveStakeKeyCommandShould.cs b/Tests/ConsoleTool.UnitTests/DeriveStakeKeyCommandShould.cs index bbf3da2..f7533e5 100644 --- a/Tests/ConsoleTool.UnitTests/DeriveStakeKeyCommandShould.cs +++ b/Tests/ConsoleTool.UnitTests/DeriveStakeKeyCommandShould.cs @@ -1,4 +1,4 @@ -using Cscli.ConsoleTool.Commands; +using Cscli.ConsoleTool.Wallet; using FluentAssertions; using Xunit; diff --git a/Tests/ConsoleTool.UnitTests/EncodeBech32CommandShould.cs b/Tests/ConsoleTool.UnitTests/EncodeBech32CommandShould.cs index 29a2b73..046fe6f 100644 --- a/Tests/ConsoleTool.UnitTests/EncodeBech32CommandShould.cs +++ b/Tests/ConsoleTool.UnitTests/EncodeBech32CommandShould.cs @@ -1,4 +1,4 @@ -using Cscli.ConsoleTool.Commands; +using Cscli.ConsoleTool.Crypto; using FluentAssertions; using Xunit; diff --git a/Tests/ConsoleTool.UnitTests/GenerateMnemonicCommandShould.cs b/Tests/ConsoleTool.UnitTests/GenerateMnemonicCommandShould.cs index 0db3be0..57f9c4f 100644 --- a/Tests/ConsoleTool.UnitTests/GenerateMnemonicCommandShould.cs +++ b/Tests/ConsoleTool.UnitTests/GenerateMnemonicCommandShould.cs @@ -1,4 +1,4 @@ -using Cscli.ConsoleTool.Commands; +using Cscli.ConsoleTool.Wallet; using FluentAssertions; using Xunit; diff --git a/Tests/ConsoleTool.UnitTests/HashBlake2bCommandShould.cs b/Tests/ConsoleTool.UnitTests/HashBlake2bCommandShould.cs index b6a6333..79dcc5f 100644 --- a/Tests/ConsoleTool.UnitTests/HashBlake2bCommandShould.cs +++ b/Tests/ConsoleTool.UnitTests/HashBlake2bCommandShould.cs @@ -1,4 +1,4 @@ -using Cscli.ConsoleTool.Commands; +using Cscli.ConsoleTool.Crypto; using FluentAssertions; using Xunit; diff --git a/Tests/ConsoleTool.UnitTests/QueryAccountAssetCommandShould.cs b/Tests/ConsoleTool.UnitTests/QueryAccountAssetCommandShould.cs new file mode 100644 index 0000000..b502734 --- /dev/null +++ b/Tests/ConsoleTool.UnitTests/QueryAccountAssetCommandShould.cs @@ -0,0 +1,151 @@ +using Cscli.ConsoleTool.Query; +using FluentAssertions; +using Xunit; + +namespace Cscli.ConsoleTool.UnitTests; + +public class QueryAccountAssetCommandShould +{ + [Theory] + [InlineData("")] + [InlineData("a")] + [InlineData("test")] + [InlineData("tastnet")] + [InlineData("Mainet")] + [InlineData("mainet")] + [InlineData("mainnetwork")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Network_Is_Not_Valid( + string networkTag) + { + var command = new QueryAccountAssetCommand() + { + Network = networkTag, + StakeAddress = "stake1uxwlj9umavxw38uyqf0q9ts3cx9nqql8dyq5nj4xvta06qg8t55dn", + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().StartWith("Invalid option --network must be either testnet or mainnet"); + } + + [Theory] + [InlineData("testnet")] + [InlineData("mainnet")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Stake_Address_And_Payment_Address_Are_Both_Not_Supplied( + string network) + { + var command = new QueryAccountAssetCommand() + { + Network = network + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be("Invalid option, one of either --stake-address or --address is required"); + } + + [Theory] + [InlineData("testnet")] + [InlineData("mainnet")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Valid_Stake_Address_And_Payment_Address_Are_Both_Supplied( + string network) + { + var command = new QueryAccountAssetCommand() + { + Network = network, + StakeAddress = "stake1u9wqktpz964g6jaemt5wr5tspy9cqxpdkw98d022d85kxxc2n2yxj", + Address = "addr1qy5zuhh9685fup86syuzmu3e6eengzv8t46mfqxg086cvqzupvkzyt42349mnkhgu8ghqzgtsqvzmvu2w675560fvvdspma4ht" + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be("Invalid option, one of either --stake-address or --address is required"); + } + + [Theory] + [InlineData("a", "testnet")] + [InlineData("a", "mainnet")] + [InlineData("test", "testnet")] + [InlineData("test", "mainnet")] + [InlineData("stake1abc1234", "testnet")] + [InlineData("stake1abc1234", "mainnet")] + [InlineData("stake_test1abc1234", "testnet")] + [InlineData("stake_test1abc1234", "mainnet")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Stake_Address_Is_Not_Valid_Bech32( + string invalidStakeAddress, string network) + { + var command = new QueryAccountAssetCommand() + { + Network = network, + StakeAddress = invalidStakeAddress, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be($"Invalid option --stake-address {invalidStakeAddress} is invalid"); + } + + [Theory] + [InlineData("stake1uyneva7s00qnhmwnl9a6kzvahcqf7v3sa4h9wh970dxl6egwp9mlw", "testnet")] + [InlineData("stake_test1uqneva7s00qnhmwnl9a6kzvahcqf7v3sa4h9wh970dxl6egft0emn", "mainnet")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Stake_Address_Is_Valid_But_For_Different_Network( + string stakeAddress, string network) + { + var command = new QueryAccountAssetCommand() + { + Network = network, + StakeAddress = stakeAddress, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be($"Invalid option --stake-address {stakeAddress} is invalid for {network}"); + } + + [Theory] + [InlineData("a", "testnet")] + [InlineData("a", "mainnet")] + [InlineData("test", "testnet")] + [InlineData("test", "mainnet")] + [InlineData("addr1abc1234", "testnet")] + [InlineData("addr1abc1234", "mainnet")] + [InlineData("addr_test1abc1234", "testnet")] + [InlineData("addr_test1abc1234", "mainnet")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Address_Is_Not_Valid_Bech32( + string invalidAddress, string network) + { + var command = new QueryAccountAssetCommand() + { + Network = network, + Address = invalidAddress, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be($"Invalid option --address {invalidAddress} is not a base address with attached staking credentials"); + } + + [Theory] + [InlineData("addr1qyg4gu4vfd3775glq8rjm85x2crmysc920hf5qjj8m7rxef8jemaq77p80ka87tm4vyem0sqnuerpmtw2awtu76dl4jssqfzz4", "testnet")] + [InlineData("addr_test1qqg4gu4vfd3775glq8rjm85x2crmysc920hf5qjj8m7rxef8jemaq77p80ka87tm4vyem0sqnuerpmtw2awtu76dl4jsnk5zw2", "mainnet")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Address_Is_Valid_But_For_Different_Network( + string address, string network) + { + var command = new QueryAccountAssetCommand() + { + Network = network, + Address = address, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be($"Invalid option --address {address} is not a base address with attached staking credentials for {network}"); + } +} diff --git a/Tests/ConsoleTool.UnitTests/QueryAccountInfoCommandShould.cs b/Tests/ConsoleTool.UnitTests/QueryAccountInfoCommandShould.cs new file mode 100644 index 0000000..698ee68 --- /dev/null +++ b/Tests/ConsoleTool.UnitTests/QueryAccountInfoCommandShould.cs @@ -0,0 +1,152 @@ +using Cscli.ConsoleTool.Query; +using FluentAssertions; +using Xunit; + +namespace Cscli.ConsoleTool.UnitTests; + +public class QueryAccountInfoCommandShould +{ + [Theory] + [InlineData("")] + [InlineData("a")] + [InlineData("test")] + [InlineData("tastnet")] + [InlineData("Mainet")] + [InlineData("mainet")] + [InlineData("mainnetwork")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Network_Is_Not_Valid( + string networkTag) + { + var command = new QueryAccountInfoCommand() + { + Network = networkTag, + StakeAddress = "stake1uxwlj9umavxw38uyqf0q9ts3cx9nqql8dyq5nj4xvta06qg8t55dn", + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().StartWith("Invalid option --network must be either testnet or mainnet"); + } + + [Theory] + [InlineData("testnet")] + [InlineData("mainnet")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Stake_Address_And_Payment_Address_Are_Both_Not_Supplied( + string network) + { + var command = new QueryAccountInfoCommand() + { + Network = network + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be("Invalid option, one of either --stake-address or --address is required"); + } + + [Theory] + [InlineData("testnet")] + [InlineData("mainnet")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Valid_Stake_Address_And_Payment_Address_Are_Both_Supplied( + string network) + { + var command = new QueryAccountInfoCommand() + { + Network = network, + StakeAddress = "stake1u9wqktpz964g6jaemt5wr5tspy9cqxpdkw98d022d85kxxc2n2yxj", + Address = "addr1qy5zuhh9685fup86syuzmu3e6eengzv8t46mfqxg086cvqzupvkzyt42349mnkhgu8ghqzgtsqvzmvu2w675560fvvdspma4ht" + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be("Invalid option, one of either --stake-address or --address is required"); + } + + [Theory] + [InlineData("a", "testnet")] + [InlineData("a", "mainnet")] + [InlineData("test", "testnet")] + [InlineData("test", "mainnet")] + [InlineData("stake1abc1234", "testnet")] + [InlineData("stake1abc1234", "mainnet")] + [InlineData("stake_test1abc1234", "testnet")] + [InlineData("stake_test1abc1234", "mainnet")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Stake_Address_Is_Not_Valid_Bech32( + string invalidStakeAddress, string network) + { + var command = new QueryAccountInfoCommand() + { + Network = network, + StakeAddress = invalidStakeAddress, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be($"Invalid option --stake-address {invalidStakeAddress} is invalid"); + } + + [Theory] + [InlineData("stake1uyneva7s00qnhmwnl9a6kzvahcqf7v3sa4h9wh970dxl6egwp9mlw", "testnet")] + [InlineData("stake_test1uqneva7s00qnhmwnl9a6kzvahcqf7v3sa4h9wh970dxl6egft0emn", "mainnet")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Stake_Address_Is_Valid_But_For_Different_Network( + string stakeAddress, string network) + { + var command = new QueryAccountInfoCommand() + { + Network = network, + StakeAddress = stakeAddress, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be($"Invalid option --stake-address {stakeAddress} is invalid for {network}"); + } + + [Theory] + [InlineData("a", "testnet")] + [InlineData("a", "mainnet")] + [InlineData("test", "testnet")] + [InlineData("test", "mainnet")] + [InlineData("addr1abc1234", "testnet")] + [InlineData("addr1abc1234", "mainnet")] + [InlineData("addr_test1abc1234", "testnet")] + [InlineData("addr_test1abc1234", "mainnet")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Address_Is_Not_Valid_Bech32( + string invalidAddress, string network) + { + var command = new QueryAccountInfoCommand() + { + Network = network, + Address = invalidAddress, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be($"Invalid option --address {invalidAddress} is not a base address with attached staking credentials"); + } + + [Theory] + [InlineData("addr1qyg4gu4vfd3775glq8rjm85x2crmysc920hf5qjj8m7rxef8jemaq77p80ka87tm4vyem0sqnuerpmtw2awtu76dl4jssqfzz4", "testnet")] + [InlineData("addr_test1qqg4gu4vfd3775glq8rjm85x2crmysc920hf5qjj8m7rxef8jemaq77p80ka87tm4vyem0sqnuerpmtw2awtu76dl4jsnk5zw2", "mainnet")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Address_Is_Valid_But_For_Different_Network( + string address, string network) + { + var command = new QueryAccountInfoCommand() + { + Network = network, + Address = address, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be($"Invalid option --address {address} is not a base address with attached staking credentials for {network}"); + } +} + diff --git a/Tests/ConsoleTool.UnitTests/QueryAddressInfoCommandShould.cs b/Tests/ConsoleTool.UnitTests/QueryAddressInfoCommandShould.cs new file mode 100644 index 0000000..377e97d --- /dev/null +++ b/Tests/ConsoleTool.UnitTests/QueryAddressInfoCommandShould.cs @@ -0,0 +1,73 @@ +using Cscli.ConsoleTool.Query; +using FluentAssertions; +using Xunit; + +namespace Cscli.ConsoleTool.UnitTests; + +public class QueryAddressInfoCommandShould +{ + [Theory] + [InlineData("")] + [InlineData("a")] + [InlineData("test")] + [InlineData("tastnet")] + [InlineData("Mainet")] + [InlineData("mainet")] + [InlineData("mainnetwork")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Network_Is_Not_Valid( + string networkTag) + { + var command = new QueryAddressInfoCommand() + { + Network = networkTag, + Address = "stake1uxwlj9umavxw38uyqf0q9ts3cx9nqql8dyq5nj4xvta06qg8t55dn", + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().StartWith("Invalid option --network must be either testnet or mainnet"); + } + + [Theory] + [InlineData("a", "testnet")] + [InlineData("a", "mainnet")] + [InlineData("test", "testnet")] + [InlineData("test", "mainnet")] + [InlineData("addr1abc1234", "testnet")] + [InlineData("addr1abc1234", "mainnet")] + [InlineData("addr_test1abc1234", "testnet")] + [InlineData("addr_test1abc1234", "mainnet")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Address_Is_Not_Valid_Bech32( + string invalidAddress, string network) + { + var command = new QueryAddressInfoCommand() + { + Network = network, + Address = invalidAddress, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be($"Invalid option --address {invalidAddress} is invalid for {network}"); + } + + [Theory] + [InlineData("addr1qyg4gu4vfd3775glq8rjm85x2crmysc920hf5qjj8m7rxef8jemaq77p80ka87tm4vyem0sqnuerpmtw2awtu76dl4jssqfzz4", "testnet")] + [InlineData("addr_test1qqg4gu4vfd3775glq8rjm85x2crmysc920hf5qjj8m7rxef8jemaq77p80ka87tm4vyem0sqnuerpmtw2awtu76dl4jsnk5zw2", "mainnet")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Address_Is_Valid_But_For_Different_Network( + string address, string network) + { + var command = new QueryAddressInfoCommand() + { + Network = network, + Address = address, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be($"Invalid option --address {address} is invalid for {network}"); + } +} diff --git a/Tests/ConsoleTool.UnitTests/QueryProtocolParametersCommandShould.cs b/Tests/ConsoleTool.UnitTests/QueryProtocolParametersCommandShould.cs new file mode 100644 index 0000000..c063fd1 --- /dev/null +++ b/Tests/ConsoleTool.UnitTests/QueryProtocolParametersCommandShould.cs @@ -0,0 +1,30 @@ +using Cscli.ConsoleTool.Query; +using FluentAssertions; +using Xunit; + +namespace Cscli.ConsoleTool.UnitTests; + +public class QueryProtocolParametersCommandShould +{ + [Theory] + [InlineData("")] + [InlineData("a")] + [InlineData("test")] + [InlineData("tastnet")] + [InlineData("Mainet")] + [InlineData("mainet")] + [InlineData("mainnetwork")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Network_Is_Not_Valid( + string networkTag) + { + var command = new QueryProtocolParametersCommand() + { + Network = networkTag, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().StartWith("Invalid option --network must be either testnet or mainnet"); + } +} diff --git a/Tests/ConsoleTool.UnitTests/QueryTipCommandShould.cs b/Tests/ConsoleTool.UnitTests/QueryTipCommandShould.cs new file mode 100644 index 0000000..ecd70ac --- /dev/null +++ b/Tests/ConsoleTool.UnitTests/QueryTipCommandShould.cs @@ -0,0 +1,30 @@ +using Cscli.ConsoleTool.Query; +using FluentAssertions; +using Xunit; + +namespace Cscli.ConsoleTool.UnitTests; + +public class QueryTipCommandShould +{ + [Theory] + [InlineData("")] + [InlineData("a")] + [InlineData("test")] + [InlineData("tastnet")] + [InlineData("Mainet")] + [InlineData("mainet")] + [InlineData("mainnetwork")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Network_Is_Not_Valid( + string networkTag) + { + var command = new QueryTipCommand() + { + Network = networkTag, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().StartWith("Invalid option --network must be either testnet or mainnet"); + } +} diff --git a/Tests/ConsoleTool.UnitTests/QueryTransactionInfoCommandShould.cs b/Tests/ConsoleTool.UnitTests/QueryTransactionInfoCommandShould.cs new file mode 100644 index 0000000..2c12cb2 --- /dev/null +++ b/Tests/ConsoleTool.UnitTests/QueryTransactionInfoCommandShould.cs @@ -0,0 +1,55 @@ +using Cscli.ConsoleTool.Query; +using FluentAssertions; +using Xunit; + +namespace Cscli.ConsoleTool.UnitTests; + +public class QueryTransactionInfoCommandShould +{ + [Theory] + [InlineData("")] + [InlineData("a")] + [InlineData("test")] + [InlineData("tastnet")] + [InlineData("Mainet")] + [InlineData("mainet")] + [InlineData("mainnetwork")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Network_Is_Not_Valid( + string networkTag) + { + var command = new QueryTransactionInfoCommand() + { + Network = networkTag, + TxId = "421734e5c8e5be24d788da2defeb9005516c20eb7d3ddef2140e836d20282a2a", + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().StartWith("Invalid option --network must be either testnet or mainnet"); + } + + [Theory] + [InlineData(null, "testnet")] + [InlineData(null, "mainnet")] + [InlineData("", "testnet")] + [InlineData("", "mainnet")] + [InlineData(" ", "testnet")] + [InlineData(" ", "mainnet")] + [InlineData(" ", "testnet")] + [InlineData(" ", "mainnet")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_TxId_Is_Null_Or_Whitespace( + string nullOrWhitespaceTxId, string network) + { + var command = new QueryTransactionInfoCommand() + { + Network = network, + TxId = nullOrWhitespaceTxId, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be("Invalid option --tx-id is required"); + } +} diff --git a/Tests/ConsoleTool.UnitTests/SubmitTransactionCommandShould.cs b/Tests/ConsoleTool.UnitTests/SubmitTransactionCommandShould.cs new file mode 100644 index 0000000..4ae8ff3 --- /dev/null +++ b/Tests/ConsoleTool.UnitTests/SubmitTransactionCommandShould.cs @@ -0,0 +1,75 @@ +using Cscli.ConsoleTool.Transaction; +using FluentAssertions; +using Xunit; + +namespace Cscli.ConsoleTool.UnitTests; + +public class SubmitTransactionCommandShould +{ + [Theory] + [InlineData("")] + [InlineData("a")] + [InlineData("test")] + [InlineData("tastnet")] + [InlineData("Mainet")] + [InlineData("mainet")] + [InlineData("mainnetwork")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_Network_Is_Not_Valid( + string networkTag) + { + var command = new SubmitTransactionCommand() + { + Network = networkTag, + CborHex = "83a500818258205f61dde2c9c05c104f81194f2cfc738b4463f098e46fa1ca62aedb1345561624000182825839005a99cb175eb944462d6bfd29d06e0a69defc091d8e5ecab740afac6f1922fcdeeb6df8592d78b20c6e22fdb73fa9446aad05626d78000b7f181982583900ae34b08e5a409e1e66683d0b04ff33bb7c5c1455046ad66ebe2b66d157664134527cd7cb875caa34251f7e063b337cd705649637c35df6c71bfffffffffffd1e5e021a0002e1ed031a037c8bb70758205fbac14ec74ffff7a1aa1b1acbf4bfed1ce774bf21a620092f647a04553b65d5a1008182582008c77407d35dab5e3f53ef4ad5066284ca269c8107e88de04fcdfb875de9a7055840d7250f2b39c8d3014aaa8fe6ba60f0b52a27240a6b8ae7f72d64079a4852acb4ecffb3559a5fb829d9868f0cd9806e567bc710f73513ba6eb9a699f71bb1800182a1183da1646e616d65783754657374202d204669786564206e6577206172726179207673206e6577206d617020666f7220656d707479207769746e6573732073657480", + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().StartWith("Invalid option --network must be either testnet or mainnet"); + } + + [Theory] + [InlineData(null, "testnet")] + [InlineData(null, "mainnet")] + [InlineData("", "testnet")] + [InlineData("", "mainnet")] + [InlineData(" ", "testnet")] + [InlineData(" ", "mainnet")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_CborHex_Is_Null_Or_Whitespace( + string invalidCborHex, string network) + { + var command = new SubmitTransactionCommand() + { + Network = network, + CborHex = invalidCborHex, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be($"Invalid option --cbor-hex is required"); + } + + [Theory] + [InlineData("fff", "testnet")] + [InlineData("fff", "mainnet")] + [InlineData("addr1qyg4gu4vfd3775glq8rjm85x2crmysc920hf5qjj8m7rxef8jemaq77p80ka87tm4vyem0sqnuerpmtw2awtu76dl4jssqfzz4", "testnet")] + [InlineData("addr1qyg4gu4vfd3775glq8rjm85x2crmysc920hf5qjj8m7rxef8jemaq77p80ka87tm4vyem0sqnuerpmtw2awtu76dl4jssqfzz4", "mainnet")] + [InlineData("stake1uyneva7s00qnhmwnl9a6kzvahcqf7v3sa4h9wh970dxl6egwp9mlw", "testnet")] + [InlineData("stake1uyneva7s00qnhmwnl9a6kzvahcqf7v3sa4h9wh970dxl6egwp9mlw", "mainnet")] + public async Task Execute_Unsuccessfully_With_FailureInvalidOptions_When_CborHex_Is_Not_Valid_Hexadecimal( + string invalidCborHex, string network) + { + var command = new SubmitTransactionCommand() + { + Network = network, + CborHex = invalidCborHex, + }; + + var executionResult = await command.ExecuteAsync(CancellationToken.None); + + executionResult.Outcome.Should().Be(CommandOutcome.FailureInvalidOptions); + executionResult.Result.Should().Be($"Invalid option --cbor-hex {invalidCborHex} is not in hexadecimal format"); + } +}