1010
1111# TODO
1212
13- - [ ] add wallet name
1413- [ ] add version birthday to new config
1514- [ ] allow manual coin selection when sending
16- - [ ] address labeling
1715- [ ] implement scrolling in the curses balance panel
1816- [ ] implement --json, --csv
1917- [ ] implement command-on-monitor
@@ -59,6 +57,9 @@ from typing import IO, Optional as Op
5957from configparser import ConfigParser
6058
6159
60+ __VERSION__ = "0.3.0"
61+
62+
6263# --- command line parsing ------------------------------------------------------------
6364# -------------------------------------------------------------------------------------
6465
@@ -332,6 +333,10 @@ class JSONRPCError(Exception):
332333 )
333334 self .error = rpc_error
334335
336+ @property
337+ def code (self ) -> int :
338+ return int (self .error ["code" ])
339+
335340
336341class BitcoinRPC (object ):
337342 """Base JSON-RPC proxy class. Contains only private methods; do not use
@@ -1004,65 +1009,7 @@ def run_setup(config) -> t.Tuple[t.Any, t.Any]:
10041009 done (f"wrote config to { config .loaded_from } " )
10051010 p ()
10061011
1007- section ("wallet setup in Core" )
1008- rpc_wallet_create (rpc , wallet )
1009- done (f"created wallet { yellow (wallet .name )} in Core as watch-only" )
1010-
1011- rpcw = config .rpc (wallet )
1012- rpcw .importmulti (* wallet .importmulti_args ())
1013- done ("imported descriptors 0/* and 1/* (change)" )
1014-
1015- scan_result = {} # type: ignore
1016- scan_thread = threading .Thread (
1017- target = _run_scantxoutset ,
1018- args = (config .rpc (wallet ), wallet .scantxoutset_args (), scan_result ),
1019- )
1020- scan_thread .start ()
1021-
1022- p ()
1023- section ("scanning the chain for balance and history" )
1024- while scan_thread .is_alive ():
1025- spin ("scanning the UTXO set for your balance (few minutes) " )
1026- time .sleep (0.2 )
1027-
1028- p ()
1029- done ("scan of UTXO set complete!" )
1030-
1031- # TODO this will fail if we timed out
1032- unspents = scan_result ["result" ]["unspents" ]
1033- bal = sum ([i ["amount" ] for i in unspents ])
1034- bal_str = yellow (bold (f"{ bal } BTC" ))
1035- bal_count = yellow (bold (f"{ len (unspents )} UTXOs" ))
1036- blank (f"found an existing balance of { yellow (bal_str )} across { yellow (bal_count )} " )
1037-
1038- if unspents :
1039- rescan_begin_height = min ([i ["height" ] for i in unspents ])
1040- p ()
1041- blank (
1042- f"beginning chain rescan from height { bold (str (rescan_begin_height ))} "
1043- f"(minutes to hours)"
1044- )
1045- blank (" this allows us to find transactions associated with your coins\n " )
1046- rescan_thread = threading .Thread (
1047- target = _run_rescan ,
1048- args = (config .rpc (wallet ), rescan_begin_height ),
1049- daemon = True ,
1050- )
1051- rescan_thread .start ()
1052-
1053- time .sleep (2 )
1054-
1055- scan_info = rpcw .getwalletinfo ()["scanning" ]
1056- while scan_info :
1057- spin (f"scan progress: { scan_info ['progress' ] * 100 :.2f} % " )
1058- time .sleep (0.5 )
1059- scan_info = rpcw .getwalletinfo ()["scanning" ]
1060-
1061- name = yellow (wallet .name )
1062- p ()
1063- done (f"scan complete. wallet { name } ready to use." )
1064- info (f"Hint: check out your UTXOs with `coldcore -w { wallet .name } balance`" )
1065-
1012+ rpcw = init_wallet_in_core (config , rpc , wallet )
10661013 p ()
10671014
10681015 got = inp ("do you want to perform some test transactions? [Y/n] " ).lower ()
@@ -1889,8 +1836,6 @@ def start_ui(config, wallet_configs, action=None):
18891836# --- main/CLI ------------------------------------------------------------------------
18901837# -------------------------------------------------------------------------------------
18911838
1892- __VERSION__ = "0.2.0-beta"
1893-
18941839root_logger = logging .getLogger ()
18951840logger = logging .getLogger ("main" )
18961841
@@ -2124,7 +2069,97 @@ def newaddr(num: int = 1, clip: ClipArg = False): # type: ignore
21242069@cli .cmd
21252070def ui ():
21262071 config , walls = _get_config (require_wallets = False )
2127- start_ui (config , walls )
2072+ try :
2073+ start_ui (config , walls )
2074+ except JSONRPCError as e :
2075+ if e .code == - 18 :
2076+ print ()
2077+ print ("Wallet not found - are you using a new instance of bitcoind?" )
2078+ print ("If so, reinitialize with `coldcore reinit-wallet`" )
2079+ sys .exit (1 )
2080+ else :
2081+ raise
2082+
2083+
2084+ @cli .cmd
2085+ def reinit_wallet ():
2086+ """
2087+ Reinitialize the wallet on an instance of bitcoind which hasn't seen it before.
2088+ """
2089+ config , walls = _get_config (require_wallets = False )
2090+ assert walls
2091+ rpc = discover_rpc (config )
2092+ assert rpc
2093+ init_wallet_in_core (config , rpc , walls [0 ])
2094+
2095+
2096+ def init_wallet_in_core (config , rpc , wallet ) -> BitcoinRPC :
2097+ """
2098+ Initialize a wallet in core useing createwallet and scan for its balance and
2099+ history.
2100+ """
2101+ F .section ("wallet setup in Core" )
2102+ rpc_wallet_create (rpc , wallet )
2103+ F .done (f"created wallet { yellow (wallet .name )} in Core as watch-only" )
2104+
2105+ rpcw = config .rpc (wallet )
2106+ rpcw .importmulti (* wallet .importmulti_args ())
2107+ F .done ("imported descriptors 0/* and 1/* (change)" )
2108+
2109+ scan_result = {} # type: ignore
2110+ scan_thread = threading .Thread (
2111+ target = _run_scantxoutset ,
2112+ args = (config .rpc (wallet ), wallet .scantxoutset_args (), scan_result ),
2113+ )
2114+ scan_thread .start ()
2115+
2116+ print ()
2117+ F .section ("scanning the chain for balance and history" )
2118+ while scan_thread .is_alive ():
2119+ F .spin ("scanning the UTXO set for your balance (few minutes) " )
2120+ time .sleep (0.2 )
2121+
2122+ print ()
2123+ F .done ("scan of UTXO set complete!" )
2124+
2125+ # TODO this will fail if we timed out
2126+ unspents = scan_result ["result" ]["unspents" ]
2127+ bal = sum ([i ["amount" ] for i in unspents ])
2128+ bal_str = yellow (bold (f"{ bal } BTC" ))
2129+ bal_count = yellow (bold (f"{ len (unspents )} UTXOs" ))
2130+ F .blank (
2131+ f"found an existing balance of { yellow (bal_str )} across { yellow (bal_count )} "
2132+ )
2133+
2134+ if unspents :
2135+ rescan_begin_height = min ([i ["height" ] for i in unspents ])
2136+ print ()
2137+ F .blank (
2138+ f"beginning chain rescan from height { bold (str (rescan_begin_height ))} "
2139+ f"(minutes to hours)"
2140+ )
2141+ F .blank (" this allows us to find transactions associated with your coins\n " )
2142+ rescan_thread = threading .Thread (
2143+ target = _run_rescan ,
2144+ args = (config .rpc (wallet ), rescan_begin_height ),
2145+ daemon = True ,
2146+ )
2147+ rescan_thread .start ()
2148+
2149+ time .sleep (2 )
2150+
2151+ scan_info = rpcw .getwalletinfo ()["scanning" ]
2152+ while scan_info :
2153+ F .spin (f"scan progress: { scan_info ['progress' ] * 100 :.2f} % " )
2154+ time .sleep (0.5 )
2155+ scan_info = rpcw .getwalletinfo ()["scanning" ]
2156+
2157+ name = yellow (wallet .name )
2158+ print ()
2159+ F .done (f"scan complete. wallet { name } ready to use." )
2160+ F .info (f"Hint: check out your UTXOs with `coldcore -w { wallet .name } balance`" )
2161+
2162+ return rpcw
21282163
21292164
21302165@cli .main
@@ -2601,7 +2636,8 @@ def discover_rpc(
26012636def _is_already_loaded_err (e : JSONRPCError ) -> bool :
26022637 msg = str (e ).lower ()
26032638 return (
2604- ("already loaded" in msg )
2639+ e .code == - 4
2640+ or ("already loaded" in msg )
26052641 or ("duplicate -wallet filename" in msg )
26062642 or ("database already exists" in msg )
26072643 )
@@ -2661,6 +2697,16 @@ def _get_rpc_inner(
26612697 )
26622698
26632699
2700+ def get_node_version (rpc : BitcoinRPC ) -> t .Tuple [int , int , int ]:
2701+ """E.g. (22, 99, 0)."""
2702+ netinfo = rpc .getnetworkinfo ()
2703+ ver = re .match (r"/Satoshi:(\d+)\.(\d+)\.(\d+)/" , netinfo ["subversion" ])
2704+ assert ver
2705+ groups = [int (i ) for i in ver .groups ()]
2706+ assert len (groups ) == 3
2707+ return (groups [0 ], groups [1 ], groups [2 ])
2708+
2709+
26642710# --- Wallet/transaction utilities --------------------------------------------
26652711# -----------------------------------------------------------------------------
26662712
@@ -2682,8 +2728,24 @@ def _sh(*args, **kwargs) -> subprocess.CompletedProcess:
26822728
26832729
26842730def rpc_wallet_create (rpc : BitcoinRPC , wall : Wallet ):
2731+ node_ver = get_node_version (rpc )
26852732 try :
2686- rpc .createwallet (wall .bitcoind_name , True )
2733+ if node_ver < (22 , 99 , 0 ):
2734+ rpc .createwallet (
2735+ wall .bitcoind_name ,
2736+ True , # disable_private_keys
2737+ )
2738+ else :
2739+ # In 22.99+ (i.e. the 23.0 release), createwallet creates descriptor wallets
2740+ # by default, which are incompatible with importmulti.
2741+ rpc .createwallet (
2742+ wall .bitcoind_name ,
2743+ True , # disable_private_keys
2744+ True , # blank
2745+ None , # passphrase
2746+ False , # avoid_reuse
2747+ False , # descriptors
2748+ )
26872749 except JSONRPCError as e :
26882750 if not _is_already_loaded_err (e ):
26892751 # Wallet already exists; ok.
0 commit comments