diff --git a/docs/Build/node-cli.md b/docs/Build/node-cli.md index c41075c4b..7f91c55b1 100644 --- a/docs/Build/node-cli.md +++ b/docs/Build/node-cli.md @@ -1,4 +1,5 @@ !!! info "Reminder !!" + Unless there is a very particular reason you want to compile (eg: running on non-popular OS flavor), you DO NOT "need" to build node binaries - `guild-deploy.sh` already provides an option to download pre-compiled binaries. Ensure the [Pre-Requisites](../basics.md#pre-requisites) are in place before you proceed. ### Build Instructions @@ -40,10 +41,10 @@ Execute `cardano-cli` and `cardano-node` to verify output as below (the exact ve ```bash cardano-cli version -# cardano-cli 8.x.x - linux-x86_64 - ghc-8.10 +# cardano-cli 9.x.x - linux-x86_64 - ghc-8.10 # git rev <...> cardano-node version -# cardano-node 8.x.x - linux-x86_64 - ghc-8.10 +# cardano-node 9.x.x - linux-x86_64 - ghc-8.10 # git rev <...> ``` @@ -106,7 +107,7 @@ This file tells your node how to connect to other nodes (especially initially to !!! important On BP, You'd want to set `useLedgerAfterSlot` to `-1` for your Block Producing (Core) node - thereby, telling your Core node to remain in non-P2P mode, and ensure `PeerSharing` is to `false`. -The resultant topology file could look something like below: +The resultant topology file on a relay could look something like below: ``` json { @@ -118,25 +119,29 @@ The resultant topology file could look something like below: { "address": "backbone.mainnet.emurgornd.com", "port": 3001 + }, + { + "address": "backbone.mainnet.cardanofoundation.org", + "port": 3001 } ], "localRoots": [ { "accessPoints": [ - {"address": "xx.xx.xx.xx", "port": 6000 }, - {"address": "xx.xx.xx.yy", "port": 6000 } + {"address": "xx.xx.xx.xx", "port": 6000 , "description": "Core"}, + {"address": "zz.zz.zz.zz", "port": 6000 , "description": "Relay2"} ], "advertise": false, "trustable": true, - "valency": 2 + "hotValency": 2 }, { "accessPoints": [ {"address": "node-dus.poolunder.com", "port": 6900, "pool": "UNDR", "location": "EU/DE/Dusseldorf" }, {"address": "node-syd.poolunder.com", "port": 6900, "pool": "UNDR", "location": "OC/AU/Sydney" }, {"address": "194.36.145.157", "port": 6000, "pool": "RDLRT", "location": "EU/DE/Baden" }, - {"address": "152.53.18.60", "port": 6000, "pool": "RDLRT", "location": "NA/US/StLouis" }, - {"address": "148.72.153.168", "port": 16000, "pool": "AAA", "location": "US/StLouis" }, + {"address": "95.216.38.251", "port": 6000, "pool": "RDLRT", "location": "EU/FI/Helsinki" }, + {"address": "148.72.153.168", "port": 16000, "pool": "AAA", "location": "NA/US/StLouis" }, {"address": "78.47.99.41", "port": 6000, "pool": "AAA", "location": "EU/DE/Nuremberg" }, {"address": "relay1-pub.ahlnet.nu", "port": 2111, "pool": "AHL", "location": "EU/SE/Malmo" }, {"address": "relay2-pub.ahlnet.nu", "port": 2111, "pool": "AHL", "location": "EU/SE/Malmo" }, @@ -146,7 +151,7 @@ The resultant topology file could look something like below: ], "advertise": false, "trustable": false, - "valency": 5, + "hotValency": 5, "warmValency": 10 } ], @@ -156,7 +161,33 @@ The resultant topology file could look something like below: "advertise": false } ], - "useLedgerAfterSlot": 119160667 + "useLedgerAfterSlot": 128908821 +} +``` + +Similarly, a typical topology file on a Core could look something like below: + +``` json +{ + "bootstrapPeers": [], + "localRoots": [ + { + "accessPoints": [ + {"address": "yy.yy.yy.yy", "port": 6000, "description": "Relay1"}, + {"address": "zz.zz.zz.zz", "port": 6000, "description": "Relay2"} + ], + "advertise": false, + "trustable": true, + "hotValency": 2 + } + ], + "publicRoots": [ + { + "accessPoints": [], + "advertise": false + } + ], + "useLedgerAfterSlot": -1 } ``` diff --git a/docs/Scripts/cntools-changelog.md b/docs/Scripts/cntools-changelog.md index a3f858299..44b9f7b29 100644 --- a/docs/Scripts/cntools-changelog.md +++ b/docs/Scripts/cntools-changelog.md @@ -6,6 +6,18 @@ All notable changes to this tool will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [13.1.0] - 2024-08-01 +#### Added +- New Vote menu option to hold everything related to voting on Cardano. + - Governance features according to CIP-1694 with Conway era. + - Catalyst vote registration. +- MultiSig wallet support through advanced menu item. +- Creation of new mnemonic(seed phrase) generated wallet. +- Custom mnemonic derivation path on mnemonic/hardware import. +- For a more in-depth list, see https://github.com/cardano-community/guild-operators/pull/1783 +#### Changed +- Removed CIP-0095 SPO poll + ## [13.0.2] - 2024-06-07 #### Fixed - Mnemonic import. diff --git a/docs/basics.md b/docs/basics.md index 181d29f57..171f37497 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -8,18 +8,19 @@ The architecture for various components are already described at [docs.cardano.o While we do not intend to hand out step-by-step instructions, the tools are often misused as a shortcut to avoid ensuring base skillsets mentioned on home page. Some of the common gotchas that we often find SPOs to miss out on: - It is imperative that pools operate with highly accurate system time, in order to propogate blocks to network in a timely manner and avoid penalties to own (or at times other competing) blocks. Please refer to sample guidance [here ](https://ubuntu.com/server/docs/network-ntp) for details - the precise steps may depend on your OS. -- Ensure your Firewall rules at Network as well as OS level are updated according to the usage of your system, you'd want to whitelist the rules that you really need to open to world (eg: You might need node, SSH, and potentially secured webserver/proxy ports to be open, depending on components you run). +- Ensure your Firewall rules at Network as well as OS level are updated according to the usage of your system, you'd want to whitelist the rules that you really need to open to world (eg: You might need node and SSH ports to be open to relays and perhaps home workstation on core, while open node to internet on relays, depending on your topology and configuration that you run). - Update your SSH Configuration to prevent password-based logon. - Ensure that you use offline workflow, you should never require to have your offline keys on online nodes. The tools provide you backup/restore functionality to only pass online keys to online nodes. #### Pre-Requisites !!! info "Reminder !!" - You're expected to run the commands below from same session, using same working directories as indicated and using a `non-root user with sudo access`. You are expected to be familiar with this as part of pre-requisite skill sets for stake pool operators. + You're expected to run the commands below from same session, using same working directories as indicated and using a `non-root user with sudo access`. You are expected to be familiar with this as part of pre-requisite skill sets for stake pool operators. ##### Set up OS packages, folder structure and fetch files from repo {: #os-prereqs} -The pre-requisites for Linux systems are automated to be executed as a single script. To download the pre-requisites scripts, execute the below: +The pre-requisites for Linux systems are automated to be executed as a single script. This script uses opt-in election of what you'd like the script to do. The defaults without any arguments will only update static part of script contents for you. +To download the pre-requisites scripts, execute the below: ```bash mkdir "$HOME/tmp";cd "$HOME/tmp" @@ -30,11 +31,14 @@ curl -sS -o guild-deploy.sh https://raw.githubusercontent.com/cardano-community/ chmod 755 guild-deploy.sh ``` -Please familiarise with the syntax of `guild-deploy.sh` before proceeding. The usage syntax can be checked using `./guild-deploy.sh -h` , sample output below: +!!! info "Important !!" + Please familiarise with the syntax of `guild-deploy.sh` before proceeding (using -h as below). The exact parameters you want to run with is dependent on your, below are only sample instructions + +The usage syntax can be checked using `./guild-deploy.sh -h` , sample output below: ``` bash -Usage: guild-deploy.sh [-n ] [-p path] [-t ] [-b ] [-u] [-s [p][b][l][m][f][d][c][o][w][x]] +Usage: guild-deploy.sh [-n ] [-p path] [-t ] [-b ] [-u] [-s [p][b][l][m][d][c][o][w][x][f][s]] Set up dependencies for building/using common tools across cardano ecosystem. The script will always update dynamic content from existing scripts retaining existing user variables @@ -48,18 +52,18 @@ The script will always update dynamic content from existing scripts retaining ex b Install OS level dependencies for tools required while building cardano-node/cardano-db-sync components (Default: skip) l Build and Install libsodium fork from IO repositories (Default: skip) m Download latest (released) binaries for mithril-signer, mithril-client (Default: skip) - f Force overwrite entire content of scripts and config files (backups of existing ones will be created) (Default: skip) - d Download latest (released) binaries for bech32, cardano-address, cardano-node, cardano-cli, cardano-db-sync and cardano-submit-api binaries (Default: skip) - c Install/Upgrade CNCLI binary (Default: skip) # (1)! - o Install/Upgrade Ogmios Server binary (Default: skip) - w Install/Upgrade Cardano Hardware CLI (Default: skip) - x Install/Upgrade Cardano Signer binary (Default: skip) + d Download latest (released) binaries for bech32, cardano-address, cardano-node, cardano-cli, cardano-db-sync and cardano-submit-api (Default: skip) + c Download latest (released) binaries for CNCLI (Default: skip) + o Download latest (released) binaries for Ogmios (Default: skip) + w Download latest (released) binaries for Cardano Hardware CLI (Default: skip) + x Download latest (released) binaries for Cardano Signer binary (Default: skip) + f Force overwrite config files (backups of existing ones will be created) (Default: skip) + s Force overwrite entire content [including user variables] of scripts (Default: skip) ``` 1. If you receive an error for `glibc`, it would likely be due to the build mismatch between pre-compiled binary and your OS, which is not uncommon. You may need to compile cncli manually on your OS as per instructions [here](https://github.com/cardano-community/cncli/blob/develop/INSTALL.md#compile-from-source) - make sure to copy the output binary to `"${HOME}/.local/bin"` folder. -This script uses opt-in election of what you'd like the script to do (as against previous version that used to try and auto-detect versions). The defaults without any arguments will only update static part of script contents for you. A typical example install to install most components but not overwrite static part of existing files for preview network would be: ``` bash @@ -82,7 +86,7 @@ Lastly, if you'd want to update your scripts but not install any additional depe ##### Folder structure -Running the script above will create the folder structure as per below, for your reference. You can replace the top level folder `/opt/cardano/cnode` by editing the value of `CNODE_HOME` in `~/.bashrc` and `$CNODE_HOME/files/env` files: +Running the script above will create the folder structure as per below, for your reference. You do NOT require `CNODE_HOME` to be set on shell level as scripts will derive the parent folder and assign it at runtime, the addition in `~/.bashrc` is only for your ease to switch to right folder: /opt/cardano/cnode # Top-Level Folder diff --git a/docs/upgrade.md b/docs/upgrade.md index 4bee21198..19ca86091 100644 --- a/docs/upgrade.md +++ b/docs/upgrade.md @@ -1,59 +1,45 @@ -??? example "One-Time major upgrade for Koios Scripts from 20-Jan-2023 (expand for details)" - - The scripts on guild-operators repository have gone through quite a few changes to accomodate for the below: - - - Replace `prereqs.sh` with `guild-deploy.sh` using minimalistic approach (i.e. anything you need to deploy is now required to be specified using command-line arguments). The old `prereqs.sh` is left as-is but will no longer be maintained. - - Improve handling of environment variables for top level folder. Prior to this point, those who were using multiple deployments on same machine were required to have their session's environment set (for instance, using `prereqs.sh -t pvnode` would have created folder structure as `/opt/cardano/pvnode` and replaced `CNODE_HOME` references within scripts with `PVNODE_HOME`. This will no longer be required. The deriving of top level folder will be done relative to scripts folder. Thus, parent of the folder containing `env` file will automatically be treated as top level folder, and no longer depend on external environment variable. One may still use them for their own comfort to switch directories. - - The above also helps for manual download of script from github as it will no longer require substituting `CNODE_HOME` references. - - Consolidate binaries deployment to `"${HOME}"/.local/bin`. Previously, we could have had binaries deployed to various locations (`"${HOME}"/.cabal/bin` for node/CLI binaries, `"${HOME}"/.cargo/bin` for cncli binary, `"${HOME}"/bin` for downloaded binaries). This occured because of different compilers used different default locations for their output binariess (cargo for rust, cabal for Haskell, etc). The guild-deploy.sh/cabal-build-all.sh scripts will now provision the binaries to be made available to "${HOME}"/.local/bin instead. Ofcourse, as before, you can still customise the location of binaries using variables (eg: `CCLI`, `CNCLI`, `CNODE_HOME`) in `env` file. - - Add option to download pre-compiled binaries instead of compiling them - and accordingly - options in `guild-deploy.sh`, giving users both the options. - - Some of the above required us to add breaking changes to some scripts, but hopefully the above explains the premise for those changes. To ease this one-time upgrade process for existing deployments, we have tried to come up with the guide below, feel free to edit this file to improve the documents based on your experience. Again, apologies in advance to those who do not agree with the above changes (the old code would ofcourse remain unimpacted at tag `legacy-scripts`, so if you'd like to stick to old scripts , you can use `-b legacy-scripts` for your tools to switch back). - -### Steps for Ugrading +### Steps for Upgrading !!! warning - Make sure you go through upgrade steps for your setup in a non-mainnet environment first! + Make sure you go through upgrade steps for your setup in a non-mainnet environment first!! -- Download the latest `guild-deploy.sh` (checkout new syntax with `guild-deploy.sh -h`) to update all the scripts and files from the guild template. The scripts modified with user content (`env`, `gLiveView.sh`, `topologyUpdater.sh`, `cnode.sh`, etc) will be backed up before overwriting. The backed up files will be in the same folder as the original files, and will be named as *`${filename}_bkp`*. More static files (genesis files or some of the scripts themselves) will not be backed up, as they're not expected to be modified. +- Download the latest `guild-deploy.sh` (always double check syntax of the script with `guild-deploy.sh -h`). The scripts modified with user content (`env`, `gLiveView.sh`, `topologyUpdater.sh`, `cnode.sh`, etc) will be backed up before overwriting. The backed up files will be in the same folder as the original files, and will be named as *`${filename}_bkp`*. More static files (genesis files or some of the scripts themselves) will not be backed up, as they're not expected to be modified. !!! warning "Remember" - Please add any environment-specific parameters (eg: custom top level folder, network flag, etc) to the execution command below, similar to prereqs.sh (check new syntax using `guild-deploy.sh -h`) + You are expected to provide appropriate environment-specific parameters (eg: custom top level folder [-p], alternate name for top level folder [-t], network flag [-n], etc) to the examples that pertain to your use case -- A basic (minimal) run of the guild-deploy script that will only update current scripts on mainnet using default paths: +- Depending on node release, you may be able to simply perform an update-in-place of scripts and node, or for some cases, you may need to overwrite configs as well. Some Examples below: -``` bash -mkdir "$HOME/tmp";cd "$HOME/tmp" -curl -sS -o guild-deploy.sh https://raw.githubusercontent.com/cardano-community/guild-operators/master/scripts/cnode-helper-scripts/guild-deploy.sh -chmod 700 guild-deploy.sh -./guild-deploy.sh -s f -b master -``` + - A typical run of the guild-deploy script that will perform update in place of current scripts on mainnet and update binaries (alongwith re-compiling libsodium dependencies). In this scenario no config files or user variables in scripts are overwritten. This is typically relevant on minor patch releases of node upgrade: -- Source your bashrc file again , and ensure `"${HOME}"/.local/bin` is now part of your $PATH environment variable. + ``` bash + mkdir "$HOME/tmp";cd "$HOME/tmp" + curl -sfS -o guild-deploy.sh https://raw.githubusercontent.com/cardano-community/guild-operators/master/scripts/cnode-helper-scripts/guild-deploy.sh && chmod 700 guild-deploy.sh + ./guild-deploy.sh -s dl -b master -n mainnet -t cnode -p /opt/cardano/cnode + ``` -``` bash -source "${HOME}"/.bashrc -echo "${PATH}" -``` + - Another scenario would be when you're required to overwrite configs (eg: node-8.1.2 to node-9.1.0 introduced change in genesis/config/topology file formats). In this case, you'd want to overwrite your config files as well. You should follow changelog in node release notes to verify if you'd need to overwrite configs. Note that every time you do this, you may need to re-add your customisations - if any - to the relevant config files (typically - almost always, you'd have to update the topology.json when overwriting configs). There are backups created of original file in `"${CNODE_HOME}"/files` folder if you'd like to compare/reuse previous version. -- Check and add back your customisations to config files (or simply restore from automatically created backup of your config/topology files). + ``` bash + mkdir "$HOME/tmp";cd "$HOME/tmp" + curl -sfS -o guild-deploy.sh https://raw.githubusercontent.com/cardano-community/guild-operators/master/scripts/cnode-helper-scripts/guild-deploy.sh && chmod 700 guild-deploy.sh + ./guild-deploy.sh -s dlf -b master -n mainnet -t cnode -p /opt/cardano/cnode + ``` -- Since one of the basic changes we start to recommend as part of this revamp is moving your binaries to `"${HOME}"/.local/bin`, you would want to *move* the binaries below from current location: - - "${HOME}"/.cabal/bin - Binaries built by `cabal build all` script (eg: `cardano-node`, `cardano-cli`, `bech32`, `cardano-address`, `cardano-submit-api`, `cardano-db-sync` - - "${HOME}"/.cargo/bin - Binaries built by `cardano install` (eg: `cncli`) - - "${HOME}"/bin - Downloaded binaries from previous `prereqs.sh` (eg: `cardano-hw-cli`) +!!! warning "Beware" + When upgrading node, depending on node versions (especially for major release) - you'd likely have to wait for node to revalidate/replay ledger. This can take a few hours. Please always plan ahead, do it first on a relay to ensure you've got "${CNODE_HOME}/db" folder ready to copy over (while source and target node have been shutdown) - prior to starting on upgrade on new machine. If mithril for target node version is ready, you can also use [mithril-client](../Scripts/mithril-client.md) to download snapshot instead of replaying, which may save you some time -You can move the binaries by using mv command (for example, if you dont have any other files in these folders, you can use the command below: - -!!! note "Note" - Ideally, you should shutdown services (eg: cnode, cnode-dbsync, etc) prior to running the below to ensure they run from new location (you can also re-deploy them if you haven't done so in a while, eg: `./cnode.sh -d`). At the end of the guide, you can start them back up. +- Once guild-deploy script has been run, source your bashrc file again (or restart session), and ensure `"${HOME}"/.local/bin` is part of your $PATH environment variable. If your shell does not auto-run bashrc, you may want to set it a call to "${HOME}"/.bashrc in your `.profile` ``` bash -mv -t "${HOME}"/.local/bin/ "${HOME}"/.cabal/bin/* "${HOME}"/.cargo/bin/* "${HOME}"/bin/* +source "${HOME}"/.bashrc +echo "${PATH}" ``` -- We've found users often confuse between $PATH variable resolution between multiple shell sessions, systemd, etc. To avoid this, edit the following files and uncomment and set the following variables to the appropriate paths as per your deployment (eg: `CCLI="${HOME}"/.local/bin/cardano-cli` if following above): +### Troubleshooting {: #troubleshooting} + +- We've found users often confuse between $PATH variable resolution between multiple shell sessions, systemd, etc. While if you only used this guide, the binaries should be in "${HOME}/.local/bin", you may have manually downloaded to another location before. To avoid this, you can edit the following files and uncomment and set the following variables to the appropriate paths as per your deployment (eg: `CCLI="${HOME}"/.local/bin/cardano-cli` if following above): - env : CCLI, CNCLI, CNODEBIN - [If applicable] dbsync.sh: DBSYNCBIN @@ -66,8 +52,14 @@ mv -t "${HOME}"/.local/bin/ "${HOME}"/.cabal/bin/* "${HOME}"/.cargo/bin/* "${HOM whereis bech32 cardano-address cardano-cli cardano-db-sync cardano-hw-cli cardano-node cardano-submit-api cncli ogmios ``` -The above might result in some lines having more than one entry (eg: you might have `cardano-cli` in `"${HOME}"/.cabal/bin` and `"${HOME}"/.local/bin`) - for which you'd want to delete the reference(s) not in `"${HOME}"/.local/bin` , while for other cases - you might have no values (eg: you may not use `cardano-db-sync`, `cncli`, `ogmios` and/or `cardano-hw-cli`. You need not take any actions for the binaries you do not use. +For some cases - you might have no values (eg: you may not use `cardano-db-sync`, `cncli`, `ogmios` and/or `cardano-hw-cli`. You need not take any actions for the binaries you do not use. + +- If you are having trouble connecting to node post upgrade from gLiveView, typically the first issue you'd want to eliminate is whether you missed that node might need ledger replay/revalidation (which could take hours as indicated earlier on the page). You can check the node status via `sudo systemctl status cnode`. If the node shows as up, you can monitor logs via `tail -100f "${CNODE_HOME}/logs/node.json`. If you're unable to start node using systemd itself, you can check the systemd output via `sudo journalctl -xeu cnode.service` and scroll back until you see a startup attempt with reason (typically this could be a couple of pages back). If nothing obvious (eg: showing files used as `/files/config.json` instead of `$CNODE_HOME/files/config.json` )comes up from there, you'd want to view node logs to see if there is something recorded in `node.json` instead. + +### Unintended update-in-place {: #unintended} + +Let's say you accidentally did an update of the files that you didnt intend to and want to revert all your scripts to previous state. While you have the backups of the scripts created while doing in-place update, you can always make use of branch flag that most scripts that perform update-in-place provide (eg: for gLiveView.sh that you want to restore to 8.1.2 state), this would be `-b node-8.1.2`. The available tags that can be used can be visited [here](https://github.com/cardano-community/guild-operators/tags) -### Support/Improvements +### Support/Improvements {: #support} -Hope the guide above helps you with the migration, but again - we could've missed some edge cases. If so, please report via chat in [Koios Discussions channel](https://t.me/CardanoKoios) only. Please DO NOT make edits to the script content based on forum/alternate guide/channels, while done with best intentions - there have been solutions put online that modify files unnecessarily instead of correcting configs and disabling updates, such actions will only cause trouble for future updates. +Hope the guide above helps you with the migration, but again - we could've missed some edge cases. If so, please report via chat in [Koios Discussions channel](https://t.me/CardanoKoios) or open an issue on github. Please DO NOT make edits to the script content based on forum/alternate guide/channels, while done with best intentions - there have been solutions put online that modify files unnecessarily instead of correcting configs and disabling updates, such actions will only cause trouble for future updates. diff --git a/files/configs/guild/config.json b/files/configs/guild/config.json index 3e9582fcb..137ea8744 100644 --- a/files/configs/guild/config.json +++ b/files/configs/guild/config.json @@ -6,7 +6,7 @@ "LastKnownBlockVersion-Alt": 0, "LastKnownBlockVersion-Major": 3, "LastKnownBlockVersion-Minor": 1, - "MinNodeVersion": "8.9.4", + "MinNodeVersion": "9.0.0", "PeerSharing": true, "Protocol": "Cardano", "RequiresNetworkMagic": "RequiresMagic", diff --git a/files/configs/guild/conway-genesis.json b/files/configs/guild/conway-genesis.json index e690f455f..cd51be825 100644 --- a/files/configs/guild/conway-genesis.json +++ b/files/configs/guild/conway-genesis.json @@ -7,32 +7,297 @@ "ppSecurityGroup": 0.51 }, "dRepVotingThresholds": { - "motionNoConfidence": 0.51, - "committeeNormal": 0.51, - "committeeNoConfidence": 0.51, - "updateToConstitution": 0.51, - "hardForkInitiation": 0.51, - "ppNetworkGroup": 0.51, - "ppEconomicGroup": 0.51, - "ppTechnicalGroup": 0.51, - "ppGovGroup": 0.51, - "treasuryWithdrawal": 0.51 + "motionNoConfidence": 0.67, + "committeeNormal": 0.67, + "committeeNoConfidence": 0.6, + "updateToConstitution": 0.75, + "hardForkInitiation": 0.6, + "ppNetworkGroup": 0.67, + "ppEconomicGroup": 0.67, + "ppTechnicalGroup": 0.67, + "ppGovGroup": 0.75, + "treasuryWithdrawal": 0.67 }, - "committeeMinSize": 0, - "committeeMaxTermLength": 200, - "govActionLifetime": 10, - "govActionDeposit": 1000000000, - "dRepDeposit": 2000000, + "committeeMinSize": 7, + "committeeMaxTermLength": 146, + "govActionLifetime": 6, + "govActionDeposit": 100000000000, + "dRepDeposit": 500000000, "dRepActivity": 20, + "minFeeRefScriptCostPerByte": 15, + "plutusV3CostModel": [ + 100788, + 420, + 1, + 1, + 1000, + 173, + 0, + 1, + 1000, + 59957, + 4, + 1, + 11183, + 32, + 201305, + 8356, + 4, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 100, + 100, + 16000, + 100, + 94375, + 32, + 132994, + 32, + 61462, + 4, + 72010, + 178, + 0, + 1, + 22151, + 32, + 91189, + 769, + 4, + 2, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 1, + 1000, + 42921, + 4, + 2, + 24548, + 29498, + 38, + 1, + 898148, + 27279, + 1, + 51775, + 558, + 1, + 39184, + 1000, + 60594, + 1, + 141895, + 32, + 83150, + 32, + 15299, + 32, + 76049, + 1, + 13169, + 4, + 22100, + 10, + 28999, + 74, + 1, + 28999, + 74, + 1, + 43285, + 552, + 1, + 44749, + 541, + 1, + 33852, + 32, + 68246, + 32, + 72362, + 32, + 7243, + 32, + 7391, + 32, + 11546, + 32, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 90434, + 519, + 0, + 1, + 74433, + 32, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 1, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 955506, + 213312, + 0, + 2, + 270652, + 22588, + 4, + 1457325, + 64566, + 4, + 20467, + 1, + 4, + 0, + 141992, + 32, + 100788, + 420, + 1, + 1, + 81663, + 32, + 59498, + 32, + 20142, + 32, + 24588, + 32, + 20744, + 32, + 25933, + 32, + 24623, + 32, + 43053543, + 10, + 53384111, + 14333, + 10, + 43574283, + 26308, + 10, + 16000, + 100, + 16000, + 100, + 962335, + 18, + 2780678, + 6, + 442008, + 1, + 52538055, + 3756, + 18, + 267929, + 18, + 76433006, + 8868, + 18, + 52948122, + 18, + 1995836, + 36, + 3227919, + 12, + 901022, + 1, + 166917843, + 4307, + 36, + 284546, + 36, + 158221314, + 26549, + 36, + 74698472, + 36, + 333849714, + 1, + 254006273, + 72, + 2174038, + 72, + 2261318, + 64571, + 4, + 207616, + 8310, + 4, + 1293828, + 28716, + 63, + 0, + 1, + 1006041, + 43623, + 251, + 0, + 1 + ], "constitution": { - "anchor": { - "url": "", - "dataHash": "0000000000000000000000000000000000000000000000000000000000000000" - } + "anchor": { + "dataHash": "ca41a91f399259bcefe57f9858e91f6d00e1a38d6d9c63d4052914ea7bd70cb2", + "url": "ipfs://bafkreifnwj6zpu3ixa4siz2lndqybyc5wnnt3jkwyutci4e2tmbnj3xrdm" + }, + "script": "fa24fb305126805cf2164c161d852a0e7330cf988f1fe558cf7d4a64" }, "committee": { "members": { + "scriptHash-a6a5e006fd4e8f51062dc431362369b2a43140abced8aa2ff2256d7b": 229, + "scriptHash-6095e643ea6f1cccb6e463ec34349026b3a48621aac5d512655ab1bf": 229, + "scriptHash-94c0de47e7ae32e3f7234ada5cf976506b68e3bb88c54dc53b4ba984": 229, + "scriptHash-5098dfd0deba725fadd692198fc33ee959fbe7e6edf1b5a695e06e61": 229, + "scriptHash-5a71f17f4ce4c1c0be053575d717ade6ad8a1d5453d02a65ce40d4b1": 229, + "scriptHash-2f4a6c6f098e20ee4bfd5b39942c164575f8ceb348e754df5d0ec04f": 229, + "scriptHash-94f51c795a6c11adb9c1e30f0b6def4230cbd0b8bc800098e2d2307b": 229 }, - "quorum": 0 + "threshold": { + "numerator": 2, + "denominator": 3 + } } } \ No newline at end of file diff --git a/files/configs/guild/db-sync-config.json b/files/configs/guild/db-sync-config.json index 864e5b346..94b1f4be6 100644 --- a/files/configs/guild/db-sync-config.json +++ b/files/configs/guild/db-sync-config.json @@ -33,8 +33,10 @@ "enable": true }, "governance": "enable", + "json_type": "text", "offchain_pool_data": "enable", - "json_type": "text" + "pool_stat": "enable", + "tx_cbor": "enable" }, "minSeverity": "Info", "options": { diff --git a/files/configs/guild/topology.json b/files/configs/guild/topology.json index 6905f143b..2bb9c973e 100644 --- a/files/configs/guild/topology.json +++ b/files/configs/guild/topology.json @@ -6,7 +6,7 @@ ], "advertise": false, "trustable": true, - "valency": 1 + "hotValency": 1 }, { "accessPoints": [ @@ -19,7 +19,7 @@ ], "advertise": false, "trustable": false, - "valency": 3, + "hotValency": 3, "warmValency": 5 } ], diff --git a/files/configs/mainnet/config.json b/files/configs/mainnet/config.json index 3e112311c..d2b1d2c1a 100644 --- a/files/configs/mainnet/config.json +++ b/files/configs/mainnet/config.json @@ -7,7 +7,7 @@ "LastKnownBlockVersion-Major": 3, "LastKnownBlockVersion-Minor": 0, "MaxKnownMajorProtocolVersion": 2, - "MinNodeVersion": "8.9.4", + "MinNodeVersion": "9.0.0", "PeerSharing": false, "Protocol": "Cardano", "RequiresNetworkMagic": "RequiresNoMagic", diff --git a/files/configs/mainnet/conway-genesis.json b/files/configs/mainnet/conway-genesis.json index e690f455f..85ca864fe 100644 --- a/files/configs/mainnet/conway-genesis.json +++ b/files/configs/mainnet/conway-genesis.json @@ -7,32 +7,297 @@ "ppSecurityGroup": 0.51 }, "dRepVotingThresholds": { - "motionNoConfidence": 0.51, - "committeeNormal": 0.51, - "committeeNoConfidence": 0.51, - "updateToConstitution": 0.51, - "hardForkInitiation": 0.51, - "ppNetworkGroup": 0.51, - "ppEconomicGroup": 0.51, - "ppTechnicalGroup": 0.51, - "ppGovGroup": 0.51, - "treasuryWithdrawal": 0.51 + "motionNoConfidence": 0.67, + "committeeNormal": 0.67, + "committeeNoConfidence": 0.6, + "updateToConstitution": 0.75, + "hardForkInitiation": 0.6, + "ppNetworkGroup": 0.67, + "ppEconomicGroup": 0.67, + "ppTechnicalGroup": 0.67, + "ppGovGroup": 0.75, + "treasuryWithdrawal": 0.67 }, - "committeeMinSize": 0, - "committeeMaxTermLength": 200, - "govActionLifetime": 10, - "govActionDeposit": 1000000000, - "dRepDeposit": 2000000, + "committeeMinSize": 7, + "committeeMaxTermLength": 146, + "govActionLifetime": 6, + "govActionDeposit": 100000000000, + "dRepDeposit": 500000000, "dRepActivity": 20, + "minFeeRefScriptCostPerByte": 15, + "plutusV3CostModel": [ + 100788, + 420, + 1, + 1, + 1000, + 173, + 0, + 1, + 1000, + 59957, + 4, + 1, + 11183, + 32, + 201305, + 8356, + 4, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 100, + 100, + 16000, + 100, + 94375, + 32, + 132994, + 32, + 61462, + 4, + 72010, + 178, + 0, + 1, + 22151, + 32, + 91189, + 769, + 4, + 2, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 1, + 1000, + 42921, + 4, + 2, + 24548, + 29498, + 38, + 1, + 898148, + 27279, + 1, + 51775, + 558, + 1, + 39184, + 1000, + 60594, + 1, + 141895, + 32, + 83150, + 32, + 15299, + 32, + 76049, + 1, + 13169, + 4, + 22100, + 10, + 28999, + 74, + 1, + 28999, + 74, + 1, + 43285, + 552, + 1, + 44749, + 541, + 1, + 33852, + 32, + 68246, + 32, + 72362, + 32, + 7243, + 32, + 7391, + 32, + 11546, + 32, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 90434, + 519, + 0, + 1, + 74433, + 32, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 1, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 955506, + 213312, + 0, + 2, + 270652, + 22588, + 4, + 1457325, + 64566, + 4, + 20467, + 1, + 4, + 0, + 141992, + 32, + 100788, + 420, + 1, + 1, + 81663, + 32, + 59498, + 32, + 20142, + 32, + 24588, + 32, + 20744, + 32, + 25933, + 32, + 24623, + 32, + 43053543, + 10, + 53384111, + 14333, + 10, + 43574283, + 26308, + 10, + 16000, + 100, + 16000, + 100, + 962335, + 18, + 2780678, + 6, + 442008, + 1, + 52538055, + 3756, + 18, + 267929, + 18, + 76433006, + 8868, + 18, + 52948122, + 18, + 1995836, + 36, + 3227919, + 12, + 901022, + 1, + 166917843, + 4307, + 36, + 284546, + 36, + 158221314, + 26549, + 36, + 74698472, + 36, + 333849714, + 1, + 254006273, + 72, + 2174038, + 72, + 2261318, + 64571, + 4, + 207616, + 8310, + 4, + 1293828, + 28716, + 63, + 0, + 1, + 1006041, + 43623, + 251, + 0, + 1 + ], "constitution": { - "anchor": { - "url": "", - "dataHash": "0000000000000000000000000000000000000000000000000000000000000000" - } + "anchor": { + "dataHash": "ca41a91f399259bcefe57f9858e91f6d00e1a38d6d9c63d4052914ea7bd70cb2", + "url": "ipfs://bafkreifnwj6zpu3ixa4siz2lndqybyc5wnnt3jkwyutci4e2tmbnj3xrdm" + }, + "script": "fa24fb305126805cf2164c161d852a0e7330cf988f1fe558cf7d4a64" }, "committee": { "members": { + "scriptHash-df0e83bde65416dade5b1f97e7f115cc1ff999550ad968850783fe50": 580, + "scriptHash-b6012034ba0a7e4afbbf2c7a1432f8824aee5299a48e38e41a952686": 580, + "scriptHash-ce8b37a72b178a37bbd3236daa7b2c158c9d3604e7aa667e6c6004b7": 580, + "scriptHash-f0dc2c00d92a45521267be2d5de1c485f6f9d14466d7e16062897cf7": 580, + "scriptHash-349e55f83e9af24813e6cb368df6a80d38951b2a334dfcdf26815558": 580, + "scriptHash-84aebcfd3e00d0f87af918fc4b5e00135f407e379893df7e7d392c6a": 580, + "scriptHash-e8165b3328027ee0d74b1f07298cb092fd99aa7697a1436f5997f625": 580 }, - "quorum": 0 + "threshold": { + "numerator": 2, + "denominator": 3 + } } } \ No newline at end of file diff --git a/files/configs/mainnet/db-sync-config.json b/files/configs/mainnet/db-sync-config.json index 732d25237..9f13c45f4 100644 --- a/files/configs/mainnet/db-sync-config.json +++ b/files/configs/mainnet/db-sync-config.json @@ -33,8 +33,10 @@ "enable": true }, "governance": "enable", + "json_type": "text", "offchain_pool_data": "enable", - "json_type": "text" + "pool_stat": "enable", + "tx_cbor": "enable" }, "minSeverity": "Info", "options": { diff --git a/files/configs/mainnet/topology.json b/files/configs/mainnet/topology.json index 5fbe413c9..0a881ee27 100644 --- a/files/configs/mainnet/topology.json +++ b/files/configs/mainnet/topology.json @@ -21,15 +21,15 @@ ], "advertise": false, "trustable": true, - "valency": 2 + "hotValency": 2 }, { "accessPoints": [ {"address": "node-dus.poolunder.com", "port": 6900, "pool": "UNDR", "location": "EU/DE/Dusseldorf" }, {"address": "node-syd.poolunder.com", "port": 6900, "pool": "UNDR", "location": "OC/AU/Sydney" }, {"address": "194.36.145.157", "port": 6000, "pool": "RDLRT", "location": "EU/DE/Baden" }, - {"address": "152.53.18.60", "port": 6000, "pool": "RDLRT", "location": "NA/US/StLouis" }, - {"address": "148.72.153.168", "port": 16000, "pool": "AAA", "location": "US/StLouis" }, + {"address": "95.216.38.251", "port": 6000, "pool": "RDLRT", "location": "EU/FI/Helsinki" }, + {"address": "148.72.153.168", "port": 16000, "pool": "AAA", "location": "NA/US/StLouis" }, {"address": "78.47.99.41", "port": 6000, "pool": "AAA", "location": "EU/DE/Nuremberg" }, {"address": "relay1-pub.ahlnet.nu", "port": 2111, "pool": "AHL", "location": "EU/SE/Malmo" }, {"address": "relay2-pub.ahlnet.nu", "port": 2111, "pool": "AHL", "location": "EU/SE/Malmo" }, @@ -38,7 +38,7 @@ ], "advertise": false, "trustable": false, - "valency": 5, + "hotValency": 5, "warmValency": 10 } ], @@ -48,5 +48,5 @@ "advertise": false } ], - "useLedgerAfterSlot": 119160667 + "useLedgerAfterSlot": 128908821 } diff --git a/files/configs/preprod/config.json b/files/configs/preprod/config.json index dcdbb2c85..1a5a27be2 100644 --- a/files/configs/preprod/config.json +++ b/files/configs/preprod/config.json @@ -6,7 +6,7 @@ "LastKnownBlockVersion-Alt": 0, "LastKnownBlockVersion-Major": 2, "LastKnownBlockVersion-Minor": 0, - "MinNodeVersion": "8.9.4", + "MinNodeVersion": "9.0.0", "PeerSharing": false, "Protocol": "Cardano", "RequiresNetworkMagic": "RequiresMagic", diff --git a/files/configs/preprod/conway-genesis.json b/files/configs/preprod/conway-genesis.json index e690f455f..cd51be825 100644 --- a/files/configs/preprod/conway-genesis.json +++ b/files/configs/preprod/conway-genesis.json @@ -7,32 +7,297 @@ "ppSecurityGroup": 0.51 }, "dRepVotingThresholds": { - "motionNoConfidence": 0.51, - "committeeNormal": 0.51, - "committeeNoConfidence": 0.51, - "updateToConstitution": 0.51, - "hardForkInitiation": 0.51, - "ppNetworkGroup": 0.51, - "ppEconomicGroup": 0.51, - "ppTechnicalGroup": 0.51, - "ppGovGroup": 0.51, - "treasuryWithdrawal": 0.51 + "motionNoConfidence": 0.67, + "committeeNormal": 0.67, + "committeeNoConfidence": 0.6, + "updateToConstitution": 0.75, + "hardForkInitiation": 0.6, + "ppNetworkGroup": 0.67, + "ppEconomicGroup": 0.67, + "ppTechnicalGroup": 0.67, + "ppGovGroup": 0.75, + "treasuryWithdrawal": 0.67 }, - "committeeMinSize": 0, - "committeeMaxTermLength": 200, - "govActionLifetime": 10, - "govActionDeposit": 1000000000, - "dRepDeposit": 2000000, + "committeeMinSize": 7, + "committeeMaxTermLength": 146, + "govActionLifetime": 6, + "govActionDeposit": 100000000000, + "dRepDeposit": 500000000, "dRepActivity": 20, + "minFeeRefScriptCostPerByte": 15, + "plutusV3CostModel": [ + 100788, + 420, + 1, + 1, + 1000, + 173, + 0, + 1, + 1000, + 59957, + 4, + 1, + 11183, + 32, + 201305, + 8356, + 4, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 100, + 100, + 16000, + 100, + 94375, + 32, + 132994, + 32, + 61462, + 4, + 72010, + 178, + 0, + 1, + 22151, + 32, + 91189, + 769, + 4, + 2, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 1, + 1000, + 42921, + 4, + 2, + 24548, + 29498, + 38, + 1, + 898148, + 27279, + 1, + 51775, + 558, + 1, + 39184, + 1000, + 60594, + 1, + 141895, + 32, + 83150, + 32, + 15299, + 32, + 76049, + 1, + 13169, + 4, + 22100, + 10, + 28999, + 74, + 1, + 28999, + 74, + 1, + 43285, + 552, + 1, + 44749, + 541, + 1, + 33852, + 32, + 68246, + 32, + 72362, + 32, + 7243, + 32, + 7391, + 32, + 11546, + 32, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 90434, + 519, + 0, + 1, + 74433, + 32, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 1, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 955506, + 213312, + 0, + 2, + 270652, + 22588, + 4, + 1457325, + 64566, + 4, + 20467, + 1, + 4, + 0, + 141992, + 32, + 100788, + 420, + 1, + 1, + 81663, + 32, + 59498, + 32, + 20142, + 32, + 24588, + 32, + 20744, + 32, + 25933, + 32, + 24623, + 32, + 43053543, + 10, + 53384111, + 14333, + 10, + 43574283, + 26308, + 10, + 16000, + 100, + 16000, + 100, + 962335, + 18, + 2780678, + 6, + 442008, + 1, + 52538055, + 3756, + 18, + 267929, + 18, + 76433006, + 8868, + 18, + 52948122, + 18, + 1995836, + 36, + 3227919, + 12, + 901022, + 1, + 166917843, + 4307, + 36, + 284546, + 36, + 158221314, + 26549, + 36, + 74698472, + 36, + 333849714, + 1, + 254006273, + 72, + 2174038, + 72, + 2261318, + 64571, + 4, + 207616, + 8310, + 4, + 1293828, + 28716, + 63, + 0, + 1, + 1006041, + 43623, + 251, + 0, + 1 + ], "constitution": { - "anchor": { - "url": "", - "dataHash": "0000000000000000000000000000000000000000000000000000000000000000" - } + "anchor": { + "dataHash": "ca41a91f399259bcefe57f9858e91f6d00e1a38d6d9c63d4052914ea7bd70cb2", + "url": "ipfs://bafkreifnwj6zpu3ixa4siz2lndqybyc5wnnt3jkwyutci4e2tmbnj3xrdm" + }, + "script": "fa24fb305126805cf2164c161d852a0e7330cf988f1fe558cf7d4a64" }, "committee": { "members": { + "scriptHash-a6a5e006fd4e8f51062dc431362369b2a43140abced8aa2ff2256d7b": 229, + "scriptHash-6095e643ea6f1cccb6e463ec34349026b3a48621aac5d512655ab1bf": 229, + "scriptHash-94c0de47e7ae32e3f7234ada5cf976506b68e3bb88c54dc53b4ba984": 229, + "scriptHash-5098dfd0deba725fadd692198fc33ee959fbe7e6edf1b5a695e06e61": 229, + "scriptHash-5a71f17f4ce4c1c0be053575d717ade6ad8a1d5453d02a65ce40d4b1": 229, + "scriptHash-2f4a6c6f098e20ee4bfd5b39942c164575f8ceb348e754df5d0ec04f": 229, + "scriptHash-94f51c795a6c11adb9c1e30f0b6def4230cbd0b8bc800098e2d2307b": 229 }, - "quorum": 0 + "threshold": { + "numerator": 2, + "denominator": 3 + } } } \ No newline at end of file diff --git a/files/configs/preprod/db-sync-config.json b/files/configs/preprod/db-sync-config.json index 68be1ace1..21c5353e2 100644 --- a/files/configs/preprod/db-sync-config.json +++ b/files/configs/preprod/db-sync-config.json @@ -33,8 +33,10 @@ "enable": true }, "governance": "enable", + "json_type": "text", "offchain_pool_data": "enable", - "json_type": "text" + "pool_stat": "enable", + "tx_cbor": "enable" }, "minSeverity": "Info", "options": { diff --git a/files/configs/preprod/topology.json b/files/configs/preprod/topology.json index ff12f6581..e96ced78d 100644 --- a/files/configs/preprod/topology.json +++ b/files/configs/preprod/topology.json @@ -13,7 +13,7 @@ ], "advertise": false, "trustable": true, - "valency": 2 + "hotValency": 2 }, { "accessPoints": [ @@ -24,7 +24,7 @@ ], "advertise": false, "trustable": false, - "valency": 2, + "hotValency": 2, "warmValency": 3 } ], @@ -34,5 +34,5 @@ "advertise": false } ], - "useLedgerAfterSlot": 55043150 + "useLedgerAfterSlot": 64454371 } \ No newline at end of file diff --git a/files/configs/preview/config.json b/files/configs/preview/config.json index 7e7beb59c..70e9fb338 100644 --- a/files/configs/preview/config.json +++ b/files/configs/preview/config.json @@ -8,7 +8,7 @@ "LastKnownBlockVersion-Alt": 0, "LastKnownBlockVersion-Major": 3, "LastKnownBlockVersion-Minor": 1, - "MinNodeVersion": "8.9.4", + "MinNodeVersion": "9.0.0", "PeerSharing": false, "Protocol": "Cardano", "RequiresNetworkMagic": "RequiresMagic", diff --git a/files/configs/preview/conway-genesis.json b/files/configs/preview/conway-genesis.json index e690f455f..b76d592ac 100644 --- a/files/configs/preview/conway-genesis.json +++ b/files/configs/preview/conway-genesis.json @@ -7,32 +7,291 @@ "ppSecurityGroup": 0.51 }, "dRepVotingThresholds": { - "motionNoConfidence": 0.51, - "committeeNormal": 0.51, - "committeeNoConfidence": 0.51, - "updateToConstitution": 0.51, - "hardForkInitiation": 0.51, - "ppNetworkGroup": 0.51, - "ppEconomicGroup": 0.51, - "ppTechnicalGroup": 0.51, - "ppGovGroup": 0.51, - "treasuryWithdrawal": 0.51 + "motionNoConfidence": 0.67, + "committeeNormal": 0.67, + "committeeNoConfidence": 0.6, + "updateToConstitution": 0.75, + "hardForkInitiation": 0.6, + "ppNetworkGroup": 0.67, + "ppEconomicGroup": 0.67, + "ppTechnicalGroup": 0.67, + "ppGovGroup": 0.75, + "treasuryWithdrawal": 0.67 }, "committeeMinSize": 0, - "committeeMaxTermLength": 200, - "govActionLifetime": 10, - "govActionDeposit": 1000000000, - "dRepDeposit": 2000000, + "committeeMaxTermLength": 365, + "govActionLifetime": 30, + "govActionDeposit": 100000000000, + "dRepDeposit": 500000000, "dRepActivity": 20, + "minFeeRefScriptCostPerByte": 15, + "plutusV3CostModel": [ + 100788, + 420, + 1, + 1, + 1000, + 173, + 0, + 1, + 1000, + 59957, + 4, + 1, + 11183, + 32, + 201305, + 8356, + 4, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 100, + 100, + 16000, + 100, + 94375, + 32, + 132994, + 32, + 61462, + 4, + 72010, + 178, + 0, + 1, + 22151, + 32, + 91189, + 769, + 4, + 2, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 1, + 1000, + 42921, + 4, + 2, + 24548, + 29498, + 38, + 1, + 898148, + 27279, + 1, + 51775, + 558, + 1, + 39184, + 1000, + 60594, + 1, + 141895, + 32, + 83150, + 32, + 15299, + 32, + 76049, + 1, + 13169, + 4, + 22100, + 10, + 28999, + 74, + 1, + 28999, + 74, + 1, + 43285, + 552, + 1, + 44749, + 541, + 1, + 33852, + 32, + 68246, + 32, + 72362, + 32, + 7243, + 32, + 7391, + 32, + 11546, + 32, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 90434, + 519, + 0, + 1, + 74433, + 32, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 1, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 955506, + 213312, + 0, + 2, + 270652, + 22588, + 4, + 1457325, + 64566, + 4, + 20467, + 1, + 4, + 0, + 141992, + 32, + 100788, + 420, + 1, + 1, + 81663, + 32, + 59498, + 32, + 20142, + 32, + 24588, + 32, + 20744, + 32, + 25933, + 32, + 24623, + 32, + 43053543, + 10, + 53384111, + 14333, + 10, + 43574283, + 26308, + 10, + 16000, + 100, + 16000, + 100, + 962335, + 18, + 2780678, + 6, + 442008, + 1, + 52538055, + 3756, + 18, + 267929, + 18, + 76433006, + 8868, + 18, + 52948122, + 18, + 1995836, + 36, + 3227919, + 12, + 901022, + 1, + 166917843, + 4307, + 36, + 284546, + 36, + 158221314, + 26549, + 36, + 74698472, + 36, + 333849714, + 1, + 254006273, + 72, + 2174038, + 72, + 2261318, + 64571, + 4, + 207616, + 8310, + 4, + 1293828, + 28716, + 63, + 0, + 1, + 1006041, + 43623, + 251, + 0, + 1 + ], "constitution": { - "anchor": { - "url": "", - "dataHash": "0000000000000000000000000000000000000000000000000000000000000000" - } + "anchor": { + "dataHash": "ca41a91f399259bcefe57f9858e91f6d00e1a38d6d9c63d4052914ea7bd70cb2", + "url": "ipfs://bafkreifnwj6zpu3ixa4siz2lndqybyc5wnnt3jkwyutci4e2tmbnj3xrdm" + }, + "script": "fa24fb305126805cf2164c161d852a0e7330cf988f1fe558cf7d4a64" }, "committee": { "members": { + "scriptHash-ff9babf23fef3f54ec29132c07a8e23807d7b395b143ecd8ff79f4c7": 1000 }, - "quorum": 0 + "threshold": { + "numerator": 2, + "denominator": 3 + } } } \ No newline at end of file diff --git a/files/configs/preview/db-sync-config.json b/files/configs/preview/db-sync-config.json index 90f9594d5..d2903db0c 100644 --- a/files/configs/preview/db-sync-config.json +++ b/files/configs/preview/db-sync-config.json @@ -33,8 +33,10 @@ "enable": true }, "governance": "enable", + "json_type": "text", "offchain_pool_data": "enable", - "json_type": "text" + "pool_stat": "enable", + "tx_cbor": "enable" }, "minSeverity": "Info", "options": { diff --git a/files/configs/preview/topology.json b/files/configs/preview/topology.json index b3717d82f..b26301bec 100644 --- a/files/configs/preview/topology.json +++ b/files/configs/preview/topology.json @@ -13,7 +13,7 @@ ], "advertise": false, "trustable": true, - "valency": 2 + "hotValency": 2 }, { "accessPoints": [ @@ -24,7 +24,7 @@ ], "advertise": false, "trustable": false, - "valency": 2, + "hotValency": 2, "warmValency": 3 } ], @@ -34,5 +34,5 @@ "advertise": false } ], - "useLedgerAfterSlot": 44070103 + "useLedgerAfterSlot": 53827185 } \ No newline at end of file diff --git a/files/configs/sanchonet/config.json b/files/configs/sanchonet/config.json index 471fb23b9..4d6643164 100644 --- a/files/configs/sanchonet/config.json +++ b/files/configs/sanchonet/config.json @@ -8,7 +8,7 @@ "LastKnownBlockVersion-Alt": 0, "LastKnownBlockVersion-Major": 3, "LastKnownBlockVersion-Minor": 1, - "MinNodeVersion": "8.10.0", + "MinNodeVersion": "9.0.0", "PeerSharing": false, "Protocol": "Cardano", "RequiresNetworkMagic": "RequiresMagic", diff --git a/files/configs/sanchonet/conway-genesis.json b/files/configs/sanchonet/conway-genesis.json index e690f455f..61598cba8 100644 --- a/files/configs/sanchonet/conway-genesis.json +++ b/files/configs/sanchonet/conway-genesis.json @@ -1,38 +1,298 @@ { "poolVotingThresholds": { - "committeeNormal": 0.51, - "committeeNoConfidence": 0.51, + "committeeNormal": 0.65, + "committeeNoConfidence": 0.65, "hardForkInitiation": 0.51, - "motionNoConfidence": 0.51, - "ppSecurityGroup": 0.51 + "motionNoConfidence": 0.6, + "ppSecurityGroup": 0.6 }, "dRepVotingThresholds": { - "motionNoConfidence": 0.51, - "committeeNormal": 0.51, - "committeeNoConfidence": 0.51, - "updateToConstitution": 0.51, - "hardForkInitiation": 0.51, - "ppNetworkGroup": 0.51, - "ppEconomicGroup": 0.51, - "ppTechnicalGroup": 0.51, - "ppGovGroup": 0.51, - "treasuryWithdrawal": 0.51 + "motionNoConfidence": 0.67, + "committeeNormal": 0.67, + "committeeNoConfidence": 0.65, + "updateToConstitution": 0.75, + "hardForkInitiation": 0.6, + "ppNetworkGroup": 0.67, + "ppEconomicGroup": 0.67, + "ppTechnicalGroup": 0.67, + "ppGovGroup": 0.75, + "treasuryWithdrawal": 0.67 }, - "committeeMinSize": 0, - "committeeMaxTermLength": 200, - "govActionLifetime": 10, - "govActionDeposit": 1000000000, - "dRepDeposit": 2000000, + "committeeMinSize": 5, + "committeeMaxTermLength": 146, + "govActionLifetime": 14, + "govActionDeposit": 100000000000, + "dRepDeposit": 500000000, "dRepActivity": 20, + "minFeeRefScriptCostPerByte": 15, + "plutusV3CostModel": [ + 100788, + 420, + 1, + 1, + 1000, + 173, + 0, + 1, + 1000, + 59957, + 4, + 1, + 11183, + 32, + 201305, + 8356, + 4, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 16000, + 100, + 100, + 100, + 16000, + 100, + 94375, + 32, + 132994, + 32, + 61462, + 4, + 72010, + 178, + 0, + 1, + 22151, + 32, + 91189, + 769, + 4, + 2, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 1, + 1000, + 42921, + 4, + 2, + 24548, + 29498, + 38, + 1, + 898148, + 27279, + 1, + 51775, + 558, + 1, + 39184, + 1000, + 60594, + 1, + 141895, + 32, + 83150, + 32, + 15299, + 32, + 76049, + 1, + 13169, + 4, + 22100, + 10, + 28999, + 74, + 1, + 28999, + 74, + 1, + 43285, + 552, + 1, + 44749, + 541, + 1, + 33852, + 32, + 68246, + 32, + 72362, + 32, + 7243, + 32, + 7391, + 32, + 11546, + 32, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 90434, + 519, + 0, + 1, + 74433, + 32, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 1, + 85848, + 123203, + 7305, + -900, + 1716, + 549, + 57, + 85848, + 0, + 1, + 955506, + 213312, + 0, + 2, + 270652, + 22588, + 4, + 1457325, + 64566, + 4, + 20467, + 1, + 4, + 0, + 141992, + 32, + 100788, + 420, + 1, + 1, + 81663, + 32, + 59498, + 32, + 20142, + 32, + 24588, + 32, + 20744, + 32, + 25933, + 32, + 24623, + 32, + 43053543, + 10, + 53384111, + 14333, + 10, + 43574283, + 26308, + 10, + 16000, + 100, + 16000, + 100, + 962335, + 18, + 2780678, + 6, + 442008, + 1, + 52538055, + 3756, + 18, + 267929, + 18, + 76433006, + 8868, + 18, + 52948122, + 18, + 1995836, + 36, + 3227919, + 12, + 901022, + 1, + 166917843, + 4307, + 36, + 284546, + 36, + 158221314, + 26549, + 36, + 74698472, + 36, + 333849714, + 1, + 254006273, + 72, + 2174038, + 72, + 2261318, + 64571, + 4, + 207616, + 8310, + 4, + 1293828, + 28716, + 63, + 0, + 1, + 1006041, + 43623, + 251, + 0, + 1 + ], "constitution": { "anchor": { - "url": "", - "dataHash": "0000000000000000000000000000000000000000000000000000000000000000" - } + "url": "ipfs://QmQq5hWDNzvDR1ForEktAHrdCQmfSL2u5yctNpzDwoSBu4", + "dataHash": "23b43bebac48a4acc39e578715aa06635d6d900fa3ea7441dfffd6e43b914f7b" + }, + "script": "edcd84c10e36ae810dc50847477083069db796219b39ccde790484e0" }, "committee": { "members": { + "scriptHash-7ceede7d6a89e006408e6b7c6acb3dd094b3f6817e43b4a36d01535b": 500, + "scriptHash-6095e643ea6f1cccb6e463ec34349026b3a48621aac5d512655ab1bf": 500, + "scriptHash-27999ed757d6dac217471ae61d69b1b067b8b240d9e3ff36eb66b5d0": 500, + "scriptHash-87f867a31c0f81360d4d7dcddb6b025ba8383db9bf77a2af7797799d": 500, + "scriptHash-a19a7ba1caede8f3ab3e5e2a928b3798d7d011af18fbd577f7aeb0ec": 500 }, - "quorum": 0 + "threshold": 0.67 } } \ No newline at end of file diff --git a/files/configs/sanchonet/db-sync-config.json b/files/configs/sanchonet/db-sync-config.json index f85ecca57..f9e1eca07 100644 --- a/files/configs/sanchonet/db-sync-config.json +++ b/files/configs/sanchonet/db-sync-config.json @@ -33,8 +33,10 @@ "enable": true }, "governance": "enable", + "json_type": "text", "offchain_pool_data": "enable", - "json_type": "text" + "pool_stat": "enable", + "tx_cbor": "enable" }, "minSeverity": "Info", "options": { diff --git a/files/configs/sanchonet/topology.json b/files/configs/sanchonet/topology.json index f57a90a83..f84ff6e8c 100644 --- a/files/configs/sanchonet/topology.json +++ b/files/configs/sanchonet/topology.json @@ -10,7 +10,7 @@ "accessPoints": [], "advertise": false, "trustable": false, - "valency": 1 + "hotValency": 1 } ], "publicRoots": [ @@ -19,5 +19,5 @@ "advertise": false } ], - "useLedgerAfterSlot": 21599922 + "useLedgerAfterSlot": 33695977 } \ No newline at end of file diff --git a/files/docker/node/release-versions/cardano-node-latest.txt b/files/docker/node/release-versions/cardano-node-latest.txt index 031d3e411..e977f5eae 100644 --- a/files/docker/node/release-versions/cardano-node-latest.txt +++ b/files/docker/node/release-versions/cardano-node-latest.txt @@ -1 +1 @@ -8.9.4 \ No newline at end of file +9.1.0 \ No newline at end of file diff --git a/scripts/cnode-helper-scripts/cncli.sh b/scripts/cnode-helper-scripts/cncli.sh index 923c8d2fa..7db963483 100755 --- a/scripts/cnode-helper-scripts/cncli.sh +++ b/scripts/cnode-helper-scripts/cncli.sh @@ -131,7 +131,7 @@ getConsensus() { if versionCheck "10.0" "${PROT_VERSION}"; then consensus="cpraos" stability_window_factor=3 - elif versionCheck "7.0" "${PROT_VERSION}"; then + elif versionCheck "8.0" "${PROT_VERSION}"; then consensus="praos" stability_window_factor=2 else diff --git a/scripts/cnode-helper-scripts/cnode.sh b/scripts/cnode-helper-scripts/cnode.sh index 189dd21b1..19f179a1a 100755 --- a/scripts/cnode-helper-scripts/cnode.sh +++ b/scripts/cnode-helper-scripts/cnode.sh @@ -120,9 +120,10 @@ while getopts :ds opt; do esac done +[[ ${0} != '-bash' ]] && PARENT="$(dirname $0)" || PARENT="$(pwd)" # Check if env file is missing in current folder (no update checks as will mostly run as daemon), source env if present -[[ ! -f "$(dirname $0)"/env ]] && echo -e "\nCommon env file missing, please ensure latest guild-deploy.sh was run and this script is being run from ${CNODE_HOME}/scripts folder! \n" && exit 1 -. "$(dirname $0)"/env offline +[[ ! -f "${PARENT}"/env ]] && echo -e "\nCommon env file missing in \"${PARENT}\", please ensure latest guild-deploy.sh was run and this script is being run from ${CNODE_HOME}/scripts folder! \n" && exit 1 +. "${PARENT}"/env offline case $? in 1) echo -e "ERROR: Failed to load common env file\nPlease verify set values in 'User Variables' section in env file or log an issue on GitHub" && exit 1;; 2) clear ;; diff --git a/scripts/cnode-helper-scripts/cntools.library b/scripts/cnode-helper-scripts/cntools.library index db84f546b..4983b047f 100644 --- a/scripts/cnode-helper-scripts/cntools.library +++ b/scripts/cnode-helper-scripts/cntools.library @@ -13,11 +13,12 @@ # Major: Any considerable change in the code base, big feature, workflow or breaking change from previous version CNTOOLS_MAJOR_VERSION=13 # Minor: Changes and features of minor character that can be applied without breaking existing functionality or workflow -CNTOOLS_MINOR_VERSION=0 +CNTOOLS_MINOR_VERSION=1 # Patch: Backwards compatible bug fixes. No additional functionality or major changes -CNTOOLS_PATCH_VERSION=2 +CNTOOLS_PATCH_VERSION=0 CNTOOLS_VERSION="${CNTOOLS_MAJOR_VERSION}.${CNTOOLS_MINOR_VERSION}.${CNTOOLS_PATCH_VERSION}" +DUMMYFEE=20000 ############################################################ # Default config values # @@ -50,6 +51,8 @@ printf -v launch_modes_info "${FG_BLUE}INFO${NC}: Available launch modes, re-run [[ -z ${ENABLE_DIALOG} ]] && ENABLE_DIALOG=false [[ ${ENABLE_ADVANCED} = "true" ]] && ADVANCED_MODE="true" [[ -z ${CHECK_KES} ]] && CHECK_KES=true +[[ -z ${CATALYST_API} ]] && CATALYST_API=https://api.projectcatalyst.io/api/v1 +[[ -z ${EXPLORER_TX} ]] && EXPLORER_TX=https://adastat.net/transactions/__tx_id__ [[ -z ${CNTOOLS_LOG} ]] && CNTOOLS_LOG="${LOG_DIR}/cntools-history.log" [[ -z ${CURRENCY} ]] && CURRENCY="off" || CURRENCY=${CURRENCY,,} [[ ${CURRENCY} = off || ${NETWORK_NAME} != Mainnet ]] && unset CURRENCY_URL || CURRENCY_URL="https://api.coingecko.com/api/v3/simple/price?ids=cardano&vs_currencies=${CURRENCY}&include_24hr_change=true" @@ -89,14 +92,19 @@ println() { shift local newline="\n" if [[ $1 = false && $# -gt 2 ]]; then unset newline; shift; elif [[ $1 = true && $# -gt 2 ]]; then shift; fi + local msg_list=() + for msg in "$@"; do + [[ -z ${msg} ]] && continue + msg_list+=( "${msg}" ) + done case $log_level in - OFF) printf "%b${newline}" "$@" ;; - LOG) logln "DEBUG" "$@" ;; - DEBUG) printf "%b${newline}" "$@"; logln "DEBUG" "$@" ;; - INFO) printf "%b${newline}" "$@"; logln "INFO" "$@" ;; - ACTION) logln "ACTION" "$@" ;; - ERROR) printf "%b${newline}" "$@"; logln "ERROR" "$@" ;; - *) println INFO "${log_level}" "$@" ;; + OFF) printf "%b${newline}" "${msg_list[@]}" ;; + LOG) logln "DEBUG" "${msg_list[@]}" ;; + DEBUG) printf "%b${newline}" "${msg_list[@]}"; logln "DEBUG" "${msg_list[@]}" ;; + INFO) printf "%b${newline}" "${msg_list[@]}"; logln "INFO" "${msg_list[@]}" ;; + ACTION) logln "ACTION" "${msg_list[@]}" ;; + ERROR) printf "%b${newline}" "${msg_list[@]}"; logln "ERROR" "${msg_list[@]}" ;; + *) println INFO "${log_level}" "${msg_list[@]}" ;; esac } @@ -151,15 +159,50 @@ protectionPreRequisites() { return 0 } +# Command : download_catalyst_toolbox +# Description : Downloads Catalyst Toolbox +download_catalyst_toolbox() { + cmdAvailable "catalyst-toolbox" &>/dev/null && return 0 + println DEBUG "Downloading prerequisite tool: catalyst-toolbox" + local ARCH; ARCH=$(uname -a) + if [[ ${ARCH,,} != *x86_64*linux* ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: Only x86_64 architecture on Linux supported, please manually build for your system from:\nhttps://github.com/input-output-hk/catalyst-core" + waitToProceed && return 1 + fi + rm -rf /tmp/catalyst-toolbox-bin && mkdir /tmp/catalyst-toolbox-bin + pushd /tmp/catalyst-toolbox-bin >/dev/null || return 1 + if curl -sL -f -m ${CURL_TIMEOUT} -o "${HOME}"/.local/bin/catalyst-toolbox "https://share.koios.rest/api/public/dl/Q0m8-F2X%2Fbinaries%2Fcatalyst-toolbox-x86_64-gnu"; then + chmod +x "${HOME}"/.local/bin/catalyst-toolbox + local catalyst_toolbox_version; catalyst_toolbox_version=$(catalyst-toolbox --full-version) + println DEBUG " ${catalyst_toolbox_version%% - *} ${FG_GREEN}installed!${NC}" + else + println ERROR "\n${FG_RED}ERROR${NC}: Download of Catalyst Toolbox from Koios share failed! Please retry or build it manually from:\nhttps://github.com/input-output-hk/catalyst-core" + waitToProceed && return 1 + fi + return 0 +} + +# Command : generateCatalystBech32 [wallet name] +# Description : create and store Catalyst signing key in bech32 format (ed25519extended) +# Parameters : wallet name > the name of the wallet +generateCatalystBech32() { + catalyst_sk_file="${WALLET_FOLDER}/${1}/${WALLET_CATALYST_SK_FILENAME}" + catalyst_sk_file_bech32="${catalyst_sk_file}-bech32" + [[ -f "${catalyst_sk_file_bech32}" ]] && return 0 + println ACTION "jq -r .cborHex ${catalyst_sk_file} | cut -c 5-132 | bech32 ed25519e_sk" + if ! stdout=$(jq -r .cborHex "${catalyst_sk_file}" | cut -c 5-132 | bech32 "ed25519e_sk" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during catalyst signing key bech32 conversion!\n${stdout}"; waitToProceed && return 1 + fi + echo ${stdout} > "${catalyst_sk_file_bech32}" +} + # Command : safeDel [path] -# Description : unlock and use secure delete (srm) if available to delete file|dir -# Note : srm mainly effective for traditional magnetic HDDs and non copy-on-write or journal file systems +# Description : unlock and delete file|dir # Parameters : command > The command to check safeDel() { path=$1 [[ ${ENABLE_CHATTR} = true && -f "${path}" && $(lsattr -R "${path}") =~ -i- ]] && sudo chattr -i "${path}" - command -v "srm" &>/dev/null && delcommand='srm' || delcommand='rm' - if "${delcommand}" -rf "${path}"; then + if rm -rf "${path}"; then println "Deleted: ${path}" else println ERROR "${FG_RED}ERROR${NC}: delete failed for ${path}" @@ -250,7 +293,7 @@ fileDialog() { if [[ ${ENABLE_DIALOG} = "false" ]]; then getAnswerAnyCust file "$1" && return else - println DEBUG false "${1}: " + println DEBUG "${1}: " waitToProceed "Press any key to open the file explorer [cancel to skip!]" fi dialogSetup @@ -269,7 +312,7 @@ dirDialog() { if [[ ${ENABLE_DIALOG} = "false" ]]; then getAnswerAnyCust dir "$1" && return else - println DEBUG false "${1}: " + println DEBUG "${1}: " waitToProceed "Press any key to open the file explorer [cancel to skip!]" fi dialogSetup @@ -303,7 +346,7 @@ key_input() { key2="" else echo ${key1}; fi; } opt_shortcut() { [[ "$1" =~ ^\[([[:alnum:]]+)\].* ]] && echo ${BASH_REMATCH[1]}; } opt_firstchar() { printf "${1:0:1}" | tr '[:upper:]' '[:lower:]'; } -clrbuf() { read -r -t 0.1 -s -e; stty echo echok; } +clrbuf() { read -r -t 0.1 -s; stty echo echok; } selectOption() { # initially print empty new lines (scroll down if at bottom of screen) @@ -433,7 +476,7 @@ selectDir() { # Command : selectWallet [mode] [file1 file2 ... | wallet_name1 wallet_name1 ... ] # Description : A helper function to select a CNTools wallet -# Parameters : mode > a string containing some of the following: none|encrypted|non-reg|reg|balance|delegate|reward|assets to be added next to wallet in selection menu +# Parameters : mode > a string containing some of the following: none|encrypted|non-reg|reg|balance|delegate|reward|assets|non-ms|non-gov to be added next to wallet in selection menu # : arg array > array of files required to exist in wallet folder for it to be selectable **OR** the name of wallet to exclude from selection # Return : populates ${wallet_name} variable with wallet selection selectWallet() { @@ -449,7 +492,7 @@ selectWallet() { wallet_dirs=() if ! getDirs "${WALLET_FOLDER}"; then return 1; fi # dirs() array populated with all wallet folders - if [[ ${CNTOOLS_MODE} != "OFFLINE" && ${mode} != "none" ]]; then + if [[ ${CNTOOLS_MODE} != "OFFLINE" && ${mode} != "none" && ${mode} != "non-ms" && ${mode} != "non-gov" ]]; then tput sc wallet_count=${#dirs[@]} if [[ ${wallet_count} -le ${WALLET_SELECTION_FILTER_LIMIT} ]]; then @@ -483,6 +526,15 @@ selectWallet() { else wallet_dirs+=("${dir} (${FG_YELLOW}unprotected${NC})") fi + elif [[ ${mode} = "non-ms" ]]; then + ms_payment_vk_file="${WALLET_FOLDER}/${dir}/${WALLET_MULTISIG_PREFIX}${WALLET_PAY_VK_FILENAME}" + ms_stake_vk_file="${WALLET_FOLDER}/${dir}/${WALLET_MULTISIG_PREFIX}${WALLET_STAKE_VK_FILENAME}" + if [[ -f "${ms_payment_vk_file}" || -f "${ms_stake_vk_file}" ]]; then continue; else wallet_dirs+=("${dir}"); fi + elif [[ ${mode} = "non-gov" ]]; then + drep_vk_file="${WALLET_FOLDER}/${dir}/${WALLET_MULTISIG_PREFIX}${WALLET_GOV_DREP_VK_FILENAME}" + cc_cold_vk_file="${WALLET_FOLDER}/${dir}/${WALLET_MULTISIG_PREFIX}${WALLET_GOV_CC_COLD_VK_FILENAME}" + cc_hot_vk_file="${WALLET_FOLDER}/${dir}/${WALLET_MULTISIG_PREFIX}${WALLET_GOV_CC_HOT_VK_FILENAME}" + if [[ -f "${drep_vk_file}" || -f "${cc_cold_vk_file}" || -f "${cc_hot_vk_file}" ]]; then continue; else wallet_dirs+=("${dir}"); fi elif [[ ${CNTOOLS_MODE} != "OFFLINE" && ${mode} != "none" && ${wallet_count} -le ${WALLET_SELECTION_FILTER_LIMIT} ]]; then if [[ ${mode} = "reg" || ${mode} = "non-reg" ]]; then if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then @@ -610,32 +662,40 @@ selectWallet() { [[ -v asset_cnt[${base_addr}] ]] && base_asset_str=" + ${FG_LBLUE}${asset_cnt[${base_addr}]}${NC} additional assets" || unset base_asset_str [[ -v asset_cnt[${pay_addr}] ]] && pay_asset_str=" + ${FG_LBLUE}${asset_cnt[${pay_addr}]}${NC} additional assets" || unset pay_asset_str if [[ ${base_lovelace} -gt 0 && ${pay_lovelace} -gt 0 ]]; then - wallet_dirs_filtered+=("${wallet_dir} (Funds: ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA${base_asset_str} | Enterprise Funds: ${FG_LBLUE}$(formatLovelace ${pay_lovelace})${NC} ADA${pay_asset_str})") + wallet_dirs_filtered+=("${wallet_dir} (Base Funds: ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA${base_asset_str} | Payment Funds: ${FG_LBLUE}$(formatLovelace ${pay_lovelace})${NC} ADA${pay_asset_str})") elif [[ ${pay_lovelace} -gt 0 ]]; then - wallet_dirs_filtered+=("${wallet_dir} (Enterprise Funds: ${FG_LBLUE}$(formatLovelace ${pay_lovelace})${NC} ADA${pay_asset_str})") + wallet_dirs_filtered+=("${wallet_dir} (Payment Funds: ${FG_LBLUE}$(formatLovelace ${pay_lovelace})${NC} ADA${pay_asset_str})") else - wallet_dirs_filtered+=("${wallet_dir} (Funds: ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA${base_asset_str})") + wallet_dirs_filtered+=("${wallet_dir} (Base Funds: ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA${base_asset_str})") fi else wallet_dirs_filtered+=("${dir}") fi done - if [[ ${CNTOOLS_MODE} != "OFFLINE" && ${mode} != "none" && ${wallet_count} -le ${WALLET_SELECTION_FILTER_LIMIT} ]]; then tput rc && tput ed; fi + if [[ ${CNTOOLS_MODE} != "OFFLINE" && ${mode} != "none" && ${mode} != "non-ms" && ${mode} != "non-gov" && ${wallet_count} -le ${WALLET_SELECTION_FILTER_LIMIT} ]]; then tput rc && tput ed; fi if [[ ${#wallet_dirs_filtered[@]} -eq 0 ]]; then if [[ ${mode} = "balance" ]]; then - println ERROR "\n${FG_YELLOW}WARN${NC}: No wallets with funds available for selection! Required files:\n$(printf '%b\n' "$@")" + println INFO "\n${FG_YELLOW}WARN${NC}: No wallets available for selection!" elif [[ ${mode} = "delegate" ]]; then - println ERROR "\n${FG_YELLOW}WARN${NC}: No wallets available that can be delegated or used as pool pledge/owner/reward wallet! Required files:\n$(printf '%b\n' "$@")" + println INFO "\n${FG_YELLOW}WARN${NC}: No wallets available that can be delegated or used as pool pledge/owner/reward wallet! Required files:\n$(printf '%b\n' "$@")" elif [[ ${mode} = "reward" ]]; then - println ERROR "\n${FG_YELLOW}WARN${NC}: No wallets available that have rewards to withdraw or signing keys to do so!" + println INFO "\n${FG_YELLOW}WARN${NC}: No wallets available that have rewards to withdraw or signing keys to do so!" elif [[ ${mode} = "reg" ]]; then - println ERROR "\n${FG_YELLOW}WARN${NC}: No wallets available that are registered on chain!" + println INFO "\n${FG_YELLOW}WARN${NC}: No wallets available that are registered on chain!" elif [[ ${mode} = "non-reg" ]]; then - println ERROR "\n${FG_YELLOW}WARN${NC}: No wallets available that are unregistered!" + println INFO "\n${FG_YELLOW}WARN${NC}: No wallets available that are unregistered!" else - println ERROR "\n${FG_YELLOW}WARN${NC}: No wallets available for selection! Required files:\n$(printf '%b\n' "$@")" + println INFO "\n${FG_YELLOW}WARN${NC}: No wallets available for selection! Required files:\n$(printf '%b\n' "$@")" fi + required_files=() + already_selected_wallets=() + for arg in "$@"; do + [[ ${arg} = *"."* ]] && required_files+=("${arg}") + [[ ${arg} != *"."* ]] && already_selected_wallets+=("${arg}") + done + [[ "${#already_selected_wallets[@]}" -gt 0 ]] && println INFO "Already selected wallets:\n$(printf ' %b\n' "${already_selected_wallets[@]}")" + [[ "${#required_files[@]}" -gt 0 ]] && println INFO "Required files:\n$(printf ' %b\n' "${required_files[@]}")" return 1 fi @@ -920,64 +980,34 @@ lockFile() { fi } -# Command : waitNewBlockCreated [optional: silent] -# Description : Wait for a new block to be created -# Parameters : silent > any argument to function will prevent DEBUG output to tty -waitNewBlockCreated() { - counter=${TIMEOUT_NO_OF_SLOTS} - [[ $# -eq 0 ]] && println DEBUG "Waiting for new block to be created (timeout = ${counter} slots, $(( counter * SLOT_LENGTH ))s)" - [[ $# -eq 0 ]] && println DEBUG "${FG_BLUE}INFO${NC}: press any key to cancel and return (won't stop transaction)" - getNodeMetrics - initialTip=${slotnum} - actualTip=${slotnum} - - while [[ ${actualTip} -eq ${initialTip} ]]; do - read -r -n 1 -s -t 5 abort - if [[ $? -eq 0 ]]; then - println "${FG_YELLOW}WARN${NC}: aborted!! transaction still in queue!" - return 1 - fi - getNodeMetrics - actualTip=${slotnum} - counter=$((counter - SLOT_LENGTH)) - if [ ${counter} -lt ${SLOT_LENGTH} ]; then - println "${FG_YELLOW}WARN${NC}: waited $(( TIMEOUT_NO_OF_SLOTS * SLOT_LENGTH )) secs and no new block created" - return 1 - fi - done - println LOG "New block was created - ${actualTip}" -} - # Command : verifyTx [address] # Description : Verify that the transaction was successfully registered by checking address balance against $newBalance # Parameters : address > the address to compare with verifyTx() { - if [[ -n ${KOIOS_API} && -n ${tx_id} ]]; then - println DEBUG "Waiting for transaction to be seen on chain" - println DEBUG "Id: ${tx_id}" - println DEBUG "${FG_BLUE}INFO${NC}: press any key to cancel and return (won't stop transaction)" - while :; do - read -r -n 1 -s -t 5 abort - if [[ $? -eq 0 ]]; then - println "\n${FG_YELLOW}WARN${NC}: aborted!! transaction still in queue!" - return 1 - fi - println ACTION "curl -sSL -f -X POST -H \"Content-Type: application/json\" -H \"accept: text/csv\" -d '{\"_tx_hashes\":[\"${tx_id}\"]' ${KOIOS_API}/tx_status?select=num_confirmations" - ! num_confirmations=$(curl -sSL -f -X POST -H "Content-Type: application/json" -H "accept: text/csv" -d '{"_tx_hashes":["'${tx_id}'"]}' "${KOIOS_API}/tx_status?select=num_confirmations" 2>&1) && println "ERROR" "\n${FG_RED}KOIOS_API ERROR${NC}: ${num_confirmations}\n" && return 1 # print error and return - num_confirmations=$(tail -n +2 <<< ${num_confirmations}) - [[ -n "${num_confirmations}" ]] && { println DEBUG "\nTx put on chain !!"; break; } || printf . - done + [[ -z ${tx_id} ]] && println "\n${FG_RED}ERROR${NC}: transaction id not set, unable to verify transaction!" && return 1 + println DEBUG "Waiting for transaction to be seen on chain" + if [[ ${NWMAGIC} == "764824073" ]]; then + println DEBUG "Explore in detail: ${EXPLORER_TX//__tx_id__/${tx_id}}" else - if ! waitNewBlockCreated; then return 1; fi - getAddressBalance ${1} true - while [[ ${lovelace} -ne ${newBalance} ]]; do - println DEBUG "${FG_YELLOW}WARN${NC}: Balance mismatch, transaction not included in latest block... waiting for next block!" - println LOG "$(formatLovelace ${lovelace}) != $(formatLovelace ${newBalance})" - if ! waitNewBlockCreated "silent"; then return 1; fi - getAddressBalance ${1} true - done - return 0 + println DEBUG "Transaction ID: ${tx_id}" fi + println DEBUG "${FG_BLUE}INFO${NC}: press any key to cancel and return (won't stop transaction)" + while :; do + read -r -n 1 -s -t 5 abort + if [[ $? -eq 0 ]]; then + println "\n${FG_YELLOW}WARN${NC}: aborted!! transaction still in queue!" + return 1 + fi + if [[ -n ${KOIOS_API} ]]; then + println ACTION "curl -sSL -f -X POST -H \"Content-Type: application/json\" -H \"accept: text/csv\" -d '{\"_tx_hashes\":[\"${tx_id}\"]' ${KOIOS_API}/tx_status?select=num_confirmations" + ! num_confirmations=$(curl -sSL -f -X POST -H "Content-Type: application/json" -H "accept: text/csv" -d '{"_tx_hashes":["'${tx_id}'"]}' "${KOIOS_API}/tx_status?select=num_confirmations" 2>&1) && println "ERROR" "\n${FG_RED}KOIOS_API ERROR${NC}: ${num_confirmations}\n" && return 1 # print error and return + result=$(tail -n +2 <<< ${num_confirmations}) + else + println ACTION "${CCLI} ${NETWORK_ERA} query utxo --tx-in ${tx_id}#0 ${NETWORK_IDENTIFIER}| tail -n +3" + result=$(${CCLI} ${NETWORK_ERA} query utxo --tx-in "${tx_id}#0" ${NETWORK_IDENTIFIER}| tail -n +3) + fi + [[ -n "${result}" ]] && { println DEBUG "\nTx put on chain !!"; break; } || printf . + done } # Command : getPayAddress [wallet name] @@ -985,10 +1015,11 @@ verifyTx() { # Parameters : wallet name > the name of the wallet # Return : populates ${pay_addr} getPayAddress() { + unset pay_addr payment_vk_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_VK_FILENAME}" + payment_script_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_SCRIPT_FILENAME}" payment_addr_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_ADDR_FILENAME}" [[ -f ${payment_addr_file} ]] && pay_addr=$(cat "${payment_addr_file}") && return 0 - pay_addr="" if [[ -f "${payment_vk_file}" ]]; then println ACTION "${CCLI} ${NETWORK_ERA} address build --payment-verification-key-file ${payment_vk_file} --out-file ${payment_addr_file} ${NETWORK_IDENTIFIER}" if stdout=$(${CCLI} ${NETWORK_ERA} address build --payment-verification-key-file "${payment_vk_file}" --out-file "${payment_addr_file}" ${NETWORK_IDENTIFIER} 2>&1); then @@ -997,21 +1028,199 @@ getPayAddress() { else println LOG "\n${FG_RED}ERROR${NC}: failure during payment address creation!\n${stdout}" fi + elif [[ -f "${payment_script_file}" ]]; then + println ACTION "${CCLI} ${NETWORK_ERA} address build --payment-script-file ${payment_script_file} --out-file ${payment_addr_file} ${NETWORK_IDENTIFIER}" + if stdout=$(${CCLI} ${NETWORK_ERA} address build --payment-script-file "${payment_script_file}" --out-file "${payment_addr_file}" ${NETWORK_IDENTIFIER} 2>&1); then + pay_addr=$(cat "${payment_addr_file}") + return 0 + else + println LOG "\n${FG_RED}ERROR${NC}: failure during payment script address creation!\n${stdout}" + fi + fi + return 1 +} + +# Command : getGovKeyInfo [wallet name] +# Description : generate DRep ID and committee key hash +getGovKeyInfo() { + unset drep_id drep_hash hash_type cc_cold_id cc_hot_id ms_drep_id ms_drep_hash + drep_vk_file="${WALLET_FOLDER}/${1}/${WALLET_GOV_DREP_VK_FILENAME}" + drep_sk_file="${WALLET_FOLDER}/${1}/${WALLET_GOV_DREP_SK_FILENAME}" + drep_script_file="${WALLET_FOLDER}/${1}/${WALLET_GOV_DREP_SCRIPT_FILENAME}" + drep_id_file="${WALLET_FOLDER}/${1}/${WALLET_GOV_DREP_ID_FILENAME}" + cc_cold_vk_file="${WALLET_FOLDER}/${1}/${WALLET_GOV_CC_COLD_VK_FILENAME}" + cc_cold_sk_file="${WALLET_FOLDER}/${1}/${WALLET_GOV_CC_COLD_SK_FILENAME}" + cc_cold_id_file="${WALLET_FOLDER}/${1}/${WALLET_GOV_CC_COLD_ID_FILENAME}" + cc_hot_vk_file="${WALLET_FOLDER}/${1}/${WALLET_GOV_CC_HOT_VK_FILENAME}" + cc_hot_sk_file="${WALLET_FOLDER}/${1}/${WALLET_GOV_CC_HOT_SK_FILENAME}" + cc_hot_id_file="${WALLET_FOLDER}/${1}/${WALLET_GOV_CC_HOT_ID_FILENAME}" + ms_drep_vk_file="${WALLET_FOLDER}/${1}/${WALLET_MULTISIG_PREFIX}${WALLET_GOV_DREP_VK_FILENAME}" + ms_drep_sk_file="${WALLET_FOLDER}/${1}/${WALLET_MULTISIG_PREFIX}${WALLET_GOV_DREP_SK_FILENAME}" + ms_drep_id_file="${WALLET_FOLDER}/${1}/${WALLET_MULTISIG_PREFIX}${WALLET_GOV_DREP_ID_FILENAME}" + [[ -f ${drep_id_file} ]] && drep_id=$(cat "${drep_id_file}") + [[ -f ${cc_cold_id_file} ]] && cc_cold_id=$(cat "${cc_cold_id_file}") + [[ -f ${cc_hot_id_file} ]] && cc_hot_id=$(cat "${cc_hot_id_file}") + [[ -f ${ms_drep_id_file} ]] && ms_drep_id=$(cat "${ms_drep_id_file}") + if [[ -z ${drep_id} && -f ${drep_vk_file} ]]; then + println ACTION "${CCLI} conway governance drep id --drep-verification-key-file ${drep_vk_file}" + drep_id=$(${CCLI} conway governance drep id --drep-verification-key-file "${drep_vk_file}") + printf "${drep_id}" > "${drep_id_file}" + elif [[ -z ${drep_id} && -f ${drep_script_file} ]]; then + println ACTION "${CCLI} hash script --script-file ${drep_script_file}" + drep_id=$(${CCLI} hash script --script-file "${drep_script_file}") + printf "${drep_id}" > "${drep_id_file}" + fi + if [[ -z ${cc_cold_id} && -f ${cc_cold_vk_file} ]]; then + println ACTION "bech32 cc_cold <<< \$(${CCLI} conway governance committee key-hash --verification-key-file ${cc_cold_vk_file})" + cc_cold_id=$(bech32 cc_cold <<< "$(${CCLI} conway governance committee key-hash --verification-key-file "${cc_cold_vk_file}")") + printf "${cc_cold_id}" > "${cc_cold_id_file}" + fi + if [[ -z ${cc_hot_id} && -f ${cc_hot_vk_file} ]]; then + println ACTION "bech32 cc_hot <<< \$(${CCLI} conway governance committee key-hash --verification-key-file ${cc_hot_vk_file})" + cc_hot_id=$(bech32 cc_hot <<< "$(${CCLI} conway governance committee key-hash --verification-key-file "${cc_hot_vk_file}")") + printf "${cc_hot_id}" > "${cc_hot_id_file}" + fi + if [[ -z ${ms_drep_id} && -f ${ms_drep_vk_file} ]]; then + println ACTION "${CCLI} conway governance drep id --drep-verification-key-file ${ms_drep_vk_file}" + ms_drep_id=$(${CCLI} conway governance drep id --drep-verification-key-file "${ms_drep_vk_file}") + printf "${ms_drep_id}" > "${ms_drep_id_file}" + fi + if [[ -n ${drep_id} ]]; then + if [[ ${drep_id} = drep* ]]; then + hash_type="keyHash" + drep_hash="$(bech32 <<< ${drep_id})" + else + hash_type="scriptHash" + drep_hash="${drep_id}" + drep_id="$(bech32 drep <<< "${drep_hash}")" + fi + fi + if [[ -n ${ms_drep_id} ]]; then + ms_drep_hash="$(bech32 <<< ${ms_drep_id})" + fi +} + +# Command : getDRepStatus [type] [hash] +# Description : query status of drep id +# Return : populates ${hash_type} ${drep_anchor_url} ${drep_anchor_hash} ${drep_deposit_amt} ${drep_expiry} +getDRepStatus() { + unset hash_type drep_anchor_url drep_anchor_hash drep_deposit_amt drep_expiry + [[ -z $1 || -z $2 || ($1 != keyHash && $1 != scriptHash) ]] && return 1 + hash_type="$1" + [[ ${hash_type} = keyHash ]] && hash_param="--drep-key-hash" || hash_param="--drep-script-hash" + println ACTION "${CCLI} conway query drep-state ${hash_param} ${2} ${NETWORK_IDENTIFIER} | jq -r .[0][1]" + drep_state=$(${CCLI} conway query drep-state ${hash_param} ${2} ${NETWORK_IDENTIFIER} | jq -r .[0][1]) + [[ ${drep_state} = null ]] && return 1 + IFS=',' read -r drep_anchor_url drep_anchor_hash drep_deposit_amt drep_expiry < <( jq -cr '"\(.anchor.url//""),\(.anchor.dataHash//""),\(.deposit),\(.expiry)"' <<< "${drep_state}" ) + return 0 +} + +# Command : getDRepAnchor [url] [hash] +# Description : download anchor data and verify hash +# Return : populates ${drep_anchor_file} ${drep_anchor_real_hash} +# : 0 = ok, 1 = invalid url, 2 = hash doesn't match +getDRepAnchor() { + unset drep_anchor_file drep_anchor_real_hash + [[ -z $1 ]] && return 1 + drep_anchor_file="${TMP_DIR}/metadata_$(date '+%Y%m%d%H%M%S').json" + if ! curl -sL -m ${CURL_TIMEOUT} -o "${drep_anchor_file}" ${1}; then + [[ -f "${drep_anchor_file}" ]] && rm -f "${drep_anchor_file}" + unset drep_anchor_file + return 1 + fi + println ACTION "${CCLI} hash anchor-data --file-text ${drep_anchor_file}" + drep_anchor_real_hash="$(${CCLI} hash anchor-data --file-text "${drep_anchor_file}")" + [[ ${drep_anchor_real_hash} != "${2}" ]] && return 2 + return 0 +} + +# Command : getDRepVotePower [type] +# Description : query status of drep id +# Return : populates ${vote_power} ${vote_power_total} ${vote_power_pct} +getDRepVotePower() { + unset vote_power vote_power_total vote_power_pct + [[ -z $1 ]] && return 1 + println ACTION "${CCLI} conway query drep-stake-distribution --all-dreps ${NETWORK_IDENTIFIER}" + all_drep_vote_power=$(${CCLI} conway query drep-stake-distribution --all-dreps ${NETWORK_IDENTIFIER}) + vote_power_total=$(jq -r '[.[][1]] | add' <<< "${all_drep_vote_power}") + search_string="drep-${1}" + [[ -n $2 ]] && search_string+="-${2}" + vote_power=$(jq -r --arg v "${search_string}" 'first(.[] | select(.[0] == $v)) | .[1]' <<< "${all_drep_vote_power}") + [[ -z ${vote_power} ]] && return 1 + vote_power_pct=$(fractionToPCT "$(bc -l <<< "${vote_power}/${vote_power_total}")") + [[ -z ${vote_power_pct%.*} || ${vote_power_pct%.*} = 0 ]] && pct_precision=4 || pct_precision=2 + vote_power_pct=$(printf "%.${pct_precision}f" ${vote_power_pct}) + return 0 +} + +# Command : getWalletVoteDelegation [wallet name] [force] +# Description : check vote delegation status +# Parameters : wallet name > the name of the wallet +# Return : populates ${vote_delegation} +getWalletVoteDelegation() { + if isWalletRegistered $1; then + if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then + [[ -n ${vote_delegation} ]] && return 0 + fi fi return 1 } +# Command : getGovAction [action id] +# Description : fetch governance action information by id +# Return : populates ${vote_action} ${proposal_url} ${proposal_hash} +# : 0 = ok, 1 = action not found, 2 = invalid url or content, 3 = hash doesn't match +getGovAction() { + unset vote_action proposal_url proposal_hash proposal_meta_file + [[ -z $1 ]] && return 1 + println ACTION "${CCLI} conway query gov-state ${NETWORK_IDENTIFIER} | jq -r --arg govActionId \"$1\" 'first(.proposals | to_entries[] | select(.value.actionId.txId | contains(\$govActionId))) | .value'" + vote_action=$(${CCLI} conway query gov-state ${NETWORK_IDENTIFIER} | jq -r --arg govActionId "$1" 'first(.proposals | to_entries[] | select(.value.actionId.txId | contains($govActionId))) | .value') + [[ -z ${vote_action} ]] && return 1 + IFS=',' read -r proposal_url proposal_hash < <( jq -cr '"\(.proposalProcedure.anchor.url//""),\(.proposalProcedure.anchor.dataHash//"")"' <<< "${vote_action}" ) + [[ ! "${proposal_url}" =~ https?://.* ]] && return 2 + proposal_meta_file="${TMP_DIR}/metadata_$(date '+%Y%m%d%H%M%S').json" + if ! curl -sL -m ${CURL_TIMEOUT} -o "${proposal_meta_file}" ${proposal_url}; then + [[ -f "${proposal_meta_file}" ]] && rm -f "${proposal_meta_file}" + return 2 + fi + println ACTION "${CCLI} hash anchor-data --file-text ${proposal_meta_file}" + proposal_meta_hash="$(${CCLI} hash anchor-data --file-text "${proposal_meta_file}")" + [[ ${proposal_meta_hash} != "${proposal_hash}" ]] && return 3 + return 0 +} + +getAllGovActions() { + vote_action_list=(); _vote_action_list=() + println ACTION "${CCLI} conway query gov-state ${NETWORK_IDENTIFIER} | jq -er '.proposals[] | @base64'" + for vote_action in $(${CCLI} conway query gov-state ${NETWORK_IDENTIFIER} | jq -er '.proposals[] | @base64' 2>/dev/null); do + _vote_action_list+=( "$(jq -cr '"\((.actionId.txId//"") + "#" + (.actionId.govActionIx//0|tostring)),\(.proposalProcedure.govAction.tag//""),\(.proposedIn//0),\(.expiresAfter//0),\(.proposalProcedure.anchor.url//""),\(.dRepVotes | reduce(..|select(strings=="VoteYes")) as $_ (0; .+1)),\(.dRepVotes | reduce(..|select(strings=="VoteNo")) as $_ (0; .+1)),\(.dRepVotes | reduce(..|select(strings=="Abstain")) as $_ (0; .+1)),\(.stakePoolVotes | reduce(..|select(strings=="VoteYes")) as $_ (0; .+1)),\(.stakePoolVotes | reduce(..|select(strings=="VoteNo")) as $_ (0; .+1)),\(.stakePoolVotes | reduce(..|select(strings=="Abstain")) as $_ (0; .+1)),\(.committeeVotes | reduce(..|select(strings=="VoteYes")) as $_ (0; .+1)),\(.committeeVotes | reduce(..|select(strings=="VoteNo")) as $_ (0; .+1)),\(.committeeVotes | reduce(..|select(strings=="Abstain")) as $_ (0; .+1))"' <<< "$(base64 -d <<< "${vote_action}")")" ) + done + # reverse order + for ((i=${#_vote_action_list[@]}-1; i>=0; i--)); do + vote_action_list+=( "${_vote_action_list[$i]}" ) + done +} + +# Command : isCommitteeMember [hash] +# Description : check if supplied hash is part of current committee list +isCommitteeMember() { + [[ -z $1 ]] && return 1 + println ACTION "${CCLI} conway query gov-state ${NETWORK_IDENTIFIER} | jq -r '.committee.members | keys[]' | grep -q $1" + ${CCLI} conway query gov-state ${NETWORK_IDENTIFIER} | jq -r '.committee.members | keys[]' | grep -q $1 +} + # Command : getBaseAddress [wallet name] | [payment.vkey] [stake.vkey] # Description : create, store and save base address # Parameters : wallet name > the name of the wallet # Return : populates ${base_addr} getBaseAddress() { payment_vk_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_VK_FILENAME}" - payment_script_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_SCRIPT_FILENAME}" stake_vk_file="${WALLET_FOLDER}/${1}/${WALLET_STAKE_VK_FILENAME}" + payment_script_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_SCRIPT_FILENAME}" + stake_script_file="${WALLET_FOLDER}/${1}/${WALLET_STAKE_SCRIPT_FILENAME}" base_addr_file="${WALLET_FOLDER}/${1}/${WALLET_BASE_ADDR_FILENAME}" [[ -f ${base_addr_file} ]] && base_addr=$(cat "${base_addr_file}") && return 0 - base_addr="" + unset base_addr if [[ -f "${payment_vk_file}" && -f "${stake_vk_file}" ]]; then println ACTION "${CCLI} ${NETWORK_ERA} address build --payment-verification-key-file ${payment_vk_file} --stake-verification-key-file ${stake_vk_file} --out-file ${base_addr_file} ${NETWORK_IDENTIFIER}" if stdout=$(${CCLI} ${NETWORK_ERA} address build --payment-verification-key-file "${payment_vk_file}" --stake-verification-key-file "${stake_vk_file}" --out-file "${base_addr_file}" ${NETWORK_IDENTIFIER} 2>&1); then @@ -1020,6 +1229,14 @@ getBaseAddress() { else println LOG "\n${FG_RED}ERROR${NC}: failure during base address creation!\n${stdout}" fi + elif [[ -f "${payment_script_file}" && -f "${stake_script_file}" ]]; then + println ACTION "${CCLI} ${NETWORK_ERA} address build --payment-script-file ${payment_script_file} --stake-script-file ${stake_script_file} --out-file ${base_addr_file} ${NETWORK_IDENTIFIER}" + if stdout=$(${CCLI} ${NETWORK_ERA} address build --payment-script-file "${payment_script_file}" --stake-script-file "${stake_script_file}" --out-file "${base_addr_file}" ${NETWORK_IDENTIFIER} 2>&1); then + base_addr=$(cat "${base_addr_file}") + return 0 + else + println LOG "\n${FG_RED}ERROR${NC}: failure during base address creation!\n${stdout}" + fi elif [[ -f "${payment_script_file}" && -f "${stake_vk_file}" ]]; then println ACTION "${CCLI} ${NETWORK_ERA} address build --payment-script-file ${payment_script_file} --stake-verification-key-file ${stake_vk_file} --out-file ${base_addr_file} ${NETWORK_IDENTIFIER}" if stdout=$(${CCLI} ${NETWORK_ERA} address build --payment-script-file "${payment_script_file}" --stake-verification-key-file "${stake_vk_file}" --out-file "${base_addr_file}" ${NETWORK_IDENTIFIER} 2>&1); then @@ -1028,6 +1245,14 @@ getBaseAddress() { else println LOG "\n${FG_RED}ERROR${NC}: failure during base address creation!\n${stdout}" fi + elif [[ -f "${payment_vk_file}" && -f "${stake_script_file}" ]]; then + println ACTION "${CCLI} ${NETWORK_ERA} address build --payment-verification-key-file ${payment_vk_file} --stake-script-file ${stake_script_file} --out-file ${base_addr_file} ${NETWORK_IDENTIFIER}" + if stdout=$(${CCLI} ${NETWORK_ERA} address build --payment-verification-key-file "${payment_vk_file}" --stake-script-file "${stake_script_file}" --out-file "${base_addr_file}" ${NETWORK_IDENTIFIER} 2>&1); then + base_addr=$(cat "${base_addr_file}") + return 0 + else + println LOG "\n${FG_RED}ERROR${NC}: failure during base address creation!\n${stdout}" + fi elif [[ $# -eq 2 && -f "${1}" && -f "${2}" ]]; then println ACTION "${CCLI} ${NETWORK_ERA} address build --payment-verification-key-file ${1} --stake-verification-key-file ${2} ${NETWORK_IDENTIFIER}" if base_addr=$(${CCLI} ${NETWORK_ERA} address build --payment-verification-key-file "${1}" --stake-verification-key-file "${2}" ${NETWORK_IDENTIFIER} 2>&1); then @@ -1042,12 +1267,14 @@ getBaseAddress() { # Command : getRewardAddress [wallet name] # Description : create, store and save reward address # Parameters : wallet name > the name of the wallet -# Return : populates ${reward_addr} +# Return : populates ${reward_addr} ${is_reward_script_addr} getRewardAddress() { + unset reward_addr is_reward_script_addr stake_vk_file="${WALLET_FOLDER}/${1}/${WALLET_STAKE_VK_FILENAME}" + stake_script_file="${WALLET_FOLDER}/${1}/${WALLET_STAKE_SCRIPT_FILENAME}" stake_addr_file="${WALLET_FOLDER}/${1}/${WALLET_STAKE_ADDR_FILENAME}" + [[ -f "${stake_script_file}" ]] && is_reward_script_addr=true || is_reward_script_addr=false [[ -f ${stake_addr_file} ]] && reward_addr=$(cat "${stake_addr_file}") && return 0 - reward_addr="" if [[ -f "${stake_vk_file}" ]]; then println ACTION "${CCLI} ${NETWORK_ERA} stake-address build --stake-verification-key-file ${stake_vk_file} --out-file ${stake_addr_file} ${NETWORK_IDENTIFIER}" if stdout=$(${CCLI} ${NETWORK_ERA} stake-address build --stake-verification-key-file "${stake_vk_file}" --out-file "${stake_addr_file}" ${NETWORK_IDENTIFIER} 2>&1); then @@ -1056,6 +1283,14 @@ getRewardAddress() { else println LOG "\n${FG_RED}ERROR${NC}: failure during reward address creation!\n${stdout}" fi + elif [[ -f "${stake_script_file}" ]]; then + println ACTION "${CCLI} ${NETWORK_ERA} stake-address build --stake-script-file ${stake_script_file} --out-file ${stake_addr_file} ${NETWORK_IDENTIFIER}" + if stdout=$(${CCLI} ${NETWORK_ERA} stake-address build --stake-script-file "${stake_script_file}" --out-file "${stake_addr_file}" ${NETWORK_IDENTIFIER} 2>&1); then + reward_addr=$(cat "${stake_addr_file}") + return 0 + else + println LOG "\n${FG_RED}ERROR${NC}: failure during reward script address creation!\n${stdout}" + fi elif [[ -f "${1}" ]]; then getRewardAddressFromKey ${1} return $? @@ -1075,54 +1310,121 @@ getRewardAddressFromKey() { fi } -# Command : getPayScriptAddress [wallet name] -# Description : create, store and save multi-sig payment script address -# Parameters : wallet name > the name of the wallet -# Return : populates ${pay_script_addr} -getPayScriptAddress() { - payment_script_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_SCRIPT_FILENAME}" - payment_script_addr_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_SCRIPT_ADDR_FILENAME}" - [[ -f ${payment_script_addr_file} ]] && pay_script_addr=$(cat "${payment_script_addr_file}") && return 0 - unset pay_script_addr - if [[ -f "${payment_script_file}" ]]; then - println ACTION "${CCLI} ${NETWORK_ERA} address build --payment-script-file ${payment_script_file} --out-file ${payment_script_addr_file} ${NETWORK_IDENTIFIER}" - if stdout=$(${CCLI} ${NETWORK_ERA} address build --payment-script-file "${payment_script_file}" --out-file "${payment_script_addr_file}" ${NETWORK_IDENTIFIER} 2>&1); then - pay_script_addr=$(cat "${payment_script_addr_file}") - return 0 - else - println LOG "\n${FG_RED}ERROR${NC}: failure during payment script address creation!\n${stdout}" +# Command : getCredential [type] [file] +# Description : create and save wallet credentials (key hash) for payment and stake keys (incl MultiSig) +# Parameters : type > payment | stake | drep +# : file > path to verification file +# Return : populates ${cred} +getCredential() { + unset cred + [[ ! -f "$2" ]] && return 1 + if [[ $1 = drep ]]; then + println ACTION "${CCLI} conway governance drep id --drep-verification-key-file $2 | bech32" + if ! _drep_id=$(${CCLI} conway governance drep id --drep-verification-key-file "$2" 2>&1); then + println LOG "\n${FG_RED}ERROR${NC}: failure during key hash creation!\n${cred}" + unset cred + return 1 + fi + cred=$(bech32 <<< "${_drep_id}") + else + [[ $1 = payment ]] && CLI_ARGS=("--payment-verification-key-file" "$2") || CLI_ARGS=("--stake-verification-key-file" "$2") + println ACTION "${CCLI} ${NETWORK_ERA} address key-hash ${CLI_ARGS[*]}" + if ! cred=$(${CCLI} ${NETWORK_ERA} address key-hash "${CLI_ARGS[@]}" 2>&1); then + println LOG "\n${FG_RED}ERROR${NC}: failure during key hash creation!\n${cred}" + unset cred + return 1 fi fi - return 1 + return 0 } # Command : getCredentials [wallet name] -# Description : create and save wallet credentials (key hash) for payment and stake keys +# Description : create and save wallet credentials (key hash) for payment and stake keys (incl MultiSig) # Parameters : wallet name > the name of the wallet -# Return : populates ${pay_cred} & ${stake_cred} getCredentials() { + unset pay_cred stake_cred ms_pay_cred ms_stake_cred script_pay_cred script_stake_cred payment_cred_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_CRED_FILENAME}" stake_cred_file="${WALLET_FOLDER}/${1}/${WALLET_STAKE_CRED_FILENAME}" - [[ -f ${payment_cred_file} && -f ${stake_cred_file} ]] && pay_cred=$(cat "${payment_cred_file}") && stake_cred=$(cat "${stake_cred_file}") && return 0 - unset pay_cred stake_cred - payment_vk_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_VK_FILENAME}" - stake_vk_file="${WALLET_FOLDER}/${1}/${WALLET_STAKE_VK_FILENAME}" - if [[ -f "${payment_vk_file}" ]]; then - println ACTION "${CCLI} ${NETWORK_ERA} address key-hash --payment-verification-key-file ${payment_vk_file} --out-file ${payment_cred_file}" - if stdout=$(${CCLI} ${NETWORK_ERA} address key-hash --payment-verification-key-file "${payment_vk_file}" --out-file "${payment_cred_file}" 2>&1); then - pay_cred=$(cat "${payment_cred_file}") - else - println LOG "\n${FG_RED}ERROR${NC}: failure during payment key hash creation!\n${stdout}" - return 1 + ms_payment_cred_file="${WALLET_FOLDER}/${1}/${WALLET_MULTISIG_PREFIX}${WALLET_PAY_CRED_FILENAME}" + ms_stake_cred_file="${WALLET_FOLDER}/${1}/${WALLET_MULTISIG_PREFIX}${WALLET_STAKE_CRED_FILENAME}" + script_payment_cred_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_SCRIPT_CRED_FILENAME}" + script_stake_cred_file="${WALLET_FOLDER}/${1}/${WALLET_STAKE_SCRIPT_CRED_FILENAME}" + [[ -f ${payment_cred_file} ]] && pay_cred=$(cat "${payment_cred_file}") + [[ -f ${stake_cred_file} ]] && stake_cred=$(cat "${stake_cred_file}") + [[ -f ${ms_payment_cred_file} ]] && ms_pay_cred=$(cat "${ms_payment_cred_file}") + [[ -f ${ms_stake_cred_file} ]] && ms_stake_cred=$(cat "${ms_stake_cred_file}") + [[ -f ${script_payment_cred_file} ]] && script_pay_cred=$(cat "${script_payment_cred_file}") + [[ -f ${script_stake_cred_file} ]] && script_stake_cred=$(cat "${script_stake_cred_file}") + if [[ -z ${pay_cred} ]]; then + payment_vk_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_VK_FILENAME}" + if [[ -f "${payment_vk_file}" ]]; then + println ACTION "${CCLI} ${NETWORK_ERA} address key-hash --payment-verification-key-file ${payment_vk_file} --out-file ${payment_cred_file}" + if stdout=$(${CCLI} ${NETWORK_ERA} address key-hash --payment-verification-key-file "${payment_vk_file}" --out-file "${payment_cred_file}" 2>&1); then + pay_cred=$(cat "${payment_cred_file}") + else + println LOG "\n${FG_RED}ERROR${NC}: failure during payment key hash creation!\n${stdout}" + return 1 + fi fi fi - if [[ -f "${stake_vk_file}" ]]; then - println ACTION "${CCLI} ${NETWORK_ERA} stake-address key-hash --stake-verification-key-file ${stake_vk_file} --out-file ${stake_cred_file}" - if stdout=$(${CCLI} ${NETWORK_ERA} stake-address key-hash --stake-verification-key-file "${stake_vk_file}" --out-file "${stake_cred_file}" 2>&1); then - stake_cred=$(cat "${stake_cred_file}") - else - println LOG "\n${FG_RED}ERROR${NC}: failure during stake key hash creation!\n${stdout}" - return 1 + if [[ -z ${stake_cred} ]]; then + stake_vk_file="${WALLET_FOLDER}/${1}/${WALLET_STAKE_VK_FILENAME}" + if [[ -f "${stake_vk_file}" ]]; then + println ACTION "${CCLI} ${NETWORK_ERA} stake-address key-hash --stake-verification-key-file ${stake_vk_file} --out-file ${stake_cred_file}" + if stdout=$(${CCLI} ${NETWORK_ERA} stake-address key-hash --stake-verification-key-file "${stake_vk_file}" --out-file "${stake_cred_file}" 2>&1); then + stake_cred=$(cat "${stake_cred_file}") + else + println LOG "\n${FG_RED}ERROR${NC}: failure during stake key hash creation!\n${stdout}" + return 1 + fi + fi + fi + if [[ -z ${ms_pay_cred} ]]; then + ms_payment_vk_file="${WALLET_FOLDER}/${1}/${WALLET_MULTISIG_PREFIX}${WALLET_PAY_VK_FILENAME}" + if [[ -f "${ms_payment_vk_file}" ]]; then + println ACTION "${CCLI} ${NETWORK_ERA} address key-hash --payment-verification-key-file ${ms_payment_vk_file} --out-file ${ms_payment_cred_file}" + if stdout=$(${CCLI} ${NETWORK_ERA} address key-hash --payment-verification-key-file "${ms_payment_vk_file}" --out-file "${ms_payment_cred_file}" 2>&1); then + ms_pay_cred=$(cat "${ms_payment_cred_file}") + else + println LOG "\n${FG_RED}ERROR${NC}: failure during MultiSig payment key hash creation!\n${stdout}" + return 1 + fi + fi + fi + if [[ -z ${ms_stake_cred} ]]; then + ms_stake_vk_file="${WALLET_FOLDER}/${1}/${WALLET_MULTISIG_PREFIX}${WALLET_STAKE_VK_FILENAME}" + if [[ -f "${ms_stake_vk_file}" ]]; then + println ACTION "${CCLI} ${NETWORK_ERA} stake-address key-hash --stake-verification-key-file ${ms_stake_vk_file} --out-file ${ms_stake_cred_file}" + if stdout=$(${CCLI} ${NETWORK_ERA} stake-address key-hash --stake-verification-key-file "${ms_stake_vk_file}" --out-file "${ms_stake_cred_file}" 2>&1); then + ms_stake_cred=$(cat "${ms_stake_cred_file}") + else + println LOG "\n${FG_RED}ERROR${NC}: failure during MultiSig stake key hash creation!\n${stdout}" + return 1 + fi + fi + fi + if [[ -z ${script_pay_cred} ]]; then + payment_script_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_SCRIPT_FILENAME}" + if [[ -f "${payment_script_file}" ]]; then + println ACTION "${CCLI} ${NETWORK_ERA} transaction policyid --script-file ${payment_script_file} --out-file ${script_payment_cred_file}" + if stdout=$(${CCLI} ${NETWORK_ERA} transaction policyid --script-file "${payment_script_file}" --out-file "${script_payment_cred_file}" 2>&1); then + script_pay_cred=$(cat "${script_payment_cred_file}") + else + println LOG "\n${FG_RED}ERROR${NC}: failure during script payment policy creation!\n${stdout}" + return 1 + fi + fi + fi + if [[ -z ${script_stake_cred} ]]; then + stake_script_file="${WALLET_FOLDER}/${1}/${WALLET_STAKE_SCRIPT_FILENAME}" + if [[ -f "${stake_script_file}" ]]; then + println ACTION "${CCLI} ${NETWORK_ERA} transaction policyid --script-file ${stake_script_file} --out-file ${script_stake_cred_file}" + if stdout=$(${CCLI} ${NETWORK_ERA} transaction policyid --script-file "${stake_script_file}" --out-file "${script_stake_cred_file}" 2>&1); then + script_stake_cred=$(cat "${script_stake_cred_file}") + else + println LOG "\n${FG_RED}ERROR${NC}: failure during script stake policy creation!\n${stdout}" + return 1 + fi fi fi } @@ -1358,10 +1660,7 @@ getWalletRewards() { if isWalletRegistered $1; then reward_lovelace=0 if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then - for reward_entry in $(jq -r '.[] | @base64' <<< "${stake_address_info}"); do - _jq() { base64 -d <<< ${reward_entry} | jq -r "${1}"; } - reward_lovelace=$(( reward_lovelace + $(_jq '.rewardAccountBalance //0') )) - done + : # do nothing, variables populated through isWalletRegistered else reward_lovelace=${rewards_available[${reward_addr}]:-0} fi @@ -1402,13 +1701,11 @@ getRewardInfoKoios() { # Parameters : stake address > the address from stake.vkey # Return : populates ${reward_lovelace} getRewardsFromAddr() { - reward_lovelace=0 + unset stake_address pool_delegation vote_delegation + reward_lovelace=0; stake_deposit=0 println ACTION "${CCLI} ${NETWORK_ERA} query stake-address-info ${NETWORK_IDENTIFIER} --address ${1}" ! stake_address_info=$(${CCLI} ${NETWORK_ERA} query stake-address-info ${NETWORK_IDENTIFIER} --address ${1}) && println "ERROR" "\n${FG_RED}NODE CLI ERROR${NC}: ${stake_address_info}\n" && return 1 # print error and return - for reward_entry in $(jq -r '.[] | @base64' <<< "${stake_address_info}"); do - _jq() { base64 -d <<< ${reward_entry} | jq -r "${1}"; } - reward_lovelace=$(( reward_lovelace + $(_jq '.rewardAccountBalance //0') )) - done + IFS=',' read -r stake_address reward_lovelace stake_deposit pool_delegation vote_delegation < <( jq -cr '"\(.[0].address//""),\(.[0].rewardAccountBalance//0),\(.[0].delegationDeposit//0),\(.[0].stakeDelegation//""),\(.[0].voteDelegation//"")"' <<< "${stake_address_info}" ) } # Command : isWalletRegistered [wallet name] @@ -1420,16 +1717,15 @@ isWalletRegistered() { [[ ! -v "reward_status[${reward_addr}]" ]] && reward_addr_list=( ${reward_addr} ) && getRewardInfoKoios [[ ${reward_status[${reward_addr}]} = registered ]] && return 0 else - println ACTION "${CCLI} ${NETWORK_ERA} query stake-address-info ${NETWORK_IDENTIFIER} --address ${reward_addr}" - ! stake_address_info=$(${CCLI} ${NETWORK_ERA} query stake-address-info ${NETWORK_IDENTIFIER} --address ${reward_addr}) && println "ERROR" "\n${FG_RED}NODE CLI ERROR${NC}: ${stake_address_info}\n" && return 1 # print error and return - [[ -n "${stake_address_info}" && $(jq -r 'length' <<< ${stake_address_info}) -gt 0 ]] && return 0 + getRewardsFromAddr ${reward_addr} + [[ -n "${stake_address}" ]] && return 0 fi fi return 1 } # Command : getWalletType [wallet name] -# Description : check if wallet is a hardware wallet, 0=yes, 1=cli, 2=cli & encrypted, 3=signing keys missing, 4=verification keys missing, 5=multi-sig +# Description : check if wallet is a hardware wallet, 0=hw, 1=cli, 2=cli & encrypted, 3=signing keys missing, 4=verification keys missing, 5=MultiSig # Parameters : wallet name > the name of the wallet getWalletType() { payment_vk_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_VK_FILENAME}" @@ -1437,23 +1733,89 @@ getWalletType() { payment_script_file="${WALLET_FOLDER}/${1}/${WALLET_PAY_SCRIPT_FILENAME}" stake_vk_file="${WALLET_FOLDER}/${1}/${WALLET_STAKE_VK_FILENAME}" stake_sk_file="${WALLET_FOLDER}/${1}/${WALLET_STAKE_SK_FILENAME}" + stake_script_file="${WALLET_FOLDER}/${1}/${WALLET_STAKE_SCRIPT_FILENAME}" + ms_payment_vk_file="${WALLET_FOLDER}/${1}/${WALLET_MULTISIG_PREFIX}${WALLET_PAY_VK_FILENAME}" + ms_payment_sk_file="${WALLET_FOLDER}/${1}/${WALLET_MULTISIG_PREFIX}${WALLET_PAY_SK_FILENAME}" + ms_stake_vk_file="${WALLET_FOLDER}/${1}/${WALLET_MULTISIG_PREFIX}${WALLET_STAKE_VK_FILENAME}" + ms_stake_sk_file="${WALLET_FOLDER}/${1}/${WALLET_MULTISIG_PREFIX}${WALLET_STAKE_SK_FILENAME}" if [[ -f "${WALLET_FOLDER}/${1}/${WALLET_PAY_VK_FILENAME}" && -f "${WALLET_FOLDER}/${1}/${WALLET_STAKE_VK_FILENAME}" ]]; then # CNTools wallet - if [[ $(jq -r '.description' "${payment_vk_file}") = *"Hardware"* ]]; then + wallet_desc=$(jq -r '.description' "${payment_vk_file}") + if [[ ${wallet_desc} = *"Hardware"* ]]; then payment_sk_file="${WALLET_FOLDER}/${1}/${WALLET_HW_PAY_SK_FILENAME}" stake_sk_file="${WALLET_FOLDER}/${1}/${WALLET_HW_STAKE_SK_FILENAME}" + ms_payment_vk_file="${WALLET_FOLDER}/${1}/${WALLET_MULTISIG_PREFIX}${WALLET_HW_PAY_VK_FILENAME}" + ms_payment_sk_file="${WALLET_FOLDER}/${1}/${WALLET_MULTISIG_PREFIX}${WALLET_HW_PAY_SK_FILENAME}" + ms_stake_vk_file="${WALLET_FOLDER}/${1}/${WALLET_MULTISIG_PREFIX}${WALLET_HW_STAKE_VK_FILENAME}" + ms_stake_sk_file="${WALLET_FOLDER}/${1}/${WALLET_MULTISIG_PREFIX}${WALLET_HW_STAKE_SK_FILENAME}" [[ ${op_mode} = "online" && ( ! -f ${payment_sk_file} || ! -f ${stake_sk_file} ) ]] && return 3 || return 0 elif [[ -f "${WALLET_FOLDER}/${1}/${WALLET_PAY_SK_FILENAME}.gpg" || -f "${WALLET_FOLDER}/${1}/${WALLET_STAKE_SK_FILENAME}.gpg" ]]; then return 2 else [[ ${op_mode} = "online" && ( ! -f ${payment_sk_file} || ! -f ${stake_sk_file} ) ]] && return 3 || return 1 fi - elif [[ -f "${WALLET_FOLDER}/${1}/${WALLET_PAY_SCRIPT_FILENAME}" && -f "${WALLET_FOLDER}/${1}/${WALLET_STAKE_VK_FILENAME}" ]]; then # CNTools multi-sig wallet + elif [[ -f "${WALLET_FOLDER}/${1}/${WALLET_PAY_SCRIPT_FILENAME}" || -f "${WALLET_FOLDER}/${1}/${WALLET_STAKE_SCRIPT_FILENAME}" ]]; then # CNTools MultiSig wallet return 5 else return 4 fi } +# Command : getAllMultiSigKeys [script] +# Description : parse json MultiSig script for all signatures +# Parameters : script > the native script in json format +# Return : populates: ${script_sig_list} (associative array that acts as a set) +getAllMultiSigKeys() { + unset script_sig_list + declare -gA script_sig_list=() + while read -r _keyHash; do + script_sig_list[${_keyHash}]=1 + done < <( jq -r '.. | select(.type?=="sig") | .keyHash' <<< "$1" ) +} + +# Command : validateMultiSigScript [verbose] [script] [sigs...] +# Description : parse json MultiSig script to check if needed signatures exist +# : NOTE !! please unset required_total before calling this function +# Parameters : verbose > true|false, print time lock warnings +# : script > the native script in json format +# : sigs > array of creds for wallets found +# Return : 0 = success, 1 = failed, populates ${required_total} with total needed signatures +validateMultiSigScript() { + local found=0 + local required=1 + local verbose=$1 + local script=$2 + shift + IFS=',' read -r _type _scripts _scripts_cnt _required <<< "$(jq -cr '"\(.type),\(.scripts|@base64),\(.scripts|length),\(.required)"' <<< "${script}")" + if [[ ${_type} = atLeast ]]; then + required=${_required} + elif [[ ${_type} = all ]]; then + required=${_scripts_cnt} + fi + while IFS=',' read -r __type __key_hash __slot __script; do + if [[ ${__type} = sig ]]; then + for _sig in "$@"; do + [[ ${_sig} = "${__key_hash}" ]] && ((found++)) + done + elif [[ ${__type} = before ]]; then + if [[ $(getSlotTipRef) -lt ${__slot} ]]; then + ((found++)) + else + [[ ${verbose} = true ]] && println ERROR "${FG_RED}Time locked!${NC} This script is locked with a before condition that has passed, before slot = ${__slot}, current slot = $(getSlotTipRef)" + fi + elif [[ ${__type} = after ]]; then + if [[ $(getSlotTipRef) -gt ${__slot} ]]; then + ((found++)) + else + [[ ${verbose} = true ]] && println ERROR "${FG_RED}Time locked!${NC} This script is locked with an after condition that has yet to pass, after slot = ${__slot}, current slot = $(getSlotTipRef)" + fi + else + validateMultiSigScript ${verbose} "$(base64 -d <<< ${__script})" "$@" && ((found++)) + fi + done < <( base64 -d <<< ${_scripts} | jq -cr '.[] | "\(.type),\(.keyHash),\(.slot),\(.|@base64)"' ) + required_total=$(( required_total + required )) + [[ ${found} -ge ${required} ]] && return 0 || return 1 +} + # Command : getPoolType [pool name] # Description : check if pool is a hardware pool, 0=yes, 1=cli, 2=cli & encrypted, 3=signing keys missing, 4=verification keys missing # Parameters : pool name > the name of the pool @@ -1474,11 +1836,11 @@ getPoolType() { fi } -# Command : getTTL +# Command : getTTL [force, true|false] # Description : query node for slot tip and calculate/get TTL from input depending on op_mode getTTL() { tip_ref=$(getSlotTipRef) - if [[ ${op_mode} = "hybrid" ]]; then + if [[ ${op_mode} = "hybrid" || $1 = true ]]; then println DEBUG "\nHow long do you want the transaction to be valid?" getAnswerAnyCust ttl_enter "TTL (in seconds, default: 1800/30min)" ttl_enter=${ttl_enter:-1800} @@ -1493,6 +1855,326 @@ getTTL() { println LOG "Current slot is ${tip_ref}, setting ttl to ${ttl}" } +# Command : getCustomDerivationPath +# Description : ask user for custom derivation path (account and key index) +# Return : populates: ${acct_idx} ${key_idx} +getCustomDerivationPath() { + println DEBUG "Enter a custom account index to derive keys for (enter for default)" + getAnswerAnyCust acct_idx "Account (default: 0)" + acct_idx=${acct_idx:-0} + if ! isNumber ${acct_idx}; then + println ERROR "${FG_RED}ERROR${NC}: Invalid account index, must be a number!" + waitToProceed && return 1 + fi + println DEBUG "\nEnter a custom key index to derive keys for (enter for default)" + getAnswerAnyCust key_idx "Key index (default: 0)" + key_idx=${key_idx:-0} + if ! isNumber ${key_idx}; then + println ERROR "${FG_RED}ERROR${NC}: Invalid key index, must be a number!" + waitToProceed && return 1 + fi + return 0 +} + +# Command : getSavedDerivationPath [derivation_path_file] +# Description : parse saved derivation path file for acct_idx and key_idx +# Return : populates: ${derivation_path} ${acct_idx} ${key_idx} +getSavedDerivationPath() { + unset derivation_path acct_idx key_idx + [[ ! -f $1 ]] && return 1 + derivation_path=$(cat "$1") + IFS='/' read -ra array <<< "${derivation_path}" + acct_idx="${array[2]//H}" + key_idx="${array[4]}" + return 0 +} + +# Command : createNewWallet +# Description : creates a new empty wallet folder +createNewWallet() { + getAnswerAnyCust wallet_name "Name of wallet (non-alphanumeric characters will be replaced with a space)" + # Remove unwanted characters from wallet name + wallet_name=${wallet_name//[^[:alnum:]]/_} + if [[ -z "${wallet_name}" ]]; then + println ERROR "${FG_RED}ERROR${NC}: Empty wallet name, please retry!" + waitToProceed && return 1 + fi + echo + if ! mkdir -p "${WALLET_FOLDER}/${wallet_name}"; then + println ERROR "${FG_RED}ERROR${NC}: Failed to create directory for wallet:\n${WALLET_FOLDER}/${wallet_name}" + waitToProceed && return 1 + fi + if [[ $(find "${WALLET_FOLDER}/${wallet_name}" -type f -print0 | wc -c) -gt 0 ]]; then + println "${FG_RED}WARN${NC}: A wallet ${FG_GREEN}$wallet_name${NC} already exists" + println " Choose another name or delete the existing one" + waitToProceed && return 1 + fi + return 0 +} + +# Command : createNewWallet +# Description : creates a new +# Return : populates: ${acct_idx} ${key_idx} +createMnemonicWallet() { + if ! cmdAvailable "bech32" &>/dev/null || \ + ! cmdAvailable "cardano-address" &>/dev/null; then + println ERROR "${FG_RED}ERROR${NC}: bech32 and/or cardano-address not found in '\$PATH'" + println ERROR "Please run updated guild-deploy.sh and re-build/re-download cardano-node" + waitToProceed && return 1 + fi + derivation_path_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_DERIVATION_PATH_FILENAME}" + payment_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_SK_FILENAME}" + payment_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_VK_FILENAME}" + stake_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_SK_FILENAME}" + stake_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_VK_FILENAME}" + drep_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_DREP_VK_FILENAME}" + drep_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_DREP_SK_FILENAME}" + cc_cold_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_CC_COLD_VK_FILENAME}" + cc_cold_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_CC_COLD_SK_FILENAME}" + cc_hot_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_CC_HOT_VK_FILENAME}" + cc_hot_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_CC_HOT_SK_FILENAME}" + ms_payment_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_PAY_SK_FILENAME}" + ms_payment_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_PAY_VK_FILENAME}" + ms_stake_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_STAKE_SK_FILENAME}" + ms_stake_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_STAKE_VK_FILENAME}" + ms_drep_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_GOV_DREP_SK_FILENAME}" + ms_drep_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_GOV_DREP_VK_FILENAME}" + isImport=true + if [[ -z ${mnemonic} ]]; then + println ACTION "cardano-address recovery-phrase generate" + mnemonic=$(cardano-address recovery-phrase generate) + isImport=false + fi + IFS=" " read -r -a words <<< "${mnemonic}" + if [[ ${#words[@]} -ne 24 ]] && [[ ${#words[@]} -ne 15 ]]; then + println ERROR "${FG_RED}ERROR${NC}: 24 or 15 words expected, found ${FG_RED}${#words[@]}${NC}" + echo && safeDel "${WALLET_FOLDER}/${wallet_name}" + unset mnemonic; unset words + waitToProceed && return 1 + fi + getCustomDerivationPath || return 1 + echo "1852H/1815H/${acct_idx}H/x/${key_idx}" > "${derivation_path_file}" + caddr_v="$(cardano-address -v | awk '{print $1}')" + [[ "${caddr_v}" == 3* ]] && caddr_arg="--with-chain-code" || caddr_arg="" + println ACTION "cardano-address key from-recovery-phrase Shelley <<< " + if ! root_prv=$(cardano-address key from-recovery-phrase Shelley <<< ${mnemonic}); then + echo && safeDel "${WALLET_FOLDER}/${wallet_name}" + unset mnemonic; unset words + waitToProceed && return 1 + fi + unset mnemonic + [[ ${isImport} = true ]] && unset words + println ACTION "cardano-address key child "1852H/1815H/${acct_idx}H/0/${key_idx}" <<< " + payment_xprv=$(cardano-address key child "1852H/1815H/${acct_idx}H/0/${key_idx}" <<< ${root_prv}) + println ACTION "cardano-address key child "1852H/1815H/${acct_idx}H/2/${key_idx}" <<< " + stake_xprv=$(cardano-address key child "1852H/1815H/${acct_idx}H/2/${key_idx}" <<< ${root_prv}) + println ACTION "cardano-address key child "1852H/1815H/${acct_idx}H/3/${key_idx}" <<< " + drep_xprv=$(cardano-address key child "1852H/1815H/${acct_idx}H/3/${key_idx}" <<< ${root_prv}) + println ACTION "cardano-address key child "1852H/1815H/${acct_idx}H/4/${key_idx}" <<< " + cc_cold_xprv=$(cardano-address key child "1852H/1815H/${acct_idx}H/4/${key_idx}" <<< ${root_prv}) + println ACTION "cardano-address key child "1852H/1815H/${acct_idx}H/5/${key_idx}" <<< " + cc_hot_xprv=$(cardano-address key child "1852H/1815H/${acct_idx}H/5/${key_idx}" <<< ${root_prv}) + println ACTION "cardano-address key child "1854H/1815H/${acct_idx}H/0/${key_idx}" <<< " + ms_payment_xprv=$(cardano-address key child "1854H/1815H/${acct_idx}H/0/${key_idx}" <<< ${root_prv}) + println ACTION "cardano-address key child "1854H/1815H/${acct_idx}H/2/${key_idx}" <<< " + ms_stake_xprv=$(cardano-address key child "1854H/1815H/${acct_idx}H/2/${key_idx}" <<< ${root_prv}) + println ACTION "cardano-address key child "1854H/1815H/${acct_idx}H/3/${key_idx}" <<< " + ms_drep_xprv=$(cardano-address key child "1854H/1815H/${acct_idx}H/3/${key_idx}" <<< ${root_prv}) + println ACTION "cardano-address key public ${caddr_arg} <<< " + payment_xpub=$(cardano-address key public ${caddr_arg} <<< ${payment_xprv}) + println ACTION "cardano-address key public ${caddr_arg} <<< " + stake_xpub=$(cardano-address key public ${caddr_arg} <<< ${stake_xprv}) + println ACTION "cardano-address key public ${caddr_arg} <<< " + drep_xpub=$(cardano-address key public ${caddr_arg} <<< ${drep_xprv}) + println ACTION "cardano-address key public ${caddr_arg} <<< " + cc_cold_xpub=$(cardano-address key public ${caddr_arg} <<< ${cc_cold_xprv}) + println ACTION "cardano-address key public ${caddr_arg} <<< " + cc_hot_xpub=$(cardano-address key public ${caddr_arg} <<< ${cc_hot_xprv}) + println ACTION "cardano-address key public ${caddr_arg} <<< " + ms_payment_xpub=$(cardano-address key public ${caddr_arg} <<< ${ms_payment_xprv}) + println ACTION "cardano-address key public ${caddr_arg} <<< " + ms_stake_xpub=$(cardano-address key public ${caddr_arg} <<< ${ms_stake_xprv}) + println ACTION "cardano-address key public ${caddr_arg} <<< " + ms_drep_xpub=$(cardano-address key public ${caddr_arg} <<< ${ms_drep_xprv}) + [[ "${NWMAGIC}" == "764824073" ]] && network_tag=1 || network_tag=0 + println ACTION "cardano-address address delegation ${stake_xpub} <<< $(cardano-address address payment --network-tag ${network_tag} <<< ${payment_xpub})" + base_addr_candidate=$(cardano-address address delegation ${stake_xpub} <<< "$(cardano-address address payment --network-tag ${network_tag} <<< ${payment_xpub})") + if [[ "${caddr_v}" == 2* ]] && [[ "${NWMAGIC}" != "764824073" ]]; then + println LOG "TestNet, converting address to 'addr_test'" + println ACTION "bech32 addr_test <<< ${base_addr_candidate}" + base_addr_candidate=$(bech32 addr_test <<< ${base_addr_candidate}) + fi + println LOG "Base address candidate = ${base_addr_candidate}" + println LOG "Address Inspection:\n$(cardano-address address inspect <<< ${base_addr_candidate})" + println ACTION "\$(bech32 <<< \${payment_xprv} | cut -b -128)\$(bech32 <<< ${payment_xpub})" + pes_key=$(bech32 <<< ${payment_xprv} | cut -b -128)$(bech32 <<< ${payment_xpub}) + println ACTION "\$(bech32 <<< \${stake_xprv} | cut -b -128)\$(bech32 <<< ${stake_xpub})" + ses_key=$(bech32 <<< ${stake_xprv} | cut -b -128)$(bech32 <<< ${stake_xpub}) + println ACTION "\$(bech32 <<< \${drep_xprv} | cut -b -128)\$(bech32 <<< ${drep_xpub})" + drep_es_key=$(bech32 <<< ${drep_xprv} | cut -b -128)$(bech32 <<< ${drep_xpub}) + println ACTION "\$(bech32 <<< \${cc_cold_xprv} | cut -b -128)\$(bech32 <<< ${cc_cold_xpub})" + cc_cold_es_key=$(bech32 <<< ${cc_cold_xprv} | cut -b -128)$(bech32 <<< ${cc_cold_xpub}) + println ACTION "\$(bech32 <<< \${cc_hot_xprv} | cut -b -128)\$(bech32 <<< ${cc_hot_xpub})" + cc_hot_es_key=$(bech32 <<< ${cc_hot_xprv} | cut -b -128)$(bech32 <<< ${cc_hot_xpub}) + println ACTION "\$(bech32 <<< \${ms_payment_xprv} | cut -b -128)\$(bech32 <<< ${ms_payment_xpub})" + ms_pes_key=$(bech32 <<< ${ms_payment_xprv} | cut -b -128)$(bech32 <<< ${ms_payment_xpub}) + println ACTION "\$(bech32 <<< \${ms_stake_xprv} | cut -b -128)\$(bech32 <<< ${ms_stake_xpub})" + ms_ses_key=$(bech32 <<< ${ms_stake_xprv} | cut -b -128)$(bech32 <<< ${ms_stake_xpub}) + println ACTION "\$(bech32 <<< \${ms_drep_xprv} | cut -b -128)\$(bech32 <<< ${ms_drep_xpub})" + ms_drep_es_key=$(bech32 <<< ${ms_drep_xprv} | cut -b -128)$(bech32 <<< ${ms_drep_xpub}) + cat <<-EOF > "${payment_sk_file}" + { + "type": "PaymentExtendedSigningKeyShelley_ed25519_bip32", + "description": "Payment Signing Key", + "cborHex": "5880${pes_key}" + } + EOF + cat <<-EOF > "${stake_sk_file}" + { + "type": "StakeExtendedSigningKeyShelley_ed25519_bip32", + "description": "Stake Signing Key", + "cborHex": "5880${ses_key}" + } + EOF + cat <<-EOF > "${drep_sk_file}" + { + "type": "DRepExtendedSigningKey_ed25519_bip32", + "description": "Delegate Representative Signing Key", + "cborHex": "5880${drep_es_key}" + } + EOF + cat <<-EOF > "${cc_cold_sk_file}" + { + "type": "ConstitutionalCommitteeColdExtendedSigningKey_ed25519_bip32", + "description": "Constitutional Committee Cold Signing Key", + "cborHex": "5880${cc_cold_es_key}" + } + EOF + cat <<-EOF > "${cc_hot_sk_file}" + { + "type": "ConstitutionalCommitteeHotExtendedSigningKey_ed25519_bip32", + "description": "Constitutional Committee Hot Signing Key", + "cborHex": "5880${cc_hot_es_key}" + } + EOF + cat <<-EOF > "${ms_payment_sk_file}" + { + "type": "PaymentExtendedSigningKeyShelley_ed25519_bip32", + "description": "MultiSig Payment Signing Key", + "cborHex": "5880${pes_key}" + } + EOF + cat <<-EOF > "${ms_stake_sk_file}" + { + "type": "StakeExtendedSigningKeyShelley_ed25519_bip32", + "description": "MultiSig Stake Signing Key", + "cborHex": "5880${ses_key}" + } + EOF + cat <<-EOF > "${ms_drep_sk_file}" + { + "type": "DRepExtendedSigningKey_ed25519_bip32", + "description": "MultiSig Delegate Representative Signing Key", + "cborHex": "5880${drep_es_key}" + } + EOF + println ACTION "${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file ${payment_sk_file} --verification-key-file ${TMP_DIR}/payment.evkey" + if ! stdout=$(${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${payment_sk_file}" --verification-key-file "${TMP_DIR}/payment.evkey" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during payment extended verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + println ACTION "${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file ${stake_sk_file} --verification-key-file ${TMP_DIR}/stake.evkey" + if ! stdout=$(${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${stake_sk_file}" --verification-key-file "${TMP_DIR}/stake.evkey" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during stake extended verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + println ACTION "${CCLI} conway key verification-key --signing-key-file ${drep_sk_file} --verification-key-file ${TMP_DIR}/drep.evkey" + if ! stdout=$(${CCLI} conway key verification-key --signing-key-file "${drep_sk_file}" --verification-key-file "${TMP_DIR}/drep.evkey" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during drep extended verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + println ACTION "${CCLI} conway key verification-key --signing-key-file ${cc_cold_sk_file} --verification-key-file ${TMP_DIR}/cc-cold.evkey" + if ! stdout=$(${CCLI} conway key verification-key --signing-key-file "${cc_cold_sk_file}" --verification-key-file "${TMP_DIR}/cc-cold.evkey" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during cc-cold extended verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + println ACTION "${CCLI} conway key verification-key --signing-key-file ${cc_hot_sk_file} --verification-key-file ${TMP_DIR}/cc-hot.evkey" + if ! stdout=$(${CCLI} conway key verification-key --signing-key-file "${cc_hot_sk_file}" --verification-key-file "${TMP_DIR}/cc-hot.evkey" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during cc-hot extended verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + println ACTION "${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file ${ms_payment_sk_file} --verification-key-file ${TMP_DIR}/ms_payment.evkey" + if ! stdout=$(${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${ms_payment_sk_file}" --verification-key-file "${TMP_DIR}/ms_payment.evkey" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during MultiSig payment extended verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + println ACTION "${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file ${ms_stake_sk_file} --verification-key-file ${TMP_DIR}/ms_stake.evkey" + if ! stdout=$(${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${ms_stake_sk_file}" --verification-key-file "${TMP_DIR}/ms_stake.evkey" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during MultiSig stake extended verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + println ACTION "${CCLI} conway key verification-key --signing-key-file ${ms_drep_sk_file} --verification-key-file ${TMP_DIR}/ms_drep.evkey" + if ! stdout=$(${CCLI} conway key verification-key --signing-key-file "${ms_drep_sk_file}" --verification-key-file "${TMP_DIR}/ms_drep.evkey" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during MultiSig drep extended verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + println ACTION "${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file ${TMP_DIR}/payment.evkey --verification-key-file ${payment_vk_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file "${TMP_DIR}/payment.evkey" --verification-key-file "${payment_vk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during payment verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + println ACTION "${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file ${TMP_DIR}/stake.evkey --verification-key-file ${stake_vk_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file "${TMP_DIR}/stake.evkey" --verification-key-file "${stake_vk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during stake verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + println ACTION "${CCLI} conway key non-extended-key --extended-verification-key-file ${TMP_DIR}/drep.evkey --verification-key-file ${drep_vk_file}" + if ! stdout=$(${CCLI} conway key non-extended-key --extended-verification-key-file "${TMP_DIR}/drep.evkey" --verification-key-file "${drep_vk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during drep verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + println ACTION "${CCLI} conway key non-extended-key --extended-verification-key-file ${TMP_DIR}/cc-cold.evkey --verification-key-file ${cc_cold_vk_file}" + if ! stdout=$(${CCLI} conway key non-extended-key --extended-verification-key-file "${TMP_DIR}/cc-cold.evkey" --verification-key-file "${cc_cold_vk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during cc-cold verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + println ACTION "${CCLI} conway key non-extended-key --extended-verification-key-file ${TMP_DIR}/cc-hot.evkey --verification-key-file ${cc_hot_vk_file}" + if ! stdout=$(${CCLI} conway key non-extended-key --extended-verification-key-file "${TMP_DIR}/cc-hot.evkey" --verification-key-file "${cc_hot_vk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during cc-hot verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + println ACTION "${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file ${TMP_DIR}/ms_payment.evkey --verification-key-file ${ms_payment_vk_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file "${TMP_DIR}/ms_payment.evkey" --verification-key-file "${ms_payment_vk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during MultiSig payment verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + println ACTION "${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file ${TMP_DIR}/ms_stake.evkey --verification-key-file ${ms_stake_vk_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file "${TMP_DIR}/ms_stake.evkey" --verification-key-file "${ms_stake_vk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during MultiSig stake verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + println ACTION "${CCLI} conway key non-extended-key --extended-verification-key-file ${TMP_DIR}/ms_drep.evkey --verification-key-file ${ms_drep_vk_file}" + if ! stdout=$(${CCLI} conway key non-extended-key --extended-verification-key-file "${TMP_DIR}/ms_drep.evkey" --verification-key-file "${ms_drep_vk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during MultiSig drep verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + chmod 600 "${WALLET_FOLDER}/${wallet_name}/"* + getBaseAddress ${wallet_name} + getPayAddress ${wallet_name} + getRewardAddress ${wallet_name} + getCredentials ${wallet_name} + if [[ ${base_addr} != "${base_addr_candidate}" ]]; then + println ERROR "${FG_RED}ERROR${NC}: base address generated doesn't match base address candidate." + println ERROR "base_addr[${FG_LGRAY}${base_addr}${NC}]\n!=\nbase_addr_candidate[${FG_LGRAY}${base_addr_candidate}${NC}]" + println ERROR "Create a GitHub issue and include log file from failed CNTools session." + echo && safeDel "${WALLET_FOLDER}/${wallet_name}" + waitToProceed && return 1 + fi + return 0 +} + +printWalletInfo() { + println DEBUG "You can now send and receive ADA using the above addresses. Note that Payment Address will not take part in staking" + println DEBUG "Wallet will be automatically registered on chain if you choose to delegate or pledge wallet when registering a stake pool" + echo + println DEBUG "${FG_FG_LBLUE}INFO!${NC} Using a mnemonic or hardware wallet in CNTools comes with a few limitations" + echo + println DEBUG "Only the specified address in the HD wallet is extracted and because of this the following apply if used elsewhere:" + println DEBUG " ${FG_LGRAY}>${NC} If restored wallet balance doesn't match, send all ADA to address shown in CNTools" + println DEBUG " ${FG_LGRAY}>${NC} Only use receive address shown in CNTools (enable 'Single Address Mode' in wallet if available)" + echo + println DEBUG "Some of the advantages of using a mnemonic imported wallet instead of CLI are:" + println DEBUG " ${FG_LGRAY}>${NC} Wallet can be restored from saved mnemonic/hardware device if keys are lost/deleted" + println DEBUG " ${FG_LGRAY}>${NC} Wallet can be shared and used in multiple wallets concurrently, including CNTools" + echo + println DEBUG "Please read more about HD wallets at:" + println DEBUG "https://cardano-community.github.io/support-faq/Wallets/wallets/#heirarchical-deterministic-hd-wallets" +} + # Command : buildOfflineJSON [type] # Description : construct a json containing all data for offline signing # Parameters : type > type of transaction, e.g 'payment' @@ -1503,6 +2185,9 @@ buildOfflineJSON() { if ! offlineJSON=$(jq ". += { \"date-created\": \"$(date --iso-8601=s)\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { \"date-expire\": \"$(date --iso-8601=s --date="@$(($(date +%s)+ttl_enter))")\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { ttl: \"${ttl}\" }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { \"signing-file\": [] }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { \"script-file\": [] }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { witness: [] }" <<< ${offlineJSON}); then return 1; fi } # Command : registerStakeWallet [wallet name] [optional: skip validation] @@ -1514,6 +2199,9 @@ registerStakeWallet() { wallet_name=$1 wallet_source="base" + getWalletType ${wallet_name} + wallet_type=$? + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then utxo_cnt=${utxos_cnt[${base_addr}]} tx_in=${tx_in_arr[${base_addr}]} @@ -1524,32 +2212,53 @@ registerStakeWallet() { waitToProceed "press any key to continue with registration" fi - stake_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_VK_FILENAME}" stake_cert_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_CERT_FILENAME}" - - println ACTION "${CCLI} ${NETWORK_ERA} stake-address registration-certificate --stake-verification-key-file ${stake_vk_file} --out-file ${stake_cert_file}" - if ! stdout=$(${CCLI} ${NETWORK_ERA} stake-address registration-certificate --stake-verification-key-file "${stake_vk_file}" --out-file "${stake_cert_file}" 2>&1); then + + if [[ ${wallet_type} -eq 5 ]]; then + op_mode=hybrid + unset required_total + validateMultiSigScript false "$(cat "${payment_script_file}")" + witness_cnt=$(( required_total + 1 )) + stake_param=("--stake-script-file" "${stake_script_file}") + else + witness_cnt=2 + stake_param=("--stake-verification-key-file" "${stake_vk_file}") + fi + + if versionCheck "10.0" "${PROT_VERSION}"; then + stake_param+=("--key-reg-deposit-amt" ${KEY_DEPOSIT}) + fi + + println ACTION "${CCLI} ${NETWORK_ERA} stake-address registration-certificate ${stake_param[*]} --out-file ${stake_cert_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} stake-address registration-certificate "${stake_param[@]}" --out-file "${stake_cert_file}" 2>&1); then println ERROR "\n${FG_RED}ERROR${NC}: failure during stake registration certificate creation!\n${stdout}"; return 1 fi - if ! getTTL; then return 1; fi + if ! getTTL "$([[ ${wallet_type} -eq 5 ]] && echo true)"; then return 1; fi println LOG "Key Deposit is ${KEY_DEPOSIT}" getAssetsTxOut - + + unset script_args + if [[ ${wallet_type} -eq 5 ]]; then + script_args=( --tx-in-script-file "${payment_script_file}" ) + fi + + tmpNewBalance=$(( base_lovelace - KEY_DEPOSIT )) build_args=( ${tx_in} - --tx-out "${base_addr}+0${assets_tx_out}" + "${script_args[@]}" + --tx-out "${base_addr}+${tmpNewBalance}${assets_tx_out}" --invalid-hereafter ${ttl} - --fee 0 + --fee ${DUMMYFEE} --certificate-file "${stake_cert_file}" --out-file "${TMP_DIR}"/tx0.tmp ) if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 || return 1 + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 ${witness_cnt} || return 1 newBalance=$(( base_lovelace - min_fee - KEY_DEPOSIT )) println LOG "New balance after tx fee and key deposit is $(formatLovelace ${newBalance}) ADA ($(formatLovelace ${base_lovelace}) - $(formatLovelace ${min_fee}) - $(formatLovelace ${KEY_DEPOSIT}))" @@ -1570,6 +2279,7 @@ registerStakeWallet() { build_args=( ${tx_in} + "${script_args[@]}" --tx-out "${tx_out}" --invalid-hereafter ${ttl} --fee ${min_fee} @@ -1578,23 +2288,32 @@ registerStakeWallet() { ) if ! buildTx; then return 1; fi - + if [[ ${op_mode} = "hybrid" ]]; then if ! buildOfflineJSON "Wallet Registration"; then return 1; fi if ! offlineJSON=$(jq ". += { \"wallet-name\": \"${wallet_name}\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { txFee: \"$(( min_fee + KEY_DEPOSIT ))\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { txBody: $(jq -c . "${TMP_DIR}"/tx.raw) }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { \"signing-file\": [] }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' stake signing key\", vkey: $(jq -c . "${stake_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + if [[ ${wallet_type} -eq 5 ]]; then + if ! offlineJSON=$(jq ".\"script-file\" += [{ name: \"Wallet '${wallet_name}' payment script\", script: $(jq -c . "${payment_script_file}") }]" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ".\"script-file\" += [{ name: \"Wallet '${wallet_name}' stake script\", script: $(jq -c . "${stake_script_file}") }]" <<< ${offlineJSON}); then return 1; fi + else + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' stake signing key\", vkey: $(jq -c . "${stake_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + fi if ! offlineJSON=$(jq ". += { \"signed-txBody\": {} }" <<< ${offlineJSON}); then return 1; fi offline_tx="${TMP_DIR}/offline_tx_$(jq -r .id <<< ${offlineJSON}).json" jq -r . <<< "${offlineJSON}" > "${offline_tx}" echo - println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" - println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" - println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${payment_sk_file})${NC}" - println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${stake_sk_file})${NC}" + if [[ ${wallet_type} -eq 5 ]]; then + println "MultiSig wallet registration transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "Use CNTools [Transaction >> Sign] to witness the transaction with MultiSig wallet participants." + else + println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" + println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${payment_sk_file})${NC}" + println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${stake_sk_file})${NC}" + fi return 2 # return as failed to stop main processing and return to home menu fi @@ -1614,35 +2333,66 @@ deregisterStakeWallet() { wallet_source="base" + getWalletType ${wallet_name} + wallet_type=$? + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then utxo_cnt=${utxos_cnt[${base_addr}]} tx_in=${tx_in_arr[${base_addr}]} fi - stake_dereg_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_DEREG_FILENAME}" - println ACTION "${CCLI} ${NETWORK_ERA} stake-address deregistration-certificate --stake-verification-key-file ${stake_vk_file} --out-file ${stake_dereg_file}" - if ! stdout=$(${CCLI} ${NETWORK_ERA} stake-address deregistration-certificate --stake-verification-key-file "${stake_vk_file}" --out-file "${stake_dereg_file}" 2>&1); then - println ERROR "\n${FG_RED}ERROR${NC}: failure during stake deregistration certificate creation!\n${stdout}"; return 1 + if [[ ${wallet_type} -eq 5 ]]; then + op_mode=hybrid + unset required_total + validateMultiSigScript false "$(cat "${payment_script_file}")" + witness_cnt=${required_total} + unset required_total + validateMultiSigScript false "$(cat "${stake_script_file}")" + witness_cnt=$(( witness_cnt + required_total )) + stake_param=("--stake-script-file" "${stake_script_file}") + else + witness_cnt=2 + stake_param=("--stake-verification-key-file" "${stake_vk_file}") fi - if ! getTTL; then return 1; fi + if versionCheck "10.0" "${PROT_VERSION}"; then + stake_param+=("--key-reg-deposit-amt" ${stake_deposit}) + fi + + stake_dereg_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_DEREG_FILENAME}" + println ACTION "${CCLI} ${NETWORK_ERA} stake-address deregistration-certificate ${stake_param[*]} --out-file ${stake_dereg_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} stake-address deregistration-certificate "${stake_param[@]}" --out-file "${stake_dereg_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during stake deregistration certificate creation!\n${stdout}"; return 1 + fi + + if ! getTTL "$([[ ${wallet_type} -eq 5 ]] && echo true)"; then return 1; fi println LOG "Key Deposit is ${KEY_DEPOSIT}" getAssetsTxOut + unset script_args + if [[ ${wallet_type} -eq 5 ]]; then + script_args=( + --tx-in-script-file "${payment_script_file}" + --certificate-script-file "${stake_script_file}" + ) + fi + + tmpNewBalance=$(( base_lovelace + KEY_DEPOSIT )) build_args=( ${tx_in} - --tx-out "${base_addr}+0${assets_tx_out}" + "${script_args[@]}" + --tx-out "${base_addr}+${tmpNewBalance}${assets_tx_out}" --invalid-hereafter ${ttl} - --fee 0 + --fee ${DUMMYFEE} --certificate-file "${stake_dereg_file}" --out-file "${TMP_DIR}"/tx0.tmp ) if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 || return 1 + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 ${witness_cnt} || return 1 newBalance=$(( base_lovelace + KEY_DEPOSIT - min_fee )) println LOG "New balance after returned key deposit and subtracted tx fee is $(formatLovelace ${newBalance}) ADA ($(formatLovelace ${base_lovelace}) + $(formatLovelace ${KEY_DEPOSIT}) - $(formatLovelace ${min_fee}))" @@ -1663,6 +2413,7 @@ deregisterStakeWallet() { build_args=( ${tx_in} + "${script_args[@]}" --tx-out "${tx_out}" --invalid-hereafter ${ttl} --fee ${min_fee} @@ -1678,17 +2429,26 @@ deregisterStakeWallet() { if ! offlineJSON=$(jq ". += { \"amount-returned\": \"${KEY_DEPOSIT}\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { txFee: \"${min_fee}\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { txBody: $(jq -c . "${TMP_DIR}"/tx.raw) }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { \"signing-file\": [] }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' stake signing key\", vkey: $(jq -c . "${stake_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + if [[ ${wallet_type} -eq 5 ]]; then + if ! offlineJSON=$(jq ".\"script-file\" += [{ name: \"Wallet '${wallet_name}' payment script\", script: $(jq -c . "${payment_script_file}") }]" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ".\"script-file\" += [{ name: \"Wallet '${wallet_name}' stake script\", script: $(jq -c . "${stake_script_file}") }]" <<< ${offlineJSON}); then return 1; fi + else + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' stake signing key\", vkey: $(jq -c . "${stake_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + fi if ! offlineJSON=$(jq ". += { \"signed-txBody\": {} }" <<< ${offlineJSON}); then return 1; fi offline_tx="${TMP_DIR}/offline_tx_$(jq -r .id <<< ${offlineJSON}).json" jq -r . <<< "${offlineJSON}" > "${offline_tx}" echo - println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" - println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" - println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${payment_sk_file})${NC}" - println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${stake_sk_file})${NC}" + if [[ ${wallet_type} -eq 5 ]]; then + println "MultiSig wallet de-registration transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "Use CNTools [Transaction >> Sign] to witness the transaction with MultiSig wallet participants." + else + println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" + println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${payment_sk_file})${NC}" + println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${stake_sk_file})${NC}" + fi return 2 # return as failed to stop main processing and return to home menu fi @@ -1703,14 +2463,26 @@ deregisterStakeWallet() { # : supports fee to be payed by sender(default) or receiver by reducing amount to send sendAssets() { - [[ $(cat "${WALLET_FOLDER}/${s_wallet}/${WALLET_PAY_ADDR_FILENAME}" 2>/dev/null) = "${s_addr}" ]] && wallet_source="enterprise" || wallet_source="base" + [[ $(cat "${WALLET_FOLDER}/${s_wallet}/${WALLET_PAY_ADDR_FILENAME}" 2>/dev/null) = "${s_addr}" ]] && wallet_source="payment" || wallet_source="base" + + getWalletType ${s_wallet} + wallet_type=$? if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then utxo_cnt=${utxos_cnt[${s_addr}]} tx_in=${tx_in_arr[${s_addr}]} fi - if ! getTTL; then return 1; fi + if [[ ${wallet_type} -eq 5 ]]; then + op_mode=hybrid + unset required_total + validateMultiSigScript false "$(cat "${payment_script_file}")" + witness_cnt=${required_total} + else + witness_cnt=1 + fi + + if ! getTTL "$([[ ${wallet_type} -eq 5 ]] && echo true)"; then return 1; fi if [[ -n ${metafile} && -f ${metafile} ]]; then metafile_param="--json-metadata-no-schema --metadata-json-file ${metafile}" @@ -1731,25 +2503,32 @@ sendAssets() { [[ ${assets_to_send[${idx}]} -gt 0 ]] && assets_tx_out_d+="+${assets_to_send[${idx}]} ${idx}" done + unset script_args + if [[ ${wallet_type} -eq 5 ]]; then + script_args=( --tx-in-script-file "${payment_script_file}" ) + fi + build_args=( ${tx_in} + "${script_args[@]}" --invalid-hereafter ${ttl} - --fee 0 + --fee ${DUMMYFEE} ${metafile_param} --out-file "${TMP_DIR}"/tx0.tmp ) if [[ ${outCount} -eq 1 ]]; then - build_args+=( --tx-out "${d_addr}+0${assets_tx_out_d}" ) + build_args+=( --tx-out "${d_addr}+${assets_to_send[lovelace]}${assets_tx_out_d}" ) else - build_args+=( --tx-out "${s_addr}+0${assets_tx_out_s}" --tx-out "${d_addr}+0${assets_tx_out_d}" ) + build_args+=( --tx-out "${s_addr}+${assets_left[lovelace]}${assets_tx_out_s}" --tx-out "${d_addr}+${assets_to_send[lovelace]}${assets_tx_out_d}" ) fi if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} ${outCount} 1 || return 1 + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} ${outCount} ${witness_cnt} || return 1 build_args=( ${tx_in} + "${script_args[@]}" --invalid-hereafter ${ttl} --fee ${min_fee} ${metafile_param} @@ -1809,15 +2588,23 @@ sendAssets() { fi if ! offlineJSON=$(jq ". += { txFee: \"${min_fee}\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { txBody: $(jq -c . "${TMP_DIR}"/tx.raw) }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { \"signing-file\": [] }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${s_wallet}' payment signing key\", vkey: $(jq -c . "${s_payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + if [[ ${wallet_type} -eq 5 ]]; then + if ! offlineJSON=$(jq ".\"script-file\" += [{ name: \"Wallet '${s_wallet}' payment script\", script: $(jq -c . "${payment_script_file}") }]" <<< ${offlineJSON}); then return 1; fi + else + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${s_wallet}' payment signing key\", vkey: $(jq -c . "${s_payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + fi if ! offlineJSON=$(jq ". += { \"signed-txBody\": {} }" <<< ${offlineJSON}); then return 1; fi offline_tx="${TMP_DIR}/offline_tx_$(jq -r .id <<< ${offlineJSON}).json" jq -r . <<< "${offlineJSON}" > "${offline_tx}" echo - println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" - println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" - println DEBUG "Source Wallet ${FG_GREEN}${s_wallet} ${FG_LGRAY}$(basename ${s_payment_sk_file})${NC}" + if [[ ${wallet_type} -eq 5 ]]; then + println "MultiSig wallet transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "Use CNTools [Transaction >> Sign] to witness the transaction with MultiSig wallet participants." + else + println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" + println DEBUG "Source Wallet ${FG_GREEN}${s_wallet} ${FG_LGRAY}$(basename ${s_payment_sk_file})${NC}" + fi return 2 # return as failed to stop main processing and return to home menu fi @@ -1832,27 +2619,60 @@ delegate() { wallet_source="base" + getWalletType ${wallet_name} + wallet_type=$? + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then utxo_cnt=${utxos_cnt[${base_addr}]} tx_in=${tx_in_arr[${base_addr}]} fi - if ! getTTL; then return 1; fi + if [[ ${wallet_type} -eq 5 ]]; then + op_mode=hybrid + unset required_total + validateMultiSigScript false "$(cat "${payment_script_file}")" + witness_cnt=${required_total} + unset required_total + validateMultiSigScript false "$(cat "${stake_script_file}")" + witness_cnt=$(( witness_cnt + required_total )) + stake_param=("--stake-script-file" "${stake_script_file}") + else + witness_cnt=2 + stake_param=("--stake-verification-key-file" "${stake_vk_file}") + fi + + pool_delegcert_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_DELEGCERT_FILENAME}" + println ACTION "${CCLI} ${NETWORK_ERA} stake-address stake-delegation-certificate ${stake_param[*]} --stake-pool-id ${pool_id} --out-file ${pool_delegcert_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} stake-address stake-delegation-certificate "${stake_param[@]}" --stake-pool-id "${pool_id}" --out-file "${pool_delegcert_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during stake delegation certificate creation!\n${stdout}"; return 1 + fi + + if ! getTTL "$([[ ${wallet_type} -eq 5 ]] && echo true)"; then return 1; fi getAssetsTxOut + unset script_args + if [[ ${wallet_type} -eq 5 ]]; then + script_args=( + --tx-in-script-file "${payment_script_file}" + --certificate-script-file "${stake_script_file}" + ) + fi + + tmpNewBalance=$(( base_lovelace )) build_args=( ${tx_in} - --tx-out "${base_addr}+0${assets_tx_out}" + "${script_args[@]}" + --tx-out "${base_addr}+${tmpNewBalance}${assets_tx_out}" --invalid-hereafter ${ttl} - --fee 0 + --fee ${DUMMYFEE} --certificate-file "${pool_delegcert_file}" --out-file "${TMP_DIR}"/tx0.tmp ) if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 || return 1 + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 ${witness_cnt} || return 1 newBalance=$(( base_lovelace - min_fee )) println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${base_lovelace}) - $(formatLovelace ${min_fee}) )" @@ -1873,6 +2693,7 @@ delegate() { build_args=( ${tx_in} + "${script_args[@]}" --tx-out "${tx_out}" --invalid-hereafter ${ttl} --fee ${min_fee} @@ -1891,17 +2712,26 @@ delegate() { fi if ! offlineJSON=$(jq ". += { txFee: \"${min_fee}\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { txBody: $(jq -c . "${TMP_DIR}"/tx.raw) }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { \"signing-file\": [] }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' stake signing key\", vkey: $(jq -c . "${stake_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + if [[ ${wallet_type} -eq 5 ]]; then + if ! offlineJSON=$(jq ".\"script-file\" += [{ name: \"Wallet '${wallet_name}' payment script\", script: $(jq -c . "${payment_script_file}") }]" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ".\"script-file\" += [{ name: \"Wallet '${wallet_name}' stake script\", script: $(jq -c . "${stake_script_file}") }]" <<< ${offlineJSON}); then return 1; fi + else + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' stake signing key\", vkey: $(jq -c . "${stake_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + fi if ! offlineJSON=$(jq ". += { \"signed-txBody\": {} }" <<< ${offlineJSON}); then return 1; fi offline_tx="${TMP_DIR}/offline_tx_$(jq -r .id <<< ${offlineJSON}).json" jq -r . <<< "${offlineJSON}" > "${offline_tx}" echo - println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" - println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" - println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${payment_sk_file})${NC}" - println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${stake_sk_file})${NC}" + if [[ ${wallet_type} -eq 5 ]]; then + println "MultiSig wallet delegation transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "Use CNTools [Transaction >> Sign] to witness the transaction with MultiSig wallet participants." + else + println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" + println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${payment_sk_file})${NC}" + println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${stake_sk_file})${NC}" + fi return 2 # return as failed to stop main processing and return to home menu fi @@ -1916,26 +2746,52 @@ withdrawRewards() { wallet_source="base" + getWalletType ${wallet_name} + wallet_type=$? + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then utxo_cnt=${utxos_cnt[${base_addr}]} tx_in=${tx_in_arr[${base_addr}]} fi - if ! getTTL; then return 1; fi + if [[ ${wallet_type} -eq 5 ]]; then + op_mode=hybrid + unset required_total + validateMultiSigScript false "$(cat "${payment_script_file}")" + witness_cnt=${required_total} + unset required_total + validateMultiSigScript false "$(cat "${stake_script_file}")" + witness_cnt=$(( witness_cnt + required_total )) + else + witness_cnt=2 + fi + + if ! getTTL "$([[ ${wallet_type} -eq 5 ]] && echo true)"; then return 1; fi getAssetsTxOut - + + unset script_args + if [[ ${wallet_type} -eq 5 ]]; then + script_args=( + --tx-in-script-file "${payment_script_file}" + --withdrawal-script-file "${stake_script_file}" + ) + fi + + tmpNewBalance=$(( base_lovelace + reward_lovelace )) build_args=( ${tx_in} - --tx-out "${base_addr}+0${assets_tx_out}" + "${script_args[@]}" + --tx-out "${base_addr}+${tmpNewBalance}${assets_tx_out}" + --withdrawal ${reward_addr}+${reward_lovelace} --invalid-hereafter ${ttl} - --fee 0 + --fee ${DUMMYFEE} --out-file "${TMP_DIR}"/tx0.tmp ) if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 || return 1 + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 ${witness_cnt} || return 1 newBalance=$(( base_lovelace - min_fee + reward_lovelace )) println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${base_lovelace}) - $(formatLovelace ${min_fee}) )" @@ -1956,6 +2812,7 @@ withdrawRewards() { build_args=( ${tx_in} + "${script_args[@]}" --tx-out "${tx_out}" --withdrawal ${reward_addr}+${reward_lovelace} --invalid-hereafter ${ttl} @@ -1971,17 +2828,26 @@ withdrawRewards() { if ! offlineJSON=$(jq ". += { rewards: \"${reward_lovelace}\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { txFee: \"${min_fee}\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { txBody: $(jq -c . "${TMP_DIR}"/tx.raw) }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { \"signing-file\": [] }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' stake signing key\", vkey: $(jq -c . "${stake_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + if [[ ${wallet_type} -eq 5 ]]; then + if ! offlineJSON=$(jq ".\"script-file\" += [{ name: \"Wallet '${wallet_name}' payment script\", script: $(jq -c . "${payment_script_file}") }]" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ".\"script-file\" += [{ name: \"Wallet '${wallet_name}' stake script\", script: $(jq -c . "${stake_script_file}") }]" <<< ${offlineJSON}); then return 1; fi + else + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' stake signing key\", vkey: $(jq -c . "${stake_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + fi if ! offlineJSON=$(jq ". += { \"signed-txBody\": {} }" <<< ${offlineJSON}); then return 1; fi offline_tx="${TMP_DIR}/offline_tx_$(jq -r .id <<< ${offlineJSON}).json" jq -r . <<< "${offlineJSON}" > "${offline_tx}" echo - println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" - println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" - println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${payment_sk_file})${NC}" - println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${stake_sk_file})${NC}" + if [[ ${wallet_type} -eq 5 ]]; then + println "MultiSig wallet withdrawal transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "Use CNTools [Transaction >> Sign] to witness the transaction with MultiSig wallet participants." + else + println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" + println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${payment_sk_file})${NC}" + println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${stake_sk_file})${NC}" + fi return 2 # return as failed to stop main processing and return to home menu fi @@ -1999,14 +2865,37 @@ registerPool() { tx_in=${tx_in_arr[${base_addr}]} fi - if ! getTTL; then return 1; fi - println LOG "Pool Deposit is ${POOL_DEPOSIT}" owner_delegation_cert="" [[ ${delegate_owner_wallet} = 'Y' ]] && owner_delegation_cert="--certificate-file ${owner_delegation_cert_file}" - - witness_count=$(( 2 + ${#owner_wallets[@]} )) # owner payment + cold + multi-owners(main owner included) + + # owner payment + cold + multi-owners(main owner included) + unset witness_cnt hasScriptOwner + script_args=() + for index in "${!owner_wallets[@]}"; do + getWalletType ${owner_wallets[${index}]} + if [[ ${wallet_type} -eq 5 ]]; then + op_mode=hybrid + hasScriptOwner=true + if [[ ${index} -eq 0 ]]; then + unset required_total + validateMultiSigScript false "$(cat "${payment_script_file}")" + witness_cnt=${required_total} + script_args+=( --tx-in-script-file "${payment_script_file}" ) + fi + unset required_total + validateMultiSigScript false "$(cat "${stake_script_file}")" + witness_cnt=$(( witness_cnt + required_total )) + script_args+=( --certificate-script-file "${stake_script_file}" ) + else + [[ ${index} -eq 0 ]] && witness_cnt=1 + witness_cnt=$(( witness_cnt + 1 )) + fi + done + witness_cnt=$(( witness_cnt + 1 )) # cold key witness + + if ! getTTL "$([[ ${hasScriptOwner} = true ]] && echo true)"; then return 1; fi owner_delegation_cert="" if [[ ${delegate_owner_wallet} = 'Y' ]]; then @@ -2014,12 +2903,14 @@ registerPool() { fi getAssetsTxOut - + + tmpNewBalance=$(( base_lovelace - POOL_DEPOSIT )) build_args=( ${tx_in} - --tx-out "${base_addr}+0${assets_tx_out}" + "${script_args[@]}" + --tx-out "${base_addr}+${tmpNewBalance}${assets_tx_out}" --invalid-hereafter ${ttl} - --fee 0 + --fee ${DUMMYFEE} --certificate-file "${pool_regcert_file}" --out-file "${TMP_DIR}"/tx0.tmp ) @@ -2027,7 +2918,7 @@ registerPool() { if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 ${witness_count} || return 1 + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 ${witness_cnt} || return 1 newBalance=$(( base_lovelace - min_fee - POOL_DEPOSIT )) println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${base_lovelace}) - $(formatLovelace ${min_fee}) - $(formatLovelace ${POOL_DEPOSIT}) )" @@ -2048,6 +2939,7 @@ registerPool() { build_args=( ${tx_in} + "${script_args[@]}" --tx-out "${tx_out}" --invalid-hereafter ${ttl} --fee ${min_fee} @@ -2078,28 +2970,39 @@ registerPool() { if ! offlineJSON=$(jq ". += { \"pool-reg-cert\": $(jq -c . "${pool_regcert_file}") }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { txFee: \"$(( min_fee + POOL_DEPOSIT ))\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { txBody: $(jq -c . "${TMP_DIR}"/tx.raw) }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { \"signing-file\": [] }" <<< ${offlineJSON}); then return 1; fi for index in "${!owner_wallets[@]}"; do + getWalletType ${owner_wallets[${index}]} if [[ ${index} -eq 0 ]]; then - if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Owner #1 '${owner_wallets[0]}' payment signing key\", vkey: $(jq -c . "${owner_payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + if [[ ${wallet_type} -eq 5 ]]; then + if ! offlineJSON=$(jq ".\"script-file\" += [{ name: \"Owner #1 '${owner_wallets[0]}' payment script\", script: $(jq -c . "${payment_script_file}") }]" <<< ${offlineJSON}); then return 1; fi + else + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Owner #1 '${owner_wallets[0]}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + fi + fi + if [[ ${wallet_type} -eq 5 ]]; then + if ! offlineJSON=$(jq ".\"script-file\" += [{ name: \"Owner #$((index+1)) '${owner_wallets[${index}]}' stake script\", script: $(jq -c . "${stake_script_file}") }]" <<< ${offlineJSON}); then return 1; fi + else + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Owner #$((index+1)) '${owner_wallets[${index}]}' stake signing key\", vkey: $(jq -c . "${stake_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi fi - stake_vk_file="${WALLET_FOLDER}/${owner_wallets[${index}]}/${WALLET_STAKE_VK_FILENAME}" - if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Owner #$((index+1)) '${owner_wallets[${index}]}' stake signing key\", vkey: $(jq -c . "${stake_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi done if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Pool '${pool_name}' cold signing key\", vkey: $(jq -c . "${pool_coldkey_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { witness: [] }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { \"signed-txBody\": {} }" <<< ${offlineJSON}); then return 1; fi offline_tx="${TMP_DIR}/offline_tx_$(jq -r .id <<< ${offlineJSON}).json" jq -r . <<< "${offlineJSON}" > "${offline_tx}" echo - println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" - println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" - println DEBUG "Pool ${FG_GREEN}${pool_name} ${FG_LGRAY}${POOL_COLDKEY_SK_FILENAME}${NC}" - println DEBUG "Owner #1 ${FG_GREEN}${owner_wallets[0]} ${FG_LGRAY}${WALLET_PAY_SK_FILENAME}${NC} & ${FG_LGRAY}${WALLET_STAKE_SK_FILENAME}${NC}" - for index in "${!owner_wallets[@]}"; do - [[ ${index} -eq 0 ]] && continue # skip main owner - println DEBUG "Owner #$((index+1)) ${FG_GREEN}${owner_wallets[${index}]} ${FG_LGRAY}${WALLET_STAKE_SK_FILENAME}${NC}" - done + if [[ ${hasScriptOwner} = true ]]; then + println "Pool registration transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "Use CNTools [Transaction >> Sign] to witness the transaction with owner wallets and pool cold key." + else + println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" + println DEBUG "Pool ${FG_GREEN}${pool_name} ${FG_LGRAY}${POOL_COLDKEY_SK_FILENAME}${NC}" + println DEBUG "Owner #1 ${FG_GREEN}${owner_wallets[0]} ${FG_LGRAY}${WALLET_PAY_SK_FILENAME}${NC} & ${FG_LGRAY}${WALLET_STAKE_SK_FILENAME}${NC}" + for index in "${!owner_wallets[@]}"; do + [[ ${index} -eq 0 ]] && continue # skip main owner + println DEBUG "Owner #$((index+1)) ${FG_GREEN}${owner_wallets[${index}]} ${FG_LGRAY}${WALLET_STAKE_SK_FILENAME}${NC}" + done + fi return 2 # return as failed to stop main processing and return to home menu fi @@ -2124,25 +3027,50 @@ modifyPool() { utxo_cnt=${utxos_cnt[${base_addr}]} tx_in=${tx_in_arr[${base_addr}]} fi - - if ! getTTL; then return 1; fi - witness_count=$(( 2 + ${#owner_wallets[@]} )) # owner payment + cold + multi-owners(main owner included) + # owner payment + cold + multi-owners(main owner included) + unset witness_cnt hasScriptOwner + script_args=() + for index in "${!owner_wallets[@]}"; do + getWalletType ${owner_wallets[${index}]} + if [[ ${wallet_type} -eq 5 ]]; then + op_mode=hybrid + hasScriptOwner=true + if [[ ${index} -eq 0 ]]; then + unset required_total + validateMultiSigScript false "$(cat "${payment_script_file}")" + witness_cnt=${required_total} + script_args+=( --tx-in-script-file "${payment_script_file}" ) + fi + unset required_total + validateMultiSigScript false "$(cat "${stake_script_file}")" + witness_cnt=$(( witness_cnt + required_total )) + script_args+=( --certificate-script-file "${stake_script_file}" ) + else + [[ ${index} -eq 0 ]] && witness_cnt=1 + witness_cnt=$(( witness_cnt + 1 )) + fi + done + witness_cnt=$(( witness_cnt + 1 )) # cold key witness + + if ! getTTL "$([[ ${hasScriptOwner} = true ]] && echo true)"; then return 1; fi getAssetsTxOut - + + tmpNewBalance=$(( base_lovelace )) build_args=( ${tx_in} - --tx-out "${base_addr}+0${assets_tx_out}" + "${script_args[@]}" + --tx-out "${base_addr}+${tmpNewBalance}${assets_tx_out}" --invalid-hereafter ${ttl} - --fee 0 + --fee ${DUMMYFEE} --certificate-file "${pool_regcert_file}" --out-file "${TMP_DIR}"/tx0.tmp ) if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 ${witness_count} || return 1 + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 ${witness_cnt} || return 1 newBalance=$(( base_lovelace - min_fee )) println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${base_lovelace}) - $(formatLovelace ${min_fee}) )" @@ -2163,6 +3091,7 @@ modifyPool() { build_args=( ${tx_in} + "${script_args[@]}" --tx-out "${tx_out}" --invalid-hereafter ${ttl} --fee ${min_fee} @@ -2192,28 +3121,39 @@ modifyPool() { if ! offlineJSON=$(jq ". += { \"pool-reg-cert\": $(jq -c . "${pool_regcert_file}") }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { txFee: \"${min_fee}\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { txBody: $(jq -c . "${TMP_DIR}"/tx.raw) }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { \"signing-file\": [] }" <<< ${offlineJSON}); then return 1; fi for index in "${!owner_wallets[@]}"; do + getWalletType ${owner_wallets[${index}]} if [[ ${index} -eq 0 ]]; then - if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Owner #1 '${owner_wallets[0]}' payment signing key\", vkey: $(jq -c . "${owner_payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + if [[ ${wallet_type} -eq 5 ]]; then + if ! offlineJSON=$(jq ".\"script-file\" += [{ name: \"Owner #1 '${owner_wallets[0]}' payment script\", script: $(jq -c . "${payment_script_file}") }]" <<< ${offlineJSON}); then return 1; fi + else + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Owner #1 '${owner_wallets[0]}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + fi + fi + if [[ ${wallet_type} -eq 5 ]]; then + if ! offlineJSON=$(jq ".\"script-file\" += [{ name: \"Owner #$((index+1)) '${owner_wallets[${index}]}' stake script\", script: $(jq -c . "${stake_script_file}") }]" <<< ${offlineJSON}); then return 1; fi + else + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Owner #$((index+1)) '${owner_wallets[${index}]}' stake signing key\", vkey: $(jq -c . "${stake_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi fi - stake_vk_file="${WALLET_FOLDER}/${owner_wallets[${index}]}/${WALLET_STAKE_VK_FILENAME}" - if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Owner #$((index+1)) '${owner_wallets[${index}]}' stake signing key\", vkey: $(jq -c . "${stake_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi done if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Pool '${pool_name}' cold signing key\", vkey: $(jq -c . "${pool_coldkey_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { witness: [] }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { \"signed-txBody\": {} }" <<< ${offlineJSON}); then return 1; fi offline_tx="${TMP_DIR}/offline_tx_$(jq -r .id <<< ${offlineJSON}).json" jq -r . <<< "${offlineJSON}" > "${offline_tx}" echo - println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" - println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" - println DEBUG "Pool ${FG_GREEN}${pool_name} ${FG_LGRAY}${POOL_COLDKEY_SK_FILENAME}${NC}" - println DEBUG "Owner #1 ${FG_GREEN}${owner_wallets[0]} ${FG_LGRAY}${WALLET_PAY_SK_FILENAME}${NC} & ${FG_LGRAY}${WALLET_STAKE_SK_FILENAME}${NC}" - for index in "${!owner_wallets[@]}"; do - [[ ${index} -eq 0 ]] && continue # skip main owner - println DEBUG "Owner #$((index+1)) ${FG_GREEN}${owner_wallets[${index}]} ${FG_LGRAY}${WALLET_STAKE_SK_FILENAME}${NC}" - done + if [[ ${hasScriptOwner} = true ]]; then + println "Pool update transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "Use CNTools [Transaction >> Sign] to witness the transaction with owner wallets and pool cold key." + else + println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" + println DEBUG "Pool ${FG_GREEN}${pool_name} ${FG_LGRAY}${POOL_COLDKEY_SK_FILENAME}${NC}" + println DEBUG "Owner #1 ${FG_GREEN}${owner_wallets[0]} ${FG_LGRAY}${WALLET_PAY_SK_FILENAME}${NC} & ${FG_LGRAY}${WALLET_STAKE_SK_FILENAME}${NC}" + for index in "${!owner_wallets[@]}"; do + [[ ${index} -eq 0 ]] && continue # skip main owner + println DEBUG "Owner #$((index+1)) ${FG_GREEN}${owner_wallets[${index}]} ${FG_LGRAY}${WALLET_STAKE_SK_FILENAME}${NC}" + done + fi return 2 # return as failed to stop main processing and return to home menu fi @@ -2233,12 +3173,24 @@ modifyPool() { # Description : Retire pool deRegisterPool() { - [[ $(cat "${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_ADDR_FILENAME}" 2>/dev/null) = "${addr}" ]] && wallet_source="enterprise" || wallet_source="base" + [[ $(cat "${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_ADDR_FILENAME}" 2>/dev/null) = "${addr}" ]] && wallet_source="payment" || wallet_source="base" - if ! getTTL; then return 1; fi + getWalletType ${wallet_name} + wallet_type=$? + + if [[ ${wallet_type} -eq 5 ]]; then + op_mode=hybrid + unset required_total + validateMultiSigScript false "$(cat "${payment_script_file}")" + witness_cnt=$(( required_total + 1 )) + else + witness_cnt=2 + fi + + if ! getTTL "$([[ ${wallet_type} -eq 5 ]] && echo true)"; then return 1; fi if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then - for key in ${!assets[@]}; do + for key in "${!assets[@]}"; do [[ ${key} != "${addr},"* ]] && unset 'assets[$key]' done utxo_cnt=${utxos_cnt[${addr}]} @@ -2249,18 +3201,25 @@ deRegisterPool() { getAssetsTxOut + unset script_args + if [[ ${wallet_type} -eq 5 ]]; then + script_args=( --tx-in-script-file "${payment_script_file}" ) + fi + + tmpNewBalance=$(( lovelace )) build_args=( ${tx_in} - --tx-out "${addr}+0${assets_tx_out}" + "${script_args[@]}" + --tx-out "${addr}+${tmpNewBalance}${assets_tx_out}" --invalid-hereafter ${ttl} - --fee 0 + --fee ${DUMMYFEE} --certificate-file "${pool_deregcert_file}" --out-file "${TMP_DIR}"/tx0.tmp ) if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 || return 1 + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 ${witness_cnt} || return 1 newBalance=$(( lovelace - min_fee )) println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${lovelace}) - $(formatLovelace ${min_fee}) )" @@ -2281,6 +3240,7 @@ deRegisterPool() { build_args=( ${tx_in} + "${script_args[@]}" --tx-out "${tx_out}" --invalid-hereafter ${ttl} --fee ${min_fee} @@ -2311,26 +3271,31 @@ deRegisterPool() { if ! offlineJSON=$(jq ". += { \"retire-epoch\": \"${epoch_enter}\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { txFee: \"${min_fee}\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { txBody: $(jq -c . "${TMP_DIR}"/tx.raw) }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { \"signing-file\": [] }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + if [[ ${wallet_type} -eq 5 ]]; then + if ! offlineJSON=$(jq ".\"script-file\" += [{ name: \"Wallet '${wallet_name}' payment script\", script: $(jq -c . "${payment_script_file}") }]" <<< ${offlineJSON}); then return 1; fi + else + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + fi if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Pool '${pool_name}' cold signing key\", vkey: $(jq -c . "${pool_coldkey_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { \"signed-txBody\": {} }" <<< ${offlineJSON}); then return 1; fi offline_tx="${TMP_DIR}/offline_tx_$(jq -r .id <<< ${offlineJSON}).json" jq -r . <<< "${offlineJSON}" > "${offline_tx}" echo - println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" - println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" - println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${payment_sk_file})${NC}" - println DEBUG "Pool ${FG_GREEN}${pool_name} ${FG_LGRAY}$(basename ${pool_coldkey_sk_file})${NC}" + if [[ ${wallet_type} -eq 5 ]]; then + println "Pool de-registration transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "Use CNTools [Transaction >> Sign] to witness the transaction with MultiSig wallet participants." + else + println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" + println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${payment_sk_file})${NC}" + println DEBUG "Pool ${FG_GREEN}${pool_name} ${FG_LGRAY}$(basename ${pool_coldkey_sk_file})${NC}" + fi return 2 # return as failed to stop main processing and return to home menu fi - getWalletType ${wallet_name} - if ! witnessTx "${TMP_DIR}/tx.raw" "${payment_sk_file}" "${pool_coldkey_sk_file}"; then return 1; fi if ! assembleTx "${TMP_DIR}/tx.raw"; then return 1; fi if ! submitTx "${tx_signed}"; then return 1; fi - } # Command : rotatePoolKeys @@ -2434,7 +3399,10 @@ rotatePoolKeys() { # Description : post metadata file on chain using specified wallet to pay for the transaction fee sendMetadata() { - [[ $(cat "${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_ADDR_FILENAME}" 2>/dev/null) = "${addr}" ]] && wallet_source="enterprise" || wallet_source="base" + [[ $(cat "${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_ADDR_FILENAME}" 2>/dev/null) = "${addr}" ]] && wallet_source="payment" || wallet_source="base" + + getWalletType ${wallet_name} + wallet_type=$? if [[ ${metatype} = "no-schema" ]]; then metafile_param="--json-metadata-no-schema --metadata-json-file ${metafile}" @@ -2447,10 +3415,19 @@ sendMetadata() { return 1 fi - if ! getTTL; then return 1; fi + if [[ ${wallet_type} -eq 5 ]]; then + op_mode=hybrid + unset required_total + validateMultiSigScript false "$(cat "${payment_script_file}")" + witness_cnt=${required_total} + else + witness_cnt=1 + fi + + if ! getTTL "$([[ ${wallet_type} -eq 5 ]] && echo true)"; then return 1; fi if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then - for key in ${!assets[@]}; do + for key in "${!assets[@]}"; do [[ ${key} != "${addr},"* ]] && unset 'assets[$key]' done utxo_cnt=${utxos_cnt[${addr}]} @@ -2461,18 +3438,25 @@ sendMetadata() { getAssetsTxOut + unset script_args + if [[ ${wallet_type} -eq 5 ]]; then + script_args=( --tx-in-script-file "${payment_script_file}" ) + fi + + tmpNewBalance=$(( lovelace )) build_args=( ${tx_in} - --tx-out "${addr}+0${assets_tx_out}" + "${script_args[@]}" + --tx-out "${addr}+${tmpNewBalance}${assets_tx_out}" --invalid-hereafter ${ttl} - --fee 0 + --fee ${DUMMYFEE} ${metafile_param} --out-file "${TMP_DIR}"/tx0.tmp ) if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 1 || return 1 + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 ${witness_cnt} || return 1 newBalance=$(( lovelace - min_fee )) println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${lovelace}) - $(formatLovelace ${min_fee}) )" @@ -2493,6 +3477,7 @@ sendMetadata() { build_args=( ${tx_in} + "${script_args[@]}" --tx-out "${tx_out}" ${metafile_param} --invalid-hereafter ${ttl} @@ -2508,15 +3493,23 @@ sendMetadata() { if ! offlineJSON=$(jq ". += { metadata: $(jq -c . "${metafile}") }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { txFee: \"${min_fee}\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { txBody: $(jq -c . "${TMP_DIR}"/tx.raw) }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { \"signing-file\": [] }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + if [[ ${wallet_type} -eq 5 ]]; then + if ! offlineJSON=$(jq ".\"script-file\" += [{ name: \"Wallet '${wallet_name}' payment script\", script: $(jq -c . "${payment_script_file}") }]" <<< ${offlineJSON}); then return 1; fi + else + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + fi if ! offlineJSON=$(jq ". += { \"signed-txBody\": {} }" <<< ${offlineJSON}); then return 1; fi offline_tx="${TMP_DIR}/offline_tx_$(jq -r .id <<< ${offlineJSON}).json" jq -r . <<< "${offlineJSON}" > "${offline_tx}" echo - println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" - println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" - println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${payment_sk_file})${NC}" + if [[ ${wallet_type} -eq 5 ]]; then + println "Metadata transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "Use CNTools [Transaction >> Sign] to witness the transaction with MultiSig wallet participants." + else + println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" + println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${payment_sk_file})${NC}" + fi return 2 # return as failed to stop main processing and return to home menu fi @@ -2530,10 +3523,13 @@ sendMetadata() { # Description : mint a custom asset using specified wallet to pay for the transaction fee mintAsset() { - [[ $(cat "${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_ADDR_FILENAME}" 2>/dev/null) = "${addr}" ]] && wallet_source="enterprise" || wallet_source="base" + [[ $(cat "${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_ADDR_FILENAME}" 2>/dev/null) = "${addr}" ]] && wallet_source="payment" || wallet_source="base" + + getWalletType ${wallet_name} + wallet_type=$? if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then - for key in ${!assets[@]}; do + for key in "${!assets[@]}"; do [[ ${key} != "${addr},"* ]] && unset 'assets[$key]' done index_prefix="${addr}," @@ -2544,8 +3540,17 @@ mintAsset() { unset index_prefix fi + if [[ ${wallet_type} -eq 5 ]]; then + op_mode=hybrid + unset required_total + validateMultiSigScript false "$(cat "${payment_script_file}")" + witness_cnt=$(( required_total + 1 )) + else + witness_cnt=2 + fi + if [[ ${policy_ttl} -eq 0 ]]; then - if ! getTTL; then return 1; fi + if ! getTTL "$([[ ${wallet_type} -eq 5 ]] && echo true)"; then return 1; fi else ttl=${policy_ttl} tip_ref=$(getSlotTipRef) @@ -2555,20 +3560,27 @@ mintAsset() { [[ -z ${asset_name} ]] && asset_name_out="" || asset_name_out=".$(asciiToHex "${asset_name}")" getAssetsTxOut "${index_prefix}${policy_id}${asset_name_out}" "${assets_to_mint}" + unset script_args + if [[ ${wallet_type} -eq 5 ]]; then + script_args=( --tx-in-script-file "${payment_script_file}" ) + fi + + tmpNewBalance=$(( lovelace )) build_args=( ${tx_in} - --tx-out "${addr}+0${assets_tx_out}" + "${script_args[@]}" + --tx-out "${addr}+${tmpNewBalance}${assets_tx_out}" --mint "${assets_to_mint} ${policy_id}${asset_name_out}" --mint-script-file "${policy_script_file}" ${metafile_param} --invalid-hereafter ${ttl} - --fee 0 + --fee ${DUMMYFEE} --out-file "${TMP_DIR}"/tx0.tmp ) if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 || return 1 + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 ${witness_cnt} || return 1 newBalance=$(( lovelace - min_fee )) println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${lovelace}) - $(formatLovelace ${min_fee}) )" @@ -2589,6 +3601,7 @@ mintAsset() { build_args=( ${tx_in} + "${script_args[@]}" --tx-out "${tx_out}" --mint "${assets_to_mint} ${policy_id}${asset_name_out}" --mint-script-file "${policy_script_file}" @@ -2613,17 +3626,25 @@ mintAsset() { fi if ! offlineJSON=$(jq ". += { txFee: \"${min_fee}\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { txBody: $(jq -c . "${TMP_DIR}"/tx.raw) }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { \"signing-file\": [] }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + if [[ ${wallet_type} -eq 5 ]]; then + if ! offlineJSON=$(jq ".\"script-file\" += [{ name: \"Wallet '${wallet_name}' payment script\", script: $(jq -c . "${payment_script_file}") }]" <<< ${offlineJSON}); then return 1; fi + else + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + fi if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Asset '${policy_sk_file}' policy signing key\", vkey: $(jq -c . "${policy_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { \"signed-txBody\": {} }" <<< ${offlineJSON}); then return 1; fi offline_tx="${TMP_DIR}/offline_tx_$(jq -r .id <<< ${offlineJSON}).json" jq -r . <<< "${offlineJSON}" > "${offline_tx}" echo - println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" - println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" - println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${payment_sk_file})${NC}" - println DEBUG "Policy ${FG_GREEN}${policy_name} ${FG_LGRAY}$(basename ${policy_sk_file})${NC}" + if [[ ${wallet_type} -eq 5 ]]; then + println "Asset mint transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "Use CNTools [Transaction >> Sign] to witness the transaction with MultiSig wallet participants." + else + println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" + println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${payment_sk_file})${NC}" + println DEBUG "Policy ${FG_GREEN}${policy_name} ${FG_LGRAY}$(basename ${policy_sk_file})${NC}" + fi return 2 # return as failed to stop main processing and return to home menu fi @@ -2637,8 +3658,11 @@ mintAsset() { # Description : burn custom assets on specified wallet burnAsset() { + getWalletType ${wallet_name} + wallet_type=$? + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then - for key in ${!assets[@]}; do + for key in "${!assets[@]}"; do [[ ${key} != "${addr},"* ]] && unset 'assets[$key]' done index_prefix="${addr}," @@ -2649,8 +3673,17 @@ burnAsset() { unset index_prefix fi + if [[ ${wallet_type} -eq 5 ]]; then + op_mode=hybrid + unset required_total + validateMultiSigScript false "$(cat "${payment_script_file}")" + witness_cnt=$(( required_total + 1 )) + else + witness_cnt=2 + fi + if [[ ${policy_ttl} -eq 0 ]]; then - if ! getTTL; then return 1; fi + if ! getTTL "$([[ ${wallet_type} -eq 5 ]] && echo true)"; then return 1; fi else ttl=${policy_ttl} tip_ref=$(getSlotTipRef) @@ -2660,20 +3693,27 @@ burnAsset() { [[ -z ${asset_name} ]] && asset_name_out="" || asset_name_out=".${asset_name}" getAssetsTxOut "${index_prefix}${policy_id}${asset_name_out}" "-${assets_to_burn}" + unset script_args + if [[ ${wallet_type} -eq 5 ]]; then + script_args=( --tx-in-script-file "${payment_script_file}" ) + fi + + tmpNewBalance=$(( lovelace )) build_args=( ${tx_in} - --tx-out "${addr}+0${assets_tx_out}" + "${script_args[@]}" + --tx-out "${addr}+${tmpNewBalance}${assets_tx_out}" --mint "-${assets_to_burn} ${policy_id}${asset_name_out}" --mint-script-file "${policy_script_file}" ${metafile_param} --invalid-hereafter ${ttl} - --fee 0 + --fee ${DUMMYFEE} --out-file "${TMP_DIR}"/tx0.tmp ) if ! buildTx; then return 1; fi - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 || return 1 + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 ${witness_cnt} || return 1 newBalance=$(( lovelace - min_fee )) println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${lovelace}) - $(formatLovelace ${min_fee}) )" @@ -2694,6 +3734,7 @@ burnAsset() { build_args=( ${tx_in} + "${script_args[@]}" --tx-out "${tx_out}" --mint "-${assets_to_burn} ${policy_id}${asset_name_out}" --mint-script-file "${policy_script_file}" @@ -2718,17 +3759,25 @@ burnAsset() { fi if ! offlineJSON=$(jq ". += { txFee: \"${min_fee}\" }" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { txBody: $(jq -c . "${TMP_DIR}"/tx.raw) }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { \"signing-file\": [] }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + if [[ ${wallet_type} -eq 5 ]]; then + if ! offlineJSON=$(jq ".\"script-file\" += [{ name: \"Wallet '${wallet_name}' payment script\", script: $(jq -c . "${payment_script_file}") }]" <<< ${offlineJSON}); then return 1; fi + else + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + fi if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Asset '${policy_sk_file}' policy signing key\", vkey: $(jq -c . "${policy_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi if ! offlineJSON=$(jq ". += { \"signed-txBody\": {} }" <<< ${offlineJSON}); then return 1; fi offline_tx="${TMP_DIR}/offline_tx_$(jq -r .id <<< ${offlineJSON}).json" jq -r . <<< "${offlineJSON}" > "${offline_tx}" echo - println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" - println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" - println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${payment_sk_file})${NC}" - println DEBUG "Policy ${FG_GREEN}${policy_name} ${FG_LGRAY}$(basename ${policy_sk_file})${NC}" + if [[ ${wallet_type} -eq 5 ]]; then + println "Asset burn transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "Use CNTools [Transaction >> Sign] to witness the transaction with MultiSig wallet participants." + else + println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" + println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${payment_sk_file})${NC}" + println DEBUG "Policy ${FG_GREEN}${policy_name} ${FG_LGRAY}$(basename ${policy_sk_file})${NC}" + fi return 2 # return as failed to stop main processing and return to home menu fi @@ -2737,105 +3786,597 @@ burnAsset() { if ! submitTx "${tx_signed}"; then return 1; fi } +# Command : voteDelegation +# Description : delegate wallet to a DRep for governance actions +voteDelegation() { -# Command : buildTx [build_args] -# Description : Helper function to build a raw transaction -# : populate an array variable called 'build_args' with all data -# Parameters : build_args > an array with all the arguments to assemble the transaction -buildTx() { - println ACTION "${CCLI} ${NETWORK_ERA} transaction build-raw ${build_args[*]}" - if ! stdout=$(${CCLI} ${NETWORK_ERA} transaction build-raw "${build_args[@]}" 2>&1); then - println ERROR "\n${FG_RED}ERROR${NC}: failure during transaction building!\n${stdout}"; return 1 + wallet_source="base" + + getWalletType ${wallet_name} + wallet_type=$? + + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + utxo_cnt=${utxos_cnt[${base_addr}]} + tx_in=${tx_in_arr[${base_addr}]} fi -} -# Command : calcMinFee [rax tx file] -# Description : Helper function to calculate minimum fee from a build transaction -# Parameters : $1 = raw tx file > the transaction body file to use for calculating fee -# $2 = Count of transaction inputs (spent txo count) -# $3 = Count of transaction outputs -# $4 = Count of witnesses required to sign the transaction -calcMinFee() { - min_fee_args=( - ${NETWORK_ERA} - transaction calculate-min-fee - --tx-body-file "$1" - --tx-in-count $2 - --tx-out-count $3 - ${NETWORK_IDENTIFIER} - --witness-count $4 - --byron-witness-count 0 - --protocol-params-file "${TMP_DIR}"/protparams.json + if [[ ${wallet_type} -eq 5 ]]; then + op_mode=hybrid + unset required_total + validateMultiSigScript false "$(cat "${payment_script_file}")" + witness_cnt=${required_total} + unset required_total + validateMultiSigScript false "$(cat "${stake_script_file}")" + witness_cnt=$(( witness_cnt + required_total )) + stake_param=("--stake-script-file" "${stake_script_file}") + else + witness_cnt=2 + stake_param=("--stake-verification-key-file" "${stake_vk_file}") + fi + + vote_deleg_cert_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_VOTE_DELEG_CERT_FILENAME}" + VOTE_DELEG_CMD=( + ${CCLI} ${NETWORK_ERA} stake-address vote-delegation-certificate + "${stake_param[@]}" + "${vote_param[@]}" + --out-file "${vote_deleg_cert_file}" ) - println ACTION "${CCLI} ${min_fee_args[*]}" - if ! stdout=$(${CCLI} "${min_fee_args[@]}" 2>&1); then - println ERROR "\n${FG_RED}ERROR${NC}: failure during minimum fee calculation!\n${stdout}"; return 1 + println ACTION "${VOTE_DELEG_CMD[*]}" + if ! stdout=$("${VOTE_DELEG_CMD[@]}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during vote delegation certificate creation!\n${stdout}"; return 1 fi - min_fee=$([[ ${stdout} =~ ([0-9]+) ]] && echo ${BASH_REMATCH[1]}) - println LOG "fee is $(formatLovelace ${min_fee}) ADA" -} -# Command : witnessTx [raw tx file] [signing keys ...] -# Description : Helper function to witness a raw transaction -# Parameters : raw tx file > the transaction file to sign -# : signing keys > list of signing keys to use when witnessing the transaction -witnessTx() { - tx_raw="$1" - shift - tx_witness_files=() - unset isHW - for skey in "$@"; do - [[ -z ${skey//[[:blank:]]/} ]] && continue - if [[ ! -f "${skey}" ]]; then - println ERROR "\n${FG_RED}ERROR${NC}: file not found: ${skey}" - return 1 - elif [[ $(jq -r '.description' "${skey}") = *"Hardware"* ]]; then # HW signing key - if [[ ${isHW} = 'Y' ]]; then - # just add key and output to witness_command() - tx_witness="$(mktemp "${TMP_DIR}/tx.witness_XXXXXXXXXX")" - hw_witness_command+=( - --hw-signing-file "${skey}" - --change-output-key-file "${skey}" - --out-file "${tx_witness}" - ) - else - isHW=Y - tx_witness="$(mktemp "${TMP_DIR}/tx.witness_XXXXXXXXXX")" - hw_witness_command=( - cardano-hw-cli transaction witness - --tx-file "${tx_raw}" - --hw-signing-file "${skey}" - --change-output-key-file "${skey}" - --out-file "${tx_witness}" - ${NETWORK_IDENTIFIER} - ) - fi - else - tx_witness="$(mktemp "${TMP_DIR}/tx.witness_XXXXXXXXXX")" - witness_command=( - ${CCLI} transaction witness - --tx-body-file "${tx_raw}" - --signing-key-file "${skey}" - ${NETWORK_IDENTIFIER} - --out-file "${tx_witness}" - ) - println ACTION "${witness_command[@]}" - if ! stdout=$("${witness_command[@]}" 2>&1); then println ERROR "\n${FG_RED}ERROR${NC}: during transaction signing !!\n${stdout}" && return 1; fi - fi - tx_witness_files+=( "${tx_witness}" ) - done + if ! getTTL "$([[ ${wallet_type} -eq 5 ]] && echo true)"; then return 1; fi - # Special case for HW - if [[ ${isHW} = 'Y' ]]; then - if ! unlockHWDevice "witness the transaction"; then return 1; fi - println ACTION "${hw_witness_command[@]}" - if ! stdout=$("${hw_witness_command[@]}" 2>&1); then println ERROR "\n${FG_RED}ERROR${NC}: during hardware wallet signing !!\n${stdout}" && return 1; fi + getAssetsTxOut + + unset script_args + if [[ ${wallet_type} -eq 5 ]]; then + script_args=( + --tx-in-script-file "${payment_script_file}" + --certificate-script-file "${stake_script_file}" + ) fi -} -# Command : assembleTx [raw tx file] -# Description : Helper function to witnessTx for assembling a signed tx using witnesses from tx_witness_files[] array -assembleTx() { + tmpNewBalance=$(( base_lovelace )) + build_args=( + ${tx_in} + "${script_args[@]}" + --tx-out "${base_addr}+${tmpNewBalance}${assets_tx_out}" + --invalid-hereafter ${ttl} + --fee ${DUMMYFEE} + --certificate-file "${vote_deleg_cert_file}" + --out-file "${TMP_DIR}"/tx0.tmp + ) + + if ! buildTx; then return 1; fi + + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 ${witness_cnt} || return 1 + + newBalance=$(( base_lovelace - min_fee )) + println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${base_lovelace}) - $(formatLovelace ${min_fee}) )" + + if [[ ${base_lovelace} -lt ${min_fee} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: Not enough ADA in base address for tx fee!"\ + "Funds in address: ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA"\ + "Minimum required: ${FG_LBLUE}$(formatLovelace ${min_fee})${NC} ADA" + return 1 + fi + + tx_out="${base_addr}+${newBalance}${assets_tx_out}" + getMinUTxO "${tx_out}" || return 1 + if [[ ${newBalance} -lt ${min_utxo_out} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: minimum UTxO value not fulfilled, only ${FG_LBLUE}$(formatLovelace ${newBalance})${NC} ADA left in address after tx fee, at least ${FG_LBLUE}$(formatLovelace ${min_utxo_out})${NC} ADA required!" + return 1 + fi + + build_args=( + ${tx_in} + "${script_args[@]}" + --tx-out "${tx_out}" + --invalid-hereafter ${ttl} + --fee ${min_fee} + --certificate-file "${vote_deleg_cert_file}" + --out-file "${TMP_DIR}"/tx.raw + ) + + if ! buildTx; then return 1; fi + + if [[ ${op_mode} = "hybrid" ]]; then + if ! buildOfflineJSON "Wallet Vote Delegation"; then return 1; fi + if ! offlineJSON=$(jq ". += { \"wallet-name\": \"${wallet_name}\" }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { \"drep-id\": \"${drep_id}\" }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { txFee: \"${min_fee}\" }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { txBody: $(jq -c . "${TMP_DIR}"/tx.raw) }" <<< ${offlineJSON}); then return 1; fi + if [[ ${wallet_type} -eq 5 ]]; then + if ! offlineJSON=$(jq ".\"script-file\" += [{ name: \"Wallet '${wallet_name}' payment script\", script: $(jq -c . "${payment_script_file}") }]" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ".\"script-file\" += [{ name: \"Wallet '${wallet_name}' stake script\", script: $(jq -c . "${stake_script_file}") }]" <<< ${offlineJSON}); then return 1; fi + else + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' stake signing key\", vkey: $(jq -c . "${stake_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + fi + if ! offlineJSON=$(jq ". += { \"signed-txBody\": {} }" <<< ${offlineJSON}); then return 1; fi + offline_tx="${TMP_DIR}/offline_tx_$(jq -r .id <<< ${offlineJSON}).json" + jq -r . <<< "${offlineJSON}" > "${offline_tx}" + echo + if [[ ${wallet_type} -eq 5 ]]; then + println "MultiSig wallet vote delegation transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "Use CNTools [Transaction >> Sign] to witness the transaction with MultiSig wallet participants." + else + println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" + println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${payment_sk_file})${NC}" + println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${stake_sk_file})${NC}" + fi + return 2 # return as failed to stop main processing and return to home menu + fi + + if ! witnessTx "${TMP_DIR}/tx.raw" "${stake_sk_file}" "${payment_sk_file}"; then return 1; fi + if ! assembleTx "${TMP_DIR}/tx.raw"; then return 1; fi + if ! submitTx "${tx_signed}"; then return 1; fi +} + +# Command : registerDRep +# Description : register as a DRep +registerDRep() { + + getWalletType ${wallet_name} + wallet_type=$? + wallet_source="base" + + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + utxo_cnt=${utxos_cnt[${base_addr}]} + tx_in=${tx_in_arr[${base_addr}]} + fi + + if [[ ${hash_type} = "scriptHash" ]]; then + op_mode=hybrid + unset required_total + validateMultiSigScript false "$(cat "${drep_script_file}")" + witness_cnt=$(( required_total + 1 )) + else + witness_cnt=2 + fi + + if ! getTTL; then return 1; fi + + [[ -z ${is_update} ]] && println LOG "DRep deposit is ${FG_LBLUE}$(formatLovelace ${DREP_DEPOSIT})${NC}" + + getAssetsTxOut + + unset script_args + if [[ ${hash_type} = "scriptHash" ]]; then + script_args=( + --certificate-script-file "${drep_script_file}" + ) + fi + + tmpNewBalance=$(( base_lovelace - DREP_DEPOSIT )) + build_args=( + ${tx_in} + --tx-out "${base_addr}+${tmpNewBalance}${assets_tx_out}" + --invalid-hereafter ${ttl} + --fee ${DUMMYFEE} + --certificate-file "${drep_cert_file}" + "${script_args[@]}" + --out-file "${TMP_DIR}"/tx0.tmp + ) + + if ! buildTx; then return 1; fi + + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 ${witness_cnt} || return 1 + + if [[ -z ${is_update} ]]; then + fee=$(( DREP_DEPOSIT + min_fee )) + newBalance=$(( base_lovelace - fee )) + println LOG "New balance after DRep deposit and subtracted tx fee is $(formatLovelace ${newBalance}) ADA ($(formatLovelace ${base_lovelace}) - $(formatLovelace ${DREP_DEPOSIT}) - $(formatLovelace ${min_fee}))" + else + fee=${min_fee} + newBalance=$(( base_lovelace - min_fee )) + println LOG "New balance after subtracted tx fee is $(formatLovelace ${newBalance}) ADA ($(formatLovelace ${base_lovelace}) - $(formatLovelace ${min_fee}))" + fi + + if [[ ${base_lovelace} -lt ${fee} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: Not enough ADA in base address!"\ + "Funds in address: ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA"\ + "Minimum required: ${FG_LBLUE}$(formatLovelace ${fee})${NC} ADA" + return 1 + fi + + tx_out="${base_addr}+${newBalance}${assets_tx_out}" + getMinUTxO "${tx_out}" || return 1 + if [[ ${newBalance} -lt ${min_utxo_out} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: minimum UTxO value not fulfilled, only ${FG_LBLUE}$(formatLovelace ${newBalance})${NC} ADA left in address after tx fee and DRep deposit, at least ${FG_LBLUE}$(formatLovelace ${min_utxo_out})${NC} ADA required!" + return 1 + fi + + build_args=( + ${tx_in} + --tx-out "${tx_out}" + --invalid-hereafter ${ttl} + --fee ${min_fee} + --certificate-file "${drep_cert_file}" + "${script_args[@]}" + --out-file "${TMP_DIR}"/tx.raw + ) + + if ! buildTx; then return 1; fi + + if [[ ${op_mode} = "hybrid" ]]; then + if ! buildOfflineJSON "Wallet DRep Registration"; then return 1; fi + if ! offlineJSON=$(jq ". += { \"wallet-name\": \"${wallet_name}\" }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { \"drep-wallet-name\": \"${drep_wallet_name}\" }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { txFee: \"${min_fee}\" }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { txBody: $(jq -c . "${TMP_DIR}"/tx.raw) }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + if [[ ${hash_type} = "scriptHash" ]]; then + if ! offlineJSON=$(jq ".\"script-file\" += [{ name: \"Wallet '${drep_wallet_name}' DRep script\", script: $(jq -c . "${drep_script_file}") }]" <<< ${offlineJSON}); then return 1; fi + else + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${drep_wallet_name}' DRep signing key\", vkey: $(jq -c . "${drep_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + fi + if ! offlineJSON=$(jq ". += { \"signed-txBody\": {} }" <<< ${offlineJSON}); then return 1; fi + offline_tx="${TMP_DIR}/offline_tx_$(jq -r .id <<< ${offlineJSON}).json" + jq -r . <<< "${offlineJSON}" > "${offline_tx}" + echo + if [[ ${hash_type} = "scriptHash" ]]; then + println "MultiSig DRep registration transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "Use CNTools [Transaction >> Sign] to witness the transaction with fee wallet and MultiSig DRep participants." + else + println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" + println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${payment_sk_file})${NC}" + println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${drep_sk_file})${NC}" + fi + return 2 # return as failed to stop main processing and return to home menu + fi + + if ! witnessTx "${TMP_DIR}/tx.raw" "${drep_sk_file}" "${payment_sk_file}"; then return 1; fi + if ! assembleTx "${TMP_DIR}/tx.raw"; then return 1; fi + if ! submitTx "${tx_signed}"; then return 1; fi +} + +# Command : retireDRep +# Description : retire as a DRep and get DRep deposit back +retireDRep() { + + getWalletType ${wallet_name} + wallet_type=$? + wallet_source="base" + + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + utxo_cnt=${utxos_cnt[${base_addr}]} + tx_in=${tx_in_arr[${base_addr}]} + fi + + if [[ ${hash_type} = "scriptHash" ]]; then + op_mode=hybrid + unset required_total + validateMultiSigScript false "$(cat "${drep_script_file}")" + witness_cnt=$(( required_total + 1 )) + else + witness_cnt=2 + fi + + if ! getTTL; then return 1; fi + + println LOG "DRep deposit to be returned is ${FG_LBLUE}$(formatLovelace ${drep_deposit_amt})${NC}" + + getAssetsTxOut + + unset script_args + if [[ ${hash_type} = "scriptHash" ]]; then + script_args=( + --certificate-script-file "${drep_script_file}" + ) + fi + + tmpNewBalance=$(( base_lovelace + drep_deposit_amt )) + build_args=( + ${tx_in} + --tx-out "${base_addr}+${tmpNewBalance}${assets_tx_out}" + --invalid-hereafter ${ttl} + --fee ${DUMMYFEE} + --certificate-file "${drep_cert_file}" + "${script_args[@]}" + --out-file "${TMP_DIR}"/tx0.tmp + ) + + if ! buildTx; then return 1; fi + + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 ${witness_cnt} || return 1 + + newBalance=$(( base_lovelace + drep_deposit_amt - min_fee )) + println LOG "New balance after returned DRep deposit and subtracted tx fee is $(formatLovelace ${newBalance}) ADA ($(formatLovelace ${base_lovelace}) + $(formatLovelace ${drep_deposit_amt}) - $(formatLovelace ${min_fee}))" + + if [[ $(( base_lovelace + drep_deposit_amt )) -lt ${min_fee} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: Not enough ADA in base address for tx fee!"\ + "Funds in address: ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA"\ + "Returned deposit: ${FG_LBLUE}$(formatLovelace ${drep_deposit_amt})${NC} ADA"\ + "Minimum required: ${FG_LBLUE}$(formatLovelace ${min_fee} )))${NC} ADA" + return 1 + fi + + tx_out="${base_addr}+${newBalance}${assets_tx_out}" + getMinUTxO "${tx_out}" || return 1 + if [[ ${newBalance} -lt ${min_utxo_out} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: minimum UTxO value not fulfilled, only ${FG_LBLUE}$(formatLovelace ${newBalance})${NC} ADA left in address after tx fee and DRep deposit, at least ${FG_LBLUE}$(formatLovelace ${min_utxo_out})${NC} ADA required!" + return 1 + fi + + build_args=( + ${tx_in} + --tx-out "${tx_out}" + --invalid-hereafter ${ttl} + --fee ${min_fee} + --certificate-file "${drep_cert_file}" + "${script_args[@]}" + --out-file "${TMP_DIR}"/tx.raw + ) + + if ! buildTx; then return 1; fi + + if [[ ${op_mode} = "hybrid" ]]; then + if ! buildOfflineJSON "Wallet DRep Retire"; then return 1; fi + if ! offlineJSON=$(jq ". += { \"wallet-name\": \"${wallet_name}\" }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { \"drep-wallet-name\": \"${drep_wallet_name}\" }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { txFee: \"${min_fee}\" }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { txBody: $(jq -c . "${TMP_DIR}"/tx.raw) }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + if [[ ${hash_type} = "scriptHash" ]]; then + if ! offlineJSON=$(jq ".\"script-file\" += [{ name: \"Wallet '${drep_wallet_name}' DRep script\", script: $(jq -c . "${drep_script_file}") }]" <<< ${offlineJSON}); then return 1; fi + else + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${drep_wallet_name}' DRep signing key\", vkey: $(jq -c . "${drep_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + fi + if ! offlineJSON=$(jq ". += { \"signed-txBody\": {} }" <<< ${offlineJSON}); then return 1; fi + offline_tx="${TMP_DIR}/offline_tx_$(jq -r .id <<< ${offlineJSON}).json" + jq -r . <<< "${offlineJSON}" > "${offline_tx}" + echo + if [[ ${hash_type} = "scriptHash" ]]; then + println "MultiSig DRep retire transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "Use CNTools [Transaction >> Sign] to witness the transaction with fee wallet and MultiSig DRep participants." + else + println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" + println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${payment_sk_file})${NC}" + println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${drep_sk_file})${NC}" + fi + return 2 # return as failed to stop main processing and return to home menu + fi + + if ! witnessTx "${TMP_DIR}/tx.raw" "${drep_sk_file}" "${payment_sk_file}"; then return 1; fi + if ! assembleTx "${TMP_DIR}/tx.raw"; then return 1; fi + if ! submitTx "${tx_signed}"; then return 1; fi +} + +# Command : governanceVote +# Description : cast governance vote +governanceVote() { + + getWalletType ${wallet_name} + wallet_type=$? + wallet_source="base" + + if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then + utxo_cnt=${utxos_cnt[${base_addr}]} + tx_in=${tx_in_arr[${base_addr}]} + fi + + if [[ ${vote_mode} = "drep" && ${hash_type} = "scriptHash" ]]; then + op_mode=hybrid + unset required_total + validateMultiSigScript false "$(cat "${drep_script_file}")" + witness_cnt=$(( required_total + 1 )) + else + witness_cnt=2 + fi + + if ! getTTL; then return 1; fi + + getAssetsTxOut + + unset script_args + if [[ ${vote_mode} = "drep" && ${hash_type} = "scriptHash" ]]; then + script_args=( + --tx-in-script-file "${drep_script_file}" + ) + fi + + tmpNewBalance=${base_lovelace} + build_args=( + ${tx_in} + "${script_args[@]}" + --tx-out "${base_addr}+${tmpNewBalance}${assets_tx_out}" + --invalid-hereafter ${ttl} + --fee ${DUMMYFEE} + --vote-file "${vote_file}" + --out-file "${TMP_DIR}"/tx0.tmp + ) + + if ! buildTx; then return 1; fi + + calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 ${witness_cnt} || return 1 + + newBalance=$(( base_lovelace - min_fee )) + println LOG "New balance after subtracted tx fee is $(formatLovelace ${newBalance}) ADA ($(formatLovelace ${base_lovelace}) - $(formatLovelace ${min_fee}))" + + if [[ ${base_lovelace} -lt ${fee} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: Not enough ADA in base address!"\ + "Funds in address: ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA"\ + "Minimum required: ${FG_LBLUE}$(formatLovelace ${fee})${NC} ADA" + return 1 + fi + + tx_out="${base_addr}+${newBalance}${assets_tx_out}" + getMinUTxO "${tx_out}" || return 1 + if [[ ${newBalance} -lt ${min_utxo_out} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: minimum UTxO value not fulfilled, only ${FG_LBLUE}$(formatLovelace ${newBalance})${NC} ADA left in address after tx fee, at least ${FG_LBLUE}$(formatLovelace ${min_utxo_out})${NC} ADA required!" + return 1 + fi + + build_args=( + ${tx_in} + "${script_args[@]}" + --tx-out "${tx_out}" + --invalid-hereafter ${ttl} + --fee ${min_fee} + --vote-file "${vote_file}" + --out-file "${TMP_DIR}"/tx.raw + ) + + if ! buildTx; then return 1; fi + + if [[ ${op_mode} = "hybrid" ]]; then + if ! buildOfflineJSON "Wallet Governance Vote"; then return 1; fi + if ! offlineJSON=$(jq ". += { \"wallet-name\": \"${wallet_name}\" }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { \"action-id\": \"${action_id}\" }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { vote: \"${vote_param//-}\" }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { txFee: \"${min_fee}\" }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ". += { txBody: $(jq -c . "${TMP_DIR}"/tx.raw) }" <<< ${offlineJSON}); then return 1; fi + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + if [[ ${vote_mode} = "spo" ]]; then + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Pool '${pool_name}' cold signing key\", vkey: $(jq -c . "${pool_coldkey_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + elif [[ ${vote_mode} = "drep" ]]; then + if ! offlineJSON=$(jq ". += { \"drep-wallet-name\": \"${drep_wallet_name}\" }" <<< ${offlineJSON}); then return 1; fi + if [[ ${hash_type} = "scriptHash" ]]; then + if ! offlineJSON=$(jq ".\"script-file\" += [{ name: \"Wallet '${drep_wallet_name}' DRep script\", script: $(jq -c . "${drep_script_file}") }]" <<< ${offlineJSON}); then return 1; fi + else + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${drep_wallet_name}' DRep signing key\", vkey: $(jq -c . "${drep_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + fi + else + if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' committee hot signing key\", vkey: $(jq -c . "${cc_hot_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi + fi + if ! offlineJSON=$(jq ". += { \"signed-txBody\": {} }" <<< ${offlineJSON}); then return 1; fi + offline_tx="${TMP_DIR}/offline_tx_$(jq -r .id <<< ${offlineJSON}).json" + jq -r . <<< "${offlineJSON}" > "${offline_tx}" + echo + if [[ ${vote_mode} = "drep" && ${hash_type} = "scriptHash" ]]; then + println "MultiSig DRep vote transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "Use CNTools [Transaction >> Sign] to witness the transaction with fee wallet and MultiSig DRep participants." + else + println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" + println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" + println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${payment_sk_file})${NC}" + if [[ ${vote_mode} = "spo" ]]; then + println DEBUG "Pool ${FG_GREEN}${pool_name} ${FG_LGRAY}$(basename ${pool_coldkey_sk_file})${NC}" + elif [[ ${vote_mode} = "drep" ]]; then + println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${drep_sk_file})${NC}" + else + println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${cc_hot_sk_file})${NC}" + fi + fi + return 2 # return as failed to stop main processing and return to home menu + fi + + if [[ ${vote_mode} = "spo" ]]; then + vote_key="${pool_coldkey_sk_file}" + elif [[ ${vote_mode} = "drep" ]]; then + vote_key="${drep_sk_file}" + else + vote_key="${cc_hot_sk_file}" + fi + if ! witnessTx "${TMP_DIR}/tx.raw" "${vote_key}" "${payment_sk_file}"; then return 1; fi + if ! assembleTx "${TMP_DIR}/tx.raw"; then return 1; fi + if ! submitTx "${tx_signed}"; then return 1; fi +} + +# Command : buildTx [build_args] +# Description : Helper function to build a raw transaction +# : populate an array variable called 'build_args' with all data +# Parameters : build_args > an array with all the arguments to assemble the transaction +buildTx() { + println ACTION "${CCLI} ${NETWORK_ERA} transaction build-raw ${build_args[*]}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} transaction build-raw "${build_args[@]}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during transaction building!\n${stdout}"; return 1 + fi +} + +# Command : calcMinFee [rax tx file] +# Description : Helper function to calculate minimum fee from a build transaction +# Parameters : $1 = raw tx file > the transaction body file to use for calculating fee +# $2 = Count of transaction inputs (spent txo count) +# $3 = Count of transaction outputs +# $4 = Count of witnesses required to sign the transaction +calcMinFee() { + min_fee_args=( + ${NETWORK_ERA} + transaction calculate-min-fee + --tx-body-file "$1" + --tx-in-count $2 + --tx-out-count $3 + --witness-count $4 + --byron-witness-count 0 + --protocol-params-file "${TMP_DIR}"/protparams.json + ) + println ACTION "${CCLI} ${min_fee_args[*]}" + if ! stdout=$(${CCLI} "${min_fee_args[@]}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during minimum fee calculation!\n${stdout}"; return 1 + fi + min_fee=$([[ ${stdout} =~ ([0-9]+) ]] && echo ${BASH_REMATCH[1]}) + println LOG "fee is $(formatLovelace ${min_fee}) ADA" +} + +# Command : witnessTx [raw tx file] [signing keys ...] +# Description : Helper function to witness a raw transaction +# Parameters : raw tx file > the transaction file to sign +# : signing keys > list of signing keys to use when witnessing the transaction +witnessTx() { + tx_raw="$1" + shift + tx_witness_files=() + unset isHW + for skey in "$@"; do + [[ -z ${skey//[[:blank:]]/} ]] && continue + skey_name=$(basename "${skey}") + if [[ ! -f "${skey}" ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: file not found: ${skey}" + return 1 + elif [[ $(jq -r '.description' "${skey}") = *"Hardware"* ]]; then # HW signing key + if [[ ${isHW} = 'Y' ]]; then + # just add key and output to witness_command() + tx_witness="$(mktemp "${TMP_DIR}/tx.witness_XXXXXXXXXX")" + hw_witness_command+=( + --hw-signing-file "${skey}" + --change-output-key-file "${skey}" + --out-file "${tx_witness}" + ) + else + isHW=Y + tx_witness="$(mktemp "${TMP_DIR}/tx.witness_XXXXXXXXXX")" + hw_witness_command=( + cardano-hw-cli transaction witness + --tx-file "${tx_raw}" + --hw-signing-file "${skey}" + --change-output-key-file "${skey}" + --out-file "${tx_witness}" + ${NETWORK_IDENTIFIER} + ) + fi + else + tx_witness="$(mktemp "${TMP_DIR}/tx.witness_XXXXXXXXXX")" + witness_command=( + ${CCLI} transaction witness + --tx-body-file "${tx_raw}" + --signing-key-file "${skey}" + ${NETWORK_IDENTIFIER} + --out-file "${tx_witness}" + ) + println ACTION "${witness_command[@]}" + if ! stdout=$("${witness_command[@]}" 2>&1); then println ERROR "\n${FG_RED}ERROR${NC}: during transaction signing !!\n${stdout}" && return 1; fi + fi + tx_witness_files+=( "${tx_witness}" ) + done + + # Special case for HW + if [[ ${isHW} = 'Y' ]]; then + if ! unlockHWDevice "witness the transaction"; then return 1; fi + println ACTION "${hw_witness_command[@]}" + if ! stdout=$("${hw_witness_command[@]}" 2>&1); then println ERROR "\n${FG_RED}ERROR${NC}: during hardware wallet signing !!\n${stdout}" && return 1; fi + fi +} + +# Command : assembleTx [raw tx file] +# Description : Helper function to witnessTx for assembling a signed tx using witnesses from tx_witness_files[] array +assembleTx() { tx_raw="$1" tx_signed="${TMP_DIR}/tx.signed_$(date +%s)" if [[ ${#tx_witness_files[@]} -gt 0 ]]; then # assemble witness files and sign @@ -2892,6 +4433,7 @@ submitTx() { # Description : Helper function to submit signed transaction file using local node # Parameters : signed tx file > the signed transaction file to submit submitTxNode() { + getTxId $1 || return $? submit_command=( ${CCLI} ${NETWORK_ERA} transaction submit --tx-file "$1" @@ -2985,19 +4527,6 @@ unlockHWDevice() { println ERROR "${FG_RED}ERROR${NC}: unable to identify connected hardware device, is the device plugged in and unlocked?" println ERROR "Make sure device is seen by OS using tools like lsusb etc and is working correctly" waitToProceed && return 1 - elif [[ ${device_app_vendor} = Ledger ]]; then - if ! versionCheck "5.0.1" "${device_app_version}"; then - println ERROR "${FG_RED}ERROR${NC}: Cardano app version installed on Ledger is ${FG_LGRAY}v${device_app_version}${NC}, minimum required app version is ${FG_GREEN}v5.0.1${NC} !!" - return 1 - fi - elif [[ ${device_app_vendor} = Trezor ]]; then - if ! versionCheck "2.6.4" "${device_app_version}"; then - println ERROR "${FG_RED}ERROR${NC}: Trezor firmware installed on device is ${FG_LGRAY}v${device_app_version}${NC}, minimum required version is ${FG_GREEN}v2.6.4${NC} !!" - return 1 - fi - else - println ERROR "${FG_RED}ERROR${NC}: Unknown vendor: ${FG_LGRAY}${device_app_vendor}${NC} !!" - return 1 fi println DEBUG "\n${FG_BLUE}INFO${NC}: follow directions on hardware device to $1" } @@ -3122,120 +4651,6 @@ to_cbor() { echo -n "${cbor^^}" #return the cbor in uppercase } -# Command : submitPoll -# Description : sign and submit a CIP-0094 poll answer -submitPoll() { - - [[ $(cat "${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_ADDR_FILENAME}" 2>/dev/null) = "${addr}" ]] && wallet_source="enterprise" || wallet_source="base" - POOL_ID_HASH=$(cat ${POOL_FOLDER}/${pool_name}/${POOL_ID_FILENAME}) - - if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then - for key in ${!assets[@]}; do - [[ ${key} != "${addr},"* ]] && unset 'assets[$key]' - done - utxo_cnt=${utxos_cnt[${addr}]} - tx_in=${tx_in_arr[${addr}]} - else - getBalance ${addr} - fi - - if ! getTTL; then return 1; fi - - getAssetsTxOut - - metafile_param="--metadata-cbor-file ${cborFile}" - if [[ -n ${metafile} && -f ${metafile} ]]; then - metafile_param+=" --json-metadata-no-schema --metadata-json-file ${metafile}" - fi - - build_args=( - ${tx_in} - --tx-out "${addr}+0${assets_tx_out}" - --invalid-hereafter ${ttl} - --fee 0 - ${metafile_param} - --required-signer-hash "${POOL_ID_HASH}" - --out-file "${TMP_DIR}"/tx0.tmp - ) - - if ! buildTx; then return 1; fi - - calcMinFee "${TMP_DIR}"/tx0.tmp ${utxo_cnt} 1 2 || return 1 - - newBalance=$(( lovelace - min_fee )) - println LOG "Balance left to be returned in used UTxO is $(formatLovelace ${newBalance}) ADA ( $(formatLovelace ${lovelace}) - $(formatLovelace ${min_fee}) )" - - if [[ ${lovelace} -lt ${min_fee} ]]; then - println ERROR "\n${FG_RED}ERROR${NC}: Not enough ADA in ${wallet_source} address for tx fee!"\ - "Funds in address: ${FG_LBLUE}$(formatLovelace ${lovelace})${NC} ADA"\ - "Minimum required: ${FG_LBLUE}$(formatLovelace ${min_fee})${NC} ADA" - return 1 - fi - - tx_out="${addr}+${newBalance}${assets_tx_out}" - getMinUTxO "${tx_out}" || return 1 - if [[ ${newBalance} -lt ${min_utxo_out} ]]; then - println ERROR "\n${FG_RED}ERROR${NC}: minimum UTxO value not fulfilled, only ${FG_LBLUE}$(formatLovelace ${newBalance})${NC} ADA left in address after tx fee, at least ${FG_LBLUE}$(formatLovelace ${min_utxo_out})${NC} ADA required!" - return 1 - fi - - build_args=( - ${tx_in} - --tx-out "${tx_out}" - --invalid-hereafter ${ttl} - --fee ${min_fee} - ${metafile_param} - --required-signer-hash "${POOL_ID_HASH}" - --out-file "${TMP_DIR}"/tx.raw - ) - - if ! buildTx; then return 1; fi - - needHWCLI=false - pay_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_VK_FILENAME}" - [[ $(jq .description "${pay_vk_file}") = *Hardware* ]] && needHWCLI=true - if [[ ${needHWCLI} = true ]]; then - if ! HWCLIversionCheck; then return 1; fi - if ! transformRawTx "${TMP_DIR}"/tx.raw; then return 1; fi - fi - - if [[ ${op_mode} = "hybrid" ]]; then - if ! buildOfflineJSON "Poll Cast"; then return 1; fi - if ! offlineJSON=$(jq ". += { \"wallet-name\": \"${wallet_name}\" }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { txFee: \"${min_fee}\" }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { \"poll-title\": \"${poll_title}\" }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { \"poll-txId\": \"${poll_txId}\" }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { \"poll-question\": \"${questionString}\" }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { \"poll-answer\": \"${optionString[${answer}]}\" }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { \"pool-name\": \"${pool_name}\" }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { txBody: $(jq -c . "${TMP_DIR}"/tx.raw) }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { \"signing-file\": [] }" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Wallet '${wallet_name}' payment signing key\", vkey: $(jq -c . "${payment_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ".\"signing-file\" += [{ name: \"Pool '${pool_name}' cold signing key\", vkey: $(jq -c . "${pool_coldkey_vk_file}") }]" <<< ${offlineJSON}); then return 1; fi - if ! offlineJSON=$(jq ". += { \"signed-txBody\": {} }" <<< ${offlineJSON}); then return 1; fi - offline_tx="${TMP_DIR}/offline_tx_$(jq -r .id <<< ${offlineJSON}).json" - jq -r . <<< "${offlineJSON}" > "${offline_tx}" - echo - println "Offline transaction successfully built and saved to: ${FG_LGRAY}${offline_tx}${NC}" - println DEBUG "move file to offline computer and sign it using CNTools in offline mode '-o' [Transaction >> Sign] with:" - println DEBUG "Wallet ${FG_GREEN}${wallet_name} ${FG_LGRAY}$(basename ${payment_sk_file})${NC}" - println DEBUG "Pool ${FG_GREEN}${pool_name} ${FG_LGRAY}$(basename ${pool_coldkey_sk_file})${NC}" - return 2 # return as failed to stop main processing and return to home menu - fi - - getWalletType ${wallet_name} - echo "Transaction Fee: $(formatLovelace ${min_fee}) ADA" - println "\n# Do you want to publish this answer for pool ${pool_name} on ${NETWORK_NAME}?" - select_opt "[n] No" "[y] Yes" - case $? in - 1) if ! witnessTx "${TMP_DIR}/tx.raw" "${payment_sk_file}" "${pool_coldkey_sk_file}"; then return 1; fi - if ! assembleTx "${TMP_DIR}/tx.raw"; then return 1; fi - if ! submitTx "${tx_signed}"; then return 1; fi - println "Poll ${FG_GREEN}${poll_txId}${NC} ballot casted to the network" - waitToProceed && continue - esac -} - # Command : getPriceInfo # Description : fetch current ADA price from coingecko in selected currency getPriceInfo() { diff --git a/scripts/cnode-helper-scripts/cntools.sh b/scripts/cnode-helper-scripts/cntools.sh index b4aca8e90..28095cf61 100755 --- a/scripts/cnode-helper-scripts/cntools.sh +++ b/scripts/cnode-helper-scripts/cntools.sh @@ -33,7 +33,7 @@ # If disabled standard tty input is used #ENABLE_DIALOG=false -# Enable advanced/developer features like metadata transactions, multi-asset management etc. [true|false] (not needed for SPO usage) +# Enable advanced/developer features like metadata transactions, asset management etc. [true|false] (not needed for SPO usage) #ENABLE_ADVANCED=false # Price fetching currency. Disable by setting value 'off' [off|usd|eur|...] (default: off) (https://api.coingecko.com/api/v3/simple/supported_vs_currencies) @@ -42,6 +42,12 @@ # Runtime mode, offline | local | light (default local) # CNTOOLS_MODE=local +# Project Catalyst API (only for mainnet) +#CATALYST_API=https://api.projectcatalyst.io/api/v1 + +# Url for transaction lookup on submit, __tx_id__ replaced by transaction hash +#EXPLORER_TX="https://adastat.net/transactions/__tx_id__" + ###################################### # Do NOT modify code below # ###################################### @@ -78,7 +84,7 @@ usage() { -n Local mode - run CNTools in local node mode (default) -l Light mode - run CNTools using Koios query layer for full functionallity without a local node -o Offline mode - run CNTools with a limited set of functionallity without external communication useful for air-gapped mode - -a Enable advanced/developer features like metadata transactions, multi-asset management etc (not needed for SPO usage) + -a Enable advanced/developer features like metadata transactions, asset management etc (not needed for SPO usage) -u Skip script update check overriding UPDATE_CHECK value in env -b Run CNTools and look for updates on alternate branch instead of master (only for testing/development purposes) -v Print CNTools version @@ -182,26 +188,26 @@ if [[ ${CNTOOLS_MODE} != "OFFLINE" ]]; then 2) echo -e "\n${FG_RED}ERROR${NC}: Update check of cntools.library against GitHub failed!" waitToProceed ;; esac - - # check if CNTools was recently updated, if so show whats new - if curl -s -f -m ${CURL_TIMEOUT} -o "${TMP_DIR}"/cntools-changelog.md "${URL_DOCS}/cntools-changelog.md"; then - if ! cmp -s "${TMP_DIR}"/cntools-changelog.md "${PARENT}/cntools-changelog.md"; then - # Latest changes not shown, show whats new and copy changelog - clear - if [[ ! -f "${PARENT}/cntools-changelog.md" ]]; then - # special case for first installation or 5.0.0 upgrade, print release notes until previous major version - echo -e "~ CNTools - What's New ~\n\n" "$(sed -n "/\[${CNTOOLS_MAJOR_VERSION}\.${CNTOOLS_MINOR_VERSION}\.${CNTOOLS_PATCH_VERSION}\]/,/\[$((CNTOOLS_MAJOR_VERSION-1))\.[0-9]\.[0-9]\]/p" "${TMP_DIR}"/cntools-changelog.md | head -n -2)" | less -X - else - # print release notes from current until previously installed version - [[ $(cat "${PARENT}/cntools-changelog.md") =~ \[([[:digit:]]+)\.([[:digit:]]+)\.([[:digit:]]+)\] ]] - cat <(echo -e "~ CNTools - What's New ~\n") <(awk "1;/\[${BASH_REMATCH[1]}\.${BASH_REMATCH[2]}\.${BASH_REMATCH[3]}\]/{exit}" "${TMP_DIR}"/cntools-changelog.md | head -n -2 | tail -n +7) <(echo -e "\n [Press 'q' to quit and proceed to CNTools main menu]\n") | less -X - fi - cp "${TMP_DIR}"/cntools-changelog.md "${PARENT}/cntools-changelog.md" + fi + + # check if CNTools was recently updated, if so show whats new + if curl -s -f -m ${CURL_TIMEOUT} -o "${TMP_DIR}"/cntools-changelog.md "${URL_DOCS}/cntools-changelog.md"; then + if ! cmp -s "${TMP_DIR}"/cntools-changelog.md "${PARENT}/cntools-changelog.md"; then + # Latest changes not shown, show whats new and copy changelog + clear + if [[ ! -f "${PARENT}/cntools-changelog.md" ]]; then + # special case for first installation or 5.0.0 upgrade, print release notes until previous major version + echo -e "~ CNTools - What's New ~\n\n" "$(sed -n "/\[${CNTOOLS_MAJOR_VERSION}\.${CNTOOLS_MINOR_VERSION}\.${CNTOOLS_PATCH_VERSION}\]/,/\[$((CNTOOLS_MAJOR_VERSION-1))\.[0-9]\.[0-9]\]/p" "${TMP_DIR}"/cntools-changelog.md | head -n -2)" | less -X + else + # print release notes from current until previously installed version + [[ $(cat "${PARENT}/cntools-changelog.md") =~ \[([[:digit:]]+)\.([[:digit:]]+)\.([[:digit:]]+)\] ]] + cat <(echo -e "~ CNTools - What's New ~\n") <(awk "1;/\[${BASH_REMATCH[1]}\.${BASH_REMATCH[2]}\.${BASH_REMATCH[3]}\]/{exit}" "${TMP_DIR}"/cntools-changelog.md | head -n -2 | tail -n +7) <(echo -e "\n [Press 'q' to quit and proceed to CNTools main menu]\n") | less -X fi - else - echo -e "\n${FG_RED}ERROR${NC}: failed to download changelog from GitHub!" - waitToProceed + cp "${TMP_DIR}"/cntools-changelog.md "${PARENT}/cntools-changelog.md" fi + else + echo -e "\n${FG_RED}ERROR${NC}: failed to download changelog from GitHub!" + waitToProceed fi fi @@ -282,9 +288,10 @@ function main { " ) Funds - send, withdraw and delegate"\ " ) Pool - pool creation and management"\ " ) Transaction - Sign and Submit a cold transaction (hybrid/offline mode)"\ + " ) Vote - project funding (Catalyst) and blockchain governance"\ "$([[ -f "${BLOCKLOG_DB}" ]] && echo " ) Blocks - show core node leader schedule & block production statistics")"\ " ) Backup - backup & restore of wallet/pool/config"\ - "$([[ ${ADVANCED_MODE} = true ]] && echo " ) Advanced - Developer and advanced features: metadata, multi-assets, ...")"\ + "$([[ ${ADVANCED_MODE} = true ]] && echo " ) Advanced - Developer and advanced features: metadata, assets, ...")"\ " ) Refresh - reload home screen content"\ "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println DEBUG "$(printf "%84s" "Epoch $(getEpoch) - $(timeLeft "$(timeUntilNextEpoch)") until next")" @@ -313,12 +320,13 @@ function main { else echo fi - select_opt "[w] Wallet" "[f] Funds" "[p] Pool" "[t] Transaction" "$([[ -f "${BLOCKLOG_DB}" ]] && echo "[b] Blocks")" "[z] Backup & Restore" "$([[ ${ADVANCED_MODE} = true ]] && echo "[a] Advanced")" "[r] Refresh" "[q] Quit" + select_opt "[w] Wallet" "[f] Funds" "[p] Pool" "[t] Transaction" "[v] Vote" "$([[ -f "${BLOCKLOG_DB}" ]] && echo "[b] Blocks")" "[z] Backup & Restore" "$([[ ${ADVANCED_MODE} = true ]] && echo "[a] Advanced")" "[r] Refresh" "[q] Quit" case ${selected_value} in "[w]"*) OPERATION="wallet" ;; "[f]"*) OPERATION="funds" ;; "[p]"*) OPERATION="pool" ;; "[t]"*) OPERATION="transaction" ;; + "[v]"*) OPERATION="vote" ;; "[b]"*) OPERATION="blocks" ;; "[z]"*) OPERATION="backup" ;; "[a]"*) OPERATION="advanced" ;; @@ -358,53 +366,129 @@ function main { esac case $SUBCOMMAND in new) - clear - println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - println " >> WALLET >> NEW" - println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - echo - getAnswerAnyCust wallet_name "Name of new wallet" - # Remove unwanted characters from wallet name - wallet_name=${wallet_name//[^[:alnum:]]/_} - if [[ -z "${wallet_name}" ]]; then - println ERROR "${FG_RED}ERROR${NC}: Empty wallet name, please retry!" - waitToProceed && continue - fi - echo - if ! mkdir -p "${WALLET_FOLDER}/${wallet_name}"; then - println ERROR "${FG_RED}ERROR${NC}: Failed to create directory for wallet:\n${WALLET_FOLDER}/${wallet_name}" - waitToProceed && continue - fi - # Wallet key filenames - payment_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_SK_FILENAME}" - payment_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_VK_FILENAME}" - stake_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_VK_FILENAME}" - stake_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_SK_FILENAME}" - if [[ $(find "${WALLET_FOLDER}/${wallet_name}" -type f -print0 | wc -c) -gt 0 ]]; then - println "${FG_RED}WARN${NC}: A wallet ${FG_GREEN}$wallet_name${NC} already exists" - println " Choose another name or delete the existing one" - waitToProceed && continue - fi - println ACTION "${CCLI} ${NETWORK_ERA} address key-gen --verification-key-file ${payment_vk_file} --signing-key-file ${payment_sk_file}" - if ! stdout=$(${CCLI} ${NETWORK_ERA} address key-gen --verification-key-file "${payment_vk_file}" --signing-key-file "${payment_sk_file}" 2>&1); then - println ERROR "\n${FG_RED}ERROR${NC}: failure during payment key creation!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && continue - fi - println ACTION "${CCLI} ${NETWORK_ERA} stake-address key-gen --verification-key-file ${stake_vk_file} --signing-key-file ${stake_sk_file}" - if ! stdout=$(${CCLI} ${NETWORK_ERA} stake-address key-gen --verification-key-file "${stake_vk_file}" --signing-key-file "${stake_sk_file}" 2>&1); then - println ERROR "\n${FG_RED}ERROR${NC}: failure during stake key creation!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && continue - fi - chmod 600 "${WALLET_FOLDER}/${wallet_name}/"* - getBaseAddress ${wallet_name} - getPayAddress ${wallet_name} - getRewardAddress ${wallet_name} - getCredentials ${wallet_name} - println "New Wallet : ${FG_GREEN}${wallet_name}${NC}" - println "Address : ${FG_LGRAY}${base_addr}${NC}" - println "Enterprise Address : ${FG_LGRAY}${pay_addr}${NC}" - println DEBUG "\nYou can now send and receive ADA using the above addresses." - println DEBUG "Note that Enterprise Address will not take part in staking." - println DEBUG "Wallet will be automatically registered on chain if you\nchoose to delegate or pledge wallet when registering a stake pool." - waitToProceed && continue + while true; do # Wallet >> New loop + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> WALLET >> NEW" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println OFF " Wallet New\n"\ + " ) Mnemonic - based on 24 word generated passphrase (recommended)"\ + " ) CLI - one-time generated keys"\ + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println DEBUG " Select Wallet Creation Type\n" + select_opt "[m] Mnemonic" "[c] CLI" "[b] Back" "[h] Home" + case $? in + 0) SUBCOMMAND="mnemonic" ;; + 1) SUBCOMMAND="cli" ;; + 2) break ;; + 3) break 2 ;; + esac + case $SUBCOMMAND in + mnemonic) + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> WALLET >> NEW >> MNEMONIC" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + echo + createNewWallet || continue + unset mnemonic + createMnemonicWallet || continue + echo + println "Wallet Imported : ${FG_GREEN}${wallet_name}${NC}" + println "Address : ${FG_LGRAY}${base_addr}${NC}" + println "Payment Address : ${FG_LGRAY}${pay_addr}${NC}" + echo + word_len=0 + for word in "${words[@]}"; do + [[ ${#word} -gt ${word_len} ]] && word_len=${#word} + done + println DEBUG "${FG_YELLOW}IMPORTANT!${NC} Please write down and store below words in a secure place to be able to restore wallet at a later time." + for i in "${!words[@]}"; do + idx=$(( i + 1 )) + printf "%2s: ${FG_GREEN}%-${word_len}s${NC} " "$idx" "${words[$i]}" + [[ $(( idx % 4 )) -eq 0 ]] && echo + done + unset words + echo + printWalletInfo + waitToProceed && continue + ;; ################################################################### + cli) + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> WALLET >> NEW >> CLI" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + echo + createNewWallet || continue + # Wallet key filenames + payment_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_SK_FILENAME}" + payment_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_VK_FILENAME}" + stake_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_VK_FILENAME}" + stake_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_SK_FILENAME}" + drep_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_DREP_VK_FILENAME}" + drep_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_DREP_SK_FILENAME}" + cc_cold_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_CC_COLD_VK_FILENAME}" + cc_cold_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_CC_COLD_SK_FILENAME}" + cc_hot_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_CC_HOT_VK_FILENAME}" + cc_hot_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_CC_HOT_SK_FILENAME}" + ms_payment_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_PAY_SK_FILENAME}" + ms_payment_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_PAY_VK_FILENAME}" + ms_stake_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_STAKE_SK_FILENAME}" + ms_stake_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_STAKE_VK_FILENAME}" + ms_drep_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_GOV_DREP_SK_FILENAME}" + ms_drep_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_GOV_DREP_VK_FILENAME}" + if [[ $(find "${WALLET_FOLDER}/${wallet_name}" -type f -print0 | wc -c) -gt 0 ]]; then + println "${FG_RED}WARN${NC}: A wallet ${FG_GREEN}$wallet_name${NC} already exists" + println " Choose another name or delete the existing one" + waitToProceed && continue + fi + println ACTION "${CCLI} ${NETWORK_ERA} address key-gen --verification-key-file ${payment_vk_file} --signing-key-file ${payment_sk_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} address key-gen --verification-key-file "${payment_vk_file}" --signing-key-file "${payment_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during payment key creation!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && continue + fi + println ACTION "${CCLI} ${NETWORK_ERA} stake-address key-gen --verification-key-file ${stake_vk_file} --signing-key-file ${stake_sk_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} stake-address key-gen --verification-key-file "${stake_vk_file}" --signing-key-file "${stake_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during stake key creation!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && continue + fi + println ACTION "${CCLI} conway governance drep key-gen --verification-key-file ${drep_vk_file} --signing-key-file ${drep_sk_file}" + if ! stdout=$(${CCLI} conway governance drep key-gen --verification-key-file "${drep_vk_file}" --signing-key-file "${drep_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during governance drep key creation!\n${stdout}"; waitToProceed && continue + fi + println ACTION "${CCLI} conway governance committee key-gen-cold --cold-verification-key-file ${cc_cold_vk_file} --cold-signing-key-file ${cc_cold_sk_file}" + if ! stdout=$(${CCLI} conway governance committee key-gen-cold --cold-verification-key-file "${cc_cold_vk_file}" --cold-signing-key-file "${cc_cold_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during governance committee cold key creation!\n${stdout}"; waitToProceed && continue + fi + println ACTION "${CCLI} conway governance committee key-gen-hot --verification-key-file ${cc_hot_vk_file} --signing-key-file ${cc_hot_sk_file}" + if ! stdout=$(${CCLI} conway governance committee key-gen-hot --verification-key-file "${cc_hot_vk_file}" --signing-key-file "${cc_hot_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during governance committee hot key creation!\n${stdout}"; waitToProceed && continue + fi + println ACTION "${CCLI} ${NETWORK_ERA} address key-gen --verification-key-file ${ms_payment_vk_file} --signing-key-file ${ms_payment_sk_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} address key-gen --verification-key-file "${ms_payment_vk_file}" --signing-key-file "${ms_payment_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during MultiSig payment key creation!\n${stdout}"; waitToProceed && continue + fi + println ACTION "${CCLI} ${NETWORK_ERA} stake-address key-gen --verification-key-file ${ms_stake_vk_file} --signing-key-file ${ms_stake_sk_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} stake-address key-gen --verification-key-file "${ms_stake_vk_file}" --signing-key-file "${ms_stake_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during MultiSig stake key creation!\n${stdout}"; waitToProceed && continue + fi + println ACTION "${CCLI} conway governance drep key-gen --verification-key-file ${ms_drep_vk_file} --signing-key-file ${ms_drep_sk_file}" + if ! stdout=$(${CCLI} conway governance drep key-gen --verification-key-file "${ms_drep_vk_file}" --signing-key-file "${ms_drep_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during MultiSig governance drep key creation!\n${stdout}"; waitToProceed && continue + fi + chmod 600 "${WALLET_FOLDER}/${wallet_name}/"* + getBaseAddress ${wallet_name} + getPayAddress ${wallet_name} + getRewardAddress ${wallet_name} + getCredentials ${wallet_name} + println "New Wallet : ${FG_GREEN}${wallet_name}${NC}" + println "Address : ${FG_LGRAY}${base_addr}${NC}" + println "Payment Address : ${FG_LGRAY}${pay_addr}${NC}" + println DEBUG "\nYou can now send and receive ADA using the above addresses." + println DEBUG "Note that Payment Address will not take part in staking." + println DEBUG "Wallet will be automatically registered on chain if you\nchoose to delegate or pledge wallet when registering a stake pool." + waitToProceed && continue + ;; ################################################################### + esac # wallet >> new sub OPERATION + done # Wallet >> new loop ;; ################################################################### import) while true; do # Wallet >> Import loop @@ -431,29 +515,7 @@ function main { println " >> WALLET >> IMPORT >> MNEMONIC" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo - if ! cmdAvailable "bech32" &>/dev/null || \ - ! cmdAvailable "cardano-address" &>/dev/null; then - println ERROR "${FG_RED}ERROR${NC}: bech32 and/or cardano-address not found in '\$PATH'" - println ERROR "Please run updated guild-deploy.sh and re-build/re-download cardano-node" - waitToProceed && continue - fi - getAnswerAnyCust wallet_name "Name of imported wallet" - # Remove unwanted characters from wallet name - wallet_name=${wallet_name//[^[:alnum:]]/_} - if [[ -z "${wallet_name}" ]]; then - println ERROR "${FG_RED}ERROR${NC}: Empty wallet name, please retry!" - waitToProceed && continue - fi - echo - if ! mkdir -p "${WALLET_FOLDER}/${wallet_name}"; then - println ERROR "${FG_RED}ERROR${NC}: Failed to create directory for wallet:\n${WALLET_FOLDER}/${wallet_name}" - waitToProceed && continue - fi - if [[ $(find "${WALLET_FOLDER}/${wallet_name}" -type f -print0 | wc -c) -gt 0 ]]; then - println "${FG_RED}WARN${NC}: A wallet ${FG_GREEN}$wallet_name${NC} already exists" - println " Choose another name or delete the existing one" - waitToProceed && continue - fi + createNewWallet || continue getAnswerAnyCust mnemonic false "24 or 15 word mnemonic(space separated)" echo IFS=" " read -r -a words <<< "${mnemonic}" @@ -463,98 +525,15 @@ function main { unset mnemonic; unset words waitToProceed && continue fi - payment_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_SK_FILENAME}" - payment_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_VK_FILENAME}" - stake_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_SK_FILENAME}" - stake_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_VK_FILENAME}" - caddr_v="$(cardano-address -v | awk '{print $1}')" - [[ "${caddr_v}" == 3* ]] && caddr_arg="--with-chain-code" || caddr_arg="" - if ! root_prv=$(cardano-address key from-recovery-phrase Shelley <<< ${mnemonic}); then - echo && safeDel "${WALLET_FOLDER}/${wallet_name}" - unset mnemonic; unset words - waitToProceed && continue - fi - unset mnemonic; unset words - payment_xprv=$(cardano-address key child 1852H/1815H/0H/0/0 <<< ${root_prv}) - stake_xprv=$(cardano-address key child 1852H/1815H/0H/2/0 <<< ${root_prv}) - payment_xpub=$(cardano-address key public ${caddr_arg} <<< ${payment_xprv}) - stake_xpub=$(cardano-address key public ${caddr_arg} <<< ${stake_xprv}) - [[ "${NWMAGIC}" == "764824073" ]] && network_tag=1 || network_tag=0 - base_addr_candidate=$(cardano-address address delegation ${stake_xpub} <<< "$(cardano-address address payment --network-tag ${network_tag} <<< ${payment_xpub})") - if [[ "${caddr_v}" == 2* ]] && [[ "${NWMAGIC}" != "764824073" ]]; then - println LOG "TestNet, converting address to 'addr_test'" - base_addr_candidate=$(bech32 addr_test <<< ${base_addr_candidate}) - fi - println LOG "Base address candidate = ${base_addr_candidate}" - println LOG "Address Inspection:\n$(cardano-address address inspect <<< ${base_addr_candidate})" - pes_key=$(bech32 <<< ${payment_xprv} | cut -b -128)$(bech32 <<< ${payment_xpub}) - ses_key=$(bech32 <<< ${stake_xprv} | cut -b -128)$(bech32 <<< ${stake_xpub}) - cat <<-EOF > "${payment_sk_file}" - { - "type": "PaymentExtendedSigningKeyShelley_ed25519_bip32", - "description": "Payment Signing Key", - "cborHex": "5880${pes_key}" - } - EOF - cat <<-EOF > "${stake_sk_file}" - { - "type": "StakeExtendedSigningKeyShelley_ed25519_bip32", - "description": "", - "cborHex": "5880${ses_key}" - } - EOF - println ACTION "${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file ${payment_sk_file} --verification-key-file ${TMP_DIR}/payment.evkey" - if ! stdout=$(${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${payment_sk_file}" --verification-key-file "${TMP_DIR}/payment.evkey" 2>&1); then - println ERROR "\n${FG_RED}ERROR${NC}: failure during payment signing key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && continue - fi - println ACTION "${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file ${stake_sk_file} --verification-key-file ${TMP_DIR}/stake.evkey" - if ! stdout=$(${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${stake_sk_file}" --verification-key-file "${TMP_DIR}/stake.evkey" 2>&1); then - println ERROR "\n${FG_RED}ERROR${NC}: failure during stake signing key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && continue - fi - println ACTION "${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file ${TMP_DIR}/payment.evkey --verification-key-file ${payment_vk_file}" - if ! stdout=$(${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file "${TMP_DIR}/payment.evkey" --verification-key-file "${payment_vk_file}" 2>&1); then - println ERROR "\n${FG_RED}ERROR${NC}: failure during payment verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && continue - fi - println ACTION "${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file ${TMP_DIR}/stake.evkey --verification-key-file ${stake_vk_file}" - if ! stdout=$(${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file "${TMP_DIR}/stake.evkey" --verification-key-file "${stake_vk_file}" 2>&1); then - println ERROR "\n${FG_RED}ERROR${NC}: failure during stake verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && continue - fi - chmod 600 "${WALLET_FOLDER}/${wallet_name}/"* - getBaseAddress ${wallet_name} - getPayAddress ${wallet_name} - getRewardAddress ${wallet_name} - getCredentials ${wallet_name} - if [[ ${base_addr} != "${base_addr_candidate}" ]]; then - println ERROR "${FG_RED}ERROR${NC}: base address generated doesn't match base address candidate." - println ERROR "base_addr[${FG_LGRAY}${base_addr}${NC}]\n!=\nbase_addr_candidate[${FG_LGRAY}${base_addr_candidate}${NC}]" - println ERROR "Create a GitHub issue and include log file from failed CNTools session." - echo && safeDel "${WALLET_FOLDER}/${wallet_name}" - waitToProceed && continue - fi - echo - println "Wallet Imported : ${FG_GREEN}${wallet_name}${NC}" - println "Address : ${FG_LGRAY}${base_addr}${NC}" - println "Enterprise Address : ${FG_LGRAY}${pay_addr}${NC}" - echo - println DEBUG "You can now send and receive ADA using the above addresses. Note that Enterprise Address will not take part in staking" - println DEBUG "Wallet will be automatically registered on chain if you choose to delegate or pledge wallet when registering a stake pool" + createMnemonicWallet || continue echo - println DEBUG "${FG_YELLOW}Using a mnemonic imported wallet in CNTools comes with a few limitations${NC}" + println "Wallet Imported : ${FG_GREEN}${wallet_name}${NC}" + println "Address : ${FG_LGRAY}${base_addr}${NC}" + println "Payment Address : ${FG_LGRAY}${pay_addr}${NC}" echo - println DEBUG "Only the first address in the HD wallet is extracted and because of this the following apply:" - println DEBUG " ${FG_LGRAY}>${NC} Address above should match the first address seen in the wallet where mnemonic was generated, please verify!!!" - println DEBUG " ${FG_LGRAY}>${NC} If restored wallet contain funds since before, and balance doesn't match, send all ADA to address shown in CNTools" - println DEBUG " ${FG_LGRAY}>${NC} Only use receive address shown in CNTools (enable 'Single Address Mode' in wallet if available)" - echo - println DEBUG "Some of the advantages of using a mnemonic imported wallet instead of CLI are:" - println DEBUG " ${FG_LGRAY}>${NC} Wallet can be restored from saved 24 or 15 word mnemonic if keys are lost/deleted" - println DEBUG " ${FG_LGRAY}>${NC} Wallet can be shared and used in multiple wallets, including CNTools" - echo - println DEBUG "Please read more about HD wallets at:" - println DEBUG "https://cardano-community.github.io/support-faq/wallets?id=heirarchical-deterministic-hd-wallets" + printWalletInfo waitToProceed && continue ;; ################################################################### - hardware) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" @@ -570,70 +549,104 @@ function main { esac echo if ! cmdAvailable "cardano-hw-cli" &>/dev/null; then - println ERROR "${FG_RED}ERROR${NC}: cardano-hw-cli executable not found in path!" + println ERROR "${FG_RED}ERROR${NC}: cardano-hw-cli not found in path or executable permission not set." println ERROR "Please run '${FG_YELLOW}guild-deploy.sh -s w${NC}' to add hardware wallet support and install Vaccumlabs cardano-hw-cli, '${FG_YELLOW}guild-deploy.sh -h${NC}' shows all available options" waitToProceed && continue fi - if [[ ! -x $(command -v cardano-hw-cli) ]]; then - println ERROR "${FG_RED}ERROR${NC}: cardano-hw-cli binary doesn't have execution persmission, please fix!" - waitToProceed && continue - fi if ! HWCLIversionCheck; then waitToProceed && continue; fi - getAnswerAnyCust wallet_name "Name of imported wallet" - # Remove unwanted characters from wallet name - wallet_name=${wallet_name//[^[:alnum:]]/_} - if [[ -z "${wallet_name}" ]]; then - println ERROR "${FG_RED}ERROR${NC}: Empty wallet name, please retry!" - waitToProceed && continue - fi - if ! mkdir -p "${WALLET_FOLDER}/${wallet_name}"; then - println ERROR "${FG_RED}ERROR${NC}: Failed to create directory for wallet:\n${WALLET_FOLDER}/${wallet_name}" - waitToProceed && continue - fi - if [[ $(find "${WALLET_FOLDER}/${wallet_name}" -type f -print0 | wc -c) -gt 0 ]]; then - println "${FG_RED}WARN${NC}: A wallet ${FG_GREEN}$wallet_name${NC} already exists" - println " Choose another name or delete the existing one" - waitToProceed && continue - fi + createNewWallet || continue + getCustomDerivationPath || continue + derivation_path_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_DERIVATION_PATH_FILENAME}" + echo "1852H/1815H/${acct_idx}H/x/${key_idx}" > "${derivation_path_file}" payment_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_HW_PAY_SK_FILENAME}" payment_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_VK_FILENAME}" stake_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_HW_STAKE_SK_FILENAME}" stake_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_VK_FILENAME}" - if ! unlockHWDevice "extract ${FG_LGRAY}payment keys${NC}"; then safeDel "${WALLET_FOLDER}/${wallet_name}"; continue; fi - println ACTION "cardano-hw-cli address key-gen --path 1852H/1815H/0H/0/0 --verification-key-file ${payment_vk_file} --hw-signing-file ${payment_sk_file}" - if ! stdout=$(cardano-hw-cli address key-gen --path 1852H/1815H/0H/0/0 --verification-key-file "${payment_vk_file}" --hw-signing-file "${payment_sk_file}" 2>&1); then - println ERROR "\n${FG_RED}ERROR${NC}: failure during payment key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && continue + drep_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_HW_DREP_SK_FILENAME}" + drep_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_DREP_VK_FILENAME}" + cc_cold_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_HW_CC_COLD_SK_FILENAME}" + cc_cold_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_CC_COLD_VK_FILENAME}" + cc_hot_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_HW_CC_HOT_SK_FILENAME}" + cc_hot_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_CC_HOT_VK_FILENAME}" + ms_payment_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_HW_PAY_SK_FILENAME}" + ms_payment_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_PAY_VK_FILENAME}" + ms_stake_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_HW_STAKE_SK_FILENAME}" + ms_stake_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_STAKE_VK_FILENAME}" + ms_drep_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_GOV_HW_DREP_SK_FILENAME}" + ms_drep_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_GOV_DREP_VK_FILENAME}" + if ! unlockHWDevice "extract ${FG_LGRAY}keys${NC}"; then safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && continue; fi + println "Include governance (drep & committee) keys (only Ledger supported)?" + select_opt "[n] No" "[y] Yes" + case $? in + 0) + HW_DERIVATION_CMD=( + cardano-hw-cli address key-gen + --path 1852H/1815H/${acct_idx}H/0/${key_idx} + --path 1852H/1815H/${acct_idx}H/2/${key_idx} + --path 1854H/1815H/${acct_idx}H/0/${key_idx} + --path 1854H/1815H/${acct_idx}H/2/${key_idx} + --verification-key-file "${payment_vk_file}" + --verification-key-file "${stake_vk_file}" + --verification-key-file "${ms_payment_vk_file}" + --verification-key-file "${ms_stake_vk_file}" + --hw-signing-file "${payment_sk_file}" + --hw-signing-file "${stake_sk_file}" + --hw-signing-file "${ms_payment_sk_file}" + --hw-signing-file "${ms_stake_sk_file}" + ) + ;; # do nothing + 1) + HW_DERIVATION_CMD=( + cardano-hw-cli address key-gen + --path 1852H/1815H/${acct_idx}H/0/${key_idx} + --path 1852H/1815H/${acct_idx}H/2/${key_idx} + --path 1852H/1815H/${acct_idx}H/3/${key_idx} + --path 1852H/1815H/${acct_idx}H/4/${key_idx} + --path 1852H/1815H/${acct_idx}H/5/${key_idx} + --path 1854H/1815H/${acct_idx}H/0/${key_idx} + --path 1854H/1815H/${acct_idx}H/2/${key_idx} + --path 1854H/1815H/${acct_idx}H/3/${key_idx} + --verification-key-file "${payment_vk_file}" + --verification-key-file "${stake_vk_file}" + --verification-key-file "${drep_vk_file}" + --verification-key-file "${cc_cold_vk_file}" + --verification-key-file "${cc_hot_sk_file}" + --verification-key-file "${ms_payment_vk_file}" + --verification-key-file "${ms_stake_vk_file}" + --verification-key-file "${ms_drep_vk_file}" + --hw-signing-file "${payment_sk_file}" + --hw-signing-file "${stake_sk_file}" + --hw-signing-file "${drep_sk_file}" + --hw-signing-file "${cc_cold_sk_file}" + --hw-signing-file "${cc_hot_sk_file}" + --hw-signing-file "${ms_payment_sk_file}" + --hw-signing-file "${ms_stake_sk_file}" + --hw-signing-file "${ms_drep_sk_file}" + ) + ;; + esac + println ACTION "${HW_DERIVATION_CMD[*]}" + if ! stdout=$("${HW_DERIVATION_CMD[@]}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && continue fi jq '.description = "Payment Hardware Verification Key"' "${payment_vk_file}" > "${TMP_DIR}/$(basename "${payment_vk_file}").tmp" && mv -f "${TMP_DIR}/$(basename "${payment_vk_file}").tmp" "${payment_vk_file}" - println DEBUG "${FG_BLUE}INFO${NC}: repeat and follow instructions on hardware device to extract the ${FG_LGRAY}stake keys${NC}" - println ACTION "cardano-hw-cli address key-gen --path 1852H/1815H/0H/2/0 --verification-key-file ${stake_vk_file} --hw-signing-file ${stake_sk_file}" - if ! stdout=$(cardano-hw-cli address key-gen --path 1852H/1815H/0H/2/0 --verification-key-file "${stake_vk_file}" --hw-signing-file "${stake_sk_file}" 2>&1); then - println ERROR "\n${FG_RED}ERROR${NC}: failure during stake key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && continue - fi jq '.description = "Stake Hardware Verification Key"' "${stake_vk_file}" > "${TMP_DIR}/$(basename "${stake_vk_file}").tmp" && mv -f "${TMP_DIR}/$(basename "${stake_vk_file}").tmp" "${stake_vk_file}" + jq '.description = "Delegate Representative Hardware Verification Key"' "${drep_vk_file}" > "${TMP_DIR}/$(basename "${drep_vk_file}").tmp" && mv -f "${TMP_DIR}/$(basename "${drep_vk_file}").tmp" "${drep_vk_file}" + jq '.description = "Constitutional Committee Cold Hardware Verification Key"' "${cc_cold_vk_file}" > "${TMP_DIR}/$(basename "${cc_cold_vk_file}").tmp" && mv -f "${TMP_DIR}/$(basename "${cc_cold_vk_file}").tmp" "${cc_cold_vk_file}" + jq '.description = "Constitutional Committee Hot Hardware Verification Key"' "${cc_hot_sk_file}" > "${TMP_DIR}/$(basename "${cc_hot_sk_file}").tmp" && mv -f "${TMP_DIR}/$(basename "${cc_hot_sk_file}").tmp" "${cc_hot_sk_file}" + jq '.description = "MultiSig Payment Hardware Verification Key"' "${ms_payment_vk_file}" > "${TMP_DIR}/$(basename "${ms_payment_vk_file}").tmp" && mv -f "${TMP_DIR}/$(basename "${ms_payment_vk_file}").tmp" "${ms_payment_vk_file}" + jq '.description = "MultiSig Stake Hardware Verification Key"' "${ms_stake_vk_file}" > "${TMP_DIR}/$(basename "${ms_stake_vk_file}").tmp" && mv -f "${TMP_DIR}/$(basename "${ms_stake_vk_file}").tmp" "${ms_stake_vk_file}" + jq '.description = "MultiSig Delegate Representative Hardware Verification Key"' "${ms_drep_vk_file}" > "${TMP_DIR}/$(basename "${ms_drep_vk_file}").tmp" && mv -f "${TMP_DIR}/$(basename "${ms_drep_vk_file}").tmp" "${ms_drep_vk_file}" getBaseAddress ${wallet_name} getPayAddress ${wallet_name} getRewardAddress ${wallet_name} + getCredentials ${wallet_name} echo println "HW Wallet Imported : ${FG_GREEN}${wallet_name}${NC}" println "Address : ${FG_LGRAY}${base_addr}${NC}" - println "Enterprise Address : ${FG_LGRAY}${pay_addr}${NC}" + println "Payment Address : ${FG_LGRAY}${pay_addr}${NC}" echo - println DEBUG "You can now send and receive ADA using the above addresses. Note that Enterprise Address will not take part in staking" - echo - println DEBUG "All transaction signing is now done through hardware device, please follow directions in both CNTools and the device display!" - println DEBUG "${FG_YELLOW}Using an imported hardware wallet in CNTools comes with a few limitations${NC}" - echo - println DEBUG "Most operations like delegation and sending funds is seamless. For pool registration/modification however the following apply:" - println DEBUG " ${FG_LGRAY}>${NC} Pool owner has to be a CLI wallet with enough funds to pay for pool registration deposit and transaction fee" - println DEBUG " ${FG_LGRAY}>${NC} Add the hardware wallet containing the pledge as a multi-owner to the pool" - println DEBUG " ${FG_LGRAY}>${NC} The hardware wallet can be used as the reward wallet, but has to be included as a multi-owner if it should be counted to pledge" - echo - println DEBUG "Only the first address in the HD wallet is extracted and because of this the following apply if also synced with Daedalus/Yoroi:" - println DEBUG " ${FG_LGRAY}>${NC} Address above should match the first address seen in Daedalus/Yoroi, please verify!!!" - println DEBUG " ${FG_LGRAY}>${NC} If restored wallet contain funds since before, send all ADA through Daedalus/Yoroi to address shown in CNTools" - println DEBUG " ${FG_LGRAY}>${NC} Only use the address shown in CNTools to receive funds" - println DEBUG " ${FG_LGRAY}>${NC} Only spend ADA from CNTools, if spent through Daedalus/Yoroi balance seen in CNTools wont match" + printWalletInfo waitToProceed && continue ;; ################################################################### esac # wallet >> import sub OPERATION @@ -652,9 +665,9 @@ function main { if ! selectOpMode; then continue; fi fi echo - println DEBUG "# Select wallet to register (only non-registered wallets shown)" + println DEBUG "Select wallet to register (only non-registered wallets shown)" if [[ ${op_mode} = "online" ]]; then - selectWallet "non-reg" "${WALLET_PAY_VK_FILENAME}" "${WALLET_STAKE_VK_FILENAME}" + selectWallet "non-reg" case $? in 1) waitToProceed; continue ;; 2) continue ;; @@ -665,7 +678,7 @@ function main { 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitToProceed && continue ;; esac else - selectWallet "non-reg" "${WALLET_PAY_VK_FILENAME}" "${WALLET_STAKE_VK_FILENAME}" + selectWallet "non-reg" case $? in 1) waitToProceed; continue ;; 2) continue ;; @@ -685,7 +698,7 @@ function main { if ! registerStakeWallet ${wallet_name} "true"; then waitToProceed && continue fi - println "\n${FG_GREEN}${wallet_name}${NC} successfully registered on chain!" + println "${FG_GREEN}${wallet_name}${NC} successfully registered on chain!" waitToProceed && continue ;; ################################################################### deregister) @@ -701,9 +714,9 @@ function main { if ! selectOpMode; then continue; fi fi echo - println DEBUG "# Select wallet to de-register (only registered wallets shown)" + println DEBUG "Select wallet to de-register (only registered wallets shown)" if [[ ${op_mode} = "online" ]]; then - selectWallet "reg" "${WALLET_PAY_VK_FILENAME}" "${WALLET_STAKE_VK_FILENAME}" + selectWallet "reg" case $? in 1) waitToProceed; continue ;; 2) continue ;; @@ -714,7 +727,7 @@ function main { 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitToProceed && continue ;; esac else - selectWallet "reg" "${WALLET_PAY_VK_FILENAME}" "${WALLET_STAKE_VK_FILENAME}" + selectWallet "reg" case $? in 1) waitToProceed; continue ;; 2) continue ;; @@ -783,7 +796,7 @@ function main { postfix="- ${FG_LGRAY}UNREGISTERED${NC}" fi getWalletType ${wallet_name} - [[ $? -eq 5 ]] && postfix="${postfix} (${FG_LGRAY}multi-sig${NC})" + [[ $? -eq 5 ]] && postfix="${postfix} (${FG_LGRAY}MultiSig${NC})" [[ ${enc_files} -gt 0 ]] && postfix="${postfix} (${FG_YELLOW}encrypted${NC})" if [[ ${enc_files} -gt 0 && ${registered} = "yes" ]]; then println "${FG_GREEN}${wallet_name}${NC} - ${FG_LGRAY}REGISTERED${NC} (${FG_YELLOW}encrypted${NC})" @@ -794,17 +807,21 @@ function main { else println "${FG_GREEN}${wallet_name}${NC}" fi + getWalletType ${wallet_name} + case $? in + 0) println "$(printf "%-15s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Type" "Hardware")" ;; + 1) println "$(printf "%-15s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Type" "CLI")" ;; + 5) println "$(printf "%-15s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Type" "MultiSig")" ;; + esac getBaseAddress ${wallet_name} getPayAddress ${wallet_name} - getPayScriptAddress ${wallet_name} - if [[ -z ${base_addr} && -z ${pay_addr} && -z ${pay_script_addr} ]]; then - println ERROR "${FG_RED}ERROR${NC}: wallet missing pay/base/script addr files or vkey/script files to generate them!" + if [[ -z ${base_addr} && -z ${pay_addr} ]]; then + println ERROR "${FG_RED}ERROR${NC}: wallet missing pay/base addr files or vkey/script files to generate them!" continue fi if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then [[ -n ${base_addr} ]] && println "$(printf "%-15s : ${FG_LGRAY}%s${NC}" "Address" "${base_addr}")" - [[ -n ${pay_addr} ]] && println "$(printf "%-15s : ${FG_LGRAY}%s${NC}" "Enterprise Addr" "${pay_addr}")" - [[ -n ${pay_script_addr} ]] && println "$(printf "%-15s : ${FG_LGRAY}%s${NC}" "Script Addr" "${pay_script_addr}")" + [[ -n ${pay_addr} ]] && println "$(printf "%-15s : ${FG_LGRAY}%s${NC}" "Payment Addr" "${pay_addr}")" else if [[ -n ${base_addr} ]]; then lovelace=0 @@ -820,11 +837,11 @@ function main { asset_cnt=$(( ${#assets[@]} - 1 )) fi getPriceString ${lovelace} - println "$(printf "%-19s : ${FG_LGRAY}%s${NC}" "Address" "${base_addr}")" + println "$(printf "%-15s : ${FG_LGRAY}%s${NC}" "Address" "${base_addr}")" if [[ ${asset_cnt} -eq 0 ]]; then - println "$(printf "%-19s : ${FG_LBLUE}%s${NC} ADA${price_str}" "Funds" "$(formatLovelace ${lovelace})")" + println "$(printf "%-15s : ${FG_LBLUE}%s${NC} ADA${price_str}" "Funds" "$(formatLovelace ${lovelace})")" else - println "$(printf "%-19s : ${FG_LBLUE}%s${NC} ADA${price_str} - ${FG_LBLUE}%s${NC} additional asset(s) on address! [WALLET >> SHOW for details]" "Funds" "$(formatLovelace ${lovelace})" "${asset_cnt}")" + println "$(printf "%-15s : ${FG_LBLUE}%s${NC} ADA${price_str} - ${FG_LBLUE}%s${NC} additional asset(s) on address! [WALLET >> SHOW for details]" "Base Funds" "$(formatLovelace ${lovelace})" "${asset_cnt}")" fi fi if [[ -n ${pay_addr} ]]; then @@ -842,56 +859,32 @@ function main { fi getPriceString ${lovelace} if [[ ${lovelace} -gt 0 ]]; then - println "$(printf "%-19s : ${FG_LGRAY}%s${NC}" "Enterprise Address" "${pay_addr}")" - if [[ ${asset_cnt} -eq 0 ]]; then - println "$(printf "%-19s : ${FG_LBLUE}%s${NC} ADA${price_str}" "Enterprise Funds" "$(formatLovelace ${lovelace})")" - else - println "$(printf "%-19s : ${FG_LBLUE}%s${NC} ADA${price_str} - ${FG_LBLUE}%s${NC} additional asset(s) on address! [WALLET >> SHOW for details]" "Enterprise Funds" "$(formatLovelace ${lovelace})" "${asset_cnt}")" - fi - fi - fi - if [[ -n ${pay_script_addr} ]]; then - lovelace=0 - asset_cnt=0 - if [[ -n ${KOIOS_API} ]]; then - for key in "${!assets[@]}"; do - [[ ${key} = "${pay_script_addr},lovelace" ]] && lovelace=${assets["${pay_script_addr},lovelace"]} && continue - [[ ${key} = "${pay_script_addr},"* ]] && ((asset_cnt++)) - done - else - getBalance ${pay_script_addr} - lovelace=${assets[lovelace]} - asset_cnt=$(( ${#assets[@]} - 1 )) - fi - getPriceString ${lovelace} - if [[ ${lovelace} -gt 0 ]]; then - println "$(printf "%-19s : ${FG_LGRAY}%s${NC}" "Script Address" "${pay_script_addr}")" + println "$(printf "%-15s : ${FG_LGRAY}%s${NC}" "Payment Addr" "${pay_addr}")" if [[ ${asset_cnt} -eq 0 ]]; then - println "$(printf "%-19s : ${FG_LBLUE}%s${NC} ADA${price_str}" "Script Funds" "$(formatLovelace ${lovelace})")" + println "$(printf "%-15s : ${FG_LBLUE}%s${NC} ADA${price_str}" "Payment Funds" "$(formatLovelace ${lovelace})")" else - println "$(printf "%-19s : ${FG_LBLUE}%s${NC} ADA${price_str} - ${FG_LBLUE}%s${NC} additional asset(s) on address! [WALLET >> SHOW for details]" "Script Funds" "$(formatLovelace ${lovelace})" "${asset_cnt}")" + println "$(printf "%-15s : ${FG_LBLUE}%s${NC} ADA${price_str} - ${FG_LBLUE}%s${NC} additional asset(s) on address! [WALLET >> SHOW for details]" "Payment Funds" "$(formatLovelace ${lovelace})" "${asset_cnt}")" fi fi fi if [[ -n ${KOIOS_API} ]]; then [[ -v rewards_available[${reward_addr}] ]] && reward_lovelace=${rewards_available[${reward_addr}]} || reward_lovelace=0 - delegation_pool_id=${reward_pool[${reward_addr}]} + pool_delegation=${reward_pool[${reward_addr}]} else getWalletRewards ${wallet_name} - delegation_pool_id=$(jq -r '.[0].delegation // empty' <<< "${stake_address_info}") fi if [[ ${reward_lovelace} -gt 0 ]]; then getPriceString ${reward_lovelace} - println "$(printf "%-19s : ${FG_LBLUE}%s${NC} ADA${price_str}" "Rewards" "$(formatLovelace ${reward_lovelace})")" - if [[ -n ${delegation_pool_id} ]]; then + println "$(printf "%-15s : ${FG_LBLUE}%s${NC} ADA${price_str}" "Rewards" "$(formatLovelace ${reward_lovelace})")" + if [[ -n ${pool_delegation} ]]; then unset poolName while IFS= read -r -d '' pool; do getPoolID "$(basename ${pool})" - if [[ "${pool_id_bech32}" = "${delegation_pool_id}" ]]; then + if [[ "${pool_id_bech32}" = "${pool_delegation}" ]]; then poolName=$(basename ${pool}) && break fi done < <(find "${POOL_FOLDER}" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z) - println "${FG_RED}Delegated${NC} to ${FG_GREEN}${poolName}${NC} ${FG_LGRAY}(${delegation_pool_id})${NC}" + println "${FG_RED}Delegated${NC} to ${FG_GREEN}${poolName}${NC} ${FG_LGRAY}(${pool_delegation})${NC}" fi fi fi @@ -923,12 +916,10 @@ function main { fi getBaseAddress ${wallet_name} getPayAddress ${wallet_name} - getPayScriptAddress ${wallet_name} - if [[ -z ${base_addr} && -z ${pay_addr} && -z ${pay_script_addr} ]]; then - println ERROR "\n${FG_RED}ERROR${NC}: wallet missing pay/base/script addr files or vkey/script files to generate them!" + if [[ -z ${base_addr} && -z ${pay_addr} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: wallet missing pay/base addr files or vkey/script files to generate them!" waitToProceed && continue fi - getCredentials ${wallet_name} getRewardAddress ${wallet_name} if [[ -n ${KOIOS_API} ]]; then tput sc @@ -936,7 +927,6 @@ function main { addr_list=() [[ -n ${base_addr} ]] && addr_list+=("${base_addr}") [[ -n ${pay_addr} ]] && addr_list+=("${pay_addr}") - [[ -n ${pay_script_addr} ]] && addr_list+=("${pay_script_addr}") reward_addr_list=("${reward_addr}") [[ ${#addr_list[@]} -gt 0 ]] && getBalanceKoios [[ ${#reward_addr_list[@]} -gt 0 ]] && getRewardInfoKoios @@ -944,7 +934,7 @@ function main { fi total_lovelace=0 if [[ ${CNTOOLS_MODE} != "OFFLINE" ]]; then - for i in {1..3}; do + for i in {1..2}; do if [[ $i -eq 1 ]]; then [[ -z ${base_addr} ]] && continue address_type="Base" @@ -956,32 +946,19 @@ function main { base_lovelace=${assets[lovelace]} fi total_lovelace=$((total_lovelace + base_lovelace)) - elif [[ $i -eq 2 ]]; then + else [[ -z ${pay_addr} ]] && continue - address_type="Enterprise" + address_type="Payment" address=${pay_addr} if [[ -n ${KOIOS_API} ]]; then pay_lovelace=${assets["${pay_addr},lovelace"]} - [[ ${utxos_cnt["${pay_addr}"]:-0} -eq 0 ]] && continue # Dont print Enterprise if empty + [[ ${utxos_cnt["${pay_addr}"]:-0} -eq 0 ]] && continue # Dont print if empty else getBalance ${pay_addr} pay_lovelace=${assets[lovelace]} - [[ ${utxo_cnt} -eq 0 ]] && continue # Dont print Enterprise if empty + [[ ${utxo_cnt} -eq 0 ]] && continue # Dont print if empty fi total_lovelace=$((total_lovelace + pay_lovelace)) - else - [[ -z ${pay_script_addr} ]] && continue - address_type="Script" - address=${pay_script_addr} - if [[ -n ${KOIOS_API} ]]; then - pay_script_lovelace=${assets["${pay_script_addr},lovelace"]} - [[ ${utxos_cnt["${pay_script_addr}"]:-0} -eq 0 ]] && continue # Dont print Script if empty - else - getBalance ${pay_script_addr} - pay_script_lovelace=${assets[lovelace]} - [[ ${utxo_cnt} -eq 0 ]] && continue # Dont print Script if empty - fi - total_lovelace=$((total_lovelace + pay_script_lovelace)) fi echo @@ -1053,48 +1030,72 @@ function main { case $? in 0) println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Type" "Hardware")" ;; 1) println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Type" "CLI")" ;; - 5) println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Type" "Multi-Sig")" ;; + 5) println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Type" "MultiSig")" ;; esac + derivation_path_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_DERIVATION_PATH_FILENAME}" + if getSavedDerivationPath "${derivation_path_file}"; then + println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Derivation Path" "${derivation_path}")" + fi + if [[ -f ${payment_script_file} ]]; then - if timelock_after=$(jq -er '.scripts[0].type' "${payment_script_file}") && [[ ${timelock_after} = "after" ]]; then - timelock_slot=$(jq -r '.scripts[0].slot' "${payment_script_file}") - timelock_date=$(getDateFromSlot ${timelock_slot} '%(%F %T %Z)T') - [[ $(getSlotTipRef) -gt ${timelock_slot} ]] && timelock_color="${FG_GREEN}" || timelock_color="${FG_YELLOW}" + unset timelock_after atleast total_signers script_sig_list + while read -r _slot; do + timelock_after=${_slot} + break + done < <( jq -r '.. | select(.type?=="after") | .slot' "${payment_script_file}" ) + while IFS=',' read -r _required _total _sig_list; do + atleast=${_required} + total_signers=${_total} + IFS=$'\t' read -ra script_sig_list <<< "${_sig_list}" + break + done < <( jq -r '.. | select(.type?=="atLeast") | "\(.required),\(.scripts|length),\(.scripts|map(.keyHash)|@tsv)"' "${payment_script_file}" ) + if [[ -n ${timelock_after} ]]; then + timelock_date=$(getDateFromSlot ${timelock_after} '%(%F %T %Z)T') + [[ $(getSlotTipRef) -gt ${timelock_after} ]] && timelock_color="${FG_GREEN}" || timelock_color="${FG_YELLOW}" println "$(printf "%-20s ${FG_DGRAY}:${NC} ${timelock_color}%s${NC}" "Time Locked Until" "${timelock_date}")" fi - if atleast=$(jq -er '.scripts[1].type' "${payment_script_file}") && [[ ${atleast} = "atLeast" ]]; then - cred_header="Multi-Sig Creds ($(jq -r '.scripts[1].scripts|length' "${payment_script_file}"))" - while read -r _sig; do + if [[ -n ${atleast} ]]; then + cred_header="MultiSig Creds (${total_signers})" + for _sig in "${script_sig_list[@]}"; do unset wallet_str while IFS= read -r -d '' wallet; do getCredentials "$(basename ${wallet})" - if [[ ${pay_cred} = ${_sig} ]]; then + if [[ ${ms_pay_cred} = ${_sig} ]]; then wallet_str=" (${FG_GREEN}$(basename ${wallet})${NC})" && break fi done < <(find "${WALLET_FOLDER}" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z) println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}%s" "${cred_header}" "${_sig}" "${wallet_str}")" unset cred_header - done < <( jq -r '.scripts[1].scripts[].keyHash' "${payment_script_file}") - println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Required signers" "$(jq -r '.scripts[1].required' "${payment_script_file}")")" + done + println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Required signers" "${atleast}")" fi fi [[ -n ${base_addr} ]] && println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Address" "${base_addr}")" - [[ -n ${pay_addr} ]] && println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Enterprise Address" "${pay_addr}")" - [[ -n ${pay_script_addr} ]] && println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Script Address" "${pay_script_addr}")" + if [[ -n ${pay_addr} ]]; then + println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Payment Address" "${pay_addr}")" + fi [[ -n ${reward_addr} ]] && println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Reward/Stake Address" "${reward_addr}")" - [[ -n ${pay_cred} ]] && println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Payment Credential" "${pay_cred}")" - [[ -n ${stake_cred} ]] && println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Stake Credential" "${stake_cred}")" + getCredentials ${wallet_name} + if [[ -n ${pay_cred} || -n ${stake_cred} || -n ${ms_pay_cred} || -n ${ms_stake_cred} ]]; then + println "${FG_DGRAY}# Credentials${NC}" + fi + [[ -n ${pay_cred} ]] && println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Payment" "${pay_cred}")" + [[ -n ${stake_cred} ]] && println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Stake" "${stake_cred}")" + [[ -n ${ms_pay_cred} ]] && println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "MultiSig Payment" "${ms_pay_cred}")" + [[ -n ${ms_stake_cred} ]] && println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "MultiSig Stake" "${ms_stake_cred}")" + [[ -n ${script_pay_cred} ]] && println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Script Payment" "${script_pay_cred}")" + [[ -n ${script_stake_cred} ]] && println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Script Stake" "${script_stake_cred}")" if [[ ${CNTOOLS_MODE} != "OFFLINE" ]]; then + println "${FG_DGRAY}# Funds${NC}" if [[ -n ${reward_addr} ]]; then if [[ -n ${KOIOS_API} ]]; then [[ -v rewards_available[${reward_addr}] ]] && reward_lovelace=${rewards_available[${reward_addr}]} || reward_lovelace=0 - delegation_pool_id=${reward_pool[${reward_addr}]} + pool_delegation=${reward_pool[${reward_addr}]} else getRewardsFromAddr ${reward_addr} - delegation_pool_id=$(jq -r '.[0].delegation // empty' <<< "${stake_address_info}") fi total_lovelace=$((total_lovelace + reward_lovelace)) getPriceString ${reward_lovelace} @@ -1102,25 +1103,72 @@ function main { fi getPriceString ${total_lovelace} println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LBLUE}%s${NC} ADA${price_str}" "Funds + Rewards" "$(formatLovelace ${total_lovelace})")" - if [[ -n ${delegation_pool_id} ]]; then + if [[ -n ${pool_delegation} ]]; then unset poolName while IFS= read -r -d '' pool; do getPoolID "$(basename ${pool})" - if [[ "${pool_id_bech32}" = "${delegation_pool_id}" ]]; then + if [[ "${pool_id_bech32}" = "${pool_delegation}" ]]; then poolName=$(basename ${pool}) && break fi done < <(find "${POOL_FOLDER}" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z) echo - println "${FG_RED}Delegated${NC} to ${FG_GREEN}${poolName}${NC} ${FG_LGRAY}(${delegation_pool_id})${NC}" + println "${FG_RED}Delegated${NC} to ${FG_GREEN}${poolName}${NC} ${FG_LGRAY}(${pool_delegation})${NC}" fi fi - if [[ -z ${pay_addr} || -z ${pay_script_addr} || -z ${base_addr} || -z ${reward_addr} ]]; then - echo - if [[ -z ${pay_addr} && -z ${pay_script_addr} ]]; then - println "${FG_YELLOW}INFO${NC}: '${FG_LGRAY}${WALLET_PAY_ADDR_FILENAME}${NC}' missing and '${FG_LGRAY}${WALLET_PAY_VK_FILENAME}${NC}' to generate it!" + if [[ -z ${pay_addr} && -z ${pay_script_addr} ]]; then + println "\n${FG_YELLOW}INFO${NC}: '${FG_LGRAY}${WALLET_PAY_ADDR_FILENAME}${NC}' missing and '${FG_LGRAY}${WALLET_PAY_VK_FILENAME}${NC}' to generate it!" + fi + [[ -z ${base_addr} ]] && println "\n${FG_YELLOW}INFO${NC}: '${FG_LGRAY}${WALLET_BASE_ADDR_FILENAME}${NC}' missing and '${FG_LGRAY}${WALLET_PAY_VK_FILENAME}${NC}/${FG_LGRAY}${WALLET_STAKE_VK_FILENAME}${NC}' to generate it!" + [[ -z ${reward_addr} ]] && println "\n${FG_YELLOW}INFO${NC}: '${FG_LGRAY}${WALLET_STAKE_ADDR_FILENAME}${NC}' missing and '${FG_LGRAY}${WALLET_STAKE_VK_FILENAME}${NC}' to generate it!" + + drep_script_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_DREP_SCRIPT_FILENAME}" + if [[ ${CNTOOLS_MODE} != "OFFLINE" && ! -f "${drep_script_file}" ]] && versionCheck "10.0" "${PROT_VERSION}"; then + println "DEBUG" "\nGovernance Vote Delegation Status" + unset walletName + if getWalletVoteDelegation ${wallet_name}; then + unset vote_delegation_hash + vote_delegation_type="${vote_delegation%-*}" + if [[ ${vote_delegation} = *-* ]]; then + vote_delegation_hash="${vote_delegation#*-}" + vote_delegation=$(bech32 drep <<< ${vote_delegation_hash}) + while IFS= read -r -d '' _wallet; do + getGovKeyInfo "$(basename ${_wallet})" + if [[ "${drep_id}" = "${vote_delegation}" ]]; then + walletName=" ${FG_GREEN}$(basename ${_wallet})${NC}" && break + fi + done < <(find "${WALLET_FOLDER}" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z) + fi + println "Delegation : ${FG_LGRAY}${vote_delegation}${NC}${walletName}" + if [[ ${vote_delegation} = always* ]]; then + : # do nothing + elif getDRepStatus ${vote_delegation_type} ${vote_delegation_hash}; then + [[ $(getEpoch) -lt ${drep_expiry} ]] && expire_status="${FG_GREEN}active${NC}" || expire_status="${FG_RED}inactive${NC} (vote power does not count)" + println "DRep expiry : epoch ${FG_LBLUE}${drep_expiry}${NC} - ${expire_status}" + if [[ -n ${drep_anchor_url} ]]; then + println "DRep anchor url : ${FG_LGRAY}${drep_anchor_url}${NC}" + getDRepAnchor "${drep_anchor_url}" "${drep_anchor_hash}" + case $? in + 0) println "DRep anchor data :\n${FG_LGRAY}" + jq -er "${drep_anchor_file}" 2>/dev/null || cat "${drep_anchor_file}" + println DEBUG "${NC}" + ;; + 1) println "DRep anchor data : ${FG_YELLOW}Invalid URL or currently not available${NC}" ;; + 2) println "DRep anchor data :\n${FG_LGRAY}" + jq -er "${drep_anchor_file}" 2>/dev/null || cat "${drep_anchor_file}" + println "${NC}DRep anchor hash : ${FG_YELLOW}mismatch${NC}" + println " registered : ${FG_LGRAY}${drep_anchor_hash}${NC}" + println " actual : ${FG_LGRAY}${drep_anchor_real_hash}${NC}" + ;; + esac + fi + else + println "Status : ${FG_RED}Unable to get DRep status, retired?${NC}" + fi + getDRepVotePower ${vote_delegation_type} ${vote_delegation_hash} + println "Active Vote power : ${FG_LBLUE}$(formatLovelace ${vote_power:=0})${NC} ADA (${FG_LBLUE}${vote_power_pct:=0} %${NC})" + else + println "Delegation : ${FG_YELLOW}undelegated${NC} - please note that reward withdrawals will not work in the future until wallet is vote delegated" fi - [[ -z ${base_addr} ]] && println "${FG_YELLOW}INFO${NC}: '${FG_LGRAY}${WALLET_BASE_ADDR_FILENAME}${NC}' missing and '${FG_LGRAY}${WALLET_PAY_VK_FILENAME}${NC}/${FG_LGRAY}${WALLET_STAKE_VK_FILENAME}${NC}' to generate it!" - [[ -z ${reward_addr} ]] && println "${FG_YELLOW}INFO${NC}: '${FG_LGRAY}${WALLET_STAKE_ADDR_FILENAME}${NC}' missing and '${FG_LGRAY}${WALLET_STAKE_VK_FILENAME}${NC}' to generate it!" fi waitToProceed && continue ;; ################################################################### @@ -1134,7 +1182,7 @@ function main { println DEBUG "${FG_LGRAY}OFFLINE MODE${NC}: CNTools started in offline mode, unable to verify wallet balance" fi echo - println DEBUG "# Select wallet to remove" + println DEBUG "Select wallet to remove" selectWallet "balance" case $? in 1) waitToProceed; continue ;; @@ -1179,8 +1227,8 @@ function main { esac else println "${FG_RED}WARN${NC}: wallet ${FG_GREEN}${wallet_name}${NC} not empty!" - [[ ${base_lovelace} -gt 0 ]] && println "Funds : ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA" - [[ ${pay_lovelace} -gt 0 ]] && println "Enterprise Funds : ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA" + [[ ${base_lovelace} -gt 0 ]] && println "Base Funds : ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA" + [[ ${pay_lovelace} -gt 0 ]] && println "Payment Funds : ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA" [[ ${reward_lovelace} -gt 0 ]] && println "Rewards : ${FG_LBLUE}$(formatLovelace ${reward_lovelace})${NC} ADA" echo println DEBUG "${FG_RED}WARN${NC}: Deleting this wallet is final and you can not recover it unless you have a backup\n" @@ -1202,7 +1250,7 @@ function main { println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && println "${FG_YELLOW}No wallets available!${NC}" && waitToProceed && continue - println DEBUG "# Select wallet to decrypt" + println DEBUG "Select wallet to decrypt" selectWallet "encrypted" case $? in 1) waitToProceed; continue ;; @@ -1211,7 +1259,7 @@ function main { filesUnlocked=0 keysDecrypted=0 echo - println DEBUG "# Removing write protection from all wallet files" + println DEBUG "Removing write protection from all wallet files" while IFS= read -r -d '' file; do unlockFile "${file}" filesUnlocked=$((++filesUnlocked)) @@ -1219,7 +1267,7 @@ function main { done < <(find "${WALLET_FOLDER}/${wallet_name}" -mindepth 1 -maxdepth 1 -type f -print0) if [[ $(find "${WALLET_FOLDER}/${wallet_name}" -mindepth 1 -maxdepth 1 -type f -name '*.gpg' -print0 | wc -c) -gt 0 ]]; then echo - println DEBUG "# Decrypting GPG encrypted wallet files" + println DEBUG "Decrypting GPG encrypted wallet files" echo if ! getPasswordCust; then # $password variable populated by getPasswordCust function println "\n\n" && println ERROR "${FG_RED}ERROR${NC}: password input aborted!" @@ -1250,7 +1298,7 @@ function main { println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitToProceed && continue - println DEBUG "# Select wallet to encrypt" + println DEBUG "Select wallet to encrypt" selectWallet "encrypted" case $? in 1) waitToProceed; continue ;; @@ -1260,7 +1308,7 @@ function main { keysEncrypted=0 if [[ $(find "${WALLET_FOLDER}/${wallet_name}" -mindepth 1 -maxdepth 1 -type f -name '*.gpg' -print0 | wc -c) -le 0 ]]; then echo - println DEBUG "# Encrypting sensitive wallet keys with GPG" + println DEBUG "Encrypting sensitive wallet keys with GPG" echo if ! getPasswordCust confirm; then # $password variable populated by getPasswordCust function println "\n\n" && println ERROR "${FG_RED}ERROR${NC}: password input aborted!" @@ -1284,7 +1332,7 @@ function main { waitToProceed && continue fi echo - println DEBUG "# Write protecting all wallet keys with 400 permission and if enabled 'chattr +i'" + println DEBUG "Write protecting all wallet keys with 400 permission and if enabled 'chattr +i'" while IFS= read -r -d '' file; do [[ ${file} = *.addr ]] && continue lockFile "${file}" @@ -1340,9 +1388,9 @@ function main { echo # source wallet - println DEBUG "# Select ${FG_YELLOW}source${NC} wallet" + println DEBUG "Select ${FG_YELLOW}source${NC} wallet" if [[ ${op_mode} = "online" ]]; then - selectWallet "balance" "${WALLET_PAY_VK_FILENAME}" + selectWallet "balance" case $? in 1) waitToProceed; continue ;; 2) continue ;; @@ -1353,7 +1401,7 @@ function main { 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitToProceed && continue ;; esac else - selectWallet "balance" "${WALLET_PAY_VK_FILENAME}" + selectWallet "balance" case $? in 1) waitToProceed; continue ;; 2) continue ;; @@ -1368,10 +1416,10 @@ function main { # Both payment and base address available with funds, let user choose what to use println DEBUG "Select source wallet address" if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then - println DEBUG "$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA" "Funds :" "$(formatLovelace ${base_lovelace})")" - println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Enterprise Funds :" "$(formatLovelace ${pay_lovelace})")" + println DEBUG "$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA" "Base Funds :" "$(formatLovelace ${base_lovelace})")" + println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Payment Funds :" "$(formatLovelace ${pay_lovelace})")" fi - select_opt "[b] Base (default)" "[e] Enterprise" "[Esc] Cancel" + select_opt "[b] Base (default)" "[e] Payment" "[Esc] Cancel" case $? in 0) s_addr="${base_addr}" ;; 1) s_addr="${pay_addr}" ;; @@ -1381,12 +1429,12 @@ function main { elif [[ ${pay_lovelace} -gt 0 ]]; then s_addr="${pay_addr}" if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then - println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA\n" "Enterprise Funds :" "$(formatLovelace ${pay_lovelace})")" + println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA\n" "Payment Funds :" "$(formatLovelace ${pay_lovelace})")" fi elif [[ ${base_lovelace} -gt 0 ]]; then s_addr="${base_addr}" if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then - println DEBUG "$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA\n" "Funds :" "$(formatLovelace ${base_lovelace})")" + println DEBUG "$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA\n" "Base Funds :" "$(formatLovelace ${base_lovelace})")" fi else println ERROR "${FG_RED}ERROR${NC}: no funds available for wallet ${FG_GREEN}${s_wallet}${NC}" @@ -1395,7 +1443,7 @@ function main { # Destination d_wallet="" - println DEBUG "# Select ${FG_YELLOW}destination${NC} type" + println DEBUG "Select ${FG_YELLOW}destination${NC} type" select_opt "[w] Wallet" "[a] Address" "[Esc] Cancel" case $? in 0) selectWallet "cache" @@ -1407,8 +1455,8 @@ function main { getBaseAddress ${d_wallet} getPayAddress ${d_wallet} if [[ -n "${base_addr}" && "${base_addr}" != "${s_addr}" && -n "${pay_addr}" && "${pay_addr}" != "${s_addr}" ]]; then - # Both base and enterprise address available, let user choose what to use - select_opt "[b] Base (default)" "[e] Enterprise" "[Esc] Cancel" + # Both base and payment address available, let user choose what to use + select_opt "[b] Base (default)" "[e] Payment" "[Esc] Cancel" case $? in 0) d_addr="${base_addr}" ;; 1) d_addr="${pay_addr}" ;; @@ -1500,11 +1548,11 @@ function main { [[ ${assets_to_send[${idx}]} -gt 0 ]] && assets_tx_out_d+="+${assets_to_send[${idx}]} ${idx}" done getMinUTxO "${d_addr}+1${assets_tx_out_d}" - println DEBUG "\n# Amount to Send (in ADA)" + println DEBUG "\nAmount to Send (in ADA)" println DEBUG " Valid entry:" println DEBUG " ${FG_LGRAY}>${NC} Integer (e.g. 15) or Decimal (e.g. 956.1235), commas allowed as thousand separator" println DEBUG " ${FG_LGRAY}>${NC} The string '${FG_YELLOW}all${NC}' sends all available funds in source wallet" - println DEBUG " Multi-Asset Info:" + println DEBUG " Asset Info:" println DEBUG " ${FG_LGRAY}>${NC} If '${FG_YELLOW}all${NC}' is used and the wallet contain multiple assets," println DEBUG " ${FG_LGRAY}>${NC} you will be asked to transfer all assets (incl ADA) to the destination address" println DEBUG " Minimum Amount: ${FG_LBLUE}$(formatLovelace ${min_utxo_out})${NC} ADA" @@ -1556,7 +1604,7 @@ function main { fi # Optional metadata/message - println "\n# Add a message to the transaction?" + println "\nAdd a message to the transaction?" select_opt "[n] No" "[y] Yes" case $? in 0) unset metafile ;; @@ -1604,11 +1652,12 @@ function main { fi echo if ! verifyTx ${s_addr}; then waitToProceed && continue; fi - s_balance=${assets[${index_prefix}lovelace]} + getAddressBalance ${s_addr} true + s_balance=${lovelace} getAddressBalance ${d_addr} true d_balance=${lovelace} getPayAddress ${s_wallet} - [[ "${pay_addr}" = "${s_addr}" ]] && s_wallet_type=" (Enterprise)" || s_wallet_type="" + [[ "${pay_addr}" = "${s_addr}" ]] && s_wallet_type=" (payment)" || s_wallet_type="" echo println "Transaction" println " From : ${FG_GREEN}${s_wallet}${NC}${s_wallet_type}" @@ -1619,7 +1668,7 @@ function main { done if [[ -n "${d_wallet}" ]]; then getPayAddress ${d_wallet} - [[ "${pay_addr}" = "${d_addr}" ]] && d_wallet_type=" (Enterprise)" || d_wallet_type="" + [[ "${pay_addr}" = "${d_addr}" ]] && d_wallet_type=" (payment)" || d_wallet_type="" println " To : ${FG_GREEN}${d_wallet}${NC}${d_wallet_type}" else println " To : ${FG_LGRAY}${d_addr}${NC}" @@ -1643,9 +1692,9 @@ function main { if ! selectOpMode; then continue; fi fi echo - println DEBUG "# Select wallet to delegate" + println DEBUG "Select wallet to delegate" if [[ ${op_mode} = "online" ]]; then - selectWallet "delegate" "${WALLET_PAY_VK_FILENAME}" "${WALLET_STAKE_VK_FILENAME}" + selectWallet "delegate" case $? in 1) waitToProceed; continue ;; 2) continue ;; @@ -1656,7 +1705,7 @@ function main { 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitToProceed && continue ;; esac else - selectWallet "delegate" "${WALLET_PAY_VK_FILENAME}" "${WALLET_STAKE_VK_FILENAME}" + selectWallet "delegate" case $? in 1) waitToProceed; continue ;; 2) continue ;; @@ -1666,14 +1715,13 @@ function main { getWalletBalance ${wallet_name} true true false true if [[ ${base_lovelace} -gt 0 ]]; then if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then - println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Funds in wallet:" "$(formatLovelace ${base_lovelace})")" + println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Funds on address:" "$(formatLovelace ${base_lovelace})")" fi else - println ERROR "\n${FG_RED}ERROR${NC}: no funds available for wallet ${FG_GREEN}${wallet_name}${NC}" + println ERROR "\n${FG_RED}ERROR${NC}: no base funds available for wallet ${FG_GREEN}${wallet_name}${NC}" waitToProceed && continue fi - getWalletRewards ${wallet_name} - if [[ ${reward_lovelace} -eq -1 ]]; then + if ! isWalletRegistered ${wallet_name}; then if [[ ${op_mode} = "online" ]]; then if ! registerStakeWallet ${wallet_name}; then waitToProceed && continue; fi # re-fetch balance to get a fresh set of utxos @@ -1701,12 +1749,6 @@ function main { ;; 2) continue ;; esac - stake_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_VK_FILENAME}" - pool_delegcert_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_DELEGCERT_FILENAME}" - println ACTION "${CCLI} ${NETWORK_ERA} stake-address stake-delegation-certificate --stake-verification-key-file ${stake_vk_file} --stake-pool-id ${pool_id} --out-file ${pool_delegcert_file}" - if ! stdout=$(${CCLI} ${NETWORK_ERA} stake-address stake-delegation-certificate --stake-verification-key-file "${stake_vk_file}" --stake-pool-id "${pool_id}" --out-file "${pool_delegcert_file}" 2>&1); then - println ERROR "\n${FG_RED}ERROR${NC}: failure during stake delegation certificate creation!\n${stdout}"; waitToProceed && continue - fi if ! delegate; then if [[ ${op_mode} = "online" ]]; then echo && println ERROR "${FG_RED}ERROR${NC}: failure during delegation, removing newly created delegation certificate file" @@ -1737,9 +1779,9 @@ function main { if ! selectOpMode; then continue; fi fi echo - println DEBUG "# Select wallet to withdraw funds from" + println DEBUG "Select wallet to withdraw funds from" if [[ ${op_mode} = "online" ]]; then - selectWallet "reward" "${WALLET_PAY_VK_FILENAME}" "${WALLET_STAKE_VK_FILENAME}" + selectWallet "reward" case $? in 1) waitToProceed; continue ;; 2) continue ;; @@ -1750,7 +1792,7 @@ function main { 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitToProceed && continue ;; esac else - selectWallet "reward" "${WALLET_PAY_VK_FILENAME}" "${WALLET_STAKE_VK_FILENAME}" + selectWallet "reward" case $? in 1) waitToProceed; continue ;; 2) continue ;; @@ -1764,11 +1806,11 @@ function main { println ERROR "Failed to locate any rewards associated with the chosen wallet, please try another one" waitToProceed && continue elif [[ ${base_lovelace} -eq 0 ]]; then - println ERROR "${FG_YELLOW}WARN${NC}: No funds in base address, please send funds to base address of wallet to cover withdraw transaction fee" + println ERROR "${FG_YELLOW}WARN${NC}: No funds on base address, please send funds to base address of wallet to cover withdraw transaction fee" waitToProceed && continue fi - println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Funds" "$(formatLovelace ${base_lovelace})")" - println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Rewards" "$(formatLovelace ${reward_lovelace})")" + println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Base Funds" "$(formatLovelace ${base_lovelace})")" + println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Rewards" "$(formatLovelace ${reward_lovelace})")" if ! withdrawRewards; then waitToProceed && continue fi @@ -1777,8 +1819,7 @@ function main { getWalletBalance ${wallet_name} true true false echo println "Rewards successfully withdrawn" - println "New Balance" - println " Funds : ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA" + println "Base Funds (new balance) : ${FG_LBLUE}$(formatLovelace ${base_lovelace})${NC} ADA" waitToProceed && continue ;; ################################################################### esac # funds sub OPERATION @@ -1941,7 +1982,7 @@ function main { echo unset isHWpool - println DEBUG "# Select pool to register|modify" + println DEBUG "Select pool to register|modify" [[ ${SUBCOMMAND} = "register" ]] && pool_filter="non-reg" || pool_filter="reg" if [[ ${op_mode} = "online" ]]; then selectPool "${pool_filter}" "${POOL_COLDKEY_VK_FILENAME}" "${POOL_VRF_VK_FILENAME}" @@ -1966,7 +2007,7 @@ function main { fi echo pool_config="${POOL_FOLDER}/${pool_name}/${POOL_CONFIG_FILENAME}" - println DEBUG "# Pool Parameters" + println DEBUG "Pool Parameters" if [[ ${SUBCOMMAND} = "modify" ]]; then if [[ ! -f ${pool_config} ]]; then println "${FG_YELLOW}WARN${NC}: Missing pool config file: ${pool_config}" @@ -2021,7 +2062,7 @@ function main { println ERROR "\n${FG_RED}ERROR${NC}: cost set lower than allowed" waitToProceed && continue fi - println DEBUG "\n# Pool Metadata\n" + println DEBUG "\nPool Metadata\n" pool_meta_file="${POOL_FOLDER}/${pool_name}/poolmeta.json" if [[ ! -f "${pool_config}" ]] || ! meta_json_url=$(jq -er .json_url "${pool_config}"); then meta_json_url="https://foo.bat/poolmeta.json"; fi getAnswerAnyCust json_url_enter "Enter Pool's JSON URL to host metadata file - URL length should be less than 64 chars (default: ${meta_json_url})" @@ -2109,7 +2150,7 @@ function main { fi relay_output="" relay_array=() - println DEBUG "\n# Pool Relay Registration" + println DEBUG "\nPool Relay Registration" if [[ -f "${pool_config}" && $(jq '.relays | length' "${pool_config}") -gt 0 ]]; then println DEBUG "\nPrevious relay configuration:\n" jq -r '["TYPE","ADDRESS","PORT"], (.relays[] | [.type //"-",.address //"-",.port //"-"]) | @tsv' "${pool_config}" | column -t @@ -2207,7 +2248,7 @@ function main { # Old owner/reward wallets if [[ -f ${pool_config} ]]; then - println DEBUG "# Previous Owner(s)/Reward wallets" + println DEBUG "Previous Owner(s)/Reward wallets" if jq -er '.pledgeWallet' "${pool_config}" &>/dev/null; then # legacy support owner_wallets+=( "$(jq -r '.pledgeWallet' "${pool_config}")" ) println DEBUG "Owner wallet #1 : ${FG_GREEN}${owner_wallets[0]}${NC}" @@ -2253,10 +2294,10 @@ function main { fi getWalletBalance ${wallet_name} true true false true if [[ ${base_lovelace} -eq 0 ]]; then - println ERROR "${FG_RED}ERROR${NC}: no funds available in base address for wallet ${FG_GREEN}${wallet_name}${NC}, needed to pay for registration fee" + println ERROR "${FG_RED}ERROR${NC}: no funds available on base address for wallet ${FG_GREEN}${wallet_name}${NC}, needed to pay for registration fee" waitToProceed && continue 2 fi - println DEBUG "# Wallet Registration Transaction" + println DEBUG "Wallet Registration Transaction" if ! registerStakeWallet ${wallet_name}; then waitToProceed && continue 2; fi fi done @@ -2281,9 +2322,9 @@ function main { fi if [[ ${reuse_wallets} = 'N' ]]; then - println DEBUG "# Select main ${FG_YELLOW}owner/pledge${NC} wallet (normal CLI wallet)" + println DEBUG "Select main ${FG_YELLOW}owner/pledge${NC} wallet (normal CLI wallet)" if [[ ${op_mode} = "online" ]]; then - if ! selectWallet "delegate" "${WALLET_PAY_VK_FILENAME}" "${WALLET_STAKE_VK_FILENAME}"; then # ${wallet_name} populated by selectWallet function + if ! selectWallet "delegate"; then # ${wallet_name} populated by selectWallet function [[ "${dir_name}" != "[Esc] Cancel" ]] && waitToProceed; continue fi getWalletType ${wallet_name} @@ -2294,9 +2335,12 @@ function main { waitToProceed && continue ;; 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitToProceed && continue ;; 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitToProceed && continue ;; + 5) println ERROR "${FG_RED}ERROR${NC}: MultiSig wallet pool owners not supported!" + println ERROR "Use a CLI wallet as owner with enough funds to pay for pool deposit and registration transaction fee" + waitToProceed && continue ;; esac else - selectWallet "delegate" "${WALLET_PAY_VK_FILENAME}" "${WALLET_STAKE_VK_FILENAME}" + selectWallet "delegate" case $? in 1) waitToProceed; continue ;; 2) continue ;; @@ -2311,10 +2355,10 @@ function main { fi getWalletBalance ${wallet_name} true true false true if [[ ${base_lovelace} -eq 0 ]]; then - println ERROR "${FG_RED}ERROR${NC}: no funds available in base address for wallet ${FG_GREEN}${wallet_name}${NC}, needed to pay for registration fee" + println ERROR "${FG_RED}ERROR${NC}: no funds available on base address for wallet ${FG_GREEN}${wallet_name}${NC}, needed to pay for registration fee" waitToProceed && continue fi - println DEBUG "# Wallet Registration Transaction" + println DEBUG "Wallet Registration Transaction" if ! registerStakeWallet ${wallet_name}; then waitToProceed && continue; fi fi owner_wallets+=( "${wallet_name}" ) @@ -2327,20 +2371,22 @@ function main { select_opt "[n] No" "[y] Yes" "[Esc] Cancel" case $? in 0) break ;; - 1) if selectWallet "delegate" "${WALLET_STAKE_VK_FILENAME}" "${owner_wallets[@]}"; then # ${wallet_name} populated by selectWallet function + 1) if selectWallet "delegate" "${owner_wallets[@]}"; then # ${wallet_name} populated by selectWallet function getWalletType ${wallet_name} case $? in 0) hw_owner_wallets='Y' ;; 2) if [[ ${op_mode} = "online" ]]; then - println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted for wallet ${FG_GREEN}${wallet_name}${NC}, please decrypt before use!" - waitToProceed && continue 2 - fi ;; + println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted for wallet ${FG_GREEN}${wallet_name}${NC}, please decrypt before use!" + waitToProceed && continue 2 + fi ;; 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet ${FG_GREEN}${wallet_name}${NC}!" - waitToProceed "Did you mean to run in Hybrid mode? press any key to return home!" && continue 2 ;; + waitToProceed "Did you mean to run in Hybrid mode? press any key to return home!" && continue 2 ;; 4) if [[ ! -f "${WALLET_FOLDER}/${wallet_name}/${WALLET_STAKE_VK_FILENAME}" ]]; then # ignore if payment vkey is missing - println ERROR "${FG_RED}ERROR${NC}: stake verification key missing from wallet ${FG_GREEN}${wallet_name}${NC}!" - println DEBUG "Add another owner?" && continue - fi ;; + println ERROR "${FG_RED}ERROR${NC}: stake verification key missing from wallet ${FG_GREEN}${wallet_name}${NC}!" + println DEBUG "Add another owner?" && continue + fi ;; + 5) println ERROR "${FG_RED}ERROR${NC}: MultiSig wallet pool owner not supported!" + waitToProceed && println DEBUG "Add more owners?" && continue ;; esac else println DEBUG "Add more owners?" && continue @@ -2359,7 +2405,7 @@ function main { select_opt "[n] No" "[y] Yes" "[Esc] Cancel" case $? in 0) reward_wallet="${owner_wallets[0]}" ;; - 1) if ! selectWallet "none" "${WALLET_STAKE_VK_FILENAME}" "${owner_wallets[0]}"; then # ${wallet_name} populated by selectWallet function + 1) if ! selectWallet "none" "${owner_wallets[0]}"; then # ${wallet_name} populated by selectWallet function [[ "${dir_name}" != "[Esc] Cancel" ]] && waitToProceed; continue fi reward_wallet="${wallet_name}" @@ -2372,12 +2418,13 @@ function main { esac ;; 2) continue ;; + 5) println ERROR "${FG_RED}ERROR${NC}: MultiSig wallet as rewards wallet not supported!" && waitToProceed && continue ;; esac fi getWalletBalance ${owner_wallets[0]} true true false true if [[ ${base_lovelace} -eq 0 ]]; then - println ERROR "\n${FG_RED}ERROR${NC}: no funds available in owner wallet ${FG_GREEN}${owner_wallets[0]}${NC}" + println ERROR "\n${FG_RED}ERROR${NC}: no funds available on owner wallet base address ${FG_GREEN}${owner_wallets[0]}${NC}" waitToProceed && continue fi @@ -2474,11 +2521,11 @@ function main { fi if [[ ${SUBCOMMAND} = "register" ]]; then - println DEBUG "\n# Pool Registration Transaction" + println DEBUG "\nPool Registration Transaction" registerPool rc=$? else - println DEBUG "\n# Pool Update Transaction" + println DEBUG "\nPool Update Transaction" modifyPool rc=$? fi @@ -2599,7 +2646,7 @@ function main { if ! selectOpMode; then continue; fi fi echo - println DEBUG "# Select pool to retire" + println DEBUG "Select pool to retire" if [[ ${op_mode} = "online" ]]; then selectPool "${pool_filter}" "${POOL_COLDKEY_VK_FILENAME}" case $? in @@ -2633,9 +2680,9 @@ function main { println ERROR "${FG_RED}ERROR${NC}: epoch invalid, valid range: ${epoch_start}-${epoch_end}" waitToProceed && continue fi - println DEBUG "# Select wallet for pool de-registration transaction fee" + println DEBUG "Select wallet for pool de-registration transaction fee" if [[ ${op_mode} = "online" ]]; then - selectWallet "balance" "${WALLET_PAY_VK_FILENAME}" + selectWallet "balance" case $? in 1) waitToProceed; continue ;; 2) continue ;; @@ -2647,7 +2694,7 @@ function main { 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitToProceed && continue ;; esac else - selectWallet "balance" "${WALLET_PAY_VK_FILENAME}" + selectWallet "balance" case $? in 1) waitToProceed; continue ;; 2) continue ;; @@ -2660,12 +2707,12 @@ function main { getWalletBalance ${wallet_name} true true true true if [[ ${pay_lovelace} -gt 0 && ${base_lovelace} -gt 0 ]]; then # Both payment and base address available with funds, let user choose what to use - println DEBUG "\n# Select wallet address to use" + println DEBUG "\nSelect wallet address to use" if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then - println DEBUG "$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA" "Funds :" "$(formatLovelace ${base_lovelace})")" - println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Enterprise Funds :" "$(formatLovelace ${pay_lovelace})")" + println DEBUG "$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA" "Base Funds :" "$(formatLovelace ${base_lovelace})")" + println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Payment Funds :" "$(formatLovelace ${pay_lovelace})")" fi - select_opt "[b] Base (default)" "[e] Enterprise" "[Esc] Cancel" + select_opt "[b] Base (default)" "[e] Payment" "[Esc] Cancel" case $? in 0) addr="${base_addr}"; lovelace=${base_lovelace} ;; 1) addr="${pay_addr}"; lovelace=${pay_lovelace} ;; @@ -2675,13 +2722,13 @@ function main { addr="${pay_addr}" lovelace=${pay_lovelace} if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then - println DEBUG "\n$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Enterprise Funds :" "$(formatLovelace ${pay_lovelace})")" + println DEBUG "\n$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Payment Funds :" "$(formatLovelace ${pay_lovelace})")" fi elif [[ ${base_lovelace} -gt 0 ]]; then addr="${base_addr}" lovelace=${base_lovelace} if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then - println DEBUG "\n$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA" "Funds :" "$(formatLovelace ${base_lovelace})")" + println DEBUG "\n$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA" "Base Funds :" "$(formatLovelace ${base_lovelace})")" fi else println ERROR "\n${FG_RED}ERROR${NC}: no funds available for wallet ${FG_GREEN}${wallet_name}${NC}" @@ -2784,7 +2831,7 @@ function main { getPoolID ${pool_name} tput rc && tput ed if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then - tput sc && println DEBUG "Quering pool parameters from node, can take a while...\n" + tput sc && println DEBUG "Querying pool parameters from node, can take a while...\n" println ACTION "${CCLI} ${NETWORK_ERA} query pool-params --stake-pool-id ${pool_id_bech32} ${NETWORK_IDENTIFIER}" if ! pool_params=$(${CCLI} ${NETWORK_ERA} query pool-params --stake-pool-id ${pool_id_bech32} ${NETWORK_IDENTIFIER} 2>&1); then tput rc && tput ed @@ -3106,7 +3153,7 @@ function main { println DEBUG "${FG_LGRAY}OFFLINE MODE${NC}: CNTools started in offline mode, please grab correct counter value from online node using pool info!\n" fi [[ ! $(ls -A "${POOL_FOLDER}" 2>/dev/null) ]] && println "${FG_YELLOW}No pools available!${NC}" && waitToProceed && continue - println DEBUG "# Select pool to rotate KES keys on" + println DEBUG "Select pool to rotate KES keys on" selectPool "all" "${POOL_COLDKEY_VK_FILENAME}" case $? in 1) waitToProceed; continue ;; @@ -3147,7 +3194,7 @@ function main { println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo [[ ! $(ls -A "${POOL_FOLDER}" 2>/dev/null) ]] && println "${FG_YELLOW}No pools available!${NC}" && waitToProceed && continue - println DEBUG "# Select pool to decrypt" + println DEBUG "Select pool to decrypt" selectPool "encrypted" case $? in 1) waitToProceed; continue ;; @@ -3156,7 +3203,7 @@ function main { filesUnlocked=0 keysDecrypted=0 echo - println DEBUG "# Removing write protection from all pool files" + println DEBUG "Removing write protection from all pool files" while IFS= read -r -d '' file; do unlockFile "${file}" filesUnlocked=$((++filesUnlocked)) @@ -3164,7 +3211,7 @@ function main { done < <(find "${POOL_FOLDER}/${pool_name}" -mindepth 1 -maxdepth 1 -type f -print0) if [[ $(find "${POOL_FOLDER}/${pool_name}" -mindepth 1 -maxdepth 1 -type f -name '*.gpg' -print0 | wc -c) -gt 0 ]]; then echo - println "# Decrypting GPG encrypted pool files" + println "Decrypting GPG encrypted pool files" if ! getPasswordCust; then # $password variable populated by getPasswordCust function println "\n\n" && println ERROR "${FG_RED}ERROR${NC}: password input aborted!" waitToProceed && continue @@ -3194,7 +3241,7 @@ function main { println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo [[ ! $(ls -A "${POOL_FOLDER}" 2>/dev/null) ]] && println "${FG_YELLOW}No pools available!${NC}" && waitToProceed && continue - println DEBUG "# Select pool to encrypt" + println DEBUG "Select pool to encrypt" selectPool "encrypted" case $? in 1) waitToProceed; continue ;; @@ -3204,7 +3251,7 @@ function main { keysEncrypted=0 if [[ $(find "${POOL_FOLDER}/${pool_name}" -mindepth 1 -maxdepth 1 -type f -name '*.gpg' -print0 | wc -c) -le 0 ]]; then echo - println DEBUG "# Encrypting sensitive pool keys with GPG" + println DEBUG "Encrypting sensitive pool keys with GPG" if ! getPasswordCust confirm; then # $password variable populated by getPasswordCust function println "\n\n" && println ERROR "${FG_RED}ERROR${NC}: password input aborted!" waitToProceed && continue @@ -3226,7 +3273,7 @@ function main { waitToProceed && continue fi echo - println DEBUG "# Write protecting all pool files with 400 permission and if enabled 'chattr +i'" + println DEBUG "Write protecting all pool files with 400 permission and if enabled 'chattr +i'" while IFS= read -r -d '' file; do lockFile "$file" filesLocked=$((++filesLocked)) @@ -3243,350 +3290,71 @@ function main { fi waitToProceed && continue ;; ################################################################### - vote) + esac # pool sub OPERATION + done # Pool loop + ;; ################################################################### + transaction) + while true; do # Transaction loop + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> TRANSACTION" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println OFF " Transaction Management\n"\ + " ) Sign - witness/sign offline tx with signing keys"\ + " ) Submit - submit signed offline tx to blockchain"\ + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println DEBUG " Select Transaction Operation\n" + select_opt "[s] Sign" "[t] Submit" "[h] Home" + case $? in + 0) SUBCOMMAND="sign" ;; + 1) SUBCOMMAND="submit" ;; + 2) break ;; + esac + case $SUBCOMMAND in + sign) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - println " >> POOL >> VOTE (CIP-0094)" + println " >> TRANSACTION >> SIGN" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - # check for required command line tools (xxd hexdump) - if ! cmdAvailable "xxd"; then - myExit 1 "xxd is a hexdump tool to generate the CBOR encoded poll answer" - fi - [[ ! $(ls -A "${POOL_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No pools available!${NC}" && waitToProceed && continue - [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available to pay for poll ballot casts!${NC}" && waitToProceed && continue - if [[ -z ${KOIOS_API} ]]; then - echo && println ERROR "${FG_YELLOW}Koios API required!${NC}" && waitToProceed && continue - fi - if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then - println ERROR "${FG_RED}ERROR${NC}: CNTools started in offline mode, option not available!" + echo + fileDialog "Enter path to transaction file to sign" "${TMP_DIR}/" && echo + offline_tx=${file} + [[ -z "${offline_tx}" ]] && continue + if [[ ! -f "${offline_tx}" ]]; then + println ERROR "${FG_RED}ERROR${NC}: file not found: ${offline_tx}" + waitToProceed && continue + elif ! offlineJSON=$(jq -erc . "${offline_tx}"); then + println ERROR "${FG_RED}ERROR${NC}: invalid JSON file: ${offline_tx}" waitToProceed && continue - else - if ! selectOpMode; then continue; fi fi - epoch=$(getEpoch) - echo - echo "Current ${NETWORK_NAME} epoch: ${epoch}" - NETWORK_NAME_LOWER=$(echo "$NETWORK_NAME" | awk '{print tolower($0)}') - println LOG "Query ${NETWORK_NAME} polls ..." - println ACTION "curl -sSL -f -H \"Content-Type: application/json\" ${CIP0094_POLL_URL}" - if ! polls=$(curl -sSL -f -H "Content-Type: application/json" "${CIP0094_POLL_URL}" | jq -r .networks.${NETWORK_NAME_LOWER} 2>&1); then - println ERROR "\n${FG_RED}ERROR${NC}: failure during CIP0094 poll query!\n${polls}"; waitToProceed && continue + if ! otx_type="$(jq -er '.type' <<< ${offlineJSON})"; then println ERROR "${FG_RED}ERROR${NC}: field 'type' not found in: ${offline_tx}" && waitToProceed && continue; fi + if ! otx_date_created="$(jq -er '."date-created"' <<< ${offlineJSON})"; then println ERROR "${FG_RED}ERROR${NC}: field 'date-created' not found in: ${offline_tx}" && waitToProceed && continue; fi + if ! otx_date_expire="$(jq -er '."date-expire"' <<< ${offlineJSON})"; then println ERROR "${FG_RED}ERROR${NC}: field 'date-expire' not found in: ${offline_tx}" && waitToProceed && continue; fi + if ! otx_txFee=$(jq -er '.txFee' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'txFee' not found in: ${offline_tx}" && waitToProceed && continue; fi + if ! otx_txBody=$(jq -er '.txBody' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'txBody' not found in: ${offline_tx}" && waitToProceed && continue; fi + echo -e "${otx_txBody}" > "${TMP_DIR}"/tx.raw + println DEBUG "Transaction type : ${FG_GREEN}${otx_type}${NC}" + if wallet_name=$(jq -er '."wallet-name"' <<< ${offlineJSON}); then + println DEBUG "Transaction fee : ${FG_LBLUE}$(formatLovelace ${otx_txFee})${NC} ADA, payed by ${FG_GREEN}${wallet_name}${NC}" + [[ $(cat "${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_ADDR_FILENAME}" 2>/dev/null) = "${addr}" ]] && wallet_source="payment" || wallet_source="base" + else + println DEBUG "Transaction fee : ${FG_LBLUE}$(formatLovelace ${otx_txFee})${NC} ADA" fi - poll_index=$(echo $polls | jq '. | length') - if [[ "$poll_index" -gt 0 ]]; then - poll_index_act=0 - poll_index_cnt=0 - declare poll_index_txIds=() - declare poll_index_titles=() - echo "Polls currently open for Pool answers:" - while read poll; do - poll_index_cnt=$((poll_index_cnt+1)) - if [[ "$epoch" -ge "$(jq '.epoch_cast' <<< $poll)" ]] && [[ "$epoch" -lt "$(jq '.epoch_delegation' <<< $poll)" ]]; then - # list polls who actually are open for SPO ballot casts (filter upcoming and passed ones) - poll_index_act=$((poll_index_act+1)) - poll_index_txIds+=("$(jq -r '[.tx_id] | @tsv' <<< $poll)") - poll_index_titles+=("$(jq -r '[.title] | @tsv' <<< $poll)") - echo -e "$poll_index_act) $(jq -r '[.tx_id, .title] | @tsv' <<< $poll)" - fi - done < <(echo $polls | jq -c .[]) - if [[ "$poll_index_act" -gt 0 ]]; then - while :; do - read -p "Please select a poll: " poll_index_selected - [[ $poll_index_selected =~ ^[[:digit:]]+$ ]] || continue - if [[ "$poll_index_selected" -lt "1" ]] || [[ "$poll_index_selected" -gt "$poll_index_act" ]]; then - continue - fi - break - done - poll_txId=${poll_index_txIds[$((poll_index_selected-1))]} - poll_title=${poll_index_titles[$(($poll_index_selected-1))]} - echo - println DEBUG "# Select the voting pool" - if [[ ${op_mode} = "online" ]]; then - selectPool "${pool_filter}" "${POOL_COLDKEY_VK_FILENAME}" - case $? in - 1) waitToProceed; continue ;; - 2) continue ;; - esac - getPoolType ${pool_name} - case $? in - 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitToProceed && continue ;; - 3) println ERROR "${FG_RED}ERROR${NC}: signing keys missing from pool!" && waitToProceed && continue ;; - esac - else - selectPool "${pool_filter}" "${POOL_COLDKEY_VK_FILENAME}" - case $? in - 1) waitToProceed; continue ;; - 2) continue ;; - esac - getPoolType ${pool_name} - fi - echo - println DEBUG "# Select wallet for the ballot cast transaction fee" - if [[ ${op_mode} = "online" ]]; then - selectWallet "balance" "${WALLET_PAY_VK_FILENAME}" - case $? in - 1) waitToProceed; continue ;; - 2) continue ;; - esac - getWalletType ${wallet_name} - case $? in - 0) println ERROR "${FG_RED}ERROR${NC}: please use a CLI wallet to pay for transaction fee!" && waitToProceed && continue ;; - 2) println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitToProceed && continue ;; - 3) println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitToProceed && continue ;; - esac - else - selectWallet "balance" "${WALLET_PAY_VK_FILENAME}" - case $? in - 1) waitToProceed; continue ;; - 2) continue ;; - esac - getWalletType ${wallet_name} - case $? in - 0) println ERROR "${FG_RED}ERROR${NC}: please use a CLI wallet to pay for transaction fee!" && waitToProceed && continue ;; - esac - fi - getWalletBalance ${wallet_name} true true true true - if [[ ${pay_lovelace} -gt 0 && ${base_lovelace} -gt 0 ]]; then - # Both payment and base address available with funds, let user choose what to use - println DEBUG "\n# Select wallet address to use" - if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then - println DEBUG "$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA" "Funds :" "$(formatLovelace ${base_lovelace})")" - println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Enterprise Funds :" "$(formatLovelace ${pay_lovelace})")" - fi - select_opt "[b] Base (default)" "[e] Enterprise" "[Esc] Cancel" - case $? in - 0) addr="${base_addr}"; lovelace=${base_lovelace} ;; - 1) addr="${pay_addr}"; lovelace=${pay_lovelace} ;; - 2) continue ;; - esac - elif [[ ${pay_lovelace} -gt 0 ]]; then - addr="${pay_addr}" - lovelace=${pay_lovelace} - if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then - println DEBUG "\n$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Enterprise Funds :" "$(formatLovelace ${pay_lovelace})")" - fi - elif [[ ${base_lovelace} -gt 0 ]]; then - addr="${base_addr}" - lovelace=${base_lovelace} - if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then - println DEBUG "\n$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA" "Funds :" "$(formatLovelace ${base_lovelace})")" - fi - else - println ERROR "\n${FG_RED}ERROR${NC}: no funds available for wallet ${FG_GREEN}${wallet_name}${NC}" - waitToProceed && continue - fi - echo - echo "Query ${NETWORK_NAME} ${poll_txId} metadata from Koios API..." - println ACTION "curl -sSL -f -X POST -H \"Content-Type: application/json\" -d '{\"_tx_hashes\":[\"${poll_txId}\"]}' ${KOIOS_API}/tx_metadata" - if ! tx=$(curl -sSL -f -X POST -H "Content-Type: application/json" -d '{"_tx_hashes":["'${poll_txId}'"]}' "${KOIOS_API}/tx_metadata" 2>&1); then - println ERROR "\n${FG_RED}ERROR${NC}: failure during Koios tx metadata query!\n${tx}"; waitToProceed && continue - fi - tx_meta=$(echo ${tx} | jq -r ".[0].metadata.\"94\" // empty" 2> /dev/null ) - if [[ ! -z ${tx_meta} ]]; then - echo "OK: Metadata has a CIP-0094 label" - #Variables for the Question and the Options - #this code part was originaly written by SPO Scripts (https://github.com/gitmachtl/scripts/blob/master/cardano/testnet/13a_spoPoll.sh) - questionString="" #string that holds the question - optionString=() #array of options - #Question found now convert it to cbor - cborStr="" #setup a clear new cbor string variable - cborStr+=$(to_cbor "map" 1) #map 1 - cborStr+=$(to_cbor "unsigned" 94) #unsigned 94 - cborStr+=$(to_cbor "map" 2) #map 2 - cborStr+=$(to_cbor "unsigned" 0) #unsigned 0 - #Add QuestionStrings - questionStrLength=$(jq -r ".\"0\" | length" <<< ${tx_meta} 2> /dev/null) - if [[ ${questionStrLength} -eq 0 ]]; then - echo -e "\n${FG_RED}ERROR - No question string included\n${NC}" && waitToProceed && continue - fi - cborStr+=$(to_cbor "array" ${questionStrLength}) #array with the number of entries - for (( tmpCnt=0; tmpCnt<${questionStrLength}; tmpCnt++ )) - do - strEntry=$(jq -r ".\"0\"[${tmpCnt}]" <<< ${tx_meta} 2> /dev/null) - cborStr+=$(to_cbor "string" "${strEntry}") #string - questionString+="${strEntry}" - done - cborStr+=$(to_cbor "unsigned" 1) #unsigned 1 - #Add OptionsStrings - optionsStrLength=$(jq -r ".\"1\" | length" <<< ${tx_meta} 2> /dev/null) - if [[ ${optionsStrLength} -eq 0 ]]; then - echo -e "\n${FG_RED}ERROR - No option strings included\n${NC}" && waitToProceed && continue - fi - cborStr+=$(to_cbor "array" ${optionsStrLength}) #array with the number of options - - for (( tmpCnt=0; tmpCnt<${optionsStrLength}; tmpCnt++ )) - do - optionEntryStrLength=$(jq -r ".\"1\"[${tmpCnt}] | length" <<< ${tx_meta} 2> /dev/null) - cborStr+=$(to_cbor "array" ${optionEntryStrLength}) #array with the number of entries - for (( tmpCnt2=0; tmpCnt2<${optionEntryStrLength}; tmpCnt2++ )) - do - strEntry=$(jq -r ".\"1\"[${tmpCnt}][${tmpCnt2}]" <<< ${tx_meta} 2> /dev/null) - cborStr+=$(to_cbor "string" "${strEntry}") #string - optionString[${tmpCnt}]+="${strEntry}" - done - done - #Show the question and the available answer options - echo - echo -e "${FG_GREEN}Question${NC}: ${questionString}" - echo - echo -e "There are ${optionsStrLength} answer option(s) available:" - for (( tmpCnt=0; tmpCnt<${optionsStrLength}; tmpCnt++ )) - do - echo -e "[${FG_YELLOW}${tmpCnt}${NC}] ${optionString[${tmpCnt}]}" - done - echo - #Read in the answer, loop until a valid answer index is given - answer="-1" - while [ -z "${answer##*[!0-9]*}" ] || [[ ${answer} -lt 0 ]] || [[ ${answer} -ge ${optionsStrLength} ]]; - do - read -p $'Please indicate an answer (by index): ' answer - if [[ ${answer} == "" ]]; then - echo && println "${FG_YELLOW}No answer${NC}" && waitToProceed && continue - fi - done - echo - echo -e "Your answer is '${optionString[${answer}]}'." - echo - #Generating the answer cbor - questionHash=$(echo -n "${cborStr}" | xxd -r -ps | b2sum -l 256 -b | cut -d' ' -f 1) - #Make a new cborStr with the answer - cborStr="" #setup a clear new cbor string variable - cborStr+=$(to_cbor "map" 1) #map 1 - cborStr+=$(to_cbor "unsigned" 94) #unsigned 94 - cborStr+=$(to_cbor "map" 2) #map 2 - cborStr+=$(to_cbor "unsigned" 2) #unsigned 2 - cborStr+=$(to_cbor "bytes" "${questionHash}") #bytearray of the blake2b-256 hash of the question cbor - cborStr+=$(to_cbor "unsigned" 3) #unsigned 3 - cborStr+=$(to_cbor "unsigned" ${answer}) #unsigned - answer index - #CBOR Answer is ready, write it out to disc - cborFile="${TMP_DIR}/CIP-0094_${poll_txId}_answer.cbor" - #echo -ne "Writing '${cborFile}' to disc ... " - xxd -r -ps <<< ${cborStr} 2> /dev/null > ${cborFile} - if [ $? -ne 0 ]; then echo -e "\n\n${FG_RED}ERROR, could not write to file!\n\n${NC}"; exit 1; fi - # Optional metadata/message - println "# Add a message to the answer? (Poll Dashboards will show this message)" - select_opt "[n] No" "[y] Yes" - case $? in - 0) unset metafile ;; - 1) metafile="${TMP_DIR}/metadata_$(date '+%Y%m%d%H%M%S').json" - DEFAULTEDITOR="$(command -v nano &>/dev/null && echo 'nano' || echo 'vi')" - println OFF "\nA maximum of 64 characters(bytes) is allowed per line." - println OFF "${FG_YELLOW}Please don't change default file path when saving.${NC}" - waitToProceed "press any key to open '${FG_LGRAY}${DEFAULTEDITOR}${NC}' text editor" - ${DEFAULTEDITOR} "${metafile}" - if [[ ! -f "${metafile}" ]]; then - println ERROR "${FG_RED}ERROR${NC}: file not found" - println ERROR "File: ${FG_LGRAY}${metafile}${NC}" - waitToProceed && continue - fi - tput cuu 4 && tput ed - if [[ ! -s ${metafile} ]]; then - println "Message empty, skip and continue with answer without message? No to abort!" - select_opt "[y] Yes" "[n] No" - case $? in - 0) unset metafile ;; - 1) continue ;; - esac - else - tx_msg='{"674":{"msg":[]}}' - error="" - while IFS="" read -r line || [[ -n "${line}" ]]; do - line_bytes=$(echo -n "${line}" | wc -c) - if [[ ${line_bytes} -gt 64 ]]; then - error="${FG_RED}ERROR${NC}: line contains more that 64 bytes(characters) [${line_bytes}]\nLine: ${FG_LGRAY}${line}${NC}" && break - fi - if ! tx_msg=$(jq -er ".\"674\".msg += [\"${line}\"]" <<< "${tx_msg}" 2>&1); then - error="${FG_RED}ERROR${NC}: ${tx_msg}" && break - fi - done < "${metafile}" - [[ -n ${error} ]] && println ERROR "${error}" && waitToProceed && continue - jq -c . <<< "${tx_msg}" > "${metafile}" - jq -r . "${metafile}" && echo - println LOG "Transaction message: ${tx_msg}" - fi - ;; - esac - if ! submitPoll; then - waitToProceed && continue - fi - else - echo && println "${FG_YELLOW}Cannot find valid metadata for this transaction${NC}" - waitToProceed && continue - fi - else - echo && println "${FG_YELLOW}There are currently no active polls in ${NETWORK_NAME}${NC}" - waitToProceed && continue - fi - else - echo && println "${FG_YELLOW}There are currently no polls in ${NETWORK_NAME}${NC}" - waitToProceed && continue - fi - ;; ################################################################### - esac # pool sub OPERATION - done # Pool loop - ;; ################################################################### - transaction) - while true; do # Transaction loop - clear - println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - println " >> TRANSACTION" - println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - println OFF " Transaction Management\n"\ - " ) Sign - witness/sign offline tx with signing keys"\ - " ) Submit - submit signed offline tx to blockchain"\ - "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - println DEBUG " Select Transaction Operation\n" - select_opt "[s] Sign" "[t] Submit" "[h] Home" - case $? in - 0) SUBCOMMAND="sign" ;; - 1) SUBCOMMAND="submit" ;; - 2) break ;; - esac - case $SUBCOMMAND in - sign) - clear - println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - println " >> TRANSACTION >> SIGN" - println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - echo - fileDialog "Enter path to offline tx file to sign" "${TMP_DIR}/" && echo - offline_tx=${file} - [[ -z "${offline_tx}" ]] && continue - if [[ ! -f "${offline_tx}" ]]; then - println ERROR "${FG_RED}ERROR${NC}: file not found: ${offline_tx}" - waitToProceed && continue - elif ! offlineJSON=$(jq -erc . "${offline_tx}"); then - println ERROR "${FG_RED}ERROR${NC}: invalid JSON file: ${offline_tx}" - waitToProceed && continue - fi - if ! otx_type="$(jq -er '.type' <<< ${offlineJSON})"; then println ERROR "${FG_RED}ERROR${NC}: field 'type' not found in: ${offline_tx}" && waitToProceed && continue; fi - if ! otx_date_created="$(jq -er '."date-created"' <<< ${offlineJSON})"; then println ERROR "${FG_RED}ERROR${NC}: field 'date-created' not found in: ${offline_tx}" && waitToProceed && continue; fi - if ! otx_date_expire="$(jq -er '."date-expire"' <<< ${offlineJSON})"; then println ERROR "${FG_RED}ERROR${NC}: field 'date-expire' not found in: ${offline_tx}" && waitToProceed && continue; fi - if ! otx_txFee=$(jq -er '.txFee' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'txFee' not found in: ${offline_tx}" && waitToProceed && continue; fi - if ! otx_txBody=$(jq -er '.txBody' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'txBody' not found in: ${offline_tx}" && waitToProceed && continue; fi - echo -e "${otx_txBody}" > "${TMP_DIR}"/tx.raw - [[ $(jq -r '."signed-txBody" | length' <<< ${offlineJSON}) -gt 0 ]] && println ERROR "${FG_RED}ERROR${NC}: transaction already signed, please submit transaction to complete!" && waitToProceed && continue - println DEBUG "Transaction type : ${FG_GREEN}${otx_type}${NC}" - if wallet_name=$(jq -er '."wallet-name"' <<< ${offlineJSON}); then - println DEBUG "Transaction fee : ${FG_LBLUE}$(formatLovelace ${otx_txFee})${NC} ADA, payed by ${FG_GREEN}${wallet_name}${NC}" - [[ $(cat "${WALLET_FOLDER}/${wallet_name}/${WALLET_PAY_ADDR_FILENAME}" 2>/dev/null) = "${addr}" ]] && wallet_source="enterprise" || wallet_source="base" - else - println DEBUG "Transaction fee : ${FG_LBLUE}$(formatLovelace ${otx_txFee})${NC} ADA" - fi - println DEBUG "Created : ${FG_LGRAY}$(date '+%F %T %Z' --date="${otx_date_created}")${NC}" - if [[ $(date '+%s' --date="${otx_date_expire}") -lt $(date '+%s') ]]; then - println DEBUG "Expire : ${FG_RED}$(date '+%F %T %Z' --date="${otx_date_expire}")${NC}" - println ERROR "\n${FG_RED}ERROR${NC}: offline transaction expired! please create a new one with long enough Time To Live (TTL)" - waitToProceed && continue - else - println DEBUG "Expire : ${FG_LGRAY}$(date '+%F %T %Z' --date="${otx_date_expire}")${NC}" - fi - tx_witness_files=() - tx_sign_files=() - case "${otx_type}" in - Wallet*|Payment|"Pool De-Registration"|Metadata|Asset*|"Poll Cast") + println DEBUG "Created : ${FG_LGRAY}$(date '+%F %T %Z' --date="${otx_date_created}")${NC}" + [[ $(date '+%s' --date="${otx_date_expire}") -lt $(date '+%s') ]] && expire_color="${FG_RED}" || expire_color="${FG_LGRAY}" + println DEBUG "Expire : ${expire_color}$(date '+%F %T %Z' --date="${otx_date_expire}")${NC}" + echo + tx_witness_files=() + case "${otx_type}" in + "Pool Registration"|"Pool Update") + println DEBUG "Pool name : ${FG_LGRAY}$(jq -r '."pool-metadata".name' <<< ${offlineJSON})${NC}" + println DEBUG "Ticker : ${FG_LGRAY}$(jq -r '."pool-metadata".ticker' <<< ${offlineJSON})${NC}" + println DEBUG "Pledge : ${FG_LBLUE}$(formatLovelace "$(ADAToLovelace "$(jq -r '."pool-pledge"' <<< ${offlineJSON})")")${NC} ADA" + println DEBUG "Margin : ${FG_LBLUE}$(jq -r '."pool-margin"' <<< ${offlineJSON})${NC} %" + println DEBUG "Cost : ${FG_LBLUE}$(formatLovelace "$(ADAToLovelace "$(jq -r '."pool-cost"' <<< ${offlineJSON})")")${NC} ADA" echo + ;; + *) [[ ${otx_type} = "Wallet De-Registration" ]] && println DEBUG "Amount returned : ${FG_LBLUE}$(formatLovelace "$(jq -r '."amount-returned"' <<< ${offlineJSON})")${NC} ADA" if [[ ${otx_type} = "Payment" ]]; then println DEBUG "Source addr : ${FG_LGRAY}$(jq -r '."source-address"' <<< ${offlineJSON})${NC}" @@ -3612,341 +3380,1729 @@ function main { [[ ${otx_type} = "Asset Minting" ]] && println DEBUG "Assets Minted : ${FG_LBLUE}$(formatAsset "$(jq -r '."asset-minted"' <<< ${offlineJSON})")${NC}" [[ ${otx_type} = "Asset Burning" ]] && println DEBUG "Assets To Burn : ${FG_LBLUE}$(formatAsset "$(jq -r '."asset-amount"' <<< ${offlineJSON})")${NC}" [[ ${otx_type} = "Asset Burning" ]] && println DEBUG "Assets Left : ${FG_LBLUE}$(formatAsset "$(jq -r '."asset-minted"' <<< ${offlineJSON})")${NC}" - [[ ${otx_type} = "Poll Cast" ]] && println DEBUG "Poll ID : ${FG_LGRAY}$(jq -r '."poll-txId"' <<< ${offlineJSON})${NC}" - [[ ${otx_type} = "Poll Cast" ]] && println DEBUG "Title : ${FG_LGRAY}$(jq -r '."poll-title"' <<< ${offlineJSON})${NC}" - [[ ${otx_type} = "Poll Cast" ]] && println DEBUG "Question : ${FG_LGRAY}$(jq -r '."poll-question"' <<< ${offlineJSON})${NC}" - [[ ${otx_type} = "Poll Cast" ]] && println DEBUG "Answer : ${FG_LGRAY}$(jq -r '."poll-answer"' <<< ${offlineJSON})${NC}" - for otx_signing_file in $(jq -r '."signing-file"[] | @base64' <<< "${offlineJSON}"); do - _jq() { base64 -d <<< ${otx_signing_file} | jq -r "${1}"; } - otx_signing_name=$(_jq '.name') - otx_vkey_cborHex="$(_jq '.vkey.cborHex' 2>/dev/null)" - - skey_path="" - # look for signing key in wallet folder - while IFS= read -r -d '' w_file; do - if [[ ${w_file} = */"${WALLET_PAY_SK_FILENAME}" || ${w_file} = */"${WALLET_STAKE_SK_FILENAME}" ]]; then - ! ${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${w_file}" --verification-key-file "${TMP_DIR}"/tmp.vkey && continue - if [[ $(jq -er '.type' "${w_file}" 2>/dev/null) = *"Extended"* ]]; then - ! ${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file "${TMP_DIR}/tmp.vkey" --verification-key-file "${TMP_DIR}/tmp2.vkey" && continue - mv -f "${TMP_DIR}/tmp2.vkey" "${TMP_DIR}/tmp.vkey" - fi - grep -q "${otx_vkey_cborHex}" "${TMP_DIR}"/tmp.vkey && skey_path="${w_file}" && break - elif [[ ${w_file} = */"${WALLET_HW_PAY_SK_FILENAME}" || ${w_file} = */"${WALLET_HW_STAKE_SK_FILENAME}" ]]; then - grep -q "${otx_vkey_cborHex:4}" "${w_file}" && skey_path="${w_file}" && break # strip 5820 prefix - fi - done < <(find "${WALLET_FOLDER}" -mindepth 2 -maxdepth 2 -type f -print0 2>/dev/null) - # look for cold signing key in pool folder - if [[ -z ${skey_path} ]]; then - while IFS= read -r -d '' p_file; do - ! ${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${p_file}" --verification-key-file "${TMP_DIR}"/tmp.vkey && continue - grep -q "${otx_vkey_cborHex}" "${TMP_DIR}"/tmp.vkey && skey_path="${p_file}" && break - done < <(find "${POOL_FOLDER}" -mindepth 2 -maxdepth 2 -type f -name "${POOL_COLDKEY_SK_FILENAME}" -print0 2>/dev/null) - fi - # look for signing key in asset folder - if [[ -z ${skey_path} ]]; then - while IFS= read -r -d '' a_file; do - ! ${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${a_file}" --verification-key-file "${TMP_DIR}"/tmp.vkey && continue - grep -q "${otx_vkey_cborHex}" "${TMP_DIR}"/tmp.vkey && skey_path="${a_file}" && break - done < <(find "${ASSET_FOLDER}" -mindepth 2 -maxdepth 2 -type f -name "${ASSET_POLICY_SK_FILENAME}" -print0 2>/dev/null) + jq -er '."drep-wallet-name"' <<< ${offlineJSON} &>/dev/null && println DEBUG "DRep Wallet : ${FG_GREEN}$(jq -r '."drep-wallet-name"' <<< ${offlineJSON})${NC}" + jq -er '."drep-id"' <<< ${offlineJSON} &>/dev/null && println DEBUG "DRep ID : ${FG_LGRAY}$(jq -r '."drep-id"' <<< ${offlineJSON})${NC}" + jq -er '."action-id"' <<< ${offlineJSON} &>/dev/null && println DEBUG "Action ID : ${FG_LGRAY}$(jq -r '."action-id"' <<< ${offlineJSON})${NC}" + jq -er '.vote' <<< ${offlineJSON} &>/dev/null && println DEBUG "Vote : ${FG_LGRAY}$(jq -r '.vote' <<< ${offlineJSON})${NC}" + echo + ;; + esac + println DEBUG "Signing keys required:" + for otx_signing_name_b64 in $(jq -r '."signing-file"[].name | @base64' <<< "${offlineJSON}"); do + otx_signing_name=$(base64 -d <<< "${otx_signing_name_b64}") + unset hasWitness + for otx_witness_name in $(jq -r '.witness[].name' <<< "${offlineJSON}"); do + [[ ${otx_witness_name} = "${otx_signing_name}" ]] && hasWitness=true && break + done + [[ -z ${hasWitness} ]] && println DEBUG "${FG_LGRAY}${otx_signing_name}${NC} ${FG_RED}x${NC}" || println DEBUG "${FG_LGRAY}${otx_signing_name}${NC} ${FG_GREEN}\u2714${NC}" + done + for otx_script in $(jq -r '."script-file"[] | @base64' <<< "${offlineJSON}"); do + _jq() { base64 -d <<< ${otx_script} | jq -r "${1}"; } + otx_script_name=$(_jq '.name') + otx_script_scripts="$(_jq '.script' 2>/dev/null)" + getAllMultiSigKeys "${otx_script_scripts}" + unset required_total + validateMultiSigScript false "${otx_script_scripts}" + println DEBUG "${FG_LGRAY}${otx_script_name}${NC} - required signatures: ${FG_LBLUE}${required_total}${NC}" + for sig in "${!script_sig_list[@]}"; do + unset hasWitness found_wallet_name + for otx_witness_name in $(jq -r '.witness[].name' <<< "${offlineJSON}"); do + [[ ${otx_witness_name} = "${sig}" ]] && hasWitness=true && break + done + while IFS= read -r -d '' wallet; do + wallet_name=$(basename ${wallet}) + getWalletType "${wallet_name}" + getCredentials "${wallet_name}" + getGovKeyInfo "${wallet_name}" + if [[ ${ms_pay_cred} = "${sig}" || ${ms_stake_cred} = "${sig}" || ${pay_cred} = "${sig}" || ${stake_cred} = "${sig}" || ${ms_drep_hash} = "${sig}" || ${drep_hash} = "${sig}" ]]; then + found_wallet_name="${wallet_name}"; break fi + done < <(find "${WALLET_FOLDER}" -mindepth 1 -maxdepth 1 -type d -print0) + [[ -z ${hasWitness} ]] && println DEBUG " ${FG_LGRAY}${sig}${NC} ${FG_RED}x${NC}" || println DEBUG " ${FG_LGRAY}$([[ -n ${found_wallet_name} ]] && echo ${found_wallet_name} || echo ${sig})${NC} ${FG_GREEN}\u2714${NC}" + done + done - if [[ -n ${skey_path} ]]; then - println DEBUG "\nFound a match for ${otx_signing_name}, use this file ? : ${FG_LGRAY}${skey_path}${NC}" - select_opt "[y] Yes" "[n] No, continue with manual selection" - case $? in - 0) println DEBUG "${FG_GREEN}Successfully added!${NC}" - tx_sign_files+=( "${skey_path}" ) - continue ;; - 1) : ;; # do nothing - esac + [[ $(jq -r '."signed-txBody" | length' <<< ${offlineJSON}) -gt 0 ]] && println INFO "\n${FG_GREEN}\u2714${NC} Transaction already signed, please submit transaction to complete!" && waitToProceed && continue + [[ $(date '+%s' --date="${otx_date_expire}") -lt $(date '+%s') ]] && println ERROR "\n${FG_RED}ERROR${NC}: Transaction expired! please create a new one with long enough Time To Live (TTL)" && waitToProceed && continue + + for otx_signing_file in $(jq -r '."signing-file"[] | @base64' <<< "${offlineJSON}"); do + _jq() { base64 -d <<< ${otx_signing_file} | jq -r "${1}"; } + otx_signing_name=$(_jq '.name') + otx_vkey_cborHex="$(_jq '.vkey.cborHex' 2>/dev/null)" + skey_path="" + for otx_witness in $(jq -r '.witness[] | @base64' <<< "${offlineJSON}"); do + __jq() { base64 -d <<< ${otx_witness} | jq -r "${1}"; } + [[ $(_jq '.name') = $(__jq '.name') ]] && continue 2 # offline transaction already witnessed by this signing key + done + # look for signing key in wallet folder + while IFS= read -r -d '' w_file; do + if [[ ${w_file} = */"${WALLET_PAY_SK_FILENAME}" || ${w_file} = */"${WALLET_STAKE_SK_FILENAME}" || ${w_file} = */"${WALLET_GOV_DREP_SK_FILENAME}" ]]; then + ! ${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${w_file}" --verification-key-file "${TMP_DIR}"/tmp.vkey && continue + if [[ $(jq -er '.type' "${w_file}" 2>/dev/null) = *"Extended"* ]]; then + ! ${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file "${TMP_DIR}/tmp.vkey" --verification-key-file "${TMP_DIR}/tmp2.vkey" && continue + mv -f "${TMP_DIR}/tmp2.vkey" "${TMP_DIR}/tmp.vkey" fi + grep -q "${otx_vkey_cborHex}" "${TMP_DIR}"/tmp.vkey && skey_path="${w_file}" && break + elif [[ ${w_file} = */"${WALLET_HW_PAY_SK_FILENAME}" || ${w_file} = */"${WALLET_HW_STAKE_SK_FILENAME}" ]]; then + grep -q "${otx_vkey_cborHex:4}" "${w_file}" && skey_path="${w_file}" && break # strip 5820 prefix + fi + done < <(find "${WALLET_FOLDER}" -mindepth 2 -maxdepth 2 -type f -print0 2>/dev/null) + # look for cold signing key in pool folder + if [[ -z ${skey_path} ]]; then + while IFS= read -r -d '' p_file; do + ! ${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${p_file}" --verification-key-file "${TMP_DIR}"/tmp.vkey && continue + grep -q "${otx_vkey_cborHex}" "${TMP_DIR}"/tmp.vkey && skey_path="${p_file}" && break + done < <(find "${POOL_FOLDER}" -mindepth 2 -maxdepth 2 -type f -name "${POOL_COLDKEY_SK_FILENAME}" -print0 2>/dev/null) + fi + # look for signing key in asset folder + if [[ -z ${skey_path} ]]; then + while IFS= read -r -d '' a_file; do + ! ${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${a_file}" --verification-key-file "${TMP_DIR}"/tmp.vkey && continue + grep -q "${otx_vkey_cborHex}" "${TMP_DIR}"/tmp.vkey && skey_path="${a_file}" && break + done < <(find "${ASSET_FOLDER}" -mindepth 2 -maxdepth 2 -type f -name "${ASSET_POLICY_SK_FILENAME}" -print0 2>/dev/null) + fi - if [[ ${otx_signing_name} = "Pool "* ]]; then dialog_start_path="${POOL_FOLDER}" - elif [[ ${otx_signing_name} = "Asset "* ]]; then dialog_start_path="${POOL_FOLDER}" - else dialog_start_path="${WALLET_FOLDER}"; fi - fileDialog "\nEnter path to ${otx_signing_name}" "${dialog_start_path}/" + if [[ -n ${skey_path} ]]; then + println DEBUG "\nFound a match for ${otx_signing_name}, use this file ? : ${FG_LGRAY}${skey_path}${NC}" + select_opt "[y] Yes" "[s] Skip" + case $? in + 0) if ! witnessTx "${TMP_DIR}/tx.raw" "${skey_path}"; then waitToProceed && continue 2; fi + if ! offlineJSON=$(jq ".witness += [{ name: \"${otx_signing_name}\", witnessBody: $(jq -c . "${tx_witness_files[0]}") }]" <<< ${offlineJSON}); then return 1; fi + jq -r . <<< "${offlineJSON}" > "${offline_tx}" # save this witness to disk + continue ;; + 1) continue ;; + esac + else + println DEBUG "\nDo you want to sign ${otx_type} with: ${FG_LGRAY}${otx_signing_name}${NC} ?" + select_opt "[y] Yes" "[s] Skip" + selection=$? + fi + [[ ${selection} -eq 1 ]] && continue + if [[ ${otx_signing_name} = "Pool "* ]]; then dialog_start_path="${POOL_FOLDER}" + elif [[ ${otx_signing_name} = "Asset "* ]]; then dialog_start_path="${ASSET_FOLDER}" + else dialog_start_path="${WALLET_FOLDER}"; fi + fileDialog "\nEnter path to ${otx_signing_name}" "${dialog_start_path}/" + [[ ! -f "${file}" ]] && println ERROR "${FG_RED}ERROR${NC}: file not found: ${file}" && waitToProceed && continue 2 + if [[ ${file} = "${ASSET_POLICY_SCRIPT_FILENAME}" ]]; then + if ! grep -q "$(_jq '.script.keyHash')" "${file}"; then + println ERROR "${FG_RED}ERROR${NC}: script file provided doesn't match with script hash in transaction for: ${otx_signing_name}" + println ERROR "Provided asset script keyHash: $(jq -r '.keyHash' "${file}")" + println ERROR "Transaction asset script keyHash: $(_jq '.script.keyHash')" + waitToProceed && continue 2 + fi + elif [[ $(jq -er '.description' "${file}" 2>/dev/null) = *"Hardware"* ]]; then + if ! grep -q "${otx_vkey_cborHex:4}" "${file}"; then # strip 5820 prefix + println ERROR "${FG_RED}ERROR${NC}: signing key provided doesn't match with verification key in transaction for: ${otx_signing_name}" + println ERROR "Provided hardware signing key's verification cborXPubKeyHex: $(jq -r .cborXPubKeyHex "${file}")" + println ERROR "Transaction verification cborHex: ${otx_vkey_cborHex:4}" + waitToProceed && continue 2 + fi + else + println ACTION "${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file ${file} --verification-key-file ${TMP_DIR}/tmp.vkey" + if ! stdout=$(${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${file}" --verification-key-file "${TMP_DIR}"/tmp.vkey 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during verification key creation!\n${stdout}"; waitToProceed && continue 2 + fi + if [[ $(jq -r '.type' "${file}") = *"Extended"* ]]; then + println ACTION "${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file ${TMP_DIR}/tmp.vkey --verification-key-file ${TMP_DIR}/tmp2.vkey" + if ! stdout=$(${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file "${TMP_DIR}/tmp.vkey" --verification-key-file "${TMP_DIR}/tmp2.vkey" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during non-extended verification key creation!\n${stdout}"; waitToProceed && continue 2 + fi + mv -f "${TMP_DIR}/tmp2.vkey" "${TMP_DIR}/tmp.vkey" + fi + if [[ ${otx_vkey_cborHex} != $(jq -r .cborHex "${TMP_DIR}"/tmp.vkey) ]]; then + println ERROR "${FG_RED}ERROR${NC}: signing key provided doesn't match with verification key in transaction for: ${otx_signing_name}" + println ERROR "Provided signing key's verification cborHex: $(jq -r .cborHex "${TMP_DIR}"/tmp.vkey)" + println ERROR "Transaction verification cborHex: ${otx_vkey_cborHex}" + waitToProceed && continue 2 + fi + fi + if ! witnessTx "${TMP_DIR}/tx.raw" "${file}"; then waitToProceed && continue 2; fi + if ! offlineJSON=$(jq ".witness += [{ name: \"${otx_signing_name}\", witnessBody: $(jq -c . "${tx_witness_files[0]}") }]" <<< ${offlineJSON}); then return 1; fi + jq -r . <<< "${offlineJSON}" > "${offline_tx}" # save this witness to disk + done + unset script_failed pay_script_signers stake_script_signers drep_script_signers + for otx_script in $(jq -r '."script-file"[] | @base64' <<< "${offlineJSON}"); do + _jq() { base64 -d <<< ${otx_script} | jq -r "${1}"; } + otx_script_name=$(_jq '.name') + otx_script_scripts="$(_jq '.script' 2>/dev/null)" + getAllMultiSigKeys "${otx_script_scripts}" + # loop once to add all already signed creds + missing_creds=() + script_sig_creds=() + for sig in "${!script_sig_list[@]}"; do + for otx_witness in $(jq -r '.witness[] | @base64' <<< "${offlineJSON}"); do + __jq() { base64 -d <<< ${otx_witness} | jq -r "${1}"; } + [[ ${sig} = $(__jq '.name') ]] && script_sig_creds+=( ${sig} ) && continue 2 # offline transaction already witnessed by this signing key + done + missing_creds+=( "${sig}" ) + done + # Check if script meets requirement + if validateMultiSigScript false "${otx_script_scripts}" "${script_sig_creds[@]}"; then + # script successfully validated, no more signatures needed + println DEBUG "\n${FG_LGRAY}${otx_script_name}${NC} validation ${FG_GREEN}passed${NC}! No more signatures needed!" + continue + fi + # loop again if needed + for sig in "${missing_creds[@]}"; do + # Check if script meets requirement + if validateMultiSigScript false "${otx_script_scripts}" "${script_sig_creds[@]}"; then + # script successfully validated, no more signatures needed + println DEBUG "\n${FG_LGRAY}${otx_script_name}${NC} validation ${FG_GREEN}passed${NC}! No more signatures needed!" + break + fi + unset skey_path + # look for matching credential in wallet folder + while IFS= read -r -d '' wallet; do + wallet_name=$(basename ${wallet}) + getWalletType "${wallet_name}" + getCredentials "${wallet_name}" + getGovKeyInfo "${wallet_name}" + if [[ ${ms_pay_cred} = "${sig}" ]]; then + skey_path="${ms_payment_sk_file}"; break + elif [[ ${ms_stake_cred} = "${sig}" ]]; then + skey_path="${ms_stake_sk_file}"; break + elif [[ ${pay_cred} = "${sig}" ]]; then + skey_path="${payment_sk_file}"; break + elif [[ ${stake_cred} = "${sig}" ]]; then + skey_path="${stake_sk_file}"; break + elif [[ ${ms_drep_hash} = "${sig}" ]]; then + skey_path="${ms_drep_sk_file}"; break + elif [[ ${drep_hash} = "${sig}" ]]; then + skey_path="${drep_sk_file}"; break + fi + done < <(find "${WALLET_FOLDER}" -mindepth 1 -maxdepth 1 -type d -print0) + [[ -n ${skey_path} && ! -f "${skey_path}" ]] && println ERROR "\n${FG_YELLOW}WARN${NC}: Wallet match found but signing key missing: ${skey_path}" && unset skey_path + # matching MultiSig participant wallet found? + if [[ -n ${skey_path} ]]; then + println DEBUG "\nFound a matching wallet for ${FG_LGRAY}${otx_script_name}${NC}, use this file ? : ${FG_LGRAY}${skey_path}${NC}" + select_opt "[y] Yes" "[s] Skip participant" + case $? in + 0) if ! witnessTx "${TMP_DIR}/tx.raw" "${skey_path}"; then waitToProceed && continue 2; fi + if ! offlineJSON=$(jq ".witness += [{ name: \"${sig}\", witnessBody: $(jq -c . "${tx_witness_files[0]}") }]" <<< ${offlineJSON}); then return 1; fi + jq -r . <<< "${offlineJSON}" > "${offline_tx}" # save this witness to disk + script_sig_creds+=( ${sig} ) + continue ;; + 1) continue ;; + esac + else + println DEBUG "\nNo match found, continue with manual input to signature file for ${FG_LGRAY}${otx_script_name}${NC} with credential below?\n${FG_LGRAY}${sig}${NC}" + select_opt "[p] Enter path" "[s] Skip participant" + selection=$? + fi + if [[ ${selection} -eq 1 ]]; then + continue + else + # choose + fileDialog "\nEnter path to signing key for MultiSig participant" "${WALLET_FOLDER}/" [[ ! -f "${file}" ]] && println ERROR "${FG_RED}ERROR${NC}: file not found: ${file}" && waitToProceed && continue 2 - if [[ ${file} = "${ASSET_POLICY_SCRIPT_FILENAME}" ]]; then - if ! grep -q "$(_jq '.script.keyHash')" "${file}"; then - println ERROR "${FG_RED}ERROR${NC}: script file provided doesn't match with script hash in offline transaction for: ${otx_signing_name}" - println ERROR "Provided asset script keyHash: $(jq -r '.keyHash' "${file}")" - println ERROR "Transaction asset script keyHash: $(_jq '.script.keyHash')" - waitToProceed && continue 2 + file_desc=$(jq -er '.description' "${file}" 2>/dev/null) + if [[ ${file_desc} = *"Hardware"* ]]; then + dir_path=$(dirname "${file}") + if ! vkey=$(jq -er .cborXPubKeyHex "${file}"); then + println ERROR "${FG_RED}ERROR${NC}: signing key provided is invalid, missing field 'cborXPubKeyHex'" && continue + fi + vkey=${vkey:4:64} + # find vkey file in same folder + if ! vkey_file=$(grep -l "cborHex.*${vkey}" "${dir_path}"/*); then + println ERROR "${FG_RED}ERROR${NC}: unable to find a matching verification key file for provided hardware signing key in same folder" && continue fi - elif [[ $(jq -er '.description' "${file}" 2>/dev/null) = *"Hardware"* ]]; then - if ! grep -q "${otx_vkey_cborHex:4}" "${file}"; then # strip 5820 prefix - println ERROR "${FG_RED}ERROR${NC}: signing key provided doesn't match with verification key in offline transaction for: ${otx_signing_name}" - println ERROR "Provided hardware signing key's verification cborXPubKeyHex: $(jq -r .cborXPubKeyHex "${file}")" - println ERROR "Transaction verification cborHex: ${otx_vkey_cborHex:4}" - waitToProceed && continue 2 + vkey_file=$(echo "${vkey_file}" | head -n 1) # make sure there is a single match + if [[ ${file_desc} = *"Payment"* ]]; then + cred_type=payment + elif [[ ${file_desc} = *"Stake"* ]]; then + cred_type=stake + else + cred_type=drep fi + getCredential ${cred_type} ${vkey_file} else println ACTION "${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file ${file} --verification-key-file ${TMP_DIR}/tmp.vkey" if ! stdout=$(${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${file}" --verification-key-file "${TMP_DIR}"/tmp.vkey 2>&1); then println ERROR "\n${FG_RED}ERROR${NC}: failure during verification key creation!\n${stdout}"; waitToProceed && continue 2 fi - if [[ $(jq -r '.type' "${file}") = *"Extended"* ]]; then + file_type=$(jq -r '.type' "${file}") + if [[ ${file_type} = *"Extended"* ]]; then println ACTION "${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file ${TMP_DIR}/tmp.vkey --verification-key-file ${TMP_DIR}/tmp2.vkey" if ! stdout=$(${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file "${TMP_DIR}/tmp.vkey" --verification-key-file "${TMP_DIR}/tmp2.vkey" 2>&1); then println ERROR "\n${FG_RED}ERROR${NC}: failure during non-extended verification key creation!\n${stdout}"; waitToProceed && continue 2 fi mv -f "${TMP_DIR}/tmp2.vkey" "${TMP_DIR}/tmp.vkey" fi - if [[ ${otx_vkey_cborHex} != $(jq -r .cborHex "${TMP_DIR}"/tmp.vkey) ]]; then - println ERROR "${FG_RED}ERROR${NC}: signing key provided doesn't match with verification key in offline transaction for: ${otx_signing_name}" - println ERROR "Provided signing key's verification cborHex: $(jq -r .cborHex "${TMP_DIR}"/tmp.vkey)" - println ERROR "Transaction verification cborHex: ${otx_vkey_cborHex}" - waitToProceed && continue 2 + if [[ ${file_desc} = *"Payment"* ]]; then + cred_type=payment + elif [[ ${file_desc} = *"Stake"* ]]; then + cred_type=stake + else + cred_type=drep fi + getCredential ${cred_type} "${TMP_DIR}"/tmp.vkey + fi + if [[ ${cred} != ${sig} ]]; then + println ERROR "${FG_RED}ERROR${NC}: signing key provided doesn't match with credential in MultiSig script:${FG_LGRAY}${otx_script_name}${NC}" + println ERROR "Provided signing key's credential : ${FG_LGRAY}${cred}${NC}" + println ERROR "Looking for credential : ${FG_LGRAY}${sig}${NC}" + waitToProceed && continue + fi + if ! witnessTx "${TMP_DIR}/tx.raw" "${file}"; then waitToProceed && continue 2; fi + if ! offlineJSON=$(jq ".witness += [{ name: \"${sig}\", witnessBody: $(jq -c . "${tx_witness_files[0]}") }]" <<< ${offlineJSON}); then return 1; fi + jq -r . <<< "${offlineJSON}" > "${offline_tx}" # save this witness to disk + script_sig_creds+=( ${sig} ) + fi + done + unset required_total + if ! validateMultiSigScript true "${otx_script_scripts}" "${script_sig_creds[@]}"; then + # script failed validation + script_failed=true + println ERROR "\n${FG_LGRAY}${otx_script_name}${NC} validation ${FG_RED}failed${NC}! Unable to submit transaction until needed signatures are added and/or time lock conditions if set pass!" + println DEBUG "If external participant signatures are needed, pass transaction file along to add additional signatures." + waitToProceed + fi + if [[ ${file_desc} = *"payment"* ]]; then + pay_script_signers=${required_total} + elif [[ ${file_desc} = *"stake"* ]]; then + stake_script_signers=${required_total} + else + drep_script_signers=${required_total} + fi + done + signatures_needed=$(( $(jq -r '."signing-file" | length' <<< "${offlineJSON}") + pay_script_signers + stake_script_signers + drep_script_signers )) + witness_cnt=$(jq -r '.witness | length' <<< "${offlineJSON}") + if [[ ${witness_cnt} -ge ${signatures_needed} && -z ${script_failed} ]]; then # witnessed by all needed signing keys + tx_witness_files=() + for otx_witness in $(jq -r '.witness[] | @base64' <<< "${offlineJSON}"); do + _jq() { base64 -d <<< ${otx_witness} | jq -r "${1}"; } + tx_witness="$(mktemp "${TMP_DIR}/tx.witness_XXXXXXXXXX")" + jq -r . <<< "$(_jq '.witnessBody')" > "${tx_witness}" + tx_witness_files+=( "${tx_witness}" ) + done + if ! assembleTx "${TMP_DIR}/tx.raw"; then waitToProceed && continue; fi + if jq ". += { \"signed-txBody\": $(jq -c . "${tx_signed}") }" <<< "${offlineJSON}" > "${offline_tx}"; then + println "\nTransaction successfully assembled and signed by all needed signing keys" + println "please submit on online node before ${FG_LGRAY}$(date '+%F %T %Z' --date="${otx_date_expire}")${NC}!" + else + println ERROR "${FG_RED}ERROR${NC}: failed to write signed tx body to offline transaction file!" + fi + else + println "Transaction need to be signed by ${FG_LBLUE}${signatures_needed}${NC} signing keys, signed by ${FG_LBLUE}${witness_cnt}${NC} so far!" + fi + waitToProceed && continue + ;; ################################################################### + submit) + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> TRANSACTION >> SUBMIT" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then + println ERROR "${FG_RED}ERROR${NC}: CNTools started in offline mode, option not available!" + waitToProceed && continue + fi + echo + fileDialog "Enter path to offline tx file to submit" "${TMP_DIR}/" && echo + offline_tx=${file} + [[ -z "${offline_tx}" ]] && continue + if [[ ! -f "${offline_tx}" ]]; then + println ERROR "${FG_RED}ERROR${NC}: file not found: ${offline_tx}" + waitToProceed && continue + elif ! offlineJSON=$(jq -erc . "${offline_tx}"); then + println ERROR "${FG_RED}ERROR${NC}: invalid JSON file: ${offline_tx}" + waitToProceed && continue + fi + if ! otx_type=$(jq -er '.type' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'type' not found in: ${offline_tx}" && waitToProceed && continue; fi + if ! otx_date_created=$(jq -er '."date-created"' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'date-created' not found in: ${offline_tx}" && waitToProceed && continue; fi + if ! otx_date_expire=$(jq -er '."date-expire"' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'date-expire' not found in: ${offline_tx}" && waitToProceed && continue; fi + if ! otx_txFee=$(jq -er '.txFee' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'txFee' not found in: ${offline_tx}" && waitToProceed && continue; fi + if ! otx_signed_txBody=$(jq -er '."signed-txBody"' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'signed-txBody' not found in: ${offline_tx}" && waitToProceed && continue; fi + [[ $(jq 'length' <<< ${otx_signed_txBody}) -eq 0 ]] && println ERROR "${FG_RED}ERROR${NC}: transaction not signed, please sign transaction first!" && waitToProceed && continue + println DEBUG "Transaction type : ${FG_YELLOW}${otx_type}${NC}" + if jq -er '."wallet-name"' &>/dev/null <<< ${offlineJSON}; then + println DEBUG "Transaction fee : ${FG_LBLUE}$(formatLovelace ${otx_txFee})${NC} ADA, payed by ${FG_GREEN}$(jq -r '."wallet-name"' <<< ${offlineJSON})${NC}" + else + println DEBUG "Transaction fee : ${FG_LBLUE}$(formatLovelace ${otx_txFee})${NC} ADA" + fi + println DEBUG "Created : ${FG_LGRAY}$(date '+%F %T %Z' --date="${otx_date_created}")${NC}" + [[ $(date '+%s' --date="${otx_date_expire}") -lt $(date '+%s') ]] && expire_color="${FG_RED}" || expire_color="${FG_LGRAY}" + println DEBUG "Expire : ${expire_color}$(date '+%F %T %Z' --date="${otx_date_expire}")${NC}" + echo + [[ ${otx_type} = "Wallet De-Registration" ]] && println DEBUG "Amount returned : ${FG_LBLUE}$(formatLovelace "$(jq -r '."amount-returned"' <<< ${offlineJSON})")${NC} ADA" + if [[ ${otx_type} = "Payment" ]]; then + println DEBUG "Source addr : ${FG_LGRAY}$(jq -r '."source-address"' <<< ${offlineJSON})${NC}" + println DEBUG "Destination addr : ${FG_LGRAY}$(jq -r '."destination-address"' <<< ${offlineJSON})${NC}" + println DEBUG "Amount : ${FG_LBLUE}$(formatLovelace "$(jq -r '.assets[] | select(.asset=="lovelace") | .amount' <<< ${offlineJSON})")${NC} ${FG_GREEN}ADA${NC}" + for otx_assets in $(jq -r '.assets[] | @base64' <<< "${offlineJSON}"); do + _jq() { base64 -d <<< ${otx_assets} | jq -r "${1}"; } + otx_asset=$(_jq '.asset') + [[ ${otx_asset} = "lovelace" ]] && continue + println DEBUG " ${FG_LBLUE}$(formatAsset "$(_jq '.amount')")${NC} ${FG_LGRAY}${otx_asset}${NC}" + done + fi + [[ ${otx_type} = "Wallet Rewards Withdrawal" ]] && println DEBUG "Rewards : ${FG_LBLUE}$(formatLovelace "$(jq -r '.rewards' <<< ${offlineJSON})")${NC} ADA" + jq -er '."pool-id"' <<< ${offlineJSON} &>/dev/null && println DEBUG "Pool ID : ${FG_LGRAY}$(jq -r '."pool-id"' <<< ${offlineJSON})${NC}" + if jq -er '."pool-name"' <<< ${offlineJSON} &>/dev/null; then + [[ ${otx_type} != "Pool Registration" ]] && println DEBUG "Pool name : ${FG_LGRAY}$(jq -r '."pool-name"' <<< ${offlineJSON})${NC}" + fi + [[ ${otx_type} = "Pool De-Registration" ]] && println DEBUG "Ticker : ${FG_LGRAY}$(jq -r '."pool-ticker"' <<< ${offlineJSON})${NC}" + [[ ${otx_type} = "Pool De-Registration" ]] && println DEBUG "To be retired : epoch ${FG_LGRAY}$(jq -r '."retire-epoch"' <<< ${offlineJSON})${NC}" + jq -er '.metadata' <<< ${offlineJSON} &>/dev/null && println DEBUG "Metadata :\n$(jq -r '.metadata' <<< ${offlineJSON})\n" + [[ ${otx_type} = "Pool Registration" || ${otx_type} = "Pool Update" ]] && println DEBUG "Pool name : ${FG_LGRAY}$(jq -r '."pool-metadata".name' <<< ${offlineJSON})${NC}" + [[ ${otx_type} = "Pool Registration" || ${otx_type} = "Pool Update" ]] && println DEBUG "Ticker : ${FG_LGRAY}$(jq -r '."pool-metadata".ticker' <<< ${offlineJSON})${NC}" + [[ ${otx_type} = "Pool Registration" || ${otx_type} = "Pool Update" ]] && println DEBUG "Pledge : ${FG_LBLUE}$(formatLovelace "$(ADAToLovelace "$(jq -r '."pool-pledge"' <<< ${offlineJSON})")")${NC} ADA" + [[ ${otx_type} = "Pool Registration" || ${otx_type} = "Pool Update" ]] && println DEBUG "Margin : ${FG_LBLUE}$(jq -r '."pool-margin"' <<< ${offlineJSON})${NC} %" + [[ ${otx_type} = "Pool Registration" || ${otx_type} = "Pool Update" ]] && println DEBUG "Cost : ${FG_LBLUE}$(formatLovelace "$(ADAToLovelace "$(jq -r '."pool-cost"' <<< ${offlineJSON})")")${NC} ADA" + [[ ${otx_type} = "Asset Minting" || ${otx_type} = "Asset Burning" ]] && println DEBUG "Policy Name : ${FG_LGRAY}$(jq -r '."policy-name"' <<< ${offlineJSON})${NC}" + [[ ${otx_type} = "Asset Minting" || ${otx_type} = "Asset Burning" ]] && println DEBUG "Policy ID : ${FG_LGRAY}$(jq -r '."policy-id"' <<< ${offlineJSON})${NC}" + [[ ${otx_type} = "Asset Minting" || ${otx_type} = "Asset Burning" ]] && println DEBUG "Asset Name : ${FG_LGRAY}$(jq -r '."asset-name"' <<< ${offlineJSON})${NC}" + [[ ${otx_type} = "Asset Minting" ]] && println DEBUG "Assets To Mint : ${FG_LBLUE}$(formatAsset "$(jq -r '."asset-amount"' <<< ${offlineJSON})")${NC}" + [[ ${otx_type} = "Asset Minting" ]] && println DEBUG "Assets Minted : ${FG_LBLUE}$(formatAsset "$(jq -r '."asset-minted"' <<< ${offlineJSON})")${NC}" + [[ ${otx_type} = "Asset Burning" ]] && println DEBUG "Assets To Burn : ${FG_LBLUE}$(formatAsset "$(jq -r '."asset-amount"' <<< ${offlineJSON})")${NC}" + [[ ${otx_type} = "Asset Burning" ]] && println DEBUG "Assets Left : ${FG_LBLUE}$(formatAsset "$(jq -r '."asset-minted"' <<< ${offlineJSON})")${NC}" + if [[ ${otx_type} = "Asset Minting" || ${otx_type} = "Asset Burning" ]] && otx_metadata=$(jq -er '.metadata' <<< ${offlineJSON}); then println DEBUG "Metadata : \n${otx_metadata}\n"; fi + jq -er '."drep-wallet-name"' <<< ${offlineJSON} &>/dev/null && println DEBUG "DRep Wallet : ${FG_GREEN}$(jq -r '."drep-wallet-name"' <<< ${offlineJSON})${NC}" + jq -er '."drep-id"' <<< ${offlineJSON} &>/dev/null && println DEBUG "DRep ID : ${FG_LGRAY}$(jq -r '."drep-id"' <<< ${offlineJSON})${NC}" + jq -er '."action-id"' <<< ${offlineJSON} &>/dev/null && println DEBUG "Action ID : ${FG_LGRAY}$(jq -r '."action-id"' <<< ${offlineJSON})${NC}" + jq -er '.vote' <<< ${offlineJSON} &>/dev/null && println DEBUG "Vote : ${FG_LGRAY}$(jq -r '.vote' <<< ${offlineJSON})${NC}" + + if [[ $(date '+%s' --date="${otx_date_expire}") -lt $(date '+%s') ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: Transaction expired! please create a new one with long enough Time To Live (TTL)" + waitToProceed && continue + fi + + tx_signed="${TMP_DIR}/tx.signed_$(date +%s)" + println DEBUG "\nProceed to submit transaction?" + select_opt "[y] Yes" "[n] No" + case $? in + 0) : ;; + 1) continue ;; + esac + echo -e "${otx_signed_txBody}" > "${tx_signed}" + if ! submitTx "${tx_signed}"; then waitToProceed && continue; fi + if [[ ${otx_type} = "Pool Registration" || ${otx_type} = "Pool Update" ]]; then + if otx_pool_name=$(jq -er '."pool-name"' <<< ${offlineJSON}); then + if ! jq '."pool-reg-cert"' <<< "${offlineJSON}" > "${POOL_FOLDER}/${otx_pool_name}/${POOL_REGCERT_FILENAME}"; then println ERROR "${FG_RED}ERROR${NC}: failed to write pool cert to disk"; fi + [[ -f "${POOL_FOLDER}/${otx_pool_name}/${POOL_DEREGCERT_FILENAME}" ]] && rm -f "${POOL_FOLDER}/${otx_pool_name}/${POOL_DEREGCERT_FILENAME}" # delete de-registration cert if available + else + println ERROR "${FG_RED}ERROR${NC}: field 'pool-name' not found in: ${offline_tx}" + fi + fi + echo + verifyTx + echo + println DEBUG "Delete submitted offline transaction file?" + select_opt "[y] Yes" "[n] No" + case $? in + 0) rm -f "${offline_tx}" ;; + 1) : ;; + esac + waitToProceed && continue + ;; ################################################################### + esac # transaction sub OPERATION + done # Transaction loop + ;; ################################################################### + vote) + while true; do # Vote loop + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> VOTE" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println OFF " Voting and Governance\n"\ + " ) Governance - on-chain governance according to CIP-1694"\ + " ) Catalyst - project funding platform"\ + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println DEBUG " Select Vote Operation\n" + select_opt "[g] Governance" "[c] Catalyst" "[h] Home" + case $? in + 0) SUBCOMMAND="governance" ;; + 1) SUBCOMMAND="catalyst" ;; + 2) break ;; + esac + case $SUBCOMMAND in + governance) + while true; do # Governance loop + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> VOTE >> GOVERNANCE (CIP-1694)" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println OFF " Governance\n"\ + " ) Info & Status - show wallet governance information and status"\ + " ) Delegate - delegate wallet vote power to a DRep (own, external, or one of the pre-defined 'abstain' / 'no confidence')"\ + " ) List proposals - show a list of active proposals to vote on and their current vote status"\ + " ) Cast Vote - vote on governance actions as an SPO, DRep, or Committee member"\ + " ) DRep Reg / Upd - register wallet as a DRep for voting or submit updated anchor data for already DRep registered wallet"\ + " ) DRep Retire - retire wallet as a DRep"\ + " ) MultiSig DRep - create a multi-participant (MultiSig) DRep coalition"\ + " ) Derive Keys - derive delegate representative (DRep) and committee member keys (if needed)"\ + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println DEBUG " Select Governance Operation\n" + select_opt "[i] Info & Status" "[d] Delegate" "[l] List Proposals" "[v] Cast vote" "[r] DRep Registration / Update" "[x] DRep Retire" "[m] MultiSig DRep" "[k] Derive Keys" "[b] Back" "[h] Home" + case $? in + 0) SUBCOMMAND="info-status" ;; + 1) SUBCOMMAND="delegate" ;; + 2) SUBCOMMAND="list-proposals" ;; + 3) SUBCOMMAND="vote" ;; + 4) SUBCOMMAND="drep-reg" ;; + 5) SUBCOMMAND="drep-ret" ;; + 6) SUBCOMMAND="create-ms-drep" ;; + 7) SUBCOMMAND="derive-gov-keys" ;; + 8) break ;; + 9) break 2 ;; + esac + case $SUBCOMMAND in + info-status) + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> VOTE >> GOVERNANCE >> INFO & STATUS" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + echo + if ! versionCheck "10.0" "${PROT_VERSION}"; then + println INFO "${FG_YELLOW}Not yet in Conway era, please revisit once network has crossed into Cardano governance era!${NC}"; waitToProceed && continue + fi + [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitToProceed && continue + println DEBUG "Select wallet (derive governance keys if missing)" + selectWallet "none" + case $? in + 1) waitToProceed; continue ;; + 2) continue ;; + esac + current_epoch=$(getEpoch) + drep_script_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_DREP_SCRIPT_FILENAME}" + if [[ ${CNTOOLS_MODE} != "OFFLINE" && ! -f "${drep_script_file}" ]]; then + println "DEBUG" "\nVote Delegation Status" + unset walletName + if getWalletVoteDelegation ${wallet_name}; then + unset vote_delegation_hash + vote_delegation_type="${vote_delegation%-*}" + if [[ ${vote_delegation} = *-* ]]; then + vote_delegation_hash="${vote_delegation#*-}" + vote_delegation=$(bech32 drep <<< ${vote_delegation_hash}) + while IFS= read -r -d '' _wallet; do + getGovKeyInfo "$(basename ${_wallet})" + if [[ "${drep_id}" = "${vote_delegation}" ]]; then + walletName=" ${FG_GREEN}$(basename ${_wallet})${NC}" && break + fi + done < <(find "${WALLET_FOLDER}" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z) + fi + println "Delegation : ${FG_LGRAY}${vote_delegation}${NC}${walletName}" + if [[ ${vote_delegation} = always* ]]; then + : # do nothing + elif getDRepStatus ${vote_delegation_type} ${vote_delegation_hash}; then + [[ ${current_epoch} -lt ${drep_expiry} ]] && expire_status="${FG_GREEN}active${NC}" || expire_status="${FG_RED}inactive${NC} (vote power does not count)" + println "DRep expiry : epoch ${FG_LBLUE}${drep_expiry}${NC} - ${expire_status}" + if [[ -n ${drep_anchor_url} ]]; then + println "DRep anchor url : ${FG_LGRAY}${drep_anchor_url}${NC}" + getDRepAnchor "${drep_anchor_url}" "${drep_anchor_hash}" + case $? in + 0) println "DRep anchor data :\n${FG_LGRAY}" + jq -er "${drep_anchor_file}" 2>/dev/null || cat "${drep_anchor_file}" + println DEBUG "${NC}" + ;; + 1) println "DRep anchor data : ${FG_YELLOW}Invalid URL or currently not available${NC}" ;; + 2) println "DRep anchor data :\n${FG_LGRAY}" + jq -er "${drep_anchor_file}" 2>/dev/null || cat "${drep_anchor_file}" + println "${NC}DRep anchor hash : ${FG_YELLOW}mismatch${NC}" + println " registered : ${FG_LGRAY}${drep_anchor_hash}${NC}" + println " actual : ${FG_LGRAY}${drep_anchor_real_hash}${NC}" + ;; + esac + fi + else + println "Status : ${FG_RED}Unable to get DRep status, retired?${NC}" + fi + getDRepVotePower ${vote_delegation_type} ${vote_delegation_hash} + println "Active Vote power : ${FG_LBLUE}$(formatLovelace ${vote_power:=0})${NC} ADA (${FG_LBLUE}${vote_power_pct:=0} %${NC})" + else + println "Delegation : ${FG_YELLOW}undelegated${NC} - please note that reward withdrawals will not work in the future until wallet is vote delegated" + fi + fi + getGovKeyInfo ${wallet_name} + println "DEBUG" "\nOwn DRep Status" + if [[ -z ${drep_id} ]]; then + println "Status : ${FG_YELLOW}Governance keys missing, please derive them if needed${NC}" + waitToProceed && continue + fi + println "DRep ID : ${FG_LGRAY}${drep_id}${NC}" + println "DRep Hash : ${FG_LGRAY}${drep_hash}${NC}" + if [[ ${hash_type} = keyHash ]]; then + println "DRep Type : ${FG_LGRAY}Key${NC}" + else + println "DRep Type : ${FG_LGRAY}MultiSig${NC}" + fi + if [[ ${CNTOOLS_MODE} != "OFFLINE" ]]; then + if getDRepStatus ${hash_type} ${drep_hash}; then + [[ ${current_epoch} -lt ${drep_expiry} ]] && expire_status="${FG_GREEN}active${NC}" || expire_status="${FG_RED}inactive${NC} (vote power does not count)" + println "DRep expiry : epoch ${FG_LBLUE}${drep_expiry}${NC} - ${expire_status}" + if [[ -n ${drep_anchor_url} ]]; then + println "DRep anchor url : ${FG_LGRAY}${drep_anchor_url}${NC}" + getDRepAnchor "${drep_anchor_url}" "${drep_anchor_hash}" + case $? in + 0) println "DRep anchor data :\n${FG_LGRAY}" + jq -er "${drep_anchor_file}" 2>/dev/null || cat "${drep_anchor_file}" + println DEBUG "${NC}" + ;; + 1) println "DRep anchor data : ${FG_YELLOW}Invalid URL or currently not available${NC}" ;; + 2) println "DRep anchor data :\n${FG_LGRAY}" + jq -er "${drep_anchor_file}" 2>/dev/null || cat "${drep_anchor_file}" + println "${NC}DRep anchor hash : ${FG_YELLOW}mismatch${NC}" + println " registered : ${FG_LGRAY}${drep_anchor_hash}${NC}" + println " actual : ${FG_LGRAY}${drep_anchor_real_hash}${NC}" + ;; + esac + fi + getDRepVotePower ${hash_type} ${drep_hash} + println "Active Vote power : ${FG_LBLUE}$(formatLovelace ${vote_power:=0})${NC} ADA (${FG_LBLUE}${vote_power_pct:=0} %${NC})" + else + println "Status : ${FG_YELLOW}DRep key not registered${NC}" + fi + fi + echo + println "Committee Cold ID : ${FG_LGRAY}${cc_cold_id}${NC}" + println "Committee Hot ID : ${FG_LGRAY}${cc_hot_id}${NC}" + waitToProceed && continue + ;; ################################################################### + delegate) + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> VOTE >> GOVERNANCE >> DELEGATE" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + if ! versionCheck "10.0" "${PROT_VERSION}"; then + println INFO "\n${FG_YELLOW}Not yet in Conway era, please revisit once network has crossed into Cardano governance era!${NC}"; waitToProceed && continue + fi + if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then + println ERROR "${FG_RED}ERROR${NC}: CNTools started in offline mode, option not available!" + waitToProceed && continue + else + if ! selectOpMode; then continue; fi + fi + println DEBUG "\nSelect wallet" + selectWallet "balance" "${WALLET_STAKE_VK_FILENAME}" + case $? in + 1) waitToProceed; continue ;; + 2) continue ;; + esac + _wallet_name="${wallet_name}" + if ! isWalletRegistered ${wallet_name}; then + if [[ ${op_mode} = "online" ]]; then + # maybe this block below should be a part of registerStakeWallet? + getWalletBalance ${wallet_name} true true false true + if [[ ${base_lovelace} -lt ${KEY_DEPOSIT} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: insufficient funds (${base_lovelace}) available in base address for wallet ${FG_GREEN}${wallet_name}${NC}" + println DEBUG "Funds for key deposit($(formatLovelace ${KEY_DEPOSIT}) ADA) + transaction fee needed to register the wallet" + waitToProceed && continue + fi + + if ! registerStakeWallet ${wallet_name}; then waitToProceed && continue; fi + else + println ERROR "\n${FG_YELLOW}The wallet is not a registered wallet on chain and CNTools run in hybrid mode${NC}" + println ERROR "Please first register the wallet using 'Wallet >> Register'" + waitToProceed && continue + fi + fi + unset drep_wallet drep_hash + println DEBUG "\nDo you want to delegate to a local CNTools DRep registered wallet, pre-defined type or specify the DRep?" + select_opt "[w] CNTools DRep Wallet" "[i] DRep (ID or hash)" "[a] Always Abstain" "[c] Always No Confidence" "[Esc] Cancel" + case $? in + 0) selectWallet "none" + case $? in + 1) waitToProceed; continue ;; + 2) continue ;; + esac + drep_wallet="${wallet_name}" + wallet_name="${_wallet_name}" + getGovKeyInfo "${drep_wallet}" + if [[ -z ${drep_id} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: unable to get DRep id from selected wallet :(" + waitToProceed && continue + fi + ;; + 1) getAnswerAnyCust drep_id "DRep (blank to cancel)" + [[ -z "${drep_id}" ]] && continue + [[ ${drep_id} != drep* ]] && drep_id=$(bech32 drep <<< "${drep_id}" 2>/dev/null) + [[ ${#drep_id} -ne 56 || ${drep_id} != drep* ]] && println ERROR "\n${FG_RED}ERROR${NC}: invalid DRep ID entered!" && waitToProceed && continue + ;; + 2) drep_id="alwaysAbstain"; vote_param=("--always-abstain") ;; + 3) drep_id="alwaysNoConfidence"; vote_param=("--always-no-confidence") ;; + 4) continue ;; + esac + unset drep_expiry + if [[ ${drep_id} != always* ]]; then + [[ -z ${drep_hash} ]] && drep_hash=$(bech32 <<< "${drep_id}") + getDRepStatus keyHash ${drep_hash} + [[ -z ${drep_expiry} ]] && getDRepStatus scriptHash ${drep_hash} + if [[ -z ${drep_expiry} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: selected DRep not registered" + waitToProceed && continue + fi + if [[ $(getEpoch) -ge ${drep_expiry} ]]; then + println ERROR "\n${FG_YELLOW}WARN${NC}: selected DRep is marked as inactive and its vote power doesn't currently count, continue anyway?" + select_opt "[y] Yes" "[n] No" + case $? in + 0) : ;; # do nothing + 1) continue ;; + esac + fi + [[ ${hash_type} = keyHash ]] && vote_param=("--drep-key-hash" "${drep_hash}") || vote_param=("--drep-script-hash" "${drep_hash}") + getDRepVotePower keyHash ${drep_hash} + [[ -z ${vote_power} ]] && getDRepVotePower scriptHash ${drep_hash} + if [[ -z ${vote_power} ]]; then + println ERROR "\n${FG_YELLOW}WARN${NC}: selected DRep has no active vote power associated with it, continue?" + select_opt "[y] Yes" "[n] No" + case $? in + 0) : ;; # do nothing + 1) continue ;; + esac + fi + else + getDRepVotePower "${drep_id}" + fi + getWalletBalance ${wallet_name} true true false true + if [[ ${base_lovelace} -le 0 ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: no funds available in base address for wallet ${FG_GREEN}${wallet_name}${NC}" + println DEBUG "Funds for transaction fee needed to create vote delegation transaction" + waitToProceed && continue + fi + if ! voteDelegation; then + [[ -f ${vote_deleg_cert_file} ]] && rm -f ${vote_deleg_cert_file} + waitToProceed && continue + fi + echo + if ! verifyTx ${base_addr}; then waitToProceed && continue; fi + echo + println "${FG_GREEN}${wallet_name}${NC} successfully delegated to DRep!" + println "\nDRep ID : ${FG_LGRAY}${drep_id}${NC}" + if [[ -n ${drep_expiry} ]]; then + [[ $(getEpoch) -lt ${drep_expiry} ]] && expire_status="${FG_GREEN}active${NC}" || expire_status="${FG_RED}inactive${NC} (vote power does not count)" + println "DRep expiry : epoch ${FG_LBLUE}${drep_expiry}${NC} - ${expire_status}" + fi + println "Active DRep vote power : ${FG_LBLUE}$(formatLovelace ${vote_power:=0})${NC} ADA (${FG_LBLUE}${vote_power_pct:=0} %${NC})" + waitToProceed && continue + ;; ################################################################### + list-proposals) + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> VOTE >> GOVERNANCE >> LIST PROPOSALS" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + echo + tput sc && println DEBUG "Querying for list of proposals...\n" + getAllGovActions + tput rc && tput ed + action_cnt=${#vote_action_list[@]} + if [[ ${action_cnt} -eq 0 ]]; then + println "${FG_YELLOW}No active proposals to vote on!${NC}" + waitToProceed && continue + fi + getAnswerAnyCust page_entries "Enter number of actions to display per page (enter for 5)" + page_entries=${page_entries:=5} + if ! isNumber ${page_entries} || [[ ${page_entries} -eq 0 ]]; then + println ERROR "${FG_RED}ERROR${NC}: invalid number" + waitToProceed && continue + fi + page=1 + pages=$(( (action_cnt + (page_entries - 1)) / page_entries )) + echo + tput sc + while true; do + tput rc && tput ed + start_idx=$(( (page * page_entries) - page_entries )) + # loop current page to find max length of entries + max_len=66 # assume action id (66) + for vote_action in "${vote_action_list[@]:${start_idx}:${page_entries}}"; do + IFS=',' read -r action_id action_type proposed_in expires_after anchor_url <<< "${vote_action}" + [[ ${#action_id} -gt ${max_len} ]] && max_len=${#action_id} + [[ ${#action_type} -gt ${max_len} ]] && max_len=${#action_type} + [[ ${#anchor_url} -gt ${max_len} ]] && max_len=${#anchor_url} + done + total_len=$(( max_len + 13 + 5 )) + border_line="|$(printf "%${total_len}s" | tr " " "=")|" # max value length + longest title (13) + spacing (5) + println DEBUG "Current epoch : ${FG_LBLUE}$(getEpoch)${NC}" + println DEBUG "Proposals : ${FG_LBLUE}${action_cnt}${NC}" + println DEBUG "\n${border_line}" + idx=1 + for vote_action in "${vote_action_list[@]:${start_idx}:${page_entries}}"; do + [[ $idx -ne 1 ]] && printf "|$(printf "%${total_len}s" | tr " " "-")|\n" + IFS=',' read -r action_id action_type proposed_in expires_after anchor_url drep_yes drep_no drep_abstain spo_yes spo_no spo_abstain c_yes c_no c_abstain <<< "${vote_action}" + printf "| %-13s : ${FG_LGRAY}%-${max_len}s${NC} |\n" "Action ID" "${action_id}" + printf "| %-13s : ${FG_LGRAY}%-${max_len}s${NC} |\n" "Type" "${action_type}" + printf "| %-13s : epoch ${FG_LBLUE}%-$(( max_len - 6 ))s${NC} |\n" "Proposed In" "${proposed_in}" + printf "| %-13s : epoch ${FG_LBLUE}%-$(( max_len - 6 ))s${NC} |\n" "Expires After" "${expires_after}" + printf "| %-13s : ${FG_LGRAY}%-${max_len}s${NC} |\n" "Anchor URL" "${anchor_url}" + printf "| %-13s : Yes=${FG_LBLUE}%s${NC} No=${FG_LBLUE}%s${NC} Abstain=${FG_LBLUE}%-$((max_len-4-${#drep_yes}-4-${#drep_no}-9))s${NC} |\n" "DRep" "${drep_yes}" "${drep_no}" "${drep_abstain}" + printf "| %-13s : Yes=${FG_LBLUE}%s${NC} No=${FG_LBLUE}%s${NC} Abstain=${FG_LBLUE}%-$((max_len-4-${#spo_yes}-4-${#spo_no}-9))s${NC} |\n" "SPO" "${spo_yes}" "${spo_no}" "${spo_abstain}" + printf "| %-13s : Yes=${FG_LBLUE}%s${NC} No=${FG_LBLUE}%s${NC} Abstain=${FG_LBLUE}%-$((max_len-4-${#c_yes}-4-${#c_no}-9))s${NC} |\n" "Committee" "${c_yes}" "${c_no}" "${c_abstain}" + ((idx++)) + done + println DEBUG "${border_line}" + [[ ${pages} -eq 1 ]] && waitToProceed && continue 2 + unset hasPrev hasNext + println OFF "\nPage ${FG_LBLUE}${page}${NC} of ${FG_LGRAY}${pages}${NC}\n" + if [[ ${page} -gt 1 && ${page} -lt ${pages} ]]; then + hasPrev=Y; hasNext=Y + println OFF "[p] Previous Page | [n] Next Page | [r] Return" + elif [[ ${page} -eq 1 && ${page} -lt ${pages} ]]; then + hasNext=Y + println OFF "${FG_DGRAY}[p] Previous Page${NC} | [n] Next Page | [r] Return" + else + hasPrev=Y + println OFF "[p] Previous Page | ${FG_DGRAY}[n] Next Page${NC} | [r] Return" + fi + read -rsn1 key + case ${key} in + r ) continue 2 ;; + p ) [[ -n ${hasPrev} ]] && ((page--)) ;; + n ) [[ -n ${hasNext} ]] && ((page++)) ;; + esac + done + ;; ################################################################### + vote) + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> VOTE >> GOVERNANCE >> CAST VOTE" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + echo + if ! versionCheck "10.0" "${PROT_VERSION}"; then + println INFO "${FG_YELLOW}Not yet in Conway era, please revisit once network has crossed into Cardano governance era!${NC}"; waitToProceed && continue + fi + if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then + println ERROR "${FG_RED}ERROR${NC}: CNTools started in offline mode, option not available!" + waitToProceed && continue + else + if ! selectOpMode; then continue; fi + fi + println DEBUG "\nSelect role to vote as" + select_opt "[s] SPO" "[d] DRep" "[c] Committee member" "[Esc] Cancel" + case $? in + 0) vote_mode="spo" + selectPool "reg" "${POOL_COLDKEY_VK_FILENAME}" + case $? in + 1) waitToProceed; continue ;; + 2) continue ;; + esac + println DEBUG "\nSelect wallet to pay for transaction fee" + selectWallet "balance" ${WALLET_PAY_VK_FILENAME} + case $? in + 1) waitToProceed; continue ;; + 2) continue ;; + esac + getPoolID "${pool_name}" + pool_coldkey_vk_file="${POOL_FOLDER}/${pool_name}/${POOL_COLDKEY_VK_FILENAME}" + pool_coldkey_sk_file="${POOL_FOLDER}/${pool_name}/${POOL_COLDKEY_SK_FILENAME}" + ;; + 1) vote_mode="drep" + selectWallet "none" + case $? in + 1) waitToProceed; continue ;; + 2) continue ;; + esac + drep_wallet_name="${wallet_name}" + getGovKeyInfo ${drep_wallet_name} + if [[ -z ${hash_type} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: Wallet missing governance keys!" + waitToProceed && continue + elif [[ ${hash_type} = "scriptHash" ]]; then + println DEBUG "\nSelect wallet to pay for transaction fee" + selectWallet "balance" ${WALLET_PAY_VK_FILENAME} + case $? in + 1) waitToProceed; continue ;; + 2) continue ;; + esac + fi + ;; + 2) vote_mode="committee" + selectWallet "none" "${WALLET_GOV_CC_HOT_VK_FILENAME}" + case $? in + 1) waitToProceed; continue ;; + 2) continue ;; + esac + getGovKeyInfo ${wallet_name} + if [[ -z ${cc_cold_id} || -z ${cc_hot_id} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: Wallet missing governance committee keys!" + waitToProceed && continue + fi + ;; + 4) continue ;; + esac + if [[ ${vote_mode} = "committee" ]]; then + if ! isCommitteeMember $(bech32 <<< ${cc_cold_id}); then + println ERROR "\n${FG_RED}ERROR${NC}: selected wallet is not an active committee member!" + waitToProceed && continue + fi + hash_type="keyHash" + elif [[ ${vote_mode} = "drep" ]]; then + if ! getDRepStatus ${hash_type} ${drep_hash}; then + println ERROR "\n${FG_RED}ERROR${NC}: wallet not registered as a DRep!" + waitToProceed && continue + fi + if ! getDRepVotePower ${hash_type} ${drep_hash}; then + println ERROR "\n${FG_RED}ERROR${NC}: selected wallet has no vote power associated with it!" + waitToProceed && continue + fi + fi + echo + getAnswerAnyCust action_id "Governance Action ID [#] (blank to cancel)" + [[ -z "${action_id}" ]] && continue + IFS='#' read -r action_tx_id action_idx <<< "${action_id}" + ! isNumber "${action_idx}" && println ERROR "\n${FG_RED}ERROR${NC}: invalid action id! #" && waitToProceed && continue + getGovAction "${action_tx_id}" + case $? in + 1) println ERROR "\n${FG_RED}ERROR${NC}: governance action id not found!"; waitToProceed && continue ;; + 2) println ERROR "\n${FG_YELLOW}WARN${NC}: invalid governance action proposal anchor url or content" + println DEBUG "URL : ${FG_LGRAY}${proposal_url}${NC}" + println DEBUG "\nContinue?" + select_opt "[n] No" "[y] Yes" + case $? in + 0) continue ;; + 1) : ;; # do nothing + esac + ;; + 3) println ERROR "\n${FG_YELLOW}WARN${NC}: invalid governance action proposal anchor hash" + println DEBUG "Action hash : ${FG_LGRAY}${proposal_hash}${NC}" + println DEBUG "Real hash : ${FG_LGRAY}${proposal_meta_hash}${NC}" + println DEBUG "\nContinue?" + select_opt "[n] No" "[y] Yes" + case $? in + 0) continue ;; + 1) : ;; # do nothing + esac + ;; + esac + if [[ -f "${proposal_meta_file}" ]]; then + println DEBUG "\nGovernance Action Anchor Content${FG_LGRAY}" + jq -er "${proposal_meta_file}" 2>/dev/null || cat "${proposal_meta_file}" + fi + println DEBUG "${NC}\nHow do you want to vote?" + select_opt "[y] Yes" "[n] No" "[a] Abstain" "[Esc] Cancel" + case $? in + 0) vote_param="--yes" ;; + 1) vote_param="--no" ;; + 2) vote_param="--abstain" ;; + 3) continue ;; + esac + vote_file="${TMP_DIR}/${action_tx_id}_${action_idx}_$(date '+%Y%m%d%H%M%S').vote" + VOTE_CMD=( + ${CCLI} ${NETWORK_ERA} governance vote create + ${vote_param} + --governance-action-tx-id "${action_tx_id}" + --governance-action-index "${action_idx}" + --out-file "${vote_file}" + ) + if [[ ${vote_mode} = "spo" ]]; then + VOTE_CMD+=(--cold-verification-key-file "${pool_coldkey_vk_file}") + elif [[ ${vote_mode} = "drep" ]]; then + if [[ ${hash_type} = "keyHash" ]]; then + VOTE_CMD+=(--drep-verification-key-file "${drep_vk_file}") + else + VOTE_CMD+=(--drep-script-hash "${drep_hash}") + fi + else + VOTE_CMD+=(--cc-hot-verification-key-file "${cc_hot_vk_file}") + fi + println ACTION "${VOTE_CMD[*]}" + if ! stdout=$("${VOTE_CMD[@]}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during governance vote creation!\n${stdout}"; waitToProceed && continue + fi + getWalletBalance ${wallet_name} true true false true + if [[ ${base_lovelace} -le 0 ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: no funds available in base address for wallet ${FG_GREEN}${wallet_name}${NC}" + println DEBUG "Funds for transaction fee needed to cast governance vote" + waitToProceed && continue + fi + if ! governanceVote; then + [[ -f ${vote_file} ]] && rm -f ${vote_file} + waitToProceed && continue + fi + echo + if ! verifyTx ${base_addr}; then waitToProceed && continue; fi + echo + println "successfully cast vote!" + waitToProceed && continue + ;; ################################################################### + drep-reg) + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> VOTE >> GOVERNANCE >> DREP REGISTRATION / UPDATE" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + if ! versionCheck "10.0" "${PROT_VERSION}"; then + println INFO "\n${FG_YELLOW}Not yet in Conway era, please revisit once network has crossed into Cardano governance era!${NC}"; waitToProceed && continue + fi + if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then + println ERROR "${FG_RED}ERROR${NC}: CNTools started in offline mode, option not available!" + waitToProceed && continue + else + if ! selectOpMode; then continue; fi + fi + println DEBUG "\nSelect wallet" + selectWallet "balance" + case $? in + 1) waitToProceed; continue ;; + 2) continue ;; + esac + drep_wallet_name=${wallet_name} + getGovKeyInfo "${drep_wallet_name}" + if [[ -z ${drep_id} ]]; then + println ERROR "${FG_RED}ERROR${NC}: Wallet missing governance keys, please first derive them!" + waitToProceed && continue + fi + getDRepStatus ${hash_type} ${drep_hash} && is_update=Y || is_update=N + if [[ ${hash_type} = "scriptHash" ]]; then + println DEBUG "\nSelect wallet to pay for transaction fee" + selectWallet "balance" ${WALLET_PAY_VK_FILENAME} + case $? in + 1) waitToProceed; continue ;; + 2) continue ;; + esac + fi + getWalletBalance ${wallet_name} true true false true + if [[ ${is_update} = Y && ${base_lovelace} -le 0 ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: no funds available in base address for wallet ${FG_GREEN}${wallet_name}${NC}" + println DEBUG "Funds for transaction fee needed to update DRep registration" + waitToProceed && continue + elif [[ ${is_update} = N && ${base_lovelace} -le ${DREP_DEPOSIT} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: insufficient funds in base address for wallet ${FG_GREEN}${wallet_name}${NC}" + println DEBUG "Funds for DRep deposit($(formatLovelace ${DREP_DEPOSIT}) ADA) + transaction fee needed to register as DRep" + waitToProceed && continue + fi + drep_cert_file="${WALLET_FOLDER}/${drep_wallet_name}/${WALLET_GOV_DREP_REGISTER_CERT_FILENAME}" + drep_meta_file="${WALLET_FOLDER}/${drep_wallet_name}/drep_meta.json" + unset drep_anchor_url drep_anchor_hash + println DEBUG "\nAdd DRep anchor URL?" + select_opt "[n] No" "[y] Yes" + case $? in + 0) unset drep_meta_file ;; + 1) getAnswerAnyCust drep_anchor_url "Enter DRep's anchor URL" + if [[ ! "${drep_anchor_url}" =~ https?://.* || ${#drep_anchor_url} -gt 64 ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: invalid URL format or more than 64 chars in length" + waitToProceed && continue + fi + if curl -sL -f -m ${CURL_TIMEOUT} -o "${drep_meta_file}" ${drep_anchor_url} && jq -er . "${drep_meta_file}" &>/dev/null; then + println ACTION "${CCLI} conway governance drep metadata-hash --drep-metadata-file ${drep_meta_file}" + if ! drep_anchor_hash=$(${CCLI} conway governance drep metadata-hash --drep-metadata-file "${drep_meta_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during governance drep metadata hash creation!\n${drep_anchor_hash}"; waitToProceed && continue + fi + else + println ERROR "\n${FG_RED}ERROR${NC}: failed to download anchor file or invalid json format"; waitToProceed && continue + fi + println DEBUG "\nDRep anchor metadata:" + jq -r . "${drep_meta_file}" + println DEBUG "\nDRep anchor metadata hash: ${FG_LGRAY}${drep_anchor_hash}${NC}" + ;; + esac + if [[ ${hash_type} = "scriptHash" ]]; then + drep_reg_param=(--drep-script-hash "${drep_hash}") + else + drep_reg_param=(--drep-verification-key-file "${drep_vk_file}") + fi + if [[ ${is_update} = N ]]; then + # registration + DREP_REG_CMD=( + ${CCLI} ${NETWORK_ERA} governance drep registration-certificate + "${drep_reg_param[@]}" + --key-reg-deposit-amt ${DREP_DEPOSIT} + --out-file "${drep_cert_file}" + ) + else + # update + DREP_REG_CMD=( + ${CCLI} ${NETWORK_ERA} governance drep update-certificate + "${drep_reg_param[@]}" + --out-file "${drep_cert_file}" + ) + fi + if [[ -n ${drep_anchor_url} ]]; then + DREP_REG_CMD+=( + --drep-metadata-url ${drep_anchor_url} + --drep-metadata-hash "${drep_anchor_hash}" + ) + fi + println ACTION "${DREP_REG_CMD[*]}" + if ! stdout=$("${DREP_REG_CMD[@]}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during DRep registration certificate creation!\n${stdout}"; waitToProceed && continue + fi + if ! registerDRep; then + [[ -f ${drep_cert_file} ]] && rm -f ${drep_cert_file} + waitToProceed && continue + fi + echo + if ! verifyTx ${base_addr}; then waitToProceed && continue; fi + echo + if [[ -z ${is_update} ]]; then + println "${FG_GREEN}${drep_wallet_name}${NC} successfully registered as DRep on chain!" + println "DRep deposit : ${FG_LBLUE}$(formatLovelace ${DREP_DEPOSIT})${NC} ADA (returned when retired)" + println DEBUG "\n${FG_YELLOW}NOTE:${NC} A DRep registration does not automatically delegate own wallet stake power to self!" + else + println "${FG_GREEN}${drep_wallet_name}${NC} DRep details updated!" + fi + waitToProceed && continue + ;; ################################################################### + drep-ret) + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> VOTE >> GOVERNANCE >> DREP RETIRE" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + if ! versionCheck "10.0" "${PROT_VERSION}"; then + println INFO "\n${FG_YELLOW}Not yet in Conway era, please revisit once network has crossed into Cardano governance era!${NC}"; waitToProceed && continue + fi + if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then + println ERROR "${FG_RED}ERROR${NC}: CNTools started in offline mode, option not available!" + waitToProceed && continue + else + if ! selectOpMode; then continue; fi + fi + println DEBUG "\nSelect wallet (derive governance keys if missing)" + selectWallet "balance" + case $? in + 1) waitToProceed; continue ;; + 2) continue ;; + esac + drep_wallet_name=${wallet_name} + getGovKeyInfo ${drep_wallet_name} + if [[ -z ${drep_id} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: Wallet missing governance keys!" + waitToProceed && continue + fi + if ! getDRepStatus ${hash_type} ${drep_hash}; then + println ERROR "\n${FG_RED}ERROR${NC}: Wallet not registered as a DRep, unable to retire!" + waitToProceed && continue + fi + drep_cert_file="${WALLET_FOLDER}/${drep_wallet_name}/${WALLET_GOV_DREP_RETIRE_CERT_FILENAME}" + if [[ ${hash_type} = "scriptHash" ]]; then + drep_ret_param=(--drep-script-hash "${drep_hash}") + else + drep_ret_param=(--drep-verification-key-file "${drep_vk_file}") + fi + DREP_RET_CMD=( + ${CCLI} ${NETWORK_ERA} governance drep retirement-certificate + "${drep_ret_param[@]}" + --deposit-amt ${drep_deposit_amt} + --out-file "${drep_cert_file}" + ) + println ACTION "${DREP_RET_CMD[*]}" + if ! stdout=$("${DREP_RET_CMD[@]}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during DRep retirement certificate creation!\n${stdout}"; waitToProceed && continue + fi + if [[ ${hash_type} = "scriptHash" ]]; then + println DEBUG "\nSelect wallet to pay for the transaction fee and that gets the returned DRep deposit" + selectWallet "balance" ${WALLET_PAY_VK_FILENAME} + case $? in + 1) waitToProceed; continue ;; + 2) continue ;; + esac + fi + getWalletBalance ${wallet_name} true true false true + if [[ ${base_lovelace} -le 0 ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: no funds available in base address for wallet ${FG_GREEN}${wallet_name}${NC}" + println DEBUG "Funds for transaction fee needed to retire as a DRep" + waitToProceed && continue + fi + if ! retireDRep; then + [[ -f ${drep_cert_file} ]] && rm -f ${drep_cert_file} + waitToProceed && continue + fi + echo + if ! verifyTx ${base_addr}; then waitToProceed && continue; fi + echo + println "${FG_GREEN}${drep_wallet_name}${NC} successfully retired as DRep!" + println "DRep deposit : ${FG_LBLUE}$(formatLovelace ${drep_deposit_amt})${NC} ADA returned" + waitToProceed && continue + ;; ################################################################### + create-ms-drep) + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> VOTE >> GOVERNANCE >> MULTISIG DREP" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + echo + createNewWallet || continue + ms_wallet_name="${wallet_name}" + # Wallet key filenames + ms_drep_script_file="${WALLET_FOLDER}/${ms_wallet_name}/${WALLET_GOV_DREP_SCRIPT_FILENAME}" + if [[ $(find "${WALLET_FOLDER}/${ms_wallet_name}" -type f -print0 | wc -c) -gt 0 ]]; then + println "${FG_RED}WARN${NC}: A wallet ${FG_GREEN}${ms_wallet_name}${NC} already exists" + println " Choose another name or delete the existing one" + waitToProceed && continue + fi + # drep key hashes as keys to associative array to act as a set + declare -gA key_hashes=() + println OFF "Select wallet(s) / DRep IDs to include in MultiSig DRep" + println OFF "${FG_YELLOW}!${NC} Please use 1854H (MultiSig) derived keys according to CIP-1854!" + println OFF "${FG_YELLOW}!${NC} Only wallets with these keys will be listed, use 'Derive Keys' option to generate them." + echo + selected_wallets=() + while true; do + println DEBUG "Select wallet or manually enter DRep ID?" + select_opt "[w] Wallet" "[i] DRep (ID or hash)" "[d] I'm done" "[Esc] Cancel" + case $? in + 0) selectWallet "none" "${selected_wallets[@]}" "${WALLET_MULTISIG_PREFIX}${WALLET_GOV_DREP_VK_FILENAME}" + case $? in + 1) waitToProceed; continue ;; + 2) continue ;; + esac + getGovKeyInfo ${wallet_name} + [[ -z ${ms_drep_id} || ${ms_drep_id} != drep* ]] && println ERROR "\n${FG_RED}ERROR${NC}: invalid wallet, MultiSig DRep keys not found!" && waitToProceed && continue + key_hashes["${ms_drep_hash}"]=1 + selected_wallets+=("${wallet_name}") + ;; + 1) getAnswerAnyCust drep_id "MultiSig DRep ID (bech32)" + [[ ${drep_id} != drep* ]] && drep_id=$(bech32 drep <<< "${drep_id}" 2>/dev/null) + [[ ${#drep_id} -ne 56 || ${drep_id} != drep* ]] && println ERROR "\n${FG_RED}ERROR${NC}: invalid DRep ID entered!" && waitToProceed && continue + key_hashes[$(bech32 <<< "${drep_id}")]=1 + ;; + 2) break ;; + 3) safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; continue 2 ;; + esac + println DEBUG "\nMultiSig size: ${#key_hashes[@]} - Add more wallets / DRep IDs to MultiSig?" + select_opt "[n] No" "[y] Yes" "[Esc] Cancel" + case $? in + 0) break ;; + 1) : ;; + 2) safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; continue 2 ;; + esac + done + if [[ ${#key_hashes[@]} -eq 0 ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: no signers added, please add at least one"; safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; waitToProceed; continue + fi + println DEBUG "\n${#key_hashes[@]} wallets / DRep IDs added to MultiSig, how many are required to witness the transaction?" + getAnswerAnyCust required_sig_cnt "Number of Required signatures" + if ! isNumber ${required_sig_cnt} || [[ ${required_sig_cnt} -lt 1 || ${required_sig_cnt} -gt ${#key_hashes[@]} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: invalid signature count entered, must be above 1 and max ${#key_hashes[@]}"; safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; waitToProceed; continue + fi + # build MultiSig script + drep_script=$(jq -n --argjson req_sig "${required_sig_cnt}" '{type:"atLeast",required:$req_sig,scripts:[]}') + for sig in "${!key_hashes[@]}"; do + drep_script=$(jq --arg sig "${sig}" '.scripts += [{type:"sig",keyHash:$sig}]' <<< "${drep_script}") + done + if ! stdout=$(jq -e . <<< "${drep_script}" > "${ms_drep_script_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during DRep script file creation!\n${stdout}"; safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; waitToProceed && continue + fi + chmod 600 "${WALLET_FOLDER}/${ms_wallet_name}/"* + getGovKeyInfo ${ms_wallet_name} + echo + println "New MultiSig DRep : ${FG_GREEN}${ms_wallet_name}${NC}" + println "DRep ID : ${FG_LGRAY}$(bech32 drep <<< ${drep_id} 2>/dev/null)${NC}" + println "DRep Script Hash : ${FG_LGRAY}${drep_id}${NC}" + println DEBUG "\nNote that this is not a normal wallet and can only be used to vote as a DRep coalition." + waitToProceed && continue + ;; ################################################################### + derive-gov-keys) + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> VOTE >> GOVERNANCE >> DERIVE" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + echo + println DEBUG "Select wallet to derive governance keys for (only wallets with missing keys shown)" + selectWallet "non-gov" + case $? in + 1) waitToProceed; continue ;; + 2) continue ;; + esac + getWalletType ${wallet_name} + case $? in + 0) # Hardware wallet + if ! cmdAvailable "cardano-hw-cli" &>/dev/null; then + println ERROR "${FG_RED}ERROR${NC}: cardano-hw-cli not found in path or executable permission not set." + println ERROR "Please run '${FG_YELLOW}guild-deploy.sh -s w${NC}' to add hardware wallet support and install Vaccumlabs cardano-hw-cli, '${FG_YELLOW}guild-deploy.sh -h${NC}' shows all available options" + waitToProceed && continue + fi + if ! HWCLIversionCheck; then waitToProceed && continue; fi + drep_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_DREP_VK_FILENAME}" + drep_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_HW_DREP_SK_FILENAME}" + cc_cold_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_CC_COLD_VK_FILENAME}" + cc_cold_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_HW_CC_COLD_SK_FILENAME}" + cc_hot_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_CC_HOT_VK_FILENAME}" + cc_hot_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_HW_CC_HOT_SK_FILENAME}" + ms_drep_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_GOV_HW_DREP_SK_FILENAME}" + ms_drep_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_GOV_DREP_VK_FILENAME}" + if [[ -f ${drep_sk_file} || -f ${cc_cold_sk_file} || -f ${cc_hot_sk_file} || -f ${ms_drep_sk_file} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: some governance signing keys already exist!\n${stdout}"; waitToProceed && continue + fi + derivation_path_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_DERIVATION_PATH_FILENAME}" + if ! getSavedDerivationPath "${derivation_path_file}"; then + getCustomDerivationPath || continue + echo "1852H/1815H/${acct_idx}H/x/${key_idx}" > "${derivation_path_file}" + fi + if ! unlockHWDevice "extract ${FG_LGRAY}governance keys${NC}"; then waitToProceed && continue; fi + HW_CLI_CMD=( + cardano-hw-cli address key-gen + --path 1852H/1815H/${acct_idx}H/3/${key_idx} + --path 1852H/1815H/${acct_idx}H/4/${key_idx} + --path 1852H/1815H/${acct_idx}H/5/${key_idx} + --path 1854H/1815H/${acct_idx}H/3/${key_idx} + --verification-key-file "${drep_vk_file}" + --verification-key-file "${cc_cold_vk_file}" + --verification-key-file "${cc_hot_vk_file}" + --verification-key-file "${ms_drep_vk_file}" + --hw-signing-file "${drep_sk_file}" + --hw-signing-file "${cc_cold_sk_file}" + --hw-signing-file "${cc_hot_sk_file}" + --hw-signing-file "${ms_drep_sk_file}" + ) + println ACTION "${HW_CLI_CMD[*]}" + if ! stdout=$("${HW_CLI_CMD[@]}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during governance key extraction!\n${stdout}"; waitToProceed && continue + fi + jq '.description = "Delegate Representative Hardware Verification Key"' "${drep_vk_file}" > "${TMP_DIR}/$(basename "${drep_vk_file}").tmp" && mv -f "${TMP_DIR}/$(basename "${drep_vk_file}").tmp" "${drep_vk_file}" + jq '.description = "Constitutional Committee Cold Hardware Verification Key"' "${cc_cold_vk_file}" > "${TMP_DIR}/$(basename "${cc_cold_vk_file}").tmp" && mv -f "${TMP_DIR}/$(basename "${cc_cold_vk_file}").tmp" "${cc_cold_vk_file}" + jq '.description = "Constitutional Committee Hot Hardware Verification Key"' "${cc_hot_sk_file}" > "${TMP_DIR}/$(basename "${cc_hot_sk_file}").tmp" && mv -f "${TMP_DIR}/$(basename "${cc_hot_sk_file}").tmp" "${cc_hot_sk_file}" + jq '.description = "MultiSig Delegate Representative Hardware Verification Key"' "${ms_drep_vk_file}" > "${TMP_DIR}/$(basename "${ms_drep_vk_file}").tmp" && mv -f "${TMP_DIR}/$(basename "${ms_drep_vk_file}").tmp" "${ms_drep_vk_file}" + ;; + 5) println ERROR "\n${FG_RED}ERROR${NC}: MultiSig wallets not supported as DRep wallet, only vote delegation supported!\n${stdout}"; waitToProceed && continue ;; + *) + drep_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_DREP_VK_FILENAME}" + drep_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_DREP_SK_FILENAME}" + cc_cold_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_CC_COLD_VK_FILENAME}" + cc_cold_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_CC_COLD_SK_FILENAME}" + cc_hot_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_CC_HOT_VK_FILENAME}" + cc_hot_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_GOV_CC_HOT_SK_FILENAME}" + ms_drep_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_GOV_DREP_VK_FILENAME}" + ms_drep_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_GOV_DREP_SK_FILENAME}" + if [[ -f ${drep_sk_file} || -f ${cc_cold_sk_file} || -f ${cc_hot_sk_file} || -f ${ms_drep_sk_file} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: some governance signing keys already exist!\n${stdout}"; waitToProceed && continue + fi + println DEBUG "Is selected wallet a CLI generated wallet or derived from mnemonic?" + select_opt "[c] CLI" "[m] Mnemonic" + case $? in + 0) println ACTION "${CCLI} conway governance drep key-gen --verification-key-file ${drep_vk_file} --signing-key-file ${drep_sk_file}" + if ! stdout=$(${CCLI} conway governance drep key-gen --verification-key-file "${drep_vk_file}" --signing-key-file "${drep_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during governance drep key creation!\n${stdout}"; waitToProceed && continue + fi + println ACTION "${CCLI} conway governance committee key-gen-cold --cold-verification-key-file ${cc_cold_vk_file} --cold-signing-key-file ${cc_cold_sk_file}" + if ! stdout=$(${CCLI} conway governance committee key-gen-cold --cold-verification-key-file "${cc_cold_vk_file}" --cold-signing-key-file "${cc_cold_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during governance committee cold key creation!\n${stdout}"; waitToProceed && continue + fi + println ACTION "${CCLI} conway governance committee key-gen-hot --verification-key-file ${cc_hot_vk_file} --signing-key-file ${cc_hot_sk_file}" + if ! stdout=$(${CCLI} conway governance committee key-gen-hot --verification-key-file "${cc_hot_vk_file}" --signing-key-file "${cc_hot_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during governance committee hot key creation!\n${stdout}"; waitToProceed && continue + fi + println ACTION "${CCLI} conway governance drep key-gen --verification-key-file ${ms_drep_vk_file} --signing-key-file ${ms_drep_sk_file}" + if ! stdout=$(${CCLI} conway governance drep key-gen --verification-key-file "${ms_drep_vk_file}" --signing-key-file "${ms_drep_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during MultiSig governance drep key creation!\n${stdout}"; waitToProceed && continue + fi + ;; + 1) if ! cmdAvailable "bech32" &>/dev/null || \ + ! cmdAvailable "cardano-address" &>/dev/null; then + println ERROR "${FG_RED}ERROR${NC}: bech32 and/or cardano-address not found in '\$PATH'" + println ERROR "Please run updated guild-deploy.sh and re-build/re-download cardano-node" + waitToProceed && continue + fi + getAnswerAnyCust mnemonic false "24 or 15 word mnemonic(space separated)" + echo + IFS=" " read -r -a words <<< "${mnemonic}" + if [[ ${#words[@]} -ne 24 ]] && [[ ${#words[@]} -ne 15 ]]; then + println ERROR "${FG_RED}ERROR${NC}: 24 or 15 words expected, found ${FG_RED}${#words[@]}${NC}" + unset mnemonic; unset words + waitToProceed && continue + fi + derivation_path_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_DERIVATION_PATH_FILENAME}" + if ! getSavedDerivationPath "${derivation_path_file}"; then + getCustomDerivationPath || continue + echo "1852H/1815H/${acct_idx}H/x/${key_idx}" > "${derivation_path_file}" + fi + caddr_v="$(cardano-address -v | awk '{print $1}')" + [[ "${caddr_v}" == 3* ]] && caddr_arg="--with-chain-code" || caddr_arg="" + if ! root_prv=$(cardano-address key from-recovery-phrase Shelley <<< ${mnemonic}); then + unset mnemonic; unset words + waitToProceed && continue + fi + unset mnemonic; unset words + drep_xprv=$(cardano-address key child 1852H/1815H/${acct_idx}H/3/${key_idx} <<< ${root_prv}) + cc_cold_xprv=$(cardano-address key child 1852H/1815H/${acct_idx}H/4/${key_idx} <<< ${root_prv}) + cc_hot_xprv=$(cardano-address key child 1852H/1815H/${acct_idx}H/5/${key_idx} <<< ${root_prv}) + ms_drep_xprv=$(cardano-address key child 1854H/1815H/${acct_idx}H/3/${key_idx} <<< ${root_prv}) + drep_xpub=$(cardano-address key public ${caddr_arg} <<< ${drep_xprv}) + cc_cold_xpub=$(cardano-address key public ${caddr_arg} <<< ${cc_cold_xprv}) + cc_hot_xpub=$(cardano-address key public ${caddr_arg} <<< ${cc_hot_xprv}) + ms_drep_xpub=$(cardano-address key public ${caddr_arg} <<< ${ms_drep_xprv}) + drep_es_key=$(bech32 <<< ${drep_xprv} | cut -b -128)$(bech32 <<< ${drep_xpub}) + cc_cold_es_key=$(bech32 <<< ${cc_cold_xprv} | cut -b -128)$(bech32 <<< ${cc_cold_xpub}) + cc_hot_es_key=$(bech32 <<< ${cc_hot_xprv} | cut -b -128)$(bech32 <<< ${cc_hot_xpub}) + ms_drep_es_key=$(bech32 <<< ${ms_drep_xprv} | cut -b -128)$(bech32 <<< ${ms_drep_xpub}) + cat <<-EOF > "${drep_sk_file}" + { + "type": "DRepExtendedSigningKey_ed25519_bip32", + "description": "Delegate Representative Signing Key", + "cborHex": "5880${drep_es_key}" + } + EOF + cat <<-EOF > "${cc_cold_sk_file}" + { + "type": "ConstitutionalCommitteeColdExtendedSigningKey_ed25519_bip32", + "description": "Constitutional Committee Cold Signing Key", + "cborHex": "5880${cc_cold_es_key}" + } + EOF + cat <<-EOF > "${cc_hot_sk_file}" + { + "type": "ConstitutionalCommitteeHotExtendedSigningKey_ed25519_bip32", + "description": "Constitutional Committee Hot Signing Key", + "cborHex": "5880${cc_hot_es_key}" + } + EOF + cat <<-EOF > "${ms_drep_sk_file}" + { + "type": "DRepExtendedSigningKey_ed25519_bip32", + "description": "MultiSig Delegate Representative Signing Key", + "cborHex": "5880${drep_es_key}" + } + EOF + println ACTION "${CCLI} conway key verification-key --signing-key-file ${drep_sk_file} --verification-key-file ${TMP_DIR}/drep.evkey" + if ! stdout=$(${CCLI} conway key verification-key --signing-key-file "${drep_sk_file}" --verification-key-file "${TMP_DIR}/drep.evkey" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during drep extended verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + println ACTION "${CCLI} conway key verification-key --signing-key-file ${cc_cold_sk_file} --verification-key-file ${TMP_DIR}/cc-cold.evkey" + if ! stdout=$(${CCLI} conway key verification-key --signing-key-file "${cc_cold_sk_file}" --verification-key-file "${TMP_DIR}/cc-cold.evkey" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during cc-cold extended verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + println ACTION "${CCLI} conway key verification-key --signing-key-file ${cc_hot_sk_file} --verification-key-file ${TMP_DIR}/cc-hot.evkey" + if ! stdout=$(${CCLI} conway key verification-key --signing-key-file "${cc_hot_sk_file}" --verification-key-file "${TMP_DIR}/cc-hot.evkey" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during cc-hot extended verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + println ACTION "${CCLI} conway key verification-key --signing-key-file ${ms_drep_sk_file} --verification-key-file ${TMP_DIR}/ms_drep.evkey" + if ! stdout=$(${CCLI} conway key verification-key --signing-key-file "${ms_drep_sk_file}" --verification-key-file "${TMP_DIR}/ms_drep.evkey" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during MultiSig drep extended verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + println ACTION "${CCLI} conway key non-extended-key --extended-verification-key-file ${TMP_DIR}/drep.evkey --verification-key-file ${drep_vk_file}" + if ! stdout=$(${CCLI} conway key non-extended-key --extended-verification-key-file "${TMP_DIR}/drep.evkey" --verification-key-file "${drep_vk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during drep verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + println ACTION "${CCLI} conway key non-extended-key --extended-verification-key-file ${TMP_DIR}/cc-cold.evkey --verification-key-file ${cc_cold_vk_file}" + if ! stdout=$(${CCLI} conway key non-extended-key --extended-verification-key-file "${TMP_DIR}/cc-cold.evkey" --verification-key-file "${cc_cold_vk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during cc-cold verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + println ACTION "${CCLI} conway key non-extended-key --extended-verification-key-file ${TMP_DIR}/cc-hot.evkey --verification-key-file ${cc_hot_vk_file}" + if ! stdout=$(${CCLI} conway key non-extended-key --extended-verification-key-file "${TMP_DIR}/cc-hot.evkey" --verification-key-file "${cc_hot_vk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during cc-hot verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + println ACTION "${CCLI} conway key non-extended-key --extended-verification-key-file ${TMP_DIR}/ms_drep.evkey --verification-key-file ${ms_drep_vk_file}" + if ! stdout=$(${CCLI} conway key non-extended-key --extended-verification-key-file "${TMP_DIR}/ms_drep.evkey" --verification-key-file "${ms_drep_vk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during MultiSig drep verification key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && return 1 + fi + ;; + esac + ;; + esac + chmod 600 "${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}"* + echo + getGovKeyInfo ${wallet_name} + println "Wallet : ${FG_GREEN}${wallet_name}${NC}" + println "DRep ID : ${FG_LGRAY}${drep_id}${NC}" + println "Committee Cold ID : ${FG_LGRAY}${cc_cold_id}${NC}" + println "Committee Hot ID : ${FG_LGRAY}${cc_hot_id}${NC}" + waitToProceed && continue + ;; ################################################################### + esac # vote sub OPERATION + done # vote loop + ;; ################################################################### + catalyst) + while true; do # Catalyst loop + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> VOTE >> CATALYST" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println OFF " Catalyst\n"\ + " ) Register - register wallet for Catalyst"\ + " ) Display QR - show QR code from previous Catalyst registration"\ + " ) Verify - check registration status for own or external vote key"\ + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println DEBUG " Select Catalyst Operation\n" + select_opt "[r] Registration" "[q] Display QR" "[v] Verify" "[b] Back" "[h] Home" + case $? in + 0) SUBCOMMAND="catalyst_reg" ;; + 1) SUBCOMMAND="catalyst_qr" ;; + 2) SUBCOMMAND="catalyst_verify" ;; + 3) break ;; + 4) break 2 ;; + esac + case $SUBCOMMAND in + catalyst_reg) + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> VOTE >> CATALYST >> REGISTER" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: CNTools started in offline mode, option not available!" + waitToProceed && continue + else + if ! selectOpMode; then continue; fi + fi + println DEBUG "Select wallet to register for Catalyst" + unset isHWwallet + selectWallet "balance" + case $? in + 1) waitToProceed; continue ;; + 2) continue ;; + esac + getWalletType ${wallet_name} + case $? in + 0) isHWwallet=true ;; + 2) [[ ${op_mode} = "online" ]] && println ERROR "${FG_RED}ERROR${NC}: signing keys encrypted, please decrypt before use!" && waitToProceed && continue ;; + 3) [[ ${op_mode} = "online" ]] && println ERROR "${FG_RED}ERROR${NC}: payment and/or stake signing keys missing from wallet!" && waitToProceed && continue ;; + esac + if ! isWalletRegistered ${wallet_name}; then + println ERROR "\n${FG_RED}ERROR${NC}: wallet ${FG_GREEN}${wallet_name}${NC} not a registered wallet on chain, please register/delegate it before Catalyst registration." + waitToProceed && continue + fi + getWalletBalance ${wallet_name} true true true true + if [[ ${base_lovelace} -gt 0 ]]; then + addr="${base_addr}" + lovelace=${base_lovelace} + if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then + println DEBUG "\n$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA" "Base Funds :" "$(formatLovelace ${base_lovelace})")" + fi + else + println ERROR "\n${FG_RED}ERROR${NC}: no base funds available for wallet ${FG_GREEN}${wallet_name}${NC}" + waitToProceed && continue + fi + getBaseAddress ${wallet_name} + download_catalyst_toolbox || continue + metafile="${TMP_DIR}/catalyst_reg_metadata_$(printf '%(%s)T\n' -1).cbor" + metatype="cbor" + if ! cmdAvailable "cardano-signer" &>/dev/null; then + println ERROR "\n${FG_RED}ERROR${NC}: prerequisite tool cardano-signer missing or not executable, please install using ${FG_LGRAY}guild-deploy.sh${NC}" + waitToProceed && continue + fi + catalyst_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_CATALYST_SK_FILENAME}" + catalyst_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_CATALYST_VK_FILENAME}" + catalyst_qr_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_CATALYST_QR_FILENAME}" + if [[ ! -f "${catalyst_vk_file}" && ! -f "${catalyst_sk_file}" ]]; then + println ACTION "cardano-signer keygen --cip36 --out-skey ${catalyst_sk_file} --out-vkey ${catalyst_vk_file}" + if ! stdout=$(cardano-signer keygen --cip36 --out-skey "${catalyst_sk_file}" --out-vkey "${catalyst_vk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during catalyst key creation!\n${stdout}"; waitToProceed && continue + fi + fi + generateCatalystBech32 ${wallet_name} || continue + if [[ -f "${catalyst_qr_file}" ]]; then + println "A previous registration found, continue with registration and overwrite?" + select_opt "[y] Yes" "[n] No" + case $? in + 0) : ;; # do nothing + 1) waitToProceed && continue ;; + esac + fi + if [[ -z ${isHWwallet} ]]; then + catalyst_meta_cmd=( + cardano-signer sign --cip36 + ${NETWORK_IDENTIFIER} + --payment-address "${base_addr}" + --vote-public-key "${catalyst_vk_file}" + --secret-key "${stake_sk_file}" + --out-cbor "${metafile}" + ) + else + # HW Wallet + if ! cmdAvailable "cardano-hw-cli" &>/dev/null; then + println ERROR "\n${FG_RED}ERROR${NC}: prerequisite tool cardano-hw-cli missing or not executable, please install using ${FG_LGRAY}guild-deploy.sh${NC}" + waitToProceed && continue + fi + if ! HWCLIversionCheck; then waitToProceed && continue; fi + if ! unlockHWDevice "create Catalyst vote metadata"; then waitToProceed && continue; fi + current_slot=$(getSlotTipRef) + catalyst_meta_cmd=( + cardano-hw-cli vote registration-metadata + ${NETWORK_IDENTIFIER} + --vote-public-key-file "${catalyst_vk_file}" + --payment-address "${base_addr}" + --stake-signing-key-hwsfile "${stake_sk_file}" + --nonce ${current_slot} + --payment-address-signing-key-hwsfile "${payment_sk_file}" + --metadata-cbor-out-file "${metafile}" + ) + fi + println ACTION "${catalyst_meta_cmd[*]}" + if ! stdout=$("${catalyst_meta_cmd[@]}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during catalyst metadata creation!\n${stdout}"; waitToProceed && continue + fi + if ! sendMetadata; then + waitToProceed && continue + fi + echo + if ! verifyTx ${addr}; then waitToProceed && continue; fi + echo + println "Catalyst registration metadata successfully posted on-chain" + while true; do + echo + getAnswerAnyCust pin_enter "Enter a 4-Digit PIN" + if ! isNumber ${pin_enter} || [[ ${#pin_enter} -ne 4 ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: invalid PIN entered! Please try again" + continue + fi + break + done + # save QR + catalyst_qr_cmd=( + catalyst-toolbox qr-code encode + --pin ${pin_enter} + --input "${catalyst_sk_file_bech32}" + --output "${catalyst_qr_file}" + --opts img + ) + println ACTION "${catalyst_qr_cmd[*]}" + if ! stdout=$("${catalyst_qr_cmd[@]}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during catalyst QR code creation!\n${stdout}"; waitToProceed && continue fi - - println DEBUG "${FG_GREEN}Successfully added!${NC}" - tx_sign_files+=( "${file}" ) - done - if [[ ${#tx_sign_files[@]} -gt 0 ]]; then - if ! witnessTx "${TMP_DIR}/tx.raw" "${tx_sign_files[@]}"; then waitToProceed && continue; fi - if ! assembleTx "${TMP_DIR}/tx.raw"; then waitToProceed && continue; fi + # print QR + println DEBUG "QR Code image generated: ${catalyst_qr_file}" + catalyst_qr_cmd=( + catalyst-toolbox qr-code encode + --pin ${pin_enter} + --input "${catalyst_sk_file_bech32}" + --opts img + ) + println ACTION "${catalyst_qr_cmd[*]}" + "${catalyst_qr_cmd[@]}" + println DEBUG "\nScan QR code using Catalyst app on mobile device" + println DEBUG "iOS: https://apps.apple.com/in/app/catalyst-voting/id1517473397" + println DEBUG "Android: https://play.google.com/store/apps/details?id=io.iohk.vitvoting" + println DEBUG "\nCardano Catalyst Telegram Announcements Channel: https://t.me/cardanocatalyst" + waitToProceed && continue + ;; ################################################################### + catalyst_qr) + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> VOTE >> CATALYST >> QR CODE" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo - if jq ". += { \"signed-txBody\": $(jq -c . "${tx_signed}") }" <<< "${offlineJSON}" > "${offline_tx}"; then - println "Offline transaction successfully signed" - println "please move ${offline_tx} back to online node and submit before ${FG_LGRAY}$(date '+%F %T %Z' --date="${otx_date_expire}")${NC}!" + println DEBUG "Select a Catalyst registered wallet" + selectWallet "none" "${WALLET_CATALYST_SK_FILENAME}" + case $? in + 1) waitToProceed; continue ;; + 2) continue ;; + esac + download_catalyst_toolbox || continue + while true; do + echo + getAnswerAnyCust pin_enter "Enter 4-Digit PIN" + if ! isNumber ${pin_enter} || [[ ${#pin_enter} -ne 4 ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: invalid PIN entered! Please try again" + continue + fi + break + done + catalyst_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_CATALYST_SK_FILENAME}" + catalyst_qr_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_CATALYST_QR_FILENAME}" + generateCatalystBech32 ${wallet_name} || continue + unset save_catalyst_qr + if [[ -f "${catalyst_qr_file}" ]]; then + catalyst_qr_cmd=( + catalyst-toolbox qr-code verify + --stop-at-fail + --pin ${pin_enter} + --file "${catalyst_qr_file}" + --opts img + ) + println ACTION "${catalyst_qr_cmd[*]}" + if ! "${catalyst_qr_cmd[@]}" &>/dev/null; then + println "PIN code invalid, overwrite existing QR code with updated PIN code?" + select_opt "[y] Yes" "[n] No (return)" "[c] Continue (display QR code)" + case $? in + 0) save_catalyst_qr=true ;; + 1) continue ;; + 2) : ;; + esac + fi else - println ERROR "${FG_RED}ERROR${NC}: failed to write signed tx body to offline transaction file!" + save_catalyst_qr=true fi - else - println ERROR "\n${FG_YELLOW}WARN${NC}: no signing keys added!" - fi - ;; - "Pool Registration"|"Pool Update") - echo - println DEBUG "Pool name : ${FG_LGRAY}$(jq -r '."pool-metadata".name' <<< ${offlineJSON})${NC}" - println DEBUG "Ticker : ${FG_LGRAY}$(jq -r '."pool-metadata".ticker' <<< ${offlineJSON})${NC}" - println DEBUG "Pledge : ${FG_LBLUE}$(formatLovelace "$(ADAToLovelace "$(jq -r '."pool-pledge"' <<< ${offlineJSON})")")${NC} ADA" - println DEBUG "Margin : ${FG_LBLUE}$(jq -r '."pool-margin"' <<< ${offlineJSON})${NC} %" - println DEBUG "Cost : ${FG_LBLUE}$(formatLovelace "$(ADAToLovelace "$(jq -r '."pool-cost"' <<< ${offlineJSON})")")${NC} ADA" - for otx_signing_file in $(jq -r '."signing-file"[] | @base64' <<< "${offlineJSON}"); do - _jq() { base64 -d <<< ${otx_signing_file} | jq -r "${1}"; } - otx_signing_name=$(_jq '.name') - otx_vkey_cborHex="$(_jq '.vkey.cborHex')" - - for otx_witness in $(jq -r '.witness[] | @base64' <<< "${offlineJSON}"); do - __jq() { base64 -d <<< ${otx_witness} | jq -r "${1}"; } - [[ $(_jq '.name') = $(__jq '.name') ]] && continue 2 # offline transaction already witnessed by this signing key - done - - skey_path="" - # look for signing key in wallet folder - while IFS= read -r -d '' w_file; do - if [[ ${w_file} = */"${WALLET_PAY_SK_FILENAME}" || ${w_file} = */"${WALLET_STAKE_SK_FILENAME}" ]]; then - ! ${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${w_file}" --verification-key-file "${TMP_DIR}"/tmp.vkey && continue - if [[ $(jq -er '.type' "${w_file}" 2>/dev/null) = *"Extended"* ]]; then - ! ${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file "${TMP_DIR}/tmp.vkey" --verification-key-file "${TMP_DIR}/tmp2.vkey" && continue - mv -f "${TMP_DIR}/tmp2.vkey" "${TMP_DIR}/tmp.vkey" - fi - grep -q "${otx_vkey_cborHex}" "${TMP_DIR}"/tmp.vkey && skey_path="${w_file}" && break - elif [[ ${w_file} = */"${WALLET_HW_PAY_SK_FILENAME}" || ${w_file} = */"${WALLET_HW_STAKE_SK_FILENAME}" ]]; then - grep -q "${otx_vkey_cborHex:4}" "${w_file}" && skey_path="${w_file}" && break # strip 5820 prefix + if [[ ${save_catalyst_qr} = true ]]; then + catalyst_qr_cmd=( + catalyst-toolbox qr-code encode + --pin ${pin_enter} + --input "${catalyst_sk_file_bech32}" + --output "${catalyst_qr_file}" + --opts img + ) + println ACTION "${catalyst_qr_cmd[*]}" + if ! stdout=$("${catalyst_qr_cmd[@]}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during catalyst QR code creation!\n${stdout}"; waitToProceed && continue fi - done < <(find "${WALLET_FOLDER}" -mindepth 2 -maxdepth 2 -type f -print0 2>/dev/null) - # look for cold signing key in pool folder - if [[ -z ${skey_path} ]]; then - while IFS= read -r -d '' p_file; do - ! ${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${p_file}" --verification-key-file "${TMP_DIR}"/tmp.vkey && continue - grep -q "${otx_vkey_cborHex}" "${TMP_DIR}"/tmp.vkey && skey_path="${p_file}" && break - done < <(find "${POOL_FOLDER}" -mindepth 2 -maxdepth 2 -type f -name "${POOL_COLDKEY_SK_FILENAME}" -print0 2>/dev/null) fi - - if [[ -n ${skey_path} ]]; then - println DEBUG "\nFound a match for ${otx_signing_name}, use this file ? : ${FG_LGRAY}${skey_path}${NC}" - select_opt "[y] Yes" "[n] No, continue with manual selection" "[s] Skip" - case $? in - 0) if ! witnessTx "${TMP_DIR}/tx.raw" "${skey_path}"; then waitToProceed && continue 2; fi - if ! offlineJSON=$(jq ".witness += [{ name: \"${otx_signing_name}\", witnessBody: $(jq -c . "${tx_witness_files[0]}") }]" <<< ${offlineJSON}); then return 1; fi - jq -r . <<< "${offlineJSON}" > "${offline_tx}" # save this witness to disk - continue ;; - 1) selection=0 ;; - 2) continue ;; - esac - else - println DEBUG "\nDo you want to sign ${otx_type} with: ${FG_LGRAY}${otx_signing_name}${NC} ?" - select_opt "[y] Yes" "[s] Skip" - selection=$? + catalyst_qr_cmd=( + catalyst-toolbox qr-code encode + --pin ${pin_enter} + --input "${catalyst_sk_file_bech32}" + --opts img + ) + println ACTION "${catalyst_qr_cmd[*]}" + "${catalyst_qr_cmd[@]}" + println DEBUG "\nScan QR code using Catalyst app on mobile device" + println DEBUG "iOS: https://apps.apple.com/in/app/catalyst-voting/id1517473397" + println DEBUG "Android: https://play.google.com/store/apps/details?id=io.iohk.vitvoting" + println DEBUG "\nCardano Catalyst Telegram Announcements Channel: https://t.me/cardanocatalyst" + waitToProceed && continue + ;; ################################################################### + catalyst_verify) + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> VOTE >> CATALYST >> VERIFY" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: CNTools started in offline mode, option not available!" + waitToProceed && continue fi - - case ${selection} in - 0) [[ ${otx_signing_name} = "Pool "* ]] && dialog_start_path="${POOL_FOLDER}" || dialog_start_path="${WALLET_FOLDER}" - fileDialog "Enter path to ${otx_signing_name}" "${dialog_start_path}/" - [[ ! -f "${file}" ]] && println ERROR "${FG_RED}ERROR${NC}: file not found: ${file}" && waitToProceed && continue 2 - if [[ $(jq -r '.description' "${file}") = *"Hardware"* ]]; then - if ! grep -q "${otx_vkey_cborHex:4}" "${file}"; then # strip 5820 prefix - println ERROR "${FG_RED}ERROR${NC}: signing key provided doesn't match with verification key in offline transaction for: ${otx_signing_name}" - println ERROR "Provided hardware signing key's verification cborXPubKeyHex: $(jq -r .cborXPubKeyHex "${file}")" - println ERROR "Transaction verification cborHex: ${otx_vkey_cborHex:4}" - waitToProceed && continue 2 - fi - else - println ACTION "${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file ${file} --verification-key-file ${TMP_DIR}/tmp.vkey" - if ! stdout=$(${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${file}" --verification-key-file "${TMP_DIR}"/tmp.vkey 2>&1); then - println ERROR "\n${FG_RED}ERROR${NC}: failure during verification key creation!\n${stdout}"; waitToProceed && continue 2 - fi - if [[ $(jq -r '.type' "${file}") = *"Extended"* ]]; then - println ACTION "${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file ${TMP_DIR}/tmp.vkey --verification-key-file ${TMP_DIR}/tmp2.vkey" - if ! stdout=$(${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file "${TMP_DIR}/tmp.vkey" --verification-key-file "${TMP_DIR}/tmp2.vkey" 2>&1); then - println ERROR "\n${FG_RED}ERROR${NC}: failure during non-extended verification key creation!\n${stdout}"; waitToProceed && continue 2 - fi - mv -f "${TMP_DIR}/tmp2.vkey" "${TMP_DIR}/tmp.vkey" - fi - if [[ ${otx_vkey_cborHex} != $(jq -r .cborHex "${TMP_DIR}"/tmp.vkey) ]]; then - println ERROR "${FG_RED}ERROR${NC}: signing key provided doesn't match with verification key in offline transaction for: ${otx_signing_name}" - println ERROR "Provided signing key's verification cborHex: $(jq -r .cborHex "${TMP_DIR}"/tmp.vkey)" - println ERROR "Transaction verification cborHex: ${otx_vkey_cborHex}" - waitToProceed && continue 2 - fi - fi - if ! witnessTx "${TMP_DIR}/tx.raw" "${file}"; then waitToProceed && continue 2; fi - if ! offlineJSON=$(jq ".witness += [{ name: \"${otx_signing_name}\", witnessBody: $(jq -c . "${tx_witness_files[0]}") }]" <<< ${offlineJSON}); then return 1; fi - jq -r . <<< "${offlineJSON}" > "${offline_tx}" # save this witness to disk - ;; - 1) continue ;; + if [[ ${NWMAGIC} != "764824073" ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: Catalyst registration verification only available for Mainnet at this time!" + waitToProceed && continue + fi + println DEBUG "Select wallet or enter vote public key?" + select_opt "[w] Wallet" "[p] Vote public key" + case $? in + 0) println DEBUG "\nSelect a Catalyst registered wallet" + selectWallet "none" "${WALLET_CATALYST_VK_FILENAME}" + case $? in + 1) waitToProceed; continue ;; + 2) continue ;; + esac + catalyst_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_CATALYST_VK_FILENAME}" + vote_key_hex="$(jq -r .cborHex "${catalyst_vk_file}" | cut -c 5-)" + ;; + 1) getAnswerAnyCust vote_key_hex "Enter public key" + if [[ ${#vote_key_hex} -ne 64 ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: invalid pub key, expected 64 characters! Supply public key in hex format without prefix (5820 or 0x)"; waitToProceed && continue + fi + ;; esac - done - echo - if [[ $(jq -r '."signing-file" | length' <<< "${offlineJSON}") -eq $(jq -r '.witness | length' <<< "${offlineJSON}") ]]; then # witnessed by all signing keys - tx_witness_files=() - for otx_witness in $(jq -r '.witness[] | @base64' <<< "${offlineJSON}"); do - _jq() { base64 -d <<< ${otx_witness} | jq -r "${1}"; } - tx_witness="$(mktemp "${TMP_DIR}/tx.witness_XXXXXXXXXX")" - jq -r . <<< "$(_jq '.witnessBody')" > "${tx_witness}" - tx_witness_files+=( "${tx_witness}" ) - done - if ! assembleTx "${TMP_DIR}/tx.raw"; then waitToProceed && continue; fi - if jq ". += { \"signed-txBody\": $(jq -c . "${tx_signed}") }" <<< "${offlineJSON}" > "${offline_tx}"; then - println "Offline transaction successfully assembled and signed by all signing keys" - println "please move ${offline_tx} back to online node and submit before ${FG_LGRAY}$(date '+%F %T %Z' --date="${otx_date_expire}")${NC}!" - else - println ERROR "${FG_RED}ERROR${NC}: failed to write signed tx body to offline transaction file!" + voter_status_url="${CATALYST_API}/registration/voter/0x${vote_key_hex}?with_delegators=true" + println ACTION "curl -sSL -m ${CURL_TIMEOUT} -f -H \"Content-Type: application/json\" ${voter_status_url}" + if ! catalyst_status=$(curl -sSL -m ${CURL_TIMEOUT} -f -H "Content-Type: application/json" "${voter_status_url}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during Catalyst verification query!\n${catalyst_status}"; waitToProceed && continue fi - else - println "Offline transaction need to be signed by ${FG_LBLUE}$(jq -r '."signing-file" | length' <<< "${offlineJSON}")${NC} signing keys, signed by ${FG_LBLUE}$(jq -r '.witness | length' <<< "${offlineJSON}")${NC} so far!" - fi - ;; - *) println ERROR "${FG_RED}ERROR${NC}: unsupported offline tx type: ${otx_type}" && waitToProceed && continue ;; - esac - waitToProceed && continue - ;; ################################################################### - submit) - clear - println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - println " >> TRANSACTION >> SUBMIT" - println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then - println ERROR "${FG_RED}ERROR${NC}: CNTools started in offline mode, option not available!" - waitToProceed && continue - fi - echo - fileDialog "Enter path to offline tx file to submit" "${TMP_DIR}/" && echo - offline_tx=${file} - [[ -z "${offline_tx}" ]] && continue - if [[ ! -f "${offline_tx}" ]]; then - println ERROR "${FG_RED}ERROR${NC}: file not found: ${offline_tx}" - waitToProceed && continue - elif ! offlineJSON=$(jq -erc . "${offline_tx}"); then - println ERROR "${FG_RED}ERROR${NC}: invalid JSON file: ${offline_tx}" - waitToProceed && continue - fi - if ! otx_type=$(jq -er '.type' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'type' not found in: ${offline_tx}" && waitToProceed && continue; fi - if ! otx_date_created=$(jq -er '."date-created"' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'date-created' not found in: ${offline_tx}" && waitToProceed && continue; fi - if ! otx_date_expire=$(jq -er '."date-expire"' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'date-expire' not found in: ${offline_tx}" && waitToProceed && continue; fi - if ! otx_txFee=$(jq -er '.txFee' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'txFee' not found in: ${offline_tx}" && waitToProceed && continue; fi - if ! otx_signed_txBody=$(jq -er '."signed-txBody"' <<< ${offlineJSON}); then println ERROR "${FG_RED}ERROR${NC}: field 'signed-txBody' not found in: ${offline_tx}" && waitToProceed && continue; fi - [[ $(jq 'length' <<< ${otx_signed_txBody}) -eq 0 ]] && println ERROR "${FG_RED}ERROR${NC}: transaction not signed, please sign transaction first!" && waitToProceed && continue - println DEBUG "Transaction type : ${FG_YELLOW}${otx_type}${NC}" - if jq -er '."wallet-name"' &>/dev/null <<< ${offlineJSON}; then - println DEBUG "Transaction fee : ${FG_LBLUE}$(formatLovelace ${otx_txFee})${NC} ADA, payed by ${FG_GREEN}$(jq -r '."wallet-name"' <<< ${offlineJSON})${NC}" - else - println DEBUG "Transaction fee : ${FG_LBLUE}$(formatLovelace ${otx_txFee})${NC} ADA" - fi - println DEBUG "Created : ${FG_LGRAY}$(date '+%F %T %Z' --date="${otx_date_created}")${NC}" - if [[ $(date '+%s' --date="${otx_date_expire}") -lt $(date '+%s') ]]; then - println DEBUG "Expire : ${FG_RED}$(date '+%F %T %Z' --date="${otx_date_expire}")${NC}" - println ERROR "\n${FG_RED}ERROR${NC}: offline transaction expired! please create a new one with long enough Time To Live (TTL)" - waitToProceed && continue - else - println DEBUG "Expire : ${FG_LGRAY}$(date '+%F %T %Z' --date="${otx_date_expire}")${NC}" - fi - case "${otx_type}" in - "Wallet Registration"|"Wallet De-Registration"|"Payment"|"Wallet Delegation"|"Wallet Rewards Withdrawal"|"Pool De-Registration"|"Metadata"|"Pool Registration"|"Pool Update"|"Asset Minting"|"Asset Burning"|"Poll Cast") - echo - [[ ${otx_type} = "Wallet De-Registration" ]] && println DEBUG "Amount returned : ${FG_LBLUE}$(formatLovelace "$(jq -r '."amount-returned"' <<< ${offlineJSON})")${NC} ADA" - if [[ ${otx_type} = "Payment" ]]; then - println DEBUG "Source addr : ${FG_LGRAY}$(jq -r '."source-address"' <<< ${offlineJSON})${NC}" - println DEBUG "Destination addr : ${FG_LGRAY}$(jq -r '."destination-address"' <<< ${offlineJSON})${NC}" - println DEBUG "Amount : ${FG_LBLUE}$(formatLovelace "$(jq -r '.assets[] | select(.asset=="lovelace") | .amount' <<< ${offlineJSON})")${NC} ${FG_GREEN}ADA${NC}" - for otx_assets in $(jq -r '.assets[] | @base64' <<< "${offlineJSON}"); do - _jq() { base64 -d <<< ${otx_assets} | jq -r "${1}"; } - otx_asset=$(_jq '.asset') - [[ ${otx_asset} = "lovelace" ]] && continue - println DEBUG " ${FG_LBLUE}$(formatAsset "$(_jq '.amount')")${NC} ${FG_LGRAY}${otx_asset}${NC}" - done - fi - [[ ${otx_type} = "Wallet Rewards Withdrawal" ]] && println DEBUG "Rewards : ${FG_LBLUE}$(formatLovelace "$(jq -r '.rewards' <<< ${offlineJSON})")${NC} ADA" - jq -er '."pool-id"' <<< ${offlineJSON} &>/dev/null && println DEBUG "Pool ID : ${FG_LGRAY}$(jq -r '."pool-id"' <<< ${offlineJSON})${NC}" - if jq -er '."pool-name"' <<< ${offlineJSON} &>/dev/null; then - [[ ${otx_type} != "Pool Registration" ]] && println DEBUG "Pool name : ${FG_LGRAY}$(jq -r '."pool-name"' <<< ${offlineJSON})${NC}" - fi - [[ ${otx_type} = "Pool De-Registration" ]] && println DEBUG "Ticker : ${FG_LGRAY}$(jq -r '."pool-ticker"' <<< ${offlineJSON})${NC}" - [[ ${otx_type} = "Pool De-Registration" ]] && println DEBUG "To be retired : epoch ${FG_LGRAY}$(jq -r '."retire-epoch"' <<< ${offlineJSON})${NC}" - jq -er '.metadata' <<< ${offlineJSON} &>/dev/null && println DEBUG "Metadata :\n$(jq -r '.metadata' <<< ${offlineJSON})\n" - [[ ${otx_type} = "Pool Registration" || ${otx_type} = "Pool Update" ]] && println DEBUG "Pool name : ${FG_LGRAY}$(jq -r '."pool-metadata".name' <<< ${offlineJSON})${NC}" - [[ ${otx_type} = "Pool Registration" || ${otx_type} = "Pool Update" ]] && println DEBUG "Ticker : ${FG_LGRAY}$(jq -r '."pool-metadata".ticker' <<< ${offlineJSON})${NC}" - [[ ${otx_type} = "Pool Registration" || ${otx_type} = "Pool Update" ]] && println DEBUG "Pledge : ${FG_LBLUE}$(formatLovelace "$(ADAToLovelace "$(jq -r '."pool-pledge"' <<< ${offlineJSON})")")${NC} ADA" - [[ ${otx_type} = "Pool Registration" || ${otx_type} = "Pool Update" ]] && println DEBUG "Margin : ${FG_LBLUE}$(jq -r '."pool-margin"' <<< ${offlineJSON})${NC} %" - [[ ${otx_type} = "Pool Registration" || ${otx_type} = "Pool Update" ]] && println DEBUG "Cost : ${FG_LBLUE}$(formatLovelace "$(ADAToLovelace "$(jq -r '."pool-cost"' <<< ${offlineJSON})")")${NC} ADA" - [[ ${otx_type} = "Asset Minting" || ${otx_type} = "Asset Burning" ]] && println DEBUG "Policy Name : ${FG_LGRAY}$(jq -r '."policy-name"' <<< ${offlineJSON})${NC}" - [[ ${otx_type} = "Asset Minting" || ${otx_type} = "Asset Burning" ]] && println DEBUG "Policy ID : ${FG_LGRAY}$(jq -r '."policy-id"' <<< ${offlineJSON})${NC}" - [[ ${otx_type} = "Asset Minting" || ${otx_type} = "Asset Burning" ]] && println DEBUG "Asset Name : ${FG_LGRAY}$(jq -r '."asset-name"' <<< ${offlineJSON})${NC}" - [[ ${otx_type} = "Asset Minting" ]] && println DEBUG "Assets To Mint : ${FG_LBLUE}$(formatAsset "$(jq -r '."asset-amount"' <<< ${offlineJSON})")${NC}" - [[ ${otx_type} = "Asset Minting" ]] && println DEBUG "Assets Minted : ${FG_LBLUE}$(formatAsset "$(jq -r '."asset-minted"' <<< ${offlineJSON})")${NC}" - [[ ${otx_type} = "Asset Burning" ]] && println DEBUG "Assets To Burn : ${FG_LBLUE}$(formatAsset "$(jq -r '."asset-amount"' <<< ${offlineJSON})")${NC}" - [[ ${otx_type} = "Asset Burning" ]] && println DEBUG "Assets Left : ${FG_LBLUE}$(formatAsset "$(jq -r '."asset-minted"' <<< ${offlineJSON})")${NC}" - if [[ ${otx_type} = "Asset Minting" || ${otx_type} = "Asset Burning" ]] && otx_metadata=$(jq -er '.metadata' <<< ${offlineJSON}); then println DEBUG "Metadata : \n${otx_metadata}\n"; fi - [[ ${otx_type} = "Poll Cast" ]] && println DEBUG "Poll ID : ${FG_LGRAY}$(jq -r '."poll-txId"' <<< ${offlineJSON})${NC}" - [[ ${otx_type} = "Poll Cast" ]] && println DEBUG "Title : ${FG_LGRAY}$(jq -r '."poll-title"' <<< ${offlineJSON})${NC}" - [[ ${otx_type} = "Poll Cast" ]] && println DEBUG "Question : ${FG_LGRAY}$(jq -r '."poll-question"' <<< ${offlineJSON})${NC}" - [[ ${otx_type} = "Poll Cast" ]] && println DEBUG "Answer : ${FG_LGRAY}$(jq -r '."poll-answer"' <<< ${offlineJSON})${NC}" - tx_signed="${TMP_DIR}/tx.signed_$(date +%s)" - println DEBUG "\nProceed to submit transaction?" - select_opt "[y] Yes" "[n] No" - case $? in - 0) : ;; - 1) continue ;; - esac - echo -e "${otx_signed_txBody}" > "${tx_signed}" - if ! submitTx "${tx_signed}"; then waitToProceed && continue; fi - if [[ ${otx_type} = "Pool Registration" || ${otx_type} = "Pool Update" ]]; then - if otx_pool_name=$(jq -er '."pool-name"' <<< ${offlineJSON}); then - if ! jq '."pool-reg-cert"' <<< "${offlineJSON}" > "${POOL_FOLDER}/${otx_pool_name}/${POOL_REGCERT_FILENAME}"; then println ERROR "${FG_RED}ERROR${NC}: failed to write pool cert to disk"; fi - [[ -f "${POOL_FOLDER}/${otx_pool_name}/${POOL_DEREGCERT_FILENAME}" ]] && rm -f "${POOL_FOLDER}/${otx_pool_name}/${POOL_DEREGCERT_FILENAME}" # delete de-registration cert if available - else - println ERROR "${FG_RED}ERROR${NC}: field 'pool-name' not found in: ${offline_tx}" + echo + if [[ ${catalyst_status} = *error\":* ]]; then + println DEBUG "Status: ${FG_YELLOW}$(jq -r .error <<< "${catalyst_status}")${NC}" + waitToProceed && continue fi - fi - echo - println "Offline transaction successfully submitted and set to be included in next block!" - echo - println DEBUG "Delete submitted offline transaction file?" - select_opt "[y] Yes" "[n] No" - case $? in - 0) rm -f "${offline_tx}" ;; - 1) : ;; - esac - ;; - *) println ERROR "${FG_RED}ERROR${NC}: unsupported offline tx type: ${otx_type}" && waitToProceed && continue ;; - esac - waitToProceed && continue + while IFS=',' read -r _last_updated _final _voting_power _delegations_count _delegator_addresses; do + final_color=$([[ ${_final} = false ]] && echo "${FG_YELLOW}" || echo "${FG_GREEN}") + println DEBUG "Status: ${FG_GREEN}registered${NC}" + println DEBUG "Last updated: ${FG_LGRAY}$(printf '%(%F %T %Z)T' "$(date -d"${_last_updated}" +%s)")${NC}" + println DEBUG "Is Finalized: ${final_color}${_final}${NC}" + println DEBUG "Voting power: ${FG_LBLUE}$(formatLovelace ${_voting_power})${NC}" + println DEBUG "Delegation count: ${FG_LBLUE}${_delegations_count}${NC}" + println DEBUG "\nDelegator list:" + for pubkey_hex in ${_delegator_addresses//;/ }; do + echo + unset delegation_wallet + wallet_match=$(grep -r ${pubkey_hex:2} ${WALLET_FOLDER} | head -n1) + if [[ -n ${wallet_match} ]]; then + println DEBUG "Wallet: ${FG_GREEN}$(basename ${wallet_match%/*})${NC}" + fi + println ACTION "${CCLI} ${NETWORK_ERA} stake-address build --stake-verification-key ${pubkey_hex:2} ${NETWORK_IDENTIFIER}" + stake_addr=$(${CCLI} ${NETWORK_ERA} stake-address build --stake-verification-key ${pubkey_hex:2} ${NETWORK_IDENTIFIER}) + println DEBUG "Stake address: ${FG_LGRAY}${stake_addr}${NC}" + delegator_status_url="${CATALYST_API}/registration/delegations/${pubkey_hex}" + println ACTION "curl -sSL -m ${CURL_TIMEOUT} -f -H \"Content-Type: application/json\" ${delegator_status_url}" + if ! delegator_status=$(curl -sSL -m ${CURL_TIMEOUT} -f -H "Content-Type: application/json" "${delegator_status_url}" 2>&1); then + println ERROR "${FG_RED}ERROR${NC}: failure during Catalyst delegation query!\n${delegator_status}"; continue + fi + while IFS=',' read -r _reward_address _reward_payable _raw_power; do + payable_color=$([[ ${_reward_payable} = false ]] && echo "${FG_YELLOW}" || echo "${FG_GREEN}") + println DEBUG "Reward address: ${FG_LGRAY}${_reward_address}${NC}" + println DEBUG "Reward payable: ${payable_color}${_reward_payable}${NC}" + println DEBUG "Raw power: ${FG_LBLUE}$(formatLovelace ${_raw_power})${NC}" + done < <( jq -cr '"\(.reward_address),\(.reward_payable),\(.raw_power)"' <<< "${delegator_status}" ) + done + done < <( jq -cr '"\(.last_updated),\(.final),\(.voter_info.voting_power),\(.voter_info.delegations_count),\(.voter_info.delegator_addresses | join(";"))"' <<< "${catalyst_status}" ) + waitToProceed && continue + ;; ################################################################### + esac # vote sub OPERATION + done # vote loop ;; ################################################################### - esac # transaction sub OPERATION - done # Transaction loop + esac # vote sub OPERATION + done # vote loop ;; ################################################################### blocks) clear @@ -4360,7 +5516,6 @@ function main { esac waitToProceed && continue ;; ################################################################### - advanced) while true; do # Advanced loop clear @@ -4369,31 +5524,19 @@ function main { println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println OFF " Developer & Advanced features\n"\ " ) Metadata - create and optionally post metadata on-chain"\ - " ) Multi-Asset - multi-asset nanagement"\ + " ) Asset - asset nanagement"\ + " ) MultiSig - create a multi-signature wallet"\ " ) Delete Keys - delete all sign/cold keys from CNTools (wallet|pool|asset)"\ "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - #println OFF " Developer & Advanced features\n"\ -# " ) Metadata - create and optionally post metadata on-chain"\ -# " ) Multi-Asset - multi-asset nanagement"\ -# " ) Multi-Sig - create a multi-sig/native script wallet"\ -# " ) Delete Keys - delete all sign/cold keys from CNTools (wallet|pool|asset)"\ -# "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" println DEBUG " Select Operation\n" - select_opt "[m] Metadata" "[a] Multi-Asset" "[x] Delete Private Keys" "[h] Home" + select_opt "[m] Metadata" "[a] Asset" "[s] MultiSig" "[x] Delete Private Keys" "[h] Home" case $? in 0) SUBCOMMAND="metadata" ;; - 1) SUBCOMMAND="multi-asset" ;; - 2) SUBCOMMAND="del-keys" ;; - 3) break ;; + 1) SUBCOMMAND="asset" ;; + 2) SUBCOMMAND="multisig" ;; + 3) SUBCOMMAND="del-keys" ;; + 4) break ;; esac - #select_opt "[m] Metadata" "[a] Multi-Asset" "[s] Multi-Sig" "[x] Delete Private Keys" "[h] Home" - #case $? in - # 0) SUBCOMMAND="metadata" ;; - # 1) SUBCOMMAND="multi-asset" ;; - # 2) SUBCOMMAND="multi-sig" ;; - # 3) SUBCOMMAND="del-keys" ;; - # 4) break ;; - #esac case $SUBCOMMAND in metadata) clear @@ -4473,9 +5616,9 @@ function main { 0) : ;; # do nothing 1) continue ;; esac - println DEBUG "\n# Select wallet to pay for metadata transaction fee" + println DEBUG "\nSelect wallet to pay for metadata transaction fee" if [[ ${op_mode} = "online" ]]; then - selectWallet "balance" "${WALLET_PAY_SK_FILENAME}" + selectWallet "balance" case $? in 1) waitToProceed; continue ;; 2) continue ;; @@ -4503,11 +5646,11 @@ function main { # Both payment and base address available with funds, let user choose what to use println DEBUG "Select source wallet address" if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then - println DEBUG "$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA" "Funds :" "$(formatLovelace ${base_lovelace})")" - println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Enterprise Funds :" "$(formatLovelace ${pay_lovelace})")" + println DEBUG "$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA" "Base Funds :" "$(formatLovelace ${base_lovelace})")" + println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Payment Funds :" "$(formatLovelace ${pay_lovelace})")" fi echo - select_opt "[b] Base (default)" "[e] Enterprise" "[Esc] Cancel" + select_opt "[b] Base (default)" "[e] Payment" "[Esc] Cancel" case $? in 0) addr="${base_addr}"; lovelace=${base_lovelace} ;; 1) addr="${pay_addr}"; lovelace=${pay_lovelace} ;; @@ -4517,13 +5660,13 @@ function main { addr="${pay_addr}" lovelace=${pay_lovelace} if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then - println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Enterprise Funds :" "$(formatLovelace ${pay_lovelace})")" + println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Payment Funds :" "$(formatLovelace ${pay_lovelace})")" fi elif [[ ${base_lovelace} -gt 0 ]]; then addr="${base_addr}" lovelace=${base_lovelace} if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then - println DEBUG "$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA" "Funds :" "$(formatLovelace ${base_lovelace})")" + println DEBUG "$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA" "Base Funds :" "$(formatLovelace ${base_lovelace})")" fi else println ERROR "${FG_RED}ERROR${NC}: no funds available for wallet ${FG_GREEN}${wallet_name}${NC}" @@ -4538,13 +5681,13 @@ function main { println "Metadata successfully posted on-chain" waitToProceed && continue ;; ################################################################### - multi-asset) - while true; do # Multi-Asset loop + asset) + while true; do # Asset loop clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - println " >> ADVANCED >> MULTI-ASSET" + println " >> ADVANCED >> ASSET" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - println OFF " Multi-Asset Token Management\n"\ + println OFF " Asset Token Management\n"\ " ) Create Policy - create a new asset policy"\ " ) List Assets - list created/minted policies/assets (local)"\ " ) Show Asset - show minted asset information"\ @@ -4554,7 +5697,7 @@ function main { " ) Burn Asset - burn a given amount of assets in selected wallet"\ " ) Register Asset - create/update JSON submission file for Cardano Token Registry"\ "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - println DEBUG " Select Multi-Asset Operation\n" + println DEBUG " Select Asset Operation\n" select_opt "[c] Create Policy" "[l] List Assets" "[s] Show Asset" "[d] Decrypt / Unlock Policy" "[e] Encrypt / Lock Policy" "[m] Mint Asset" "[x] Burn Asset" "[r] Register Asset" "[b] Back" "[h] Home" case $? in 0) SUBCOMMAND="create-policy" ;; @@ -4572,7 +5715,7 @@ function main { create-policy) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - println " >> ADVANCED >> MULTI-ASSET >> CREATE POLICY" + println " >> ADVANCED >> ASSET >> CREATE POLICY" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo getAnswerAnyCust policy_name "Internal name to give the generated policy" @@ -4636,7 +5779,7 @@ function main { list-assets) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - println " >> ADVANCED >> MULTI-ASSET >> LIST ASSETS" + println " >> ADVANCED >> ASSET >> LIST ASSETS" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" [[ ! $(ls -A "${ASSET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No policies or assets found!${NC}" && waitToProceed && continue while IFS= read -r -d '' policy; do @@ -4667,10 +5810,10 @@ function main { show-asset) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - println " >> ADVANCED >> MULTI-ASSET >> SHOW ASSET" + println " >> ADVANCED >> ASSET >> SHOW ASSET" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" [[ ! $(ls -A "${ASSET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No policies or assets found!${NC}" && waitToProceed && continue - println DEBUG "# Select minted asset to show information for" + println DEBUG "Select minted asset to show information for" selectAsset case $? in 1) waitToProceed; continue ;; @@ -4715,11 +5858,11 @@ function main { decrypt-policy) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - println " >> ADVANCED >> MULTI-ASSET >> DECRYPT / UNLOCK POLICY" + println " >> ADVANCED >> ASSET >> DECRYPT / UNLOCK POLICY" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo [[ ! $(ls -A "${ASSET_FOLDER}" 2>/dev/null) ]] && println "${FG_YELLOW}No policies available!${NC}" && waitToProceed && continue - println DEBUG "# Select policy to decrypt" + println DEBUG "Select policy to decrypt" selectPolicy "encrypted" case $? in 1) waitToProceed; continue ;; @@ -4728,7 +5871,7 @@ function main { filesUnlocked=0 keysDecrypted=0 echo - println DEBUG "# Removing write protection from all policy files" + println DEBUG "Removing write protection from all policy files" while IFS= read -r -d '' file; do if [[ ${ENABLE_CHATTR} = true && $(lsattr -R "$file") =~ -i- ]]; then sudo chattr -i "${file}" @@ -4739,7 +5882,7 @@ function main { done < <(find "${ASSET_FOLDER}/${policy_name}" -mindepth 1 -maxdepth 1 -type f -print0) if [[ $(find "${ASSET_FOLDER}/${policy_name}" -mindepth 1 -maxdepth 1 -type f -name '*.gpg' -print0 | wc -c) -gt 0 ]]; then echo - println "# Decrypting GPG encrypted policy key" + println "Decrypting GPG encrypted policy key" if ! getPasswordCust; then # $password variable populated by getPasswordCust function println "\n\n" && println ERROR "${FG_RED}ERROR${NC}: password input aborted!" waitToProceed && continue @@ -4758,18 +5901,18 @@ function main { if [[ ${filesUnlocked} -ne 0 || ${keysDecrypted} -ne 0 ]]; then echo println DEBUG "${FG_YELLOW}Policy files are now unprotected${NC}" - println DEBUG "Use 'ADVANCED >> MULTI-ASSET >> ENCRYPT / LOCK POLICY' to re-lock" + println DEBUG "Use 'ADVANCED >> ASSET >> ENCRYPT / LOCK POLICY' to re-lock" fi waitToProceed && continue ;; ################################################################### encrypt-policy) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - println " >> ADVANCED >> MULTI-ASSET >> ENCRYPT / LOCK POLICY" + println " >> ADVANCED >> ASSET >> ENCRYPT / LOCK POLICY" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo [[ ! $(ls -A "${ASSET_FOLDER}" 2>/dev/null) ]] && println "${FG_YELLOW}No policies available!${NC}" && waitToProceed && continue - println DEBUG "# Select policy to encrypt" + println DEBUG "Select policy to encrypt" selectPolicy "encrypted" case $? in 1) waitToProceed; continue ;; @@ -4779,7 +5922,7 @@ function main { keysEncrypted=0 if [[ $(find "${ASSET_FOLDER}/${policy_name}" -mindepth 1 -maxdepth 1 -type f -name '*.gpg' -print0 | wc -c) -le 0 ]]; then echo - println DEBUG "# Encrypting policy signing key with GPG" + println DEBUG "Encrypting policy signing key with GPG" if ! getPasswordCust confirm; then # $password variable populated by getPasswordCust function println "\n\n" && println ERROR "${FG_RED}ERROR${NC}: password input aborted!" waitToProceed && continue @@ -4801,7 +5944,7 @@ function main { waitToProceed && continue fi echo - println DEBUG "# Write protecting all policy files with 400 permission and if enabled 'chattr +i'" + println DEBUG "Write protecting all policy files with 400 permission and if enabled 'chattr +i'" while IFS= read -r -d '' file; do chmod 400 "$file" if [[ ${ENABLE_CHATTR} = true && ! $(lsattr -R "$file") =~ -i- ]]; then @@ -4817,14 +5960,14 @@ function main { if [[ ${filesLocked} -ne 0 || ${keysEncrypted} -ne 0 ]]; then echo println DEBUG "${FG_BLUE}INFO${NC}: policy files are now protected" - println DEBUG "Use 'ADVANCED >> MULTI-ASSET >> DECRYPT / UNLOCK POLICY' to unlock" + println DEBUG "Use 'ADVANCED >> ASSET >> DECRYPT / UNLOCK POLICY' to unlock" fi waitToProceed && continue ;; ################################################################### mint-asset) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - println " >> ADVANCED >> MULTI-ASSET >> MINT ASSET" + println " >> ADVANCED >> ASSET >> MINT ASSET" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitToProceed && continue if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then @@ -4835,7 +5978,7 @@ function main { fi echo [[ ! $(ls -A "${ASSET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No policies found!${NC}\n\nPlease first create a policy to mint asset with" && waitToProceed && continue - println DEBUG "# Select the policy to use when minting the asset" + println DEBUG "Select the policy to use when minting the asset" selectPolicy "all" "${ASSET_POLICY_SK_FILENAME}" "${ASSET_POLICY_VK_FILENAME}" "${ASSET_POLICY_SCRIPT_FILENAME}" "${ASSET_POLICY_ID_FILENAME}" case $? in 1) waitToProceed; continue ;; @@ -4852,7 +5995,7 @@ function main { [[ ${policy_ttl} -gt 0 && ${policy_ttl} -lt $(getSlotTipRef) ]] && println ERROR "${FG_RED}ERROR${NC}: Policy expired!" && waitToProceed && continue echo if [[ $(find "${policy_folder}" -type f -name '*.asset' -print0 | wc -c) -gt 0 ]]; then - println DEBUG "# Assets minted for this Policy\n" + println DEBUG "Assets minted for this Policy\n" asset_name_maxlen=5; asset_amount_maxlen=12 while IFS= read -r -d '' asset; do asset_name=$(jq -r '.name //empty' "${asset}") @@ -4893,9 +6036,9 @@ function main { metafile_param="--metadata-json-file ${metafile}" ;; esac - println DEBUG "\n# Select wallet to mint assets on (also used for transaction fee)" + println DEBUG "\nSelect wallet to mint assets on (also used for transaction fee)" if [[ ${op_mode} = "online" ]]; then - selectWallet "balance" "${WALLET_PAY_SK_FILENAME}" + selectWallet "balance" case $? in 1) waitToProceed; continue ;; 2) continue ;; @@ -4918,11 +6061,11 @@ function main { # Both payment and base address available with funds, let user choose what to use println DEBUG "Select source wallet address" if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then - println DEBUG "$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA" "Funds :" "$(formatLovelace ${base_lovelace})")" - println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Enterprise Funds :" "$(formatLovelace ${pay_lovelace})")" + println DEBUG "$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA" "Base Funds :" "$(formatLovelace ${base_lovelace})")" + println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA" "Payment Funds :" "$(formatLovelace ${pay_lovelace})")" fi echo - select_opt "[b] Base (default)" "[e] Enterprise" "[Esc] Cancel" + select_opt "[b] Base (default)" "[e] Payment" "[Esc] Cancel" case $? in 0) addr="${base_addr}"; lovelace=${base_lovelace} ;; 1) addr="${pay_addr}" ; lovelace=${pay_lovelace} ;; @@ -4933,13 +6076,13 @@ function main { addr="${pay_addr}" lovelace=${pay_lovelace} if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then - println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA\n" "Enterprise Funds :" "$(formatLovelace ${pay_lovelace})")" + println DEBUG "$(printf "%s\t${FG_LBLUE}%s${NC} ADA\n" "Payment Funds :" "$(formatLovelace ${pay_lovelace})")" fi elif [[ ${base_lovelace} -gt 0 ]]; then addr="${base_addr}" lovelace=${base_lovelace} if [[ -n ${wallet_count} && ${wallet_count} -gt ${WALLET_SELECTION_FILTER_LIMIT} ]]; then - println DEBUG "$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA\n" "Funds :" "$(formatLovelace ${base_lovelace})")" + println DEBUG "$(printf "%s\t\t${FG_LBLUE}%s${NC} ADA\n" "Base Funds :" "$(formatLovelace ${base_lovelace})")" fi else println ERROR "${FG_RED}ERROR${NC}: no funds available for wallet ${FG_GREEN}${wallet_name}${NC}" @@ -4976,7 +6119,7 @@ function main { burn-asset) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - println " >> ADVANCED >> MULTI-ASSET >> BURN ASSET" + println " >> ADVANCED >> ASSET >> BURN ASSET" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" [[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitToProceed && continue if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then @@ -4986,9 +6129,9 @@ function main { if ! selectOpMode; then continue; fi fi echo - println DEBUG "# Select wallet with assets to burn" + println DEBUG "Select wallet with assets to burn" if [[ ${op_mode} = "online" ]]; then - selectWallet "balance" "${WALLET_PAY_SK_FILENAME}" + selectWallet "balance" case $? in 1) waitToProceed; continue ;; 2) continue ;; @@ -5010,7 +6153,7 @@ function main { 0) println ERROR "${FG_RED}ERROR${NC}: please use a CLI wallet for asset burning!" && waitToProceed && continue ;; esac fi - # Let user choose asset on wallet to burn, both base and enterprise, fee payed with same address + # Let user choose asset on wallet to burn, both base and payment, fee payed with same address assets_on_wallet=() getWalletBalance ${wallet_name} true true true true for asset in "${!base_assets[@]}"; do @@ -5023,11 +6166,11 @@ function main { [[ ${asset} = "lovelace" ]] && continue IFS='.' read -ra asset_arr <<< "${asset}" [[ -z ${asset_arr[1]} ]] && asset_ascii_name="" || asset_ascii_name=$(hexToAscii ${asset_arr[1]}) - assets_on_wallet+=( "${asset} (${asset_ascii_name}) [enterprise addr]" ) + assets_on_wallet+=( "${asset} (${asset_ascii_name}) [payment addr]" ) done echo [[ ${#assets_on_wallet[@]} -eq 0 ]] && println ERROR "${FG_RED}ERROR${NC}: Wallet doesn't contain any assets!" && waitToProceed && continue - println DEBUG "# Select Asset to burn" + println DEBUG "Select Asset to burn" select_opt "${assets_on_wallet[@]}" "[Esc] Cancel" selection=$? [[ ${selected_value} = "[Esc] Cancel" ]] && continue @@ -5042,7 +6185,7 @@ function main { lovelace=${base_assets[lovelace]} else addr=${pay_addr} - wallet_source="enterprise" + wallet_source="payment" curr_asset_amount=${pay_assets[${asset}]} lovelace=${pay_assets[lovelace]} fi @@ -5124,7 +6267,7 @@ function main { register-asset) clear println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - println " >> ADVANCED >> MULTI-ASSET >> REGISTER ASSET" + println " >> ADVANCED >> ASSET >> REGISTER ASSET" println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo if ! cmdAvailable "token-metadata-creator"; then @@ -5133,7 +6276,7 @@ function main { waitToProceed && continue fi [[ ! $(ls -A "${ASSET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No policies found!${NC}\n\nPlease first create a policy to use for Cardano Token Registry" && waitToProceed && continue - println DEBUG "# Select the policy to use for Cardano Token Registry" + println DEBUG "Select the policy to use for Cardano Token Registry" selectPolicy "all" "${ASSET_POLICY_SK_FILENAME}" "${ASSET_POLICY_SCRIPT_FILENAME}" "${ASSET_POLICY_ID_FILENAME}" case $? in 1) waitToProceed; continue ;; @@ -5146,7 +6289,7 @@ function main { policy_id="$(cat "${policy_folder}/${ASSET_POLICY_ID_FILENAME}")" echo if [[ $(find "${policy_folder}" -type f -name '*.asset' -print0 | wc -c) -gt 0 ]]; then - println DEBUG "# Assets previously minted for this Policy\n" + println DEBUG "Assets previously minted for this Policy\n" asset_name_maxlen=5; asset_amount_maxlen=12 while IFS= read -r -d '' asset; do asset_filename=$(basename "${asset}") @@ -5180,7 +6323,7 @@ function main { echo fi fi - println DEBUG "# Enter metadata (optional fields can be left empty)" + println DEBUG "Enter metadata (optional fields can be left empty)" getAnswerAnyCust meta_name "Name [${FG_RED}required${NC}] (Max. 50 chars) " [[ -z ${meta_name} || ${#meta_name} -gt 50 ]] && println ERROR "\n${FG_RED}ERROR${NC}: Metadata name is a required field and limited to 50 chars in length!" && waitToProceed && continue getAnswerAnyCust meta_desc "Description [${FG_RED}required${NC}] (Max. 500 chars)" @@ -5268,109 +6411,282 @@ function main { waitToProceed && continue ;; ################################################################### - esac # advanced >> multi-asset sub OPERATION - done # Multi-Asset loop + esac # advanced >> asset sub OPERATION + done # Asset loop ;; ################################################################### - multi-sig) - clear - println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - println " >> ADVANCED >> MULTI-SIG" - println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - echo - getAnswerAnyCust ms_wallet_name "Name of multi-sig wallet" - # Remove unwanted characters from wallet name - ms_wallet_name=${ms_wallet_name//[^[:alnum:]]/_} - if [[ -z "${ms_wallet_name}" ]]; then - println ERROR "${FG_RED}ERROR${NC}: Empty wallet name, please retry!" - waitToProceed && continue - fi - echo - if ! mkdir -p "${WALLET_FOLDER}/${ms_wallet_name}"; then - println ERROR "${FG_RED}ERROR${NC}: Failed to create directory for wallet:\n${WALLET_FOLDER}/${ms_wallet_name}" - waitToProceed && continue - fi - # Wallet key filenames - ms_stake_vk_file="${WALLET_FOLDER}/${ms_wallet_name}/${WALLET_STAKE_VK_FILENAME}" - ms_stake_sk_file="${WALLET_FOLDER}/${ms_wallet_name}/${WALLET_STAKE_SK_FILENAME}" - ms_pay_script_file="${WALLET_FOLDER}/${ms_wallet_name}/${WALLET_PAY_SCRIPT_FILENAME}" - if [[ $(find "${WALLET_FOLDER}/${ms_wallet_name}" -type f -print0 | wc -c) -gt 0 ]]; then - println "${FG_RED}WARN${NC}: A wallet ${FG_GREEN}${ms_wallet_name}${NC} already exists" - println " Choose another name or delete the existing one" - waitToProceed && continue - fi - declare -gA key_hashes=() # key hashes as keys to assosiative array to act as a set - unset timelock_after - println OFF "Select wallet(s) / payment credentials (key hash) to include in multi-sig wallet" - while true; do - println DEBUG "\nSelect wallet or manually enter credential?" - select_opt "[w] Wallet" "[c] Payment Credential" "[Esc] Cancel" - echo + multisig) + while true; do # MultiSig loop + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> ADVANCED >> MULTISIG" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println OFF " Multi Signature Wallet Management\n"\ + " ) Create Wallet - create a new multi-signature wallet"\ + " ) Derive Keys - derive MultiSig keys using the 1854H paths according to CIP-1854"\ + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println DEBUG " Select MultiSig Operation\n" + select_opt "[c] Create" "[d] Derive Keys" "[b] Back" "[h] Home" case $? in - 0) selectWallet "balance" "${WALLET_PAY_VK_FILENAME}" + 0) SUBCOMMAND="create-ms-wallet" ;; + 1) SUBCOMMAND="derive-ms-keys" ;; + 2) break ;; + 3) break 2 ;; + esac + case $SUBCOMMAND in + create-ms-wallet) + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> ADVANCED >> MULTISIG >> CREATE WALLET" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + echo + createNewWallet || continue + ms_wallet_name="${wallet_name}" + # Wallet key filenames + ms_pay_script_file="${WALLET_FOLDER}/${ms_wallet_name}/${WALLET_PAY_SCRIPT_FILENAME}" + ms_stake_script_file="${WALLET_FOLDER}/${ms_wallet_name}/${WALLET_STAKE_SCRIPT_FILENAME}" + if [[ $(find "${WALLET_FOLDER}/${ms_wallet_name}" -type f -print0 | wc -c) -gt 0 ]]; then + println "${FG_RED}WARN${NC}: A wallet ${FG_GREEN}${ms_wallet_name}${NC} already exists" + println " Choose another name or delete the existing one" + waitToProceed && continue + fi + # pay key hashes as keys to associative array to act as a set, with stake key hash as value + declare -gA key_hashes=() + unset timelock_after + println OFF "Select wallet(s) / credentials (key hashes) to include in MultiSig wallet" + println OFF "${FG_YELLOW}!${NC} Please use 1854H (MultiSig) derived keys according to CIP-1854!" + println OFF "${FG_YELLOW}!${NC} Only wallets with these keys will be listed, use 'Derive Keys' option to generate them." + echo + selected_wallets=() + while true; do + println DEBUG "Select wallet or manually enter credentials?" + select_opt "[w] Wallet" "[c] Credentials" "[d] I'm done" "[Esc] Cancel" + case $? in + 0) selectWallet "balance" "${selected_wallets[@]}" "${WALLET_MULTISIG_PREFIX}${WALLET_PAY_VK_FILENAME}" "${WALLET_MULTISIG_PREFIX}${WALLET_STAKE_VK_FILENAME}" + case $? in + 1) waitToProceed; continue ;; + 2) continue ;; + esac + getCredentials ${wallet_name} + [[ -z ${ms_pay_cred} ]] && println ERROR "\n${FG_RED}ERROR${NC}: wallet MultiSig payment credentials not set!" && waitToProceed && continue + [[ -z ${ms_stake_cred} ]] && println ERROR "\n${FG_RED}ERROR${NC}: wallet MultiSig stake credentials not set!" && waitToProceed && continue + key_hashes[${ms_pay_cred}]="${ms_stake_cred}" + selected_wallets+=("${wallet_name}") + ;; + 1) getAnswerAnyCust ms_pay_cred "MultiSig Payment Credential (key hash)" + [[ ${#ms_pay_cred} -ne 56 ]] && println ERROR "\n${FG_RED}ERROR${NC}: invalid payment credential entered!" && waitToProceed && continue + getAnswerAnyCust ms_stake_cred "MultiSig Stake Credential (key hash)" + [[ ${#ms_stake_cred} -ne 56 ]] && println ERROR "\n${FG_RED}ERROR${NC}: invalid stake credential entered!" && waitToProceed && continue + key_hashes[${ms_pay_cred}]="${ms_stake_cred}" + ;; + 2) break ;; + 3) safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; continue 2 ;; + esac + println DEBUG "\nMultiSig size: ${#key_hashes[@]} - Add more wallets / credentials to MultiSig?" + select_opt "[n] No" "[y] Yes" "[Esc] Cancel" + case $? in + 0) break ;; + 1) : ;; + 2) safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; continue 2 ;; + esac + done + if [[ ${#key_hashes[@]} -eq 0 ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: no signers added, please add at least one"; safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; waitToProceed; continue + fi + println DEBUG "\n${#key_hashes[@]} wallets / credentials added to MultiSig, how many are required to witness the transaction?" + getAnswerAnyCust required_sig_cnt "Number of Required signatures" + if ! isNumber ${required_sig_cnt} || [[ ${required_sig_cnt} -lt 1 || ${required_sig_cnt} -gt ${#key_hashes[@]} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: invalid signature count entered, must be above 1 and max ${#key_hashes[@]}"; safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; waitToProceed; continue + fi + println DEBUG "\nAdd time lock to MultiSig wallet by only allowing spending from wallet after a certain epoch start?" + select_opt "[n] No" "[y] Yes" "[Esc] Cancel" + case $? in + 0) : ;; + 1) getAnswerAnyCust epoch_no "Epoch" + if ! isNumber ${epoch_no}; then println ERROR "${FG_RED}ERROR${NC}: invalid epoch number entered!"; safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; waitToProceed; continue; fi + timelock_after=$(getEpochStart ${epoch_no}) + ;; + 2) safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; continue ;; + esac + # build MultiSig script + pay_script=$(jq -n --argjson req_sig "${required_sig_cnt}" '{type:"atLeast",required:$req_sig,scripts:[]}') + stake_script="${pay_script}" + for sig in "${!key_hashes[@]}"; do + pay_script=$(jq --arg sig "${sig}" '.scripts += [{type:"sig",keyHash:$sig}]' <<< "${pay_script}") + stake_script=$(jq --arg sig "${key_hashes[${sig}]}" '.scripts += [{type:"sig",keyHash:$sig}]' <<< "${stake_script}") + done + if [[ -n ${timelock_after} ]]; then + pay_script=$(jq -n --argjson after "${timelock_after}" --argjson sig_script "${jsonscript}" '{type:"all",scripts:[{type:"after",slot:$after},$sig_script]}') + fi + if ! stdout=$(jq -e . <<< "${pay_script}" > "${ms_pay_script_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during payment script file creation!\n${stdout}"; safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; waitToProceed && continue + fi + if ! stdout=$(jq -e . <<< "${stake_script}" > "${ms_stake_script_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during stake script file creation!\n${stdout}"; safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; waitToProceed && continue + fi + + chmod 600 "${WALLET_FOLDER}/${ms_wallet_name}/"* + getBaseAddress ${ms_wallet_name} + getPayAddress ${ms_wallet_name} + getRewardAddress ${ms_wallet_name} + getCredentials ${ms_wallet_name} + echo + println "New MultiSig Wallet : ${FG_GREEN}${ms_wallet_name}${NC}" + println "Address : ${FG_LGRAY}${base_addr}${NC}" + println "Payment Address : ${FG_LGRAY}${pay_addr}${NC}" + println "Reward Address : ${FG_LGRAY}${reward_addr}${NC}" + println "Payment Credential : ${FG_LGRAY}${script_pay_cred}${NC}" + println "Reward Credential : ${FG_LGRAY}${script_stake_cred}${NC}" + println DEBUG "\nYou can now send and receive ADA using the above 'Address' or 'Payment Address'." + println DEBUG "Note that Payment Address will not take part in staking." + waitToProceed && continue + ;; ################################################################### + derive-ms-keys) + clear + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + println " >> ADVANCED >> MULTISIG >> DERIVE KEYS" + println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + echo + println DEBUG "Select wallet to derive MultiSig keys for (only wallets with missing keys shown)" + selectWallet "non-ms" case $? in 1) waitToProceed; continue ;; 2) continue ;; esac + getWalletType ${wallet_name} + case $? in + 0) # Hardware wallet + if ! cmdAvailable "cardano-hw-cli" &>/dev/null; then + println ERROR "${FG_RED}ERROR${NC}: cardano-hw-cli not found in path or executable permission not set." + println ERROR "Please run '${FG_YELLOW}guild-deploy.sh -s w${NC}' to add hardware wallet support and install Vaccumlabs cardano-hw-cli, '${FG_YELLOW}guild-deploy.sh -h${NC}' shows all available options" + waitToProceed && continue + fi + if ! HWCLIversionCheck; then waitToProceed && continue; fi + ms_payment_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_HW_PAY_SK_FILENAME}" + ms_payment_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_PAY_VK_FILENAME}" + ms_stake_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_HW_STAKE_SK_FILENAME}" + ms_stake_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_STAKE_VK_FILENAME}" + if [[ -f ${ms_payment_sk_file} || -f ${ms_stake_sk_file} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: MultiSig payment and/or stake signing keys already exist!\n${stdout}"; waitToProceed && continue + fi + derivation_path_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_DERIVATION_PATH_FILENAME}" + if ! getSavedDerivationPath "${derivation_path_file}"; then + getCustomDerivationPath || continue + echo "1852H/1815H/${acct_idx}H/x/${key_idx}" > "${derivation_path_file}" + fi + if ! unlockHWDevice "extract ${FG_LGRAY}MultiSig keys${NC}"; then waitToProceed && continue; fi + HW_DERIVATION_CMD=( + cardano-hw-cli address key-gen + --path 1854H/1815H/${acct_idx}H/0/${key_idx} + --path 1854H/1815H/${acct_idx}H/2/${key_idx} + --verification-key-file "${ms_payment_vk_file}" + --verification-key-file "${ms_stake_vk_file}" + --hw-signing-file "${ms_payment_sk_file}" + --hw-signing-file "${ms_stake_sk_file}" + ) + println ACTION "${HW_DERIVATION_CMD[*]}" + if ! stdout=$("${HW_DERIVATION_CMD[@]}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during key extraction!\n${stdout}"; safeDel "${WALLET_FOLDER}/${wallet_name}"; waitToProceed && continue + fi + jq '.description = "MultiSig Payment Hardware Verification Key"' "${ms_payment_vk_file}" > "${TMP_DIR}/$(basename "${ms_payment_vk_file}").tmp" && mv -f "${TMP_DIR}/$(basename "${ms_payment_vk_file}").tmp" "${ms_payment_vk_file}" + jq '.description = "MultiSig Stake Hardware Verification Key"' "${ms_stake_vk_file}" > "${TMP_DIR}/$(basename "${ms_stake_vk_file}").tmp" && mv -f "${TMP_DIR}/$(basename "${ms_stake_vk_file}").tmp" "${ms_stake_vk_file}" + ;; + *) + ms_payment_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_PAY_SK_FILENAME}" + ms_payment_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_PAY_VK_FILENAME}" + ms_stake_sk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_STAKE_SK_FILENAME}" + ms_stake_vk_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}${WALLET_STAKE_VK_FILENAME}" + if [[ -f ${ms_payment_sk_file} || -f ${ms_stake_sk_file} ]]; then + println ERROR "\n${FG_RED}ERROR${NC}: MultiSig payment and/or stake signing keys already exist!\n${stdout}"; waitToProceed && continue + fi + println DEBUG "Is selected wallet a CLI generated wallet or derived from mnemonic?" + select_opt "[c] CLI" "[m] Mnemonic" + case $? in + 0) println ACTION "${CCLI} ${NETWORK_ERA} address key-gen --verification-key-file ${ms_payment_vk_file} --signing-key-file ${ms_payment_sk_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} address key-gen --verification-key-file "${ms_payment_vk_file}" --signing-key-file "${ms_payment_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during MultiSig payment key creation!\n${stdout}"; waitToProceed && continue + fi + println ACTION "${CCLI} ${NETWORK_ERA} stake-address key-gen --verification-key-file ${ms_stake_vk_file} --signing-key-file ${ms_stake_sk_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} stake-address key-gen --verification-key-file "${ms_stake_vk_file}" --signing-key-file "${ms_stake_sk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during MultiSig stake key creation!\n${stdout}"; waitToProceed && continue + fi + ;; + 1) if ! cmdAvailable "bech32" &>/dev/null || \ + ! cmdAvailable "cardano-address" &>/dev/null; then + println ERROR "${FG_RED}ERROR${NC}: bech32 and/or cardano-address not found in '\$PATH'" + println ERROR "Please run updated guild-deploy.sh and re-build/re-download cardano-node" + waitToProceed && continue + fi + getAnswerAnyCust mnemonic false "24 or 15 word mnemonic(space separated)" + echo + IFS=" " read -r -a words <<< "${mnemonic}" + if [[ ${#words[@]} -ne 24 ]] && [[ ${#words[@]} -ne 15 ]]; then + println ERROR "${FG_RED}ERROR${NC}: 24 or 15 words expected, found ${FG_RED}${#words[@]}${NC}" + unset mnemonic; unset words + waitToProceed && continue + fi + derivation_path_file="${WALLET_FOLDER}/${wallet_name}/${WALLET_DERIVATION_PATH_FILENAME}" + if ! getSavedDerivationPath "${derivation_path_file}"; then + getCustomDerivationPath || continue + echo "1852H/1815H/${acct_idx}H/x/${key_idx}" > "${derivation_path_file}" + fi + caddr_v="$(cardano-address -v | awk '{print $1}')" + [[ "${caddr_v}" == 3* ]] && caddr_arg="--with-chain-code" || caddr_arg="" + if ! root_prv=$(cardano-address key from-recovery-phrase Shelley <<< ${mnemonic}); then + unset mnemonic; unset words + waitToProceed && continue + fi + unset mnemonic; unset words + payment_xprv=$(cardano-address key child 1854H/1815H/${acct_idx}H/0/${key_idx} <<< ${root_prv}) + stake_xprv=$(cardano-address key child 1854H/1815H/${acct_idx}H/2/${key_idx} <<< ${root_prv}) + payment_xpub=$(cardano-address key public ${caddr_arg} <<< ${payment_xprv}) + stake_xpub=$(cardano-address key public ${caddr_arg} <<< ${stake_xprv}) + pes_key=$(bech32 <<< ${payment_xprv} | cut -b -128)$(bech32 <<< ${payment_xpub}) + ses_key=$(bech32 <<< ${stake_xprv} | cut -b -128)$(bech32 <<< ${stake_xpub}) + cat <<-EOF > "${ms_payment_sk_file}" + { + "type": "PaymentExtendedSigningKeyShelley_ed25519_bip32", + "description": "MultiSig Payment Signing Key", + "cborHex": "5880${pes_key}" + } + EOF + cat <<-EOF > "${ms_stake_sk_file}" + { + "type": "StakeExtendedSigningKeyShelley_ed25519_bip32", + "description": "MultiSig Stake Signing Key", + "cborHex": "5880${ses_key}" + } + EOF + println ACTION "${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file ${ms_payment_sk_file} --verification-key-file ${TMP_DIR}/ms_payment.evkey" + if ! stdout=$(${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${ms_payment_sk_file}" --verification-key-file "${TMP_DIR}/ms_payment.evkey" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during MultiSig payment signing key extraction!\n${stdout}"; waitToProceed && continue + fi + println ACTION "${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file ${ms_stake_sk_file} --verification-key-file ${TMP_DIR}/ms_stake.evkey" + if ! stdout=$(${CCLI} ${NETWORK_ERA} key verification-key --signing-key-file "${ms_stake_sk_file}" --verification-key-file "${TMP_DIR}/ms_stake.evkey" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during MultiSig stake signing key extraction!\n${stdout}"; waitToProceed && continue + fi + println ACTION "${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file ${TMP_DIR}/ms_payment.evkey --verification-key-file ${ms_payment_vk_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file "${TMP_DIR}/ms_payment.evkey" --verification-key-file "${ms_payment_vk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during MultiSig payment verification key extraction!\n${stdout}"; waitToProceed && continue + fi + println ACTION "${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file ${TMP_DIR}/ms_stake.evkey --verification-key-file ${ms_stake_vk_file}" + if ! stdout=$(${CCLI} ${NETWORK_ERA} key non-extended-key --extended-verification-key-file "${TMP_DIR}/ms_stake.evkey" --verification-key-file "${ms_stake_vk_file}" 2>&1); then + println ERROR "\n${FG_RED}ERROR${NC}: failure during MultiSig stake verification key extraction!\n${stdout}"; waitToProceed && continue + fi + ;; + esac + ;; + esac + chmod 600 "${WALLET_FOLDER}/${wallet_name}/${WALLET_MULTISIG_PREFIX}"* + echo getCredentials ${wallet_name} - [[ -z ${pay_cred} ]] && println ERROR "${FG_RED}ERROR${NC}: wallet payment credentials not set!" && waitToProceed && continue - key_hashes[${pay_cred}]=1 - ;; - 1) getAnswerAnyCust pay_cred "Payment Credential (key hash)" - [[ ${#pay_cred} -ne 56 ]] && println ERROR "${FG_RED}ERROR${NC}: invalid payment credential entered!" && waitToProceed && continue - key_hashes[${pay_cred}]=1 - ;; - 2) safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; continue 2 ;; - esac - println DEBUG "\nMulti-Sig size: ${#key_hashes[@]} - Add more wallets / credentials to multi-sig?" - select_opt "[n] No" "[y] Yes" "[Esc] Cancel" - case $? in - 0) break ;; - 1) : ;; - 2) safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; continue 2 ;; - esac - done - println DEBUG "\n${#key_hashes[@]} wallets / credentials added to multi-sig, how many are required to witness the transaction?" - getAnswerAnyCust required_sig_cnt "Required signatures" - if ! isNumber ${required_sig_cnt} || [[ ${required_sig_cnt} -lt 1 || ${required_sig_cnt} -gt ${#key_hashes[@]} ]]; then - println ERROR "\n${FG_RED}ERROR${NC}: invalid signature count entered, must be above 1 and max ${#key_hashes[@]}"; safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; waitToProceed; continue - fi - println DEBUG "\nAdd time lock to multi-sig wallet by only allowing spending from wallet after a certain epoch start?" - select_opt "[n] No" "[y] Yes" "[Esc] Cancel" - case $? in - 0) : ;; - 1) getAnswerAnyCust epoch_no "Epoch" - if ! isNumber ${epoch_no}; then println ERROR "${FG_RED}ERROR${NC}: invalid epoch number entered!"; safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; waitToProceed; continue; fi - timelock_after=$(getEpochStart ${epoch_no}) - ;; - 2) safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; continue ;; - esac - # build multi-sig script - jsonscript=$(jq -n --argjson req_sig "${required_sig_cnt}" '{type:"atLeast",required:$req_sig,scripts:[]}') - for sig in "${!key_hashes[@]}"; do - jsonscript=$(jq --arg sig "${sig}" '.scripts += [{type:"sig",keyHash:$sig}]' <<< "${jsonscript}") - done - if [[ -n ${timelock_after} ]]; then - jsonscript=$(jq -n --argjson after "${timelock_after}" --argjson sig_script "${jsonscript}" '{type:"all",scripts:[{type:"after",slot:$after},$sig_script]}') - fi - if ! stdout=$(jq -e . <<< "${jsonscript}" > "${ms_pay_script_file}" 2>&1); then - println ERROR "\n${FG_RED}ERROR${NC}: failure during script file creation!\n${stdout}"; safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; waitToProceed && continue - fi - println ACTION "${CCLI} ${NETWORK_ERA} stake-address key-gen --verification-key-file ${ms_stake_vk_file} --signing-key-file ${ms_stake_sk_file}" - if ! stdout=$(${CCLI} ${NETWORK_ERA} stake-address key-gen --verification-key-file "${ms_stake_vk_file}" --signing-key-file "${ms_stake_sk_file}" 2>&1); then - println ERROR "\n${FG_RED}ERROR${NC}: failure during stake key creation!\n${stdout}"; safeDel "${WALLET_FOLDER}/${ms_wallet_name}"; waitToProceed && continue - fi - chmod 600 "${WALLET_FOLDER}/${ms_wallet_name}/"* - getBaseAddress ${ms_wallet_name} - getPayScriptAddress ${ms_wallet_name} - getRewardAddress ${ms_wallet_name} - echo - println "New Multi-Sig Wallet : ${FG_GREEN}${ms_wallet_name}${NC}" - println "Address : ${FG_LGRAY}${base_addr}${NC}" - println "Script Address : ${FG_LGRAY}${pay_script_addr}${NC}" - println "Reward Address : ${FG_LGRAY}${reward_addr}${NC}" - println DEBUG "\nYou can now send and receive ADA using the above 'Address' or 'Script Address'." - println DEBUG "Note that Script Address will not take part in staking." - waitToProceed && continue + println "Wallet : ${FG_GREEN}${wallet_name}${NC}" + println "MultiSig Credentials" + println "Payment : ${ms_pay_cred}" + println "Stake : ${ms_stake_cred}" + waitToProceed && continue + ;; ################################################################### + esac # advanced >> MultiSig sub OPERATION + done # MultiSig loop ;; ################################################################### del-keys) clear diff --git a/scripts/cnode-helper-scripts/dbsync.sh b/scripts/cnode-helper-scripts/dbsync.sh index 60e2167f2..d3e9e057a 100755 --- a/scripts/cnode-helper-scripts/dbsync.sh +++ b/scripts/cnode-helper-scripts/dbsync.sh @@ -14,7 +14,6 @@ #DBSYNC_SCHEMA_DIR="${CNODE_HOME}/guild-db/schema" # Path to DBSync repository's schema folder #DBSYNC_CONFIG="${CNODE_HOME}/files/dbsync.json" # Config file for dbsync instance #SYSTEMD_PGNAME="postgresql" # Name for postgres instance, if changed from default -#DBSYNC_CONSUMED_TX_OUT="Y" # Add new field consumed_by_tx_in_id in tx_out table (added unless variable is set to "N") ###################################### # Do NOT modify code below # @@ -44,7 +43,6 @@ set_defaults() { [[ -z "${DBSYNC_CONFIG}" ]] && DBSYNC_CONFIG="${CNODE_HOME}/files/dbsync.json" [[ -z "${DBSYNC_SCHEMA_DIR}" ]] && DBSYNC_SCHEMA_DIR="${CNODE_HOME}/guild-db/schema" [[ -z "${DBSYNC_STATE_DIR}" ]] && DBSYNC_STATE_DIR="${CNODE_HOME}/guild-db/ledger-state" - [[ -z "${DBSYNC_CONSUMED_TX_OUT}" ]] && DBSYNC_CONSUMED_TX_OUT="Y" [[ -z "${SYSTEMD_PGNAME}" ]] && SYSTEMD_PGNAME="postgresql" } @@ -63,7 +61,6 @@ check_defaults() { check_config_sanity() { DBSYNC_VERSION="$(${DBSYNCBIN} --version | awk '{print $2}')" - [[ "${DBSYNC_CONSUMED_TX_OUT}" != "N" ]] && versionCheck 13.1.1.3 ${DBSYNC_VERSION} && DBSYNC_ARGS=" --consumed-tx-out" BYGENHASH=$("${CCLI}" byron genesis print-genesis-hash --genesis-json "${BYRON_GENESIS_JSON}" 2>/dev/null) BYGENHASHCFG=$(jq '.ByronGenesisHash' <"${CONFIG}" 2>/dev/null) SHGENHASH=$("${CCLI}" ${NETWORK_ERA} genesis hash --genesis "${GENESIS_JSON}" 2>/dev/null) diff --git a/scripts/cnode-helper-scripts/env b/scripts/cnode-helper-scripts/env index e73b1bc3c..539471c14 100644 --- a/scripts/cnode-helper-scripts/env +++ b/scripts/cnode-helper-scripts/env @@ -50,17 +50,41 @@ #WALLET_HW_PAY_SK_FILENAME="payment.hwsfile" #WALLET_PAY_ADDR_FILENAME="payment.addr" #WALLET_PAY_SCRIPT_FILENAME="payment.script" -#WALLET_PAY_SCRIPT_ADDR_FILENAME="payment-script.addr" #WALLET_PAY_CRED_FILENAME="payment.cred" +#WALLET_PAY_SCRIPT_CRED_FILENAME="payment.script.cred" #WALLET_BASE_ADDR_FILENAME="base.addr" #WALLET_STAKE_VK_FILENAME="stake.vkey" #WALLET_STAKE_SK_FILENAME="stake.skey" #WALLET_HW_STAKE_SK_FILENAME="stake.hwsfile" #WALLET_STAKE_ADDR_FILENAME="reward.addr" +#WALLET_STAKE_SCRIPT_FILENAME="stake.script" #WALLET_STAKE_CRED_FILENAME="stake.cred" +#WALLET_STAKE_SCRIPT_CRED_FILENAME="stake.script.cred" #WALLET_STAKE_CERT_FILENAME="stake.cert" #WALLET_STAKE_DEREG_FILENAME="stake.dereg" #WALLET_DELEGCERT_FILENAME="delegation.cert" +#WALLET_VOTE_CATALYST_VK_FILENAME="catalyst.vkey" +#WALLET_VOTE_CATALYST_SK_FILENAME="catalyst.skey" +#WALLET_VOTE_CATALYST_QR_FILENAME="catalyst-qrcode.png" +#WALLET_GOV_DREP_VK_FILENAME="drep.vkey" +#WALLET_GOV_DREP_SK_FILENAME="drep.skey" +#WALLET_GOV_DREP_ID_FILENAME="drep.id" +#WALLET_GOV_DREP_SCRIPT_FILENAME="drep.script" +#WALLET_GOV_DREP_REGISTER_CERT_FILENAME="drep-reg.cert" +#WALLET_GOV_DREP_RETIRE_CERT_FILENAME="drep-ret.cert" +#WALLET_GOV_VOTE_DELEG_CERT_FILENAME="vote-deleg.cert" +#WALLET_GOV_HW_DREP_SK_FILENAME="drep.hwsfile" +#WALLET_GOV_CC_HOT_VK_FILENAME="cc-hot.vkey" +#WALLET_GOV_CC_HOT_SK_FILENAME="cc-hot.skey" +#WALLET_GOV_HW_CC_HOT_SK_FILENAME="cc-hot.hwsfile" +#WALLET_GOV_CC_HOT_ID_FILENAME="cc-hot.id" +#WALLET_GOV_CC_COLD_VK_FILENAME="cc-cold.vkey" +#WALLET_GOV_CC_COLD_SK_FILENAME="cc-cold.skey" +#WALLET_GOV_HW_CC_COLD_SK_FILENAME="cc-cold.hwsfile" +#WALLET_GOV_CC_COLD_ID_FILENAME="cc-cold.id" +#WALLET_DERIVATION_PATH_FILENAME="derivation.path" + +#WALLET_MULTISIG_PREFIX="ms_" # Filename for multisig files, uses separate keys derived using the 1854H paths #POOL_ID_FILENAME="pool.id" # Standardized names for all pool related files #POOL_HOTKEY_VK_FILENAME="hot.vkey" @@ -111,6 +135,13 @@ is_dir() { # : $1=minimal_needed_version # : $2=current_version versionCheck() { + printf '%s\n%s' "${1//v/}" "${2//v/}" | sort -C -V; +} + +# Description : Specific node/cli version check that take STRICT_VERSION_CHECK into account and only look at Major.Minor +# : $1=minimal_needed_version +# : $2=current_version +versionCheckNode() { [[ ${STRICT_VERSION_CHECK} = N ]] && return 0 w_ver=${1//v/}; w_ver_dots=${w_ver//[^.]}; [[ ${#w_ver_dots} -gt 1 && ${w_ver} =~ ^([0-9]+.[0-9]) ]] && w_ver=${BASH_REMATCH[1]} c_ver=${2//v/}; c_ver_dots=${c_ver//[^.]}; [[ ${#c_ver_dots} -gt 1 && ${c_ver} =~ ^([0-9]+.[0-9]) ]] && c_ver=${BASH_REMATCH[1]} @@ -171,7 +202,6 @@ checkUpdate() { fi fi [[ -n $5 ]] && URL="${URL_RAW}/scripts/$5" || URL="${URL_RAW}/scripts/cnode-helper-scripts" - URL_DOCS="${URL_RAW}/docs/Scripts" if curl -s -f -m ${CURL_TIMEOUT} -o "${dname}/${fname}".tmp "${URL}/${fname}" 2>/dev/null; then # make sure script exist, else just rename @@ -255,6 +285,9 @@ cmdAvailable() { if ! command -v "$1" &>/dev/null; then printf "${FG_RED}ERROR${NC}: need '$1' (command not found)\nplease install with your packet manager of choice(apt/dnf etc..) and relaunch CNTools\nhttps://command-not-found.com/$1" 1>&2 return 1 + elif [[ ! -x $(command -v "$1") ]]; then + printf "${FG_RED}ERROR${NC}: '$1' missing executable permission, please fix" 1>&2 + return 2 fi return 0 } @@ -549,7 +582,9 @@ getNodeMetrics() { .cardano.node.metrics.connectionManager.outgoingConns.val //0, .cardano.node.metrics.connectionManager.unidirectionalConns.val //0, .cardano.node.metrics.connectionManager.duplexConns.val //0, - .cardano.node.metrics.connectionManager.prunableConns.val //0 + .cardano.node.metrics.connectionManager.prunableConns.val //0, + .cardano.node.metrics.forging_enabled.val //0, + .cardano.node.metrics.cardano_build_info.val //"-" ] | @tsv' <<< "${node_metrics}") read -ra node_metrics_arr <<< ${node_metrics_tsv} blocknum=${node_metrics_arr[0]}; epochnum=${node_metrics_arr[1]}; slot_in_epoch=${node_metrics_arr[2]}; slotnum=${node_metrics_arr[3]} @@ -568,6 +603,9 @@ getNodeMetrics() { peers_cold=${node_metrics_arr[26]}; peers_warm=${node_metrics_arr[27]}; peers_hot=${node_metrics_arr[28]} conn_incoming=${node_metrics_arr[29]}; conn_outgoing=${node_metrics_arr[30]} conn_uni_dir=${node_metrics_arr[31]}; conn_bi_dir=${node_metrics_arr[32]}; conn_duplex=${node_metrics_arr[33]} + forging_enabled=${node_metrics_arr[34]} + [[ ${node_metrics_arr[35]} =~ ,version=\"([0-9.]*)\" ]] && running_node_version=${BASH_REMATCH[1]} || running_node_version="?" + [[ ${node_metrics_arr[35]} =~ ,revision=\"([0-9a-f]*)\" ]] && running_node_rev=${BASH_REMATCH[1]:0:8} || running_node_rev="?" else node_metrics=$(curl -s -m ${EKG_TIMEOUT} "http://${PROM_HOST}:${PROM_PORT}/metrics" 2>/dev/null) [[ ${node_metrics} =~ cardano_node_metrics_blockNum_int[[:space:]]([^[:space:]]*) ]] && blocknum=${BASH_REMATCH[1]} || blocknum=0 @@ -604,6 +642,9 @@ getNodeMetrics() { [[ ${node_metrics} =~ cardano_node_metrics_connectionManager_unidirectionalConns[[:space:]]([^[:space:]]*) ]] && conn_uni_dir=${BASH_REMATCH[1]} || conn_uni_dir=0 [[ ${node_metrics} =~ cardano_node_metrics_connectionManager_duplexConns[[:space:]]([^[:space:]]*) ]] && conn_bi_dir=${BASH_REMATCH[1]} || conn_bi_dir=0 [[ ${node_metrics} =~ cardano_node_metrics_connectionManager_prunableConns[[:space:]]([^[:space:]]*) ]] && conn_duplex=${BASH_REMATCH[1]} || conn_duplex=0 + [[ ${node_metrics} =~ cardano_node_metrics_forging_enabled[[:space:]]([^[:space:]]*) ]] && forging_enabled=${BASH_REMATCH[1]} || forging_enabled=0 + [[ ${node_metrics} =~ cardano_node_metrics_cardano_build_info[[:space:]].*,version=\"([0-9.]*)\" ]] && running_node_version=${BASH_REMATCH[1]} || running_node_version="?" + [[ ${node_metrics} =~ cardano_node_metrics_cardano_build_info[[:space:]].*,revision=\"([0-9a-f]*)\" ]] && running_node_rev=${BASH_REMATCH[1]:0:8} || running_node_rev="?" fi } @@ -788,13 +829,15 @@ getProtocolParams() { fi fi # Set a collection of commonly used values - read -r PROT_MAJOR PROT_MINOR KEY_DEPOSIT POOL_DEPOSIT MIN_POOL_COST POOL_RETIRE_MAX_EPOCH <<<"$(jq -r '[ + read -r PROT_MAJOR PROT_MINOR KEY_DEPOSIT POOL_DEPOSIT MIN_POOL_COST POOL_RETIRE_MAX_EPOCH DREP_DEPOSIT GOV_ACTION_DEPOSIT <<<"$(jq -r '[ .protocolVersion.major //0, .protocolVersion.minor //0, .stakeAddressDeposit //0, .stakePoolDeposit //0, .minPoolCost //0, - .poolRetireMaxEpoch //0 + .poolRetireMaxEpoch //0, + .dRepDeposit //0, + .govActionDeposit //0 ] | @tsv' <<<"${PROT_PARAMS}" 2>/dev/null)" PROT_VERSION="${PROT_MAJOR}.${PROT_MINOR}" [[ ${PROT_MAJOR} -eq 0 ]] && return 1 || return 0 @@ -819,6 +862,7 @@ set_default_vars() { [[ -z ${G_ACCOUNT} ]] && G_ACCOUNT="cardano-community" URL_RAW="https://raw.githubusercontent.com/${G_ACCOUNT}/guild-operators/${BRANCH}" + URL_DOCS="${URL_RAW}/docs/Scripts" export LC_ALL=C.UTF-8 # special mapping of coreutils gdate to date for MacOS if [[ $(uname) == Darwin ]]; then @@ -850,17 +894,40 @@ set_default_vars() { [[ -z ${WALLET_HW_PAY_SK_FILENAME} ]] && WALLET_HW_PAY_SK_FILENAME="payment.hwsfile" [[ -z ${WALLET_PAY_ADDR_FILENAME} ]] && WALLET_PAY_ADDR_FILENAME="payment.addr" [[ -z ${WALLET_PAY_SCRIPT_FILENAME} ]] && WALLET_PAY_SCRIPT_FILENAME="payment.script" - [[ -z ${WALLET_PAY_SCRIPT_ADDR_FILENAME} ]] && WALLET_PAY_SCRIPT_ADDR_FILENAME="payment-script.addr" [[ -z ${WALLET_PAY_CRED_FILENAME} ]] && WALLET_PAY_CRED_FILENAME="payment.cred" + [[ -z ${WALLET_PAY_SCRIPT_CRED_FILENAME} ]] && WALLET_PAY_SCRIPT_CRED_FILENAME="payment.script.cred" [[ -z ${WALLET_BASE_ADDR_FILENAME} ]] && WALLET_BASE_ADDR_FILENAME="base.addr" [[ -z ${WALLET_STAKE_VK_FILENAME} ]] && WALLET_STAKE_VK_FILENAME="stake.vkey" [[ -z ${WALLET_STAKE_SK_FILENAME} ]] && WALLET_STAKE_SK_FILENAME="stake.skey" [[ -z ${WALLET_HW_STAKE_SK_FILENAME} ]] && WALLET_HW_STAKE_SK_FILENAME="stake.hwsfile" [[ -z ${WALLET_STAKE_ADDR_FILENAME} ]] && WALLET_STAKE_ADDR_FILENAME="reward.addr" + [[ -z ${WALLET_STAKE_SCRIPT_FILENAME} ]] && WALLET_STAKE_SCRIPT_FILENAME="stake.script" [[ -z ${WALLET_STAKE_CRED_FILENAME} ]] && WALLET_STAKE_CRED_FILENAME="stake.cred" + [[ -z ${WALLET_STAKE_SCRIPT_CRED_FILENAME} ]] && WALLET_STAKE_SCRIPT_CRED_FILENAME="stake.script.cred" [[ -z ${WALLET_STAKE_CERT_FILENAME} ]] && WALLET_STAKE_CERT_FILENAME="stake.cert" [[ -z ${WALLET_STAKE_DEREG_FILENAME} ]] && WALLET_STAKE_DEREG_FILENAME="stake.dereg" [[ -z ${WALLET_DELEGCERT_FILENAME} ]] && WALLET_DELEGCERT_FILENAME="delegation.cert" + [[ -z ${WALLET_CATALYST_VK_FILENAME} ]] && WALLET_CATALYST_VK_FILENAME="catalyst.vkey" + [[ -z ${WALLET_CATALYST_SK_FILENAME} ]] && WALLET_CATALYST_SK_FILENAME="catalyst.skey" + [[ -z ${WALLET_CATALYST_QR_FILENAME} ]] && WALLET_CATALYST_QR_FILENAME="catalyst-qrcode.png" + [[ -z ${WALLET_GOV_DREP_VK_FILENAME} ]] && WALLET_GOV_DREP_VK_FILENAME="drep.vkey" + [[ -z ${WALLET_GOV_DREP_SK_FILENAME} ]] && WALLET_GOV_DREP_SK_FILENAME="drep.skey" + [[ -z ${WALLET_GOV_DREP_ID_FILENAME} ]] && WALLET_GOV_DREP_ID_FILENAME="drep.id" + [[ -z ${WALLET_GOV_DREP_SCRIPT_FILENAME} ]] && WALLET_GOV_DREP_SCRIPT_FILENAME="drep.script" + [[ -z ${WALLET_GOV_DREP_REGISTER_CERT_FILENAME} ]] && WALLET_GOV_DREP_REGISTER_CERT_FILENAME="drep-reg.cert" + [[ -z ${WALLET_GOV_DREP_RETIRE_CERT_FILENAME} ]] && WALLET_GOV_DREP_RETIRE_CERT_FILENAME="drep-ret.cert" + [[ -z ${WALLET_GOV_VOTE_DELEG_CERT_FILENAME} ]] && WALLET_GOV_VOTE_DELEG_CERT_FILENAME="vote-deleg.cert" + [[ -z ${WALLET_GOV_HW_DREP_SK_FILENAME} ]] && WALLET_GOV_HW_DREP_SK_FILENAME="drep.hwsfile" + [[ -z ${WALLET_GOV_CC_HOT_VK_FILENAME} ]] && WALLET_GOV_CC_HOT_VK_FILENAME="cc-hot.vkey" + [[ -z ${WALLET_GOV_CC_HOT_SK_FILENAME} ]] && WALLET_GOV_CC_HOT_SK_FILENAME="cc-hot.skey" + [[ -z ${WALLET_GOV_HW_CC_HOT_SK_FILENAME} ]] && WALLET_GOV_HW_CC_HOT_SK_FILENAME="cc-hot.hwsfile" + [[ -z ${WALLET_GOV_CC_HOT_ID_FILENAME} ]] && WALLET_GOV_CC_HOT_ID_FILENAME="cc-hot.id" + [[ -z ${WALLET_GOV_CC_COLD_VK_FILENAME} ]] && WALLET_GOV_CC_COLD_VK_FILENAME="cc-cold.vkey" + [[ -z ${WALLET_GOV_CC_COLD_SK_FILENAME} ]] && WALLET_GOV_CC_COLD_SK_FILENAME="cc-cold.skey" + [[ -z ${WALLET_GOV_HW_CC_COLD_SK_FILENAME} ]] && WALLET_GOV_HW_CC_COLD_SK_FILENAME="cc-cold.hwsfile" + [[ -z ${WALLET_GOV_CC_COLD_ID_FILENAME} ]] && WALLET_GOV_CC_COLD_ID_FILENAME="cc-cold.id" + [[ -z ${WALLET_DERIVATION_PATH_FILENAME} ]] && WALLET_DERIVATION_PATH_FILENAME="derivation.path" + [[ -z ${WALLET_MULTISIG_PREFIX} ]] && WALLET_MULTISIG_PREFIX="ms_" [[ -z ${POOL_ID_FILENAME} ]] && POOL_ID_FILENAME="pool.id" [[ -z ${POOL_HOTKEY_VK_FILENAME} ]] && POOL_HOTKEY_VK_FILENAME="hot.vkey" [[ -z ${POOL_HOTKEY_SK_FILENAME} ]] && POOL_HOTKEY_SK_FILENAME="hot.skey" @@ -961,8 +1028,8 @@ fi node_version="$(${CNODEBIN} version | head -1 | cut -d' ' -f2)" cli_version="$(${CCLI} version | head -1 | cut -d' ' -f2)" -if ! versionCheck "8.9.4" "${node_version}" || ! versionCheck "8.20.3.0" "${cli_version}"; then - echo -e "\nKoios scripts have now been upgraded to support cardano-node 8.9.x ('${node_version}' found) / cardano-cli 8.20.3.x ('${cli_version}' found).\nPlease update cardano-node binaries (ensure to read release notes and update various configs using guild-deploy (use appropriate options to download/install/overwrite parts you need) or use tagged branches for older node version (eg: ./