Skip to content

Commit

Permalink
SFT-967: basic monotonic address explorer
Browse files Browse the repository at this point in the history
  • Loading branch information
mjg-foundation committed Oct 21, 2023
1 parent e3520a3 commit 5b31d33
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 5 deletions.
1 change: 1 addition & 0 deletions ports/stm32/boards/Passport/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
freeze('$(MPY_DIR)/ports/stm32/boards/Passport/modules',
('flows/__init__.py',
'flows/about_flow.py',
'flows/address_explorer_flow.py',
'flows/apply_passphrase_flow.py',
'flows/auto_backup_flow.py',
'flows/backup_common_flow.py',
Expand Down
1 change: 1 addition & 0 deletions ports/stm32/boards/Passport/modules/flows/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from .magic_scan_flow import *

from .about_flow import *
from .address_explorer_flow import *
from .apply_passphrase_flow import *
from .auto_backup_flow import *
from .backup_flow import *
Expand Down
136 changes: 136 additions & 0 deletions ports/stm32/boards/Passport/modules/flows/address_explorer_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. <hello@foundationdevices.com>
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2018 Coinkite, Inc. <coldcardwallet.com>
# SPDX-License-Identifier: GPL-3.0-only
#
# (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_flow.py - View addresses related to the current account

from flows import Flow


class AddressExplorerFlow(Flow):
def __init__(self):
from common import ui

super().__init__(initial_state=self.choose_sig_type, name='AddressExplorerFlow')
self.account = ui.get_active_account()
self.acct_num = self.account.get('acct_num')
self.sig_type = None
self.multisig_wallet = None
self.is_multisig = False
self.addr_type = None
self.deriv_path = None
self.is_change = None
self.curr_idx = 0

async def choose_sig_type(self):
from pages import SinglesigMultisigChooserPage
from multisig_wallet import MultisigWallet
from common import settings

xfp = settings.get('xfp')
multisigs = MultisigWallet.get_by_xfp(xfp)

if len(multisigs) == 0:
self.sig_type = 'single-sig'
self.goto(self.choose_addr_type, save_curr=False) # Skipping this state
else:
result = await SinglesigMultisigChooserPage(
initial_value=self.sig_type, multisigs=multisigs).show()

if result is None:
self.set_result(False)
return

(self.sig_type, self.multisig_wallet) = result

self.is_multisig = self.sig_type == 'multisig'
self.goto(self.choose_addr_type)

async def choose_addr_type(self):
from pages import AddressTypeChooserPage
from wallets.utils import get_deriv_path_from_addr_type_and_acct

result = await AddressTypeChooserPage(is_multisig=self.is_multisig).show()

if result is None:
self.back()
return

self.addr_type = result
self.deriv_path = get_deriv_path_from_addr_type_and_acct(self.addr_type, self.acct_num, self.is_multisig)
self.goto(self.choose_change)

async def choose_change(self):
from pages import YesNoChooserPage

result = await YesNoChooserPage(text="Explore normal or change addresses?",
yes_text="Normal",
no_text="Change").show()

if result is None:
self.back()
return

self.is_change = not result
self.goto(self.explore)

async def explore(self):
import chains
from utils import get_next_addr, format_btc_address
from common import settings
import stash
import passport
from pages import SuccessPage, LongSuccessPage
import microns

xfp = settings.get('xfp')
chain = chains.current_chain()
index = get_next_addr(self.acct_num,
self.addr_type,
xfp,
chain.b44_cointype,
self.is_change)

while True:
# TODO: break this into a util function
address = None
try:
with stash.SensitiveValues() as sv:
if self.is_multisig:
(curr_idx, paths, address, script) = list(self.multisig_wallet.yield_addresses(
start_idx=index,
count=1,
change_idx=1 if self.is_change else 0))[0]
else:
addr_path = '{}/{}/{}'.format(self.deriv_path, 1 if self.is_change else 0, index)
print(addr_path)
node = sv.derive_path(addr_path)
address = sv.chain.address(node, self.addr_type)
except Exception as e:
# TODO: make error page
break
# TODO: use nice new address formatting
nice_address = format_btc_address(address, self.addr_type)

msg = '''{}
{} Address {}'''.format(
nice_address,
'Change' if self.is_change else 'Receive',
index)

# TODO: make a new page that allows navigation
page_class = SuccessPage if passport.IS_COLOR else LongSuccessPage
result = await page_class(msg, left_micron=microns.Cancel).show()

if not result:
break
else:
index += 1

self.set_result(True)
3 changes: 2 additions & 1 deletion ports/stm32/boards/Passport/modules/menus.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@


def manage_account_menu():
from flows import RenameAccountFlow, DeleteAccountFlow, ConnectWalletFlow
from flows import RenameAccountFlow, DeleteAccountFlow, ConnectWalletFlow, AddressExplorerFlow
from pages import AccountDetailsPage

return [
{'icon': 'ICON_FOLDER', 'label': 'Account Details', 'page': AccountDetailsPage},
{'icon': 'ICON_INFO', 'label': 'Rename Account', 'flow': RenameAccountFlow},
{'icon': 'ICON_CONNECT', 'label': 'Connect Wallet', 'flow': ConnectWalletFlow,
'statusbar': {'title': 'CONNECT'}},
{'icon': 'ICON_VERIFY_ADDRESS', 'label': 'Explore Addresses', 'flow': AddressExplorerFlow},
{'icon': 'ICON_CANCEL', 'label': 'Delete Account', 'flow': DeleteAccountFlow},
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@


from pages import ChooserPage
from public_constants import AF_P2WPKH, AF_P2WPKH_P2SH, AF_CLASSIC, AF_P2TR
from public_constants import AF_CLASSIC, AF_P2SH, AF_P2WPKH_P2SH, AF_P2WSH_P2SH, AF_P2WPKH, AF_P2WSH, AF_P2TR


class AddressTypeChooserPage(ChooserPage):
Expand All @@ -16,14 +16,22 @@ class AddressTypeChooserPage(ChooserPage):
AF_P2TR: 'Taproot (P2TR)',
}

def __init__(self, card_header={'title': 'Address Type'}, initial_value=None, options=None):
MULTISIG_LABELS = {
AF_P2SH: 'P2SH',
AF_P2WSH_P2SH: 'P2WSH_P2SH',
AF_P2WSH: 'P2WSH',
}

def __init__(self, card_header={'title': 'Address Type'}, initial_value=None, options=None, is_multisig=False):

labels = self.MULTISIG_LABELS if is_multisig else self.LABELS

if options is None:
options = sorted(list(self.LABELS.keys()))
options = sorted(list(labels.keys()))

final_options = []
for addr_type in options:
final_options.append({'label': self.LABELS[addr_type], 'value': addr_type})
final_options.append({'label': labels[addr_type], 'value': addr_type})

super().__init__(
card_header=card_header,
Expand Down

0 comments on commit 5b31d33

Please sign in to comment.