-
Notifications
You must be signed in to change notification settings - Fork 174
/
cntools.sh
executable file
·5225 lines (5104 loc) · 347 KB
/
cntools.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env bash
# shellcheck disable=SC1090,SC2086,SC2154,SC2034,SC2012,SC2140,SC2028,SC1091
######################################
# User Variables - Change as desired #
# Common variables set in env file #
######################################
#TIMEOUT_NO_OF_SLOTS=600 # used when waiting for a new block to be created
# Log CNTools activities
# LOG_DIR set in env file
#CNTOOLS_LOG="${LOG_DIR}/cntools-history.log"
# kes rotation warning (in seconds)
# if disabled KES check will be skipped on startup
#CHECK_KES=false
#KES_ALERT_PERIOD=172800 # default 2 days
#KES_WARNING_PERIOD=604800 # default 7 days
# Default Transaction TTL (slots after which transaction will expire from queue) to use
#TX_TTL=3600
# Limit for extended wallet selection menu filtering (balance check and delegation status)
# If more wallets exist than limit set these checks will be disabled to improve performance
#WALLET_SELECTION_FILTER_LIMIT=10
# Enable or disable chattr used to protect keys from being overwritten [true|false] (not supported on all systems)
# If disabled standard read-only permission is set instead
#ENABLE_CHATTR=true
# Enable or disable dialog used to help in file/dir selection by providing a gui to see available files and folders. [true|false] (not supported on all systems)
# 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=false
# Price fetching currency. Disable by setting value 'off' [off|usd|eur|...] (default: off) (https://api.coingecko.com/api/v3/simple/supported_vs_currencies)
#CURRENCY=usd
# Runtime mode, offline | local | light (default local)
# CNTOOLS_MODE=local
######################################
# Do NOT modify code below #
######################################
########## Global tasks ###########################################
# General exit handler
cleanup() {
sleep 0.1
[[ -n $1 ]] && err=$1 || err=$?
[[ $err -eq 0 ]] && clear
[[ -n ${exit_msg} ]] && echo -e "\n${exit_msg}\n" || echo -e "\nCNTools terminated, cleaning up...\n"
tput cnorm # restore cursor
tput sgr0 # turn off all attributes
pkill -TERM -P ${$} &>/dev/null # kill all child processes of CNTools script
exit $err
}
trap cleanup HUP INT TERM
STTY_SETTINGS="$(stty -g < /dev/tty)"
trap 'stty "$STTY_SETTINGS" < /dev/tty' EXIT
# Command : myExit [exit code] [message]
# Description : gracefully handle an exit and restore terminal to original state
myExit() {
exit_msg="$2"
cleanup "$1"
}
usage() {
cat <<-EOF
Usage: $(basename "$0") [-o] [-a] [-b <branch name>] [-v]
Koios CNTools - The Cardano SPOs best friend
-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)
-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
EOF
}
ADVANCED_MODE="false"
SKIP_UPDATE=N
PRINT_VERSION="false"
PARENT="$(dirname $0)"
[[ -f "${PARENT}"/.env_branch ]] && BRANCH="$(cat "${PARENT}"/.env_branch)" || BRANCH="master"
# save launch params
arg_copy=("$@")
while getopts :olaub:v opt; do
case ${opt} in
o ) CNTOOLS_MODE="OFFLINE" ;;
l ) CNTOOLS_MODE="LIGHT" ;;
a ) ADVANCED_MODE="true" ;;
u ) SKIP_UPDATE=Y ;;
b ) echo "${OPTARG}" > "${PARENT}"/.env_branch ;;
v ) PRINT_VERSION="true" ;;
\? ) myExit 1 "$(usage)" ;;
esac
done
shift $((OPTIND -1))
#######################################################
# Version Check #
#######################################################
clear
if [[ ! -f "${PARENT}"/env ]]; then
echo -e "\nCommon env file missing: ${PARENT}/env"
echo -e "This is a mandatory prerequisite, please install with guild-deploy.sh or manually download from GitHub\n"
myExit 1
fi
# Source env file in normal mode with node connection, else offline mode
if [[ ${CNTOOLS_MODE} = "LOCAL" ]]; then
. "${PARENT}"/env || myExit 1
else
. "${PARENT}"/env offline || myExit 1
fi
# Source cntools.library to populate defaults for CNTools
. "${PARENT}"/cntools.library || myExit 1
# If light mode, test if koios is reachable, otherwise - unset KOIOS_API
if [[ ${CNTOOLS_MODE} = "LIGHT" ]]; then
test_koios
[[ -z ${KOIOS_API} ]] && myExit 1 "ERROR: Koios query test failed, unable to launch CNTools in light mode utilizing Koios query layer\n\n${launch_modes_info}"
fi
[[ ${CNTOOLS_MODE} != "LIGHT" ]] && unset KOIOS_API
[[ ${PRINT_VERSION} = "true" ]] && myExit 0 "CNTools v${CNTOOLS_VERSION} (branch: $([[ -f "${PARENT}"/.env_branch ]] && cat "${PARENT}"/.env_branch || echo "master"))"
# Do some checks when run in connected(local|light) mode
if [[ ${CNTOOLS_MODE} != "OFFLINE" ]]; then
# check to see if there are any updates available
clear
if [[ ${UPDATE_CHECK} = Y && ${SKIP_UPDATE} != Y ]]; then
echo "Checking for script updates..."
# Check availability of checkUpdate function
if [[ ! $(command -v checkUpdate) ]]; then
myExit 1 "\nCould not find checkUpdate function in env, make sure you're using official docos for installation!"
fi
# check for env update
ENV_UPDATED=N
checkUpdate env N N N
case $? in
1) ENV_UPDATED=Y ;;
2) myExit 1 ;;
esac
# source common env variables in case it was updated
if [[ ${ENV_UPDATED} = Y ]]; then
[[ ${CNTOOLS_MODE} = "LOCAL" ]] && . "${PARENT}"/env || . "${PARENT}"/env offline
[[ ${CNTOOLS_MODE} != "LIGHT" ]] && unset KOIOS_API
case $? in
1) myExit 1 "ERROR: CNTools failed to load common env file\nPlease verify set values in 'User Variables' section in env file or log an issue on GitHub" ;;
2) clear ;;
esac
fi
# check for cntools update
checkUpdate "${PARENT}"/cntools.library "${ENV_UPDATED}" Y N
case $? in
1) checkUpdate "${PARENT}"/cntools.sh Y
if [[ $? = 2 ]]; then
echo -e "\n${FG_RED}ERROR${NC}: Update check of cntools.sh against GitHub failed!"
waitToProceed
fi
$0 "${arg_copy[@]}" "-u"; myExit 0 ;; # re-launch script with same args skipping update check
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
else
echo -e "\n${FG_RED}ERROR${NC}: failed to download changelog from GitHub!"
waitToProceed
fi
fi
fi
archiveLog # archive current log and cleanup log archive folder
# check for required command line tools
if ! cmdAvailable "curl" || \
! cmdAvailable "jq" || \
! cmdAvailable "bc" || \
! cmdAvailable "sed" || \
! cmdAvailable "awk" || \
! cmdAvailable "column" || \
! protectionPreRequisites; then myExit 1 "Missing one or more of the required command line tools, press any key to exit"
fi
# check that bash version is > 4.4.0
[[ $(bash --version | head -n 1) =~ ([0-9]+\.[0-9]+\.[0-9]+) ]] || myExit 1 "Unable to get BASH version"
if ! versionCheck "4.4.0" "${BASH_REMATCH[1]}"; then
myExit 1 "BASH does not meet the minimum required version of ${FG_LBLUE}4.4.0${NC}, found ${FG_LBLUE}${BASH_REMATCH[1]}${NC}\n\nPlease upgrade to a newer Linux distribution or compile latest BASH following official docs.\n\nINSTALL: https://www.gnu.org/software/bash/manual/html_node/Installing-Bash.html\nDOWNLOAD: http://git.savannah.gnu.org/cgit/bash.git/ (latest stable TAG)"
fi
# check if there are pools in need of KES key rotation
clear
kes_rotation_needed="no"
if [[ ${CHECK_KES} = true ]]; then
while IFS= read -r -d '' pool; do
unset pool_kes_start
[[ ${CNTOOLS_MODE} = "LOCAL" ]] && getNodeMetrics
[[ (-z ${remaining_kes_periods} || ${remaining_kes_periods} -eq 0) && -f "${pool}/${POOL_CURRENT_KES_START}" ]] && unset remaining_kes_periods && pool_kes_start="$(cat "${pool}/${POOL_CURRENT_KES_START}")"
if ! kesExpiration ${pool_kes_start}; then println ERROR "${FG_RED}ERROR${NC}: failure during KES calculation for ${FG_GREEN}$(basename ${pool})${NC}" && waitToProceed && continue; fi
if [[ ${expiration_time_sec_diff} -lt ${KES_ALERT_PERIOD} ]]; then
kes_rotation_needed="yes"
println "\n** WARNING **\nPool ${FG_GREEN}$(basename ${pool})${NC} in need of KES key rotation"
if [[ ${expiration_time_sec_diff} -lt 0 ]]; then
println DEBUG "${FG_RED}Keys expired!${NC} : ${FG_RED}$(timeLeft ${expiration_time_sec_diff:1})${NC} ago"
else
println DEBUG "Remaining KES periods : ${FG_RED}${remaining_kes_periods}${NC}"
println DEBUG "Time left : ${FG_RED}$(timeLeft ${expiration_time_sec_diff})${NC}"
fi
elif [[ ${expiration_time_sec_diff} -lt ${KES_WARNING_PERIOD} ]]; then
kes_rotation_needed="yes"
println DEBUG "\nPool ${FG_GREEN}$(basename ${pool})${NC} soon in need of KES key rotation"
println DEBUG "Remaining KES periods : ${FG_YELLOW}${remaining_kes_periods}${NC}"
println DEBUG "Time left : ${FG_YELLOW}$(timeLeft ${expiration_time_sec_diff})${NC}"
fi
done < <(find "${POOL_FOLDER}" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z)
[[ ${kes_rotation_needed} = "yes" ]] && waitToProceed
fi
# Verify that shelley transition epoch was properly identified by env
if [[ ${SHELLEY_TRANS_EPOCH} -lt 0 ]]; then # unknown network
clear
myExit 1 "${FG_YELLOW}WARN${NC}: This is an unknown network, please manually set SHELLEY_TRANS_EPOCH variable in env file"
fi
###################################################################
function main {
while true; do # Main loop
# Start with a clean slate after each completed or canceled command excluding .dialogrc from purge
find "${TMP_DIR:?}" -type f -not \( -name 'protparams.json' -o -name '.dialogrc' -o -name "offline_tx*" -o -name "*_cntools_backup*" -o -name "metadata_*" -o -name "asset*" \) -delete
unset IFS
clear
if [[ ${CNTOOLS_MODE} != "OFFLINE" ]]; then
[[ ${CNTOOLS_MODE} = "LOCAL" ]] && getNodeMetrics
getPriceInfo
updateProtocolParams
fi
println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
println "$(printf " >> Koios CNTools v%s - %s - ${CNTOOLS_MODE_COLOR}%s${NC} <<" "${CNTOOLS_VERSION}" "${NETWORK_NAME}" "${CNTOOLS_MODE}")"
println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
println OFF " Main Menu Telegram Announcement / Support channel: ${FG_YELLOW}t.me/CardanoKoios/9759${NC}\n"\
" ) Wallet - create, show, remove and protect wallets"\
" ) Funds - send, withdraw and delegate"\
" ) Pool - pool creation and management"\
" ) Transaction - Sign and Submit a cold transaction (hybrid/offline mode)"\
"$([[ -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, ...")"\
" ) Refresh - reload home screen content"\
"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
println DEBUG "$(printf "%84s" "Epoch $(getEpoch) - $(timeLeft "$(timeUntilNextEpoch)") until next")"
if [[ ${CNTOOLS_MODE} != "LOCAL" ]]; then
println DEBUG " What would you like to do?"
else
tip_diff=$(( $(getSlotTipRef) - slotnum ))
slot_interval=$(slotInterval)
if [[ ${tip_diff} -le ${slot_interval} ]]; then
println DEBUG "$(printf " What would you like to do? %$((84-29-${#tip_diff}-3))s ${FG_GREEN}%s${NC}" "Node Sync:" "${tip_diff} :)")"
elif [[ ${tip_diff} -le $(( slot_interval * 2 )) ]]; then
println DEBUG "$(printf " What would you like to do? %$((84-29-${#tip_diff}-3))s ${FG_YELLOW}%s${NC}" "Node Sync:" "${tip_diff} :|")"
else
println DEBUG "$(printf " What would you like to do? %$((84-29-${#tip_diff}-3))s ${FG_RED}%s${NC}" "Node Sync:" "${tip_diff} :(")"
fi
fi
if [[ -n ${price_now} ]]; then
getDecimalPlaces ${price_now}
decimals=$?
price_str="1 ADA = $(LC_NUMERIC=C printf "%.${decimals}f" "${price_now}") ${CURRENCY^^}"
if [[ ${price_24h:0:1} = '-' ]]; then
println DEBUG "$(printf "%$((84-${#price_24h}-9))s (24h: ${FG_RED}%s${NC}%%)" "${price_str}" "${price_24h}")"
else
println DEBUG "$(printf "%$((84-${#price_24h}-9))s (24h: ${FG_GREEN}%s${NC}%%)" "${price_str}" "${price_24h}")"
fi
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"
case ${selected_value} in
"[w]"*) OPERATION="wallet" ;;
"[f]"*) OPERATION="funds" ;;
"[p]"*) OPERATION="pool" ;;
"[t]"*) OPERATION="transaction" ;;
"[b]"*) OPERATION="blocks" ;;
"[z]"*) OPERATION="backup" ;;
"[a]"*) OPERATION="advanced" ;;
"[r]"*) continue ;;
"[q]"*) myExit 0 "CNTools closed!" ;;
esac
case $OPERATION in
wallet)
while true; do # Wallet loop
clear
println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
println " >> WALLET"
println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
println OFF " Wallet Management\n\n ) New - create a new wallet"\
" ) Import - import a Daedalus/Yoroi 24/25 mnemonic or Ledger/Trezor HW wallet"\
" ) Register - register a wallet on chain"\
" ) De-Register - De-Register (retire) a registered wallet"\
" ) List - list all available wallets in a compact view"\
" ) Show - show detailed view of a specific wallet"\
" ) Remove - remove a wallet"\
" ) Decrypt - remove write protection and decrypt wallet"\
" ) Encrypt - encrypt wallet keys and make all files immutable"\
"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
println DEBUG " Select Wallet Operation\n"
select_opt "[n] New" "[i] Import" "[r] Register" "[z] De-Register" "[l] List" "[s] Show" "[x] Remove" "[d] Decrypt" "[e] Encrypt" "[h] Home"
case $? in
0) SUBCOMMAND="new" ;;
1) SUBCOMMAND="import" ;;
2) SUBCOMMAND="register" ;;
3) SUBCOMMAND="deregister" ;;
4) SUBCOMMAND="list" ;;
5) SUBCOMMAND="show" ;;
6) SUBCOMMAND="remove" ;;
7) SUBCOMMAND="decrypt" ;;
8) SUBCOMMAND="encrypt" ;;
9) break ;;
esac
case $SUBCOMMAND in
new)
clear
println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
println " >> WALLET >> NEW"
println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
echo
getAnswerAnyCust wallet_name "Name of new wallet" wallet_name
# 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}
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
;; ###################################################################
import)
while true; do # Wallet >> Import loop
clear
println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
println " >> WALLET >> IMPORT"
println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
println OFF " Wallet Import\n"\
" ) Mnemonic - Daedalus/Yoroi 24 or 25 word mnemonic"\
" ) HW Wallet - Ledger/Trezor hardware wallet"\
"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
println DEBUG " Select Wallet Import Operation\n"
select_opt "[m] Mnemonic" "[w] HW Wallet" "[b] Back" "[h] Home"
case $? in
0) SUBCOMMAND="mnemonic" ;;
1) SUBCOMMAND="hardware" ;;
2) break ;;
3) break 2 ;;
esac
case $SUBCOMMAND in
mnemonic)
clear
println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
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
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}"
echo && safeDel "${WALLET_FOLDER}/${wallet_name}"
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}
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"
echo
println DEBUG "${FG_YELLOW}Using a mnemonic imported wallet in CNTools comes with a few limitations${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 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 receive address shown in CNTools"
println DEBUG " ${FG_LGRAY}>${NC} Only spend ADA from CNTools, if spent through Daedalus/Yoroi balance seen in CNTools wont match"
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} Track rewards in Daedalus/Yoroi"
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"
waitToProceed && continue
;; ###################################################################
hardware)
clear
println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
println " >> WALLET >> IMPORT >> HARDWARE WALLET"
println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
echo
println DEBUG "Supported HW wallets: Ledger S, Ledger X, Trezor Model T"
println "Is your hardware wallet one of these models?"
select_opt "[y] Yes" "[n] No"
case $? in
0) : ;; # do nothing
1) waitToProceed "Unsupported hardware wallet, press any key to return home" && continue ;;
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 "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
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
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}"
getBaseAddress ${wallet_name}
getPayAddress ${wallet_name}
getRewardAddress ${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}"
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"
waitToProceed && continue
;; ###################################################################
esac # wallet >> import sub OPERATION
done # Wallet >> Import loop
;; ###################################################################
register)
clear
println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
println " >> WALLET >> REGISTER"
println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
[[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitToProceed && continue
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
echo
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}"
case $? in
1) waitToProceed; continue ;;
2) continue ;;
esac
getWalletType ${wallet_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}: payment and/or stake signing keys missing from wallet!" && waitToProceed && continue ;;
esac
else
selectWallet "non-reg" "${WALLET_PAY_VK_FILENAME}" "${WALLET_STAKE_VK_FILENAME}"
case $? in
1) waitToProceed; continue ;;
2) continue ;;
esac
getWalletType ${wallet_name}
fi
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})")"
fi
else
println ERROR "\n${FG_RED}ERROR${NC}: no funds 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} "true"; then
waitToProceed && continue
fi
println "\n${FG_GREEN}${wallet_name}${NC} successfully registered on chain!"
waitToProceed && continue
;; ###################################################################
deregister)
clear
println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
println " >> WALLET >> DE-REGISTER"
println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
[[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitToProceed && continue
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
echo
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}"
case $? in
1) waitToProceed; continue ;;
2) continue ;;
esac
getWalletType ${wallet_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}: payment and/or stake signing keys missing from wallet!" && waitToProceed && continue ;;
esac
else
selectWallet "reg" "${WALLET_PAY_VK_FILENAME}" "${WALLET_STAKE_VK_FILENAME}"
case $? in
1) waitToProceed; continue ;;
2) continue ;;
esac
getWalletType ${wallet_name}
fi
getWalletRewards ${wallet_name}
if [[ "${reward_lovelace}" -gt 0 ]]; then
println "\n${FG_YELLOW}WARN${NC}: wallet has unclaimed rewards, please use 'Funds >> Withdraw Rewards' before de-registration to claim your rewards"
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 ERROR "Funds for transaction fee needed to deregister the wallet"
waitToProceed && continue
fi
if ! deregisterStakeWallet; then
[[ -f ${stake_dereg_file} ]] && rm -f ${stake_dereg_file}
waitToProceed && continue
fi
echo
if ! verifyTx ${base_addr}; then waitToProceed && continue; fi
echo
println "${FG_GREEN}${wallet_name}${NC} successfully de-registered from chain!"
println "Key deposit fee that will be refunded : ${FG_LBLUE}$(formatLovelace ${KEY_DEPOSIT})${NC} ADA"
waitToProceed && continue
;; ###################################################################
list)
clear
[[ ${CNTOOLS_MODE} != "OFFLINE" ]] && getPriceInfo
println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
println " >> WALLET >> LIST"
println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
[[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitToProceed && continue
if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then
println DEBUG "${FG_LGRAY}OFFLINE MODE${NC}: CNTools started in offline mode, wallet balance not shown!"
fi
if [[ -n ${KOIOS_API} ]]; then
tput sc
println OFF "\n${FG_YELLOW}> Querying Koios API for wallet information${NC}"
addr_list=()
reward_addr_list=()
while IFS= read -r -d '' wallet; do
wallet_name=$(basename ${wallet})
getBaseAddress ${wallet_name}
[[ -n ${base_addr} ]] && addr_list+=(${base_addr})
getPayAddress ${wallet_name}
[[ -n ${pay_addr} ]] && addr_list+=(${pay_addr})
getRewardAddress ${wallet_name}
[[ -n ${reward_addr} ]] && reward_addr_list+=(${reward_addr})
done < <(find "${WALLET_FOLDER}" -mindepth 1 -maxdepth 1 -type d -print0)
[[ ${#addr_list[@]} -gt 0 ]] && getBalanceKoios
[[ ${#reward_addr_list[@]} -gt 0 ]] && getRewardInfoKoios
tput rc && tput ed
fi
while IFS= read -r -d '' wallet; do
wallet_name=$(basename ${wallet})
enc_files=$(find "${wallet}" -mindepth 1 -maxdepth 1 -type f -name '*.gpg' -print0 | wc -c)
if [[ ${CNTOOLS_MODE} != "OFFLINE" ]] && isWalletRegistered ${wallet_name}; then registered="yes"; else registered="no"; fi
echo
if [[ ${enc_files} -gt 0 && ${registered} = "yes" ]]; then
println "${FG_GREEN}${wallet_name}${NC} - ${FG_LGRAY}REGISTERED${NC} (${FG_YELLOW}encrypted${NC})"
elif [[ ${registered} = "yes" ]]; then
println "${FG_GREEN}${wallet_name}${NC} - ${FG_LGRAY}REGISTERED${NC}"
elif [[ ${enc_files} -gt 0 ]]; then
println "${FG_GREEN}${wallet_name}${NC} (${FG_YELLOW}encrypted${NC})"
else
println "${FG_GREEN}${wallet_name}${NC}"
fi
getBaseAddress ${wallet_name}
getPayAddress ${wallet_name}
if [[ -z ${base_addr} && -z ${pay_addr} ]]; then
println ERROR "${FG_RED}ERROR${NC}: wallet missing pay/base addr files or vkey 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}")"
else
if [[ -n ${base_addr} ]]; then
lovelace=0
asset_cnt=0
if [[ -n ${KOIOS_API} ]]; then
for key in "${!assets[@]}"; do
[[ ${key} = "${base_addr},lovelace" ]] && lovelace=${assets["${base_addr},lovelace"]} && continue
[[ ${key} = "${base_addr},"* ]] && ((asset_cnt++))
done
else
getBalance ${base_addr}
lovelace=${assets[lovelace]}
asset_cnt=$(( ${#assets[@]} - 1 ))
fi
getPriceString ${lovelace}
println "$(printf "%-19s : ${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})")"
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}")"
fi
fi
if [[ -n ${pay_addr} ]]; then
lovelace=0
asset_cnt=0
if [[ -n ${KOIOS_API} ]]; then
for key in "${!assets[@]}"; do
[[ ${key} = "${pay_addr},lovelace" ]] && lovelace=${assets["${pay_addr},lovelace"]} && continue
[[ ${key} = "${pay_addr},"* ]] && ((asset_cnt++))
done
else
getBalance ${pay_addr}
lovelace=${assets[lovelace]}
asset_cnt=$(( ${#assets[@]} - 1 ))
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 ${KOIOS_API} ]]; then
[[ -v rewards_available[${reward_addr}] ]] && reward_lovelace=${rewards_available[${reward_addr}]} || reward_lovelace=0
delegation_pool_id=${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
unset poolName
while IFS= read -r -d '' pool; do
getPoolID "$(basename ${pool})"
if [[ "${pool_id_bech32}" = "${delegation_pool_id}" ]]; 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}"
fi
fi
fi
done < <(find "${WALLET_FOLDER}" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z)
waitToProceed && continue
;; ###################################################################
show)
clear
[[ ${CNTOOLS_MODE} != "OFFLINE" ]] && getPriceInfo
println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
println " >> WALLET >> SHOW"
println DEBUG "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
[[ ! $(ls -A "${WALLET_FOLDER}" 2>/dev/null) ]] && echo && println "${FG_YELLOW}No wallets available!${NC}" && waitToProceed && continue
if [[ ${CNTOOLS_MODE} = "OFFLINE" ]]; then
println DEBUG "${FG_LGRAY}OFFLINE MODE${NC}: CNTools started in offline mode, limited wallet info shown!"
fi
tput sc
selectWallet "none"
case $? in
1) waitToProceed; continue ;;
2) continue ;;
esac
tput rc && tput ed
enc_files=$(find "${WALLET_FOLDER}/${wallet_name}" -mindepth 1 -maxdepth 1 -type f -name '*.gpg' -print0 | wc -c)
if [[ ${enc_files} -gt 0 ]]; then
println "Wallet: ${FG_GREEN}${wallet_name}${NC} (${FG_YELLOW}encrypted${NC})"
else
println "Wallet: ${FG_GREEN}${wallet_name}${NC}"
fi
getBaseAddress ${wallet_name}
getPayAddress ${wallet_name}
if [[ -z ${base_addr} && -z ${pay_addr} ]]; then
println ERROR "\n${FG_RED}ERROR${NC}: wallet missing pay/base addr files or vkey files to generate them!"
waitToProceed && continue
fi
getRewardAddress ${wallet_name}
if [[ -n ${KOIOS_API} ]]; then
tput sc
println OFF "\n${FG_YELLOW}> Querying Koios API for wallet information${NC}"
addr_list=("${base_addr}" "${pay_addr}")
reward_addr_list=("${reward_addr}")
[[ ${#addr_list[@]} -gt 0 ]] && getBalanceKoios
[[ ${#reward_addr_list[@]} -gt 0 ]] && getRewardInfoKoios
tput rc && tput ed
fi
total_lovelace=0
if [[ ${CNTOOLS_MODE} != "OFFLINE" ]]; then
for i in {1..2}; do
if [[ $i -eq 1 ]]; then
address_type="Base"
address=${base_addr}
if [[ -n ${KOIOS_API} ]]; then
base_lovelace=${assets["${base_addr},lovelace"]}
else
getBalance ${base_addr}
base_lovelace=${assets[lovelace]}
fi
total_lovelace=$((total_lovelace + base_lovelace))
else
address_type="Enterprise"
address=${pay_addr}
if [[ -n ${KOIOS_API} ]]; then
pay_lovelace=${assets["${pay_addr},lovelace"]}
[[ ${utxo_cnt["${pay_addr}"]} -eq 0 ]] && continue # Dont print Enterprise if empty
else
getBalance ${pay_addr}
pay_lovelace=${assets[lovelace]}
[[ ${utxo_cnt} -eq 0 ]] && continue # Dont print Enterprise if empty
fi
total_lovelace=$((total_lovelace + pay_lovelace))
fi
echo
if [[ -n ${KOIOS_API} ]]; then
utxo_cnt=${utxos_cnt["${address}"]:-0}
asset_name_maxlen=${asset_name_maxlen_arr["${address}"]:-5}
asset_amount_maxlen=${asset_amount_maxlen_arr["${address}"]:-12}
fi
println "${FG_LBLUE}${utxo_cnt} UTxO(s)${NC} found for ${FG_GREEN}${address_type}${NC} Address!"
if [[ ${utxo_cnt} -gt 0 ]]; then
echo
println DEBUG "$(printf "%-68s ${FG_DGRAY}|${NC} %${asset_name_maxlen}s ${FG_DGRAY}|${NC} %-${asset_amount_maxlen}s\n" "UTxO Hash#Index" "Asset" "Amount")"
println DEBUG "${FG_DGRAY}$(printf "%69s+%$((asset_name_maxlen+2))s+%$((asset_amount_maxlen+1))s\n" "" "" "" | tr " " "-")${NC}"
mapfile -d '' utxos_sorted < <(printf '%s\0' "${!utxos[@]}" | sort -z)
for utxo in "${utxos_sorted[@]}"; do
[[ -n ${KOIOS_API} && ${utxo} != "${address},"* ]] && continue
IFS='.' read -ra utxo_arr <<< "${utxo#*,}"
if [[ ${#utxo_arr[@]} -eq 2 && ${utxo_arr[1]} = " ADA" ]]; then
println DEBUG "$(printf "%-68s ${FG_DGRAY}|${NC} ${FG_GREEN}%${asset_name_maxlen}s${NC} ${FG_DGRAY}|${NC} ${FG_LBLUE}%-${asset_amount_maxlen}s${NC}\n" "${utxo_arr[0]}" "ADA" "$(formatLovelace ${utxos["${utxo}"]})")"
else
[[ ${#utxo_arr[@]} -eq 3 ]] && asset_name="${utxo_arr[2]}" || asset_name=""
tname="$(hexToAscii ${asset_name})"
tname="${tname//[![:print:]]/}"
! assets_id_bech32=$(getAssetIDBech32 ${utxo_arr[1]} ${asset_name}) && continue 3
println DEBUG "$(printf "${FG_DGRAY}%20s${NC}${FG_LGRAY}%-48s${NC} ${FG_DGRAY}|${NC} ${FG_MAGENTA}%${asset_name_maxlen}s${NC} ${FG_DGRAY}|${NC} ${FG_LBLUE}%-${asset_amount_maxlen}s${NC}\n" "Asset Fingerprint: " "${assets_id_bech32}" "${tname}" "$(formatAsset ${utxos["${utxo}"]})")"
fi
done
fi
lovelace=0
asset_cnt=0
if [[ -n ${KOIOS_API} ]]; then
for key in "${!assets[@]}"; do
[[ ${key} = "${address},lovelace" ]] && lovelace=${assets["${address},lovelace"]}
[[ ${key} = "${address},"* ]] && ((asset_cnt++))
done
else
lovelace=${assets[lovelace]}
asset_cnt=${#assets[@]}
fi
if [[ ${asset_cnt} -gt 0 ]]; then
println "\nASSET SUMMARY: ${FG_LBLUE}${asset_cnt} Asset-Type(s)${NC}\n"
println DEBUG "$(printf "%${asset_amount_maxlen}s ${FG_DGRAY}|${NC} %-${asset_name_maxlen}s%s\n" "Total Amount" "Asset" "$([[ ${asset_cnt} -gt 1 ]] && echo -e " ${FG_DGRAY}|${NC} Asset Fingerprint")")"
println DEBUG "${FG_DGRAY}$(printf "%$((asset_amount_maxlen+1))s+%$((asset_name_maxlen+2))s%s\n" "" "" "$([[ ${asset_cnt} -gt 1 ]] && printf "+%57s" "")" | tr " " "-")${NC}"
println DEBUG "$(printf "${FG_LBLUE}%${asset_amount_maxlen}s${NC} ${FG_DGRAY}|${NC} ${FG_GREEN}%-${asset_name_maxlen}s${NC}%s\n" "$(formatLovelace ${lovelace})" "ADA" "$([[ ${asset_cnt} -gt 1 ]] && echo -n " ${FG_DGRAY}|${NC}")")"
mapfile -d '' assets_sorted < <(printf '%s\0' "${!assets[@]}" | sort -z)
for asset in "${assets_sorted[@]}"; do
[[ ${asset} = *"lovelace" ]] && continue
IFS='.' read -ra asset_arr <<< "${asset#*,}"
[[ ${#asset_arr[@]} -eq 1 ]] && asset_name="" || asset_name="${asset_arr[1]}"
! assets_id_bech32=$(getAssetIDBech32 ${asset_arr[0]} ${asset_name}) && assets_id_bech32="?"
tname="$(hexToAscii ${asset_name})"
tname="${tname//[![:print:]]/}"
println DEBUG "$(printf "${FG_LBLUE}%${asset_amount_maxlen}s${NC} ${FG_DGRAY}|${NC} ${FG_MAGENTA}%-${asset_name_maxlen}s${NC} ${FG_DGRAY}|${NC} ${FG_LGRAY}%s${NC}\n" "$(formatAsset ${assets["${asset}"]})" "${tname}" "${assets_id_bech32}")"
done
fi
done
println DEBUG "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
if isWalletRegistered ${wallet_name}; then
println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_GREEN}%s${NC}" "Registered" "YES")"
else
println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_RED}%s${NC}" "Registered" "NO")"
fi
else
println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Registered" "Unknown")"
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 ${reward_addr} ]] && println "$(printf "%-20s ${FG_DGRAY}:${NC} ${FG_LGRAY}%s${NC}" "Reward/Stake Address" "${reward_addr}")"