221 lines
6.4 KiB
Python
221 lines
6.4 KiB
Python
# (c) Copyright 2018 by Coinkite Inc. This file is part of Coldcard <coldcardwallet.com>
|
|
# and is covered by GPLv3 license found in COPYING.
|
|
#
|
|
# address_explorer.py
|
|
#
|
|
# Address Explorer menu functionality
|
|
#
|
|
import chains, stash
|
|
from ux import ux_show_story, the_ux, ux_confirm
|
|
from actions import goto_top_menu
|
|
from menu import MenuSystem, MenuItem, start_chooser
|
|
from public_constants import AFC_BECH32
|
|
|
|
SCREEN_CHAR_WIDTH = const(16)
|
|
|
|
async def choose_first_address(*a):
|
|
# Choose from a truncated list of index 0 common addresses, remember
|
|
# the last address the user selected and use it as the default
|
|
from main import settings, dis
|
|
chain = chains.current_chain()
|
|
|
|
dis.fullscreen('Wait...')
|
|
|
|
with stash.SensitiveValues() as sv:
|
|
|
|
def truncate_address(addr):
|
|
# Truncates address to width of screen, replacing middle chars
|
|
middle = "-"
|
|
leftover = SCREEN_CHAR_WIDTH - len(middle)
|
|
start = addr[0:(leftover+1) // 2]
|
|
end = addr[len(addr) - (leftover // 2):]
|
|
return start + middle + end
|
|
|
|
# Create list of choices (address_index_0, path, addr_fmt)
|
|
choices = []
|
|
for name, path, addr_fmt in chains.CommonDerivations:
|
|
if '{coin_type}' in path:
|
|
path = path.replace('{coin_type}', str(chain.b44_cointype))
|
|
subpath = path.format(account=0, change=0, idx=0)
|
|
node = sv.derive_path(subpath, register=False)
|
|
address = chain.address(node, addr_fmt)
|
|
choices.append( (truncate_address(address), path, addr_fmt) )
|
|
|
|
dis.progress_bar_show(len(choices) / len(chains.CommonDerivations))
|
|
|
|
stash.blank_object(node)
|
|
|
|
picked = None
|
|
|
|
async def clicked(_1,_2,item):
|
|
if picked is None:
|
|
picked = item.arg
|
|
the_ux.pop()
|
|
|
|
items = [MenuItem(address, f=clicked, arg=i) for i, (address, path, addr_fmt)
|
|
in enumerate(choices)]
|
|
menu = MenuSystem(items)
|
|
menu.goto_idx(settings.get('axi', 0))
|
|
the_ux.push(menu)
|
|
|
|
await menu.interact()
|
|
|
|
if picked is None:
|
|
return None
|
|
|
|
# update last clicked address
|
|
settings.put('axi', picked)
|
|
address, path, addr_fmt = choices[picked]
|
|
|
|
return (path, addr_fmt)
|
|
|
|
async def show_n_addresses(path, addr_fmt, start, n):
|
|
# Displays n addresses from start
|
|
from main import dis
|
|
import version
|
|
|
|
def make_msg(start):
|
|
msg = ''
|
|
if start == 0:
|
|
msg = "Press 1 to save to MicroSD."
|
|
if version.has_fatram:
|
|
msg += " 4 to view QR Codes."
|
|
msg += '\n\n'
|
|
msg += "Addresses %d..%d:\n\n" % (start, start + n - 1)
|
|
|
|
addrs = []
|
|
chain = chains.current_chain()
|
|
|
|
dis.fullscreen('Wait...')
|
|
|
|
with stash.SensitiveValues() as sv:
|
|
|
|
for idx in range(start, start + n):
|
|
subpath = path.format(account=0, change=0, idx=idx)
|
|
node = sv.derive_path(subpath, register=False)
|
|
addr = chain.address(node, addr_fmt)
|
|
addrs.append(addr)
|
|
|
|
msg += "%s =>\n%s\n\n" % (subpath, addr)
|
|
|
|
dis.progress_bar_show(idx/n)
|
|
|
|
stash.blank_object(node)
|
|
|
|
|
|
msg += "Press 9 to see next group, 7 to go back. X to quit."
|
|
|
|
return msg, addrs
|
|
|
|
msg, addrs = make_msg(start)
|
|
|
|
while 1:
|
|
ch = await ux_show_story(msg, escape='1479')
|
|
|
|
if ch == '1':
|
|
# save addresses to MicroSD signal
|
|
await make_address_summary_file(path, addr_fmt)
|
|
# .. continue on same screen in case they want to write to multiple cards
|
|
|
|
if ch == 'x':
|
|
return
|
|
|
|
if ch == '4':
|
|
if not version.has_fatram: continue
|
|
from ux import show_qr_codes
|
|
await show_qr_codes(addrs, bool(addr_fmt & AFC_BECH32), start)
|
|
continue
|
|
|
|
if ch == '7' and start>0:
|
|
# go backwards in explorer
|
|
start -= n
|
|
elif ch == '9':
|
|
# go forwards
|
|
start += n
|
|
|
|
msg, addrs = make_msg(start)
|
|
|
|
def generate_address_csv(path, addr_fmt, n):
|
|
# Produce CSV file contents as a generator
|
|
|
|
yield '"Index","Payment Address","Derivation"\n'
|
|
|
|
ch = chains.current_chain()
|
|
|
|
with stash.SensitiveValues() as sv:
|
|
for idx in range(n):
|
|
subpath = path.format(account=0, change=0, idx=idx)
|
|
node = sv.derive_path(subpath, register=False)
|
|
|
|
yield '%d,"%s","%s"\n' % (idx, ch.address(node, addr_fmt), subpath)
|
|
|
|
stash.blank_object(node)
|
|
|
|
async def make_address_summary_file(path, addr_fmt, fname_pattern='addresses.txt'):
|
|
# write addresses into a text file on the MicroSD
|
|
from main import dis
|
|
from files import CardSlot, CardMissingError
|
|
from actions import needs_microsd
|
|
|
|
# simple: always set number of addresses.
|
|
# - takes 60 seconds, to write 250 addresses on actual hardware
|
|
count = 250
|
|
|
|
dis.fullscreen('Saving 0-%d' % count)
|
|
|
|
# generator function
|
|
body = generate_address_csv(path, addr_fmt, count)
|
|
|
|
# pick filename and write
|
|
try:
|
|
with CardSlot() as card:
|
|
fname, nice = card.pick_filename(fname_pattern)
|
|
|
|
# do actual write
|
|
with open(fname, 'wb') as fd:
|
|
for idx, part in enumerate(body):
|
|
fd.write(part.encode())
|
|
|
|
if idx % 5 == 0:
|
|
dis.progress_bar_show(idx / count)
|
|
|
|
except CardMissingError:
|
|
await needs_microsd()
|
|
return
|
|
except Exception as e:
|
|
await ux_show_story('Failed to write!\n\n\n'+str(e))
|
|
return
|
|
|
|
msg = '''Address summary file written:\n\n%s''' % nice
|
|
await ux_show_story(msg)
|
|
|
|
async def address_explore(*a):
|
|
# explore addresses based on derivation path chosen
|
|
# by proxy external index=0 address
|
|
while 1:
|
|
ch = await ux_show_story('''\
|
|
The following menu lists the first payment address \
|
|
produced by various common wallet systems.
|
|
|
|
Choose the address that your desktop or mobile wallet \
|
|
has shown you as the first receive address.
|
|
|
|
WARNING: Please understand that exceeding the gap limit \
|
|
of your wallet, or choosing the wrong address on the next screen \
|
|
may make it very difficult to recover your funds.
|
|
|
|
Press 4 to start.''', escape='4')
|
|
|
|
if ch == '4': break
|
|
if ch == 'x': return
|
|
|
|
picked = await choose_first_address()
|
|
if picked is None:
|
|
return
|
|
|
|
path, addr_fmt = picked
|
|
|
|
await show_n_addresses(path, addr_fmt, 0, 10)
|
|
|
|
# EOF
|