mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-18 05:28:40 +00:00
refactor(core): convert apps.stellar to layouts
This commit is contained in:
parent
6aeaadfe16
commit
12478b1716
@ -1,8 +1,12 @@
|
||||
from trezor import strings, ui, utils
|
||||
from trezor import strings, ui
|
||||
from trezor.enums import ButtonRequestType
|
||||
from trezor.ui.components.tt.text import Text
|
||||
|
||||
from apps.common.confirm import require_confirm, require_hold_to_confirm
|
||||
from trezor.ui.constants import MONO_ADDR_PER_LINE
|
||||
from trezor.ui.layouts import (
|
||||
confirm_action,
|
||||
confirm_hex,
|
||||
confirm_metadata,
|
||||
confirm_timebounds_stellar,
|
||||
)
|
||||
|
||||
from . import consts
|
||||
|
||||
@ -10,68 +14,107 @@ from . import consts
|
||||
async def require_confirm_init(
|
||||
ctx, address: str, network_passphrase: str, accounts_match: bool
|
||||
):
|
||||
text = Text("Confirm Stellar", ui.ICON_SEND, ui.GREEN)
|
||||
text.normal("Initialize signing with")
|
||||
if accounts_match:
|
||||
text.normal("your account")
|
||||
text.mono(*split(trim_to_rows(address, 3)))
|
||||
description = "Initialize signing with\nyour account"
|
||||
else:
|
||||
text.mono(*split(address))
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
description = "Initialize signing with"
|
||||
await require_confirm_op(
|
||||
ctx,
|
||||
"confirm_init",
|
||||
title="Confirm Stellar",
|
||||
subtitle=None,
|
||||
description=description,
|
||||
data=address,
|
||||
icon=ui.ICON_SEND,
|
||||
is_account=True,
|
||||
)
|
||||
|
||||
network = get_network_warning(network_passphrase)
|
||||
if network:
|
||||
text = Text("Confirm network", ui.ICON_CONFIRM, ui.GREEN)
|
||||
text.normal("Transaction is on")
|
||||
text.bold(network)
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
await confirm_metadata(
|
||||
ctx,
|
||||
"confirm_init_network",
|
||||
title="Confirm network",
|
||||
content="Transaction is on {}",
|
||||
param=network,
|
||||
icon=ui.ICON_CONFIRM,
|
||||
br_code=ButtonRequestType.ConfirmOutput,
|
||||
hide_continue=True,
|
||||
)
|
||||
|
||||
|
||||
async def require_confirm_timebounds(ctx, start: int, end: int):
|
||||
text = Text("Confirm timebounds", ui.ICON_SEND, ui.GREEN)
|
||||
text.bold("Valid from (UTC):")
|
||||
if start:
|
||||
text.normal(str(start))
|
||||
else:
|
||||
text.mono("[no restriction]")
|
||||
|
||||
text.bold("Valid to (UTC):")
|
||||
if end:
|
||||
text.normal(str(end))
|
||||
else:
|
||||
text.mono("[no restriction]")
|
||||
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
await confirm_timebounds_stellar(ctx, start, end)
|
||||
|
||||
|
||||
async def require_confirm_memo(ctx, memo_type: int, memo_text: str):
|
||||
text = Text("Confirm memo", ui.ICON_CONFIRM, ui.GREEN)
|
||||
if memo_type == consts.MEMO_TYPE_TEXT:
|
||||
text.bold("Memo (TEXT)")
|
||||
description = "Memo (TEXT)"
|
||||
elif memo_type == consts.MEMO_TYPE_ID:
|
||||
text.bold("Memo (ID)")
|
||||
description = "Memo (ID)"
|
||||
elif memo_type == consts.MEMO_TYPE_HASH:
|
||||
text.bold("Memo (HASH)")
|
||||
description = "Memo (HASH)"
|
||||
elif memo_type == consts.MEMO_TYPE_RETURN:
|
||||
text.bold("Memo (RETURN)")
|
||||
else: # MEMO_TYPE_NONE
|
||||
text.bold("No memo set!")
|
||||
text.normal("Important: Many exchanges require a memo when depositing")
|
||||
if memo_type != consts.MEMO_TYPE_NONE:
|
||||
text.mono(*split(memo_text))
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
description = "Memo (RETURN)"
|
||||
else:
|
||||
return await confirm_action(
|
||||
ctx,
|
||||
"confirm_memo",
|
||||
title="Confirm memo",
|
||||
action="No memo set!",
|
||||
description="Important: Many exchanges require a memo when depositing",
|
||||
icon=ui.ICON_CONFIRM,
|
||||
icon_color=ui.GREEN,
|
||||
br_code=ButtonRequestType.ConfirmOutput,
|
||||
)
|
||||
|
||||
await require_confirm_op(
|
||||
ctx,
|
||||
"confirm_memo",
|
||||
title="Confirm memo",
|
||||
subtitle=description,
|
||||
data=memo_text,
|
||||
split=False,
|
||||
)
|
||||
|
||||
|
||||
async def require_confirm_final(ctx, fee: int, num_operations: int):
|
||||
op_str = str(num_operations) + " operation"
|
||||
if num_operations > 1:
|
||||
op_str += "s"
|
||||
text = Text("Final confirm", ui.ICON_SEND, ui.GREEN)
|
||||
text.normal("Sign this transaction")
|
||||
text.normal("made up of " + op_str)
|
||||
text.bold("and pay " + format_amount(fee))
|
||||
text.normal("for fee?")
|
||||
# we use SignTx, not ConfirmOutput, for compatibility with T1
|
||||
await require_hold_to_confirm(ctx, text, ButtonRequestType.SignTx)
|
||||
op_str = strings.format_plural("{count} {plural}", num_operations, "operation")
|
||||
await confirm_metadata(
|
||||
ctx,
|
||||
"confirm_final",
|
||||
title="Final confirm",
|
||||
content="Sign this transaction made up of " + op_str + " and pay {}\nfor fee?",
|
||||
param=format_amount(fee),
|
||||
hide_continue=True,
|
||||
hold=True,
|
||||
)
|
||||
|
||||
|
||||
async def require_confirm_op(
|
||||
ctx,
|
||||
br_type: str,
|
||||
subtitle: str | None,
|
||||
data: str,
|
||||
title: str = "Confirm operation",
|
||||
description: str = None,
|
||||
icon=ui.ICON_CONFIRM,
|
||||
split: bool = True,
|
||||
is_account: bool = False,
|
||||
):
|
||||
await confirm_hex(
|
||||
ctx,
|
||||
br_type,
|
||||
title=title,
|
||||
subtitle=subtitle,
|
||||
description=description,
|
||||
data=data,
|
||||
width=MONO_ADDR_PER_LINE if split else None,
|
||||
icon=icon,
|
||||
truncate=True,
|
||||
truncate_ellipsis=".." if is_account else "",
|
||||
br_code=ButtonRequestType.ConfirmOutput,
|
||||
)
|
||||
|
||||
|
||||
def format_amount(amount: int, ticker=True) -> str:
|
||||
@ -81,22 +124,6 @@ def format_amount(amount: int, ticker=True) -> str:
|
||||
return strings.format_amount(amount, consts.AMOUNT_DECIMALS) + t
|
||||
|
||||
|
||||
def split(text):
|
||||
return utils.chunks(text, 17)
|
||||
|
||||
|
||||
def trim(payload: str, length: int, dots=True) -> str:
|
||||
if len(payload) > length:
|
||||
if dots:
|
||||
return payload[: length - 2] + ".."
|
||||
return payload[: length - 2]
|
||||
return payload
|
||||
|
||||
|
||||
def trim_to_rows(payload: str, rows: int = 1) -> str:
|
||||
return trim(payload, rows * 17)
|
||||
|
||||
|
||||
def get_network_warning(network_passphrase: str):
|
||||
if network_passphrase == consts.NETWORK_PASSPHRASE_PUBLIC:
|
||||
return None
|
||||
|
@ -15,68 +15,72 @@ from trezor.messages import (
|
||||
StellarPaymentOp,
|
||||
StellarSetOptionsOp,
|
||||
)
|
||||
from trezor.ui.components.tt.text import Text
|
||||
from trezor.ui.layouts import confirm_metadata
|
||||
from trezor.wire import ProcessError
|
||||
|
||||
from .. import consts, helpers
|
||||
from ..layout import format_amount, require_confirm, split, trim_to_rows, ui
|
||||
from ..layout import format_amount, require_confirm_op, ui
|
||||
|
||||
|
||||
async def confirm_source_account(ctx, source_account: bytes):
|
||||
text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN)
|
||||
text.bold("Source account:")
|
||||
text.mono(*split(source_account))
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
async def confirm_source_account(ctx, source_account: str):
|
||||
await require_confirm_op(ctx, "confirm_source", "Source account:", source_account)
|
||||
|
||||
|
||||
async def confirm_allow_trust_op(ctx, op: StellarAllowTrustOp):
|
||||
if op.is_authorized:
|
||||
t = "Allow Trust"
|
||||
else:
|
||||
t = "Revoke Trust"
|
||||
text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN)
|
||||
text.bold(t)
|
||||
text.normal("of %s by:" % op.asset_code)
|
||||
text.mono(*split(trim_to_rows(op.trusted_account, 3)))
|
||||
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
await require_confirm_op(
|
||||
ctx,
|
||||
"op_allow_trust",
|
||||
subtitle="Allow Trust" if op.is_authorized else "Revoke Trust",
|
||||
description="of %s by:" % op.asset_code,
|
||||
data=op.trusted_account,
|
||||
is_account=True,
|
||||
)
|
||||
|
||||
|
||||
async def confirm_account_merge_op(ctx, op: StellarAccountMergeOp):
|
||||
text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN)
|
||||
text.bold("Account Merge")
|
||||
text.normal("All XLM will be sent to:")
|
||||
text.mono(*split(trim_to_rows(op.destination_account, 3)))
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
await require_confirm_op(
|
||||
ctx,
|
||||
"op_merge",
|
||||
"Account Merge",
|
||||
description="All XLM will be sent to:",
|
||||
data=op.destination_account,
|
||||
is_account=True,
|
||||
)
|
||||
|
||||
|
||||
async def confirm_bump_sequence_op(ctx, op: StellarBumpSequenceOp):
|
||||
text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN)
|
||||
text.bold("Bump Sequence")
|
||||
text.normal("Set sequence to")
|
||||
text.mono(str(op.bump_to))
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
await require_confirm_op(
|
||||
ctx,
|
||||
"op_bump",
|
||||
"Bump Sequence",
|
||||
description="Set sequence to",
|
||||
data=str(op.bump_to),
|
||||
split=False,
|
||||
)
|
||||
|
||||
|
||||
async def confirm_change_trust_op(ctx, op: StellarChangeTrustOp):
|
||||
if op.limit == 0:
|
||||
t = "Delete Trust"
|
||||
else:
|
||||
t = "Add Trust"
|
||||
text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN)
|
||||
text.bold(t)
|
||||
text.normal("Asset: %s" % op.asset.code)
|
||||
text.normal("Amount: %s" % format_amount(op.limit, ticker=False))
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
await require_confirm_op(
|
||||
ctx,
|
||||
"op_change_trust",
|
||||
subtitle="Delete Trust" if op.limit == 0 else "Add Trust",
|
||||
description="Asset: %s\nAmount: %s"
|
||||
% (op.asset.code, format_amount(op.limit, ticker=False)),
|
||||
data="",
|
||||
split=False,
|
||||
)
|
||||
await confirm_asset_issuer(ctx, op.asset)
|
||||
|
||||
|
||||
async def confirm_create_account_op(ctx, op: StellarCreateAccountOp):
|
||||
text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN)
|
||||
text.bold("Create Account")
|
||||
text.normal("with %s" % format_amount(op.starting_balance))
|
||||
text.mono(*split(trim_to_rows(op.new_account, 3)))
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
await require_confirm_op(
|
||||
ctx,
|
||||
"op_account",
|
||||
subtitle="Create Account",
|
||||
description="with %s" % format_amount(op.starting_balance),
|
||||
data=op.new_account,
|
||||
is_account=True,
|
||||
)
|
||||
|
||||
|
||||
async def confirm_create_passive_offer_op(ctx, op: StellarCreatePassiveOfferOp):
|
||||
@ -100,14 +104,20 @@ async def confirm_manage_offer_op(ctx, op: StellarManageOfferOp):
|
||||
|
||||
|
||||
async def _confirm_offer(ctx, title, op):
|
||||
text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN)
|
||||
text.bold(title)
|
||||
text.normal(
|
||||
"Sell %s %s" % (format_amount(op.amount, ticker=False), op.selling_asset.code)
|
||||
await require_confirm_op(
|
||||
ctx,
|
||||
"op_offer",
|
||||
subtitle=title,
|
||||
description="Sell %s %s\nFor %f\nPer %s"
|
||||
% (
|
||||
format_amount(op.amount, ticker=False),
|
||||
op.selling_asset.code,
|
||||
op.price_n / op.price_d,
|
||||
format_asset_code(op.buying_asset),
|
||||
),
|
||||
data="",
|
||||
split=False,
|
||||
)
|
||||
text.normal("For %f" % (op.price_n / op.price_d))
|
||||
text.normal("Per %s" % format_asset_code(op.buying_asset))
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
await confirm_asset_issuer(ctx, op.selling_asset)
|
||||
await confirm_asset_issuer(ctx, op.buying_asset)
|
||||
|
||||
@ -119,121 +129,128 @@ async def confirm_manage_data_op(ctx, op: StellarManageDataOp):
|
||||
title = "Set"
|
||||
else:
|
||||
title = "Clear"
|
||||
text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN)
|
||||
text.bold("%s data value key" % title)
|
||||
text.mono(*split(op.key))
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
await require_confirm_op(ctx, "op_data", "%s data value key" % title, op.key)
|
||||
|
||||
if op.value:
|
||||
digest = sha256(op.value).digest()
|
||||
digest_str = hexlify(digest).decode()
|
||||
text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN)
|
||||
text.bold("Value (SHA-256):")
|
||||
text.mono(*split(digest_str))
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
await require_confirm_op(ctx, "op_data_value", "Value (SHA-256):", digest_str)
|
||||
|
||||
|
||||
async def confirm_path_payment_op(ctx, op: StellarPathPaymentOp):
|
||||
text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN)
|
||||
text.bold("Path Pay %s" % format_amount(op.destination_amount, ticker=False))
|
||||
text.bold("%s to:" % format_asset_code(op.destination_asset))
|
||||
text.mono(*split(trim_to_rows(op.destination_account, 3)))
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
await require_confirm_op(
|
||||
ctx,
|
||||
"op_path_payment",
|
||||
subtitle="Path Pay %s\n%s to:"
|
||||
% (
|
||||
format_amount(op.destination_amount, ticker=False),
|
||||
format_asset_code(op.destination_asset),
|
||||
),
|
||||
data=op.destination_account,
|
||||
is_account=True,
|
||||
)
|
||||
await confirm_asset_issuer(ctx, op.destination_asset)
|
||||
# confirm what the sender is using to pay
|
||||
text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN)
|
||||
text.normal("Pay using")
|
||||
text.bold(format_amount(op.send_max, ticker=False))
|
||||
text.bold(format_asset_code(op.send_asset))
|
||||
text.normal("This amount is debited")
|
||||
text.normal("from your account.")
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
await confirm_metadata(
|
||||
ctx,
|
||||
"op_path_payment",
|
||||
"Confirm operation",
|
||||
content="Pay using\n{}\nThis amount is debited from your account.",
|
||||
param="{}\n{}".format(
|
||||
format_amount(op.send_max, ticker=False),
|
||||
format_asset_code(op.send_asset),
|
||||
),
|
||||
icon=ui.ICON_CONFIRM,
|
||||
hide_continue=True,
|
||||
br_code=ButtonRequestType.ConfirmOutput,
|
||||
)
|
||||
await confirm_asset_issuer(ctx, op.send_asset)
|
||||
|
||||
|
||||
async def confirm_payment_op(ctx, op: StellarPaymentOp):
|
||||
text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN)
|
||||
text.bold("Pay %s" % format_amount(op.amount, ticker=False))
|
||||
text.bold("%s to:" % format_asset_code(op.asset))
|
||||
text.mono(*split(trim_to_rows(op.destination_account, 3)))
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
description = "Pay {}\n{} to:".format(
|
||||
format_amount(op.amount, ticker=False), format_asset_code(op.asset)
|
||||
)
|
||||
await require_confirm_op(
|
||||
ctx, "op_payment", description, op.destination_account, is_account=True
|
||||
)
|
||||
|
||||
await confirm_asset_issuer(ctx, op.asset)
|
||||
|
||||
|
||||
async def confirm_set_options_op(ctx, op: StellarSetOptionsOp):
|
||||
if op.inflation_destination_account:
|
||||
text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN)
|
||||
text.bold("Set Inflation Destination")
|
||||
text.mono(*split(op.inflation_destination_account))
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
await require_confirm_op(
|
||||
ctx,
|
||||
"op_inflation",
|
||||
"Set Inflation Destination",
|
||||
op.inflation_destination_account,
|
||||
)
|
||||
if op.clear_flags:
|
||||
t = _format_flags(op.clear_flags)
|
||||
text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN)
|
||||
text.bold("Clear Flags")
|
||||
text.mono(*t)
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
await require_confirm_op(ctx, "op_clear_flags", "Clear Flags", t, split=False)
|
||||
if op.set_flags:
|
||||
t = _format_flags(op.set_flags)
|
||||
text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN)
|
||||
text.bold("Set Flags")
|
||||
text.mono(*t)
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
await require_confirm_op(ctx, "op_set_flags", "Set Flags", t, split=False)
|
||||
thresholds = _format_thresholds(op)
|
||||
if thresholds:
|
||||
text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN)
|
||||
text.bold("Account Thresholds")
|
||||
text.mono(*thresholds)
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
await require_confirm_op(
|
||||
ctx,
|
||||
"op_thresholds",
|
||||
"Account Thresholds",
|
||||
thresholds,
|
||||
split=False,
|
||||
)
|
||||
if op.home_domain:
|
||||
text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN)
|
||||
text.bold("Home Domain")
|
||||
text.mono(*split(op.home_domain))
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
await require_confirm_op(ctx, "op_home_domain", "Home Domain", op.home_domain)
|
||||
if op.signer_type is not None:
|
||||
if op.signer_weight > 0:
|
||||
t = "Add Signer (%s)"
|
||||
else:
|
||||
t = "Remove Signer (%s)"
|
||||
if op.signer_type == consts.SIGN_TYPE_ACCOUNT:
|
||||
text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN)
|
||||
text.bold(t % "acc")
|
||||
text.mono(*split(helpers.address_from_public_key(op.signer_key)))
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
await require_confirm_op(
|
||||
ctx,
|
||||
"op_signer",
|
||||
t % "acc",
|
||||
helpers.address_from_public_key(op.signer_key),
|
||||
)
|
||||
elif op.signer_type in (consts.SIGN_TYPE_PRE_AUTH, consts.SIGN_TYPE_HASH):
|
||||
if op.signer_type == consts.SIGN_TYPE_PRE_AUTH:
|
||||
signer_type = "auth"
|
||||
else:
|
||||
signer_type = "hash"
|
||||
text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN)
|
||||
text.bold(t % signer_type)
|
||||
text.mono(*split(hexlify(op.signer_key).decode()))
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
await require_confirm_op(
|
||||
ctx,
|
||||
"op_signer",
|
||||
t % signer_type,
|
||||
hexlify(op.signer_key).decode(),
|
||||
)
|
||||
else:
|
||||
raise ProcessError("Stellar: invalid signer type")
|
||||
|
||||
|
||||
def _format_thresholds(op: StellarSetOptionsOp) -> tuple:
|
||||
text = ()
|
||||
text = ""
|
||||
if op.master_weight is not None:
|
||||
text += ("Master Weight: %d" % op.master_weight,)
|
||||
text += "Master Weight: %d\n" % op.master_weight
|
||||
if op.low_threshold is not None:
|
||||
text += ("Low: %d" % op.low_threshold,)
|
||||
text += "Low: %d\n" % op.low_threshold
|
||||
if op.medium_threshold is not None:
|
||||
text += ("Medium: %d" % op.medium_threshold,)
|
||||
text += "Medium: %d\n" % op.medium_threshold
|
||||
if op.high_threshold is not None:
|
||||
text += ("High: %d" % op.high_threshold,)
|
||||
text += "High: %d\n" % op.high_threshold
|
||||
return text
|
||||
|
||||
|
||||
def _format_flags(flags: int) -> tuple:
|
||||
if flags > consts.FLAGS_MAX_SIZE:
|
||||
raise ProcessError("Stellar: invalid flags")
|
||||
text = ()
|
||||
if flags & consts.FLAG_AUTH_REQUIRED:
|
||||
text += ("AUTH_REQUIRED",)
|
||||
if flags & consts.FLAG_AUTH_REVOCABLE:
|
||||
text += ("AUTH_REVOCABLE",)
|
||||
if flags & consts.FLAG_AUTH_IMMUTABLE:
|
||||
text += ("AUTH_IMMUTABLE",)
|
||||
text = "{}{}{}".format(
|
||||
"AUTH_REQUIRED\n" if flags & consts.FLAG_AUTH_REQUIRED else "",
|
||||
"AUTH_REVOCABLE\n" if flags & consts.FLAG_AUTH_REVOCABLE else "",
|
||||
"AUTH_IMMUTABLE\n" if flags & consts.FLAG_AUTH_IMMUTABLE else "",
|
||||
)
|
||||
return text
|
||||
|
||||
|
||||
@ -246,7 +263,10 @@ def format_asset_code(asset: StellarAssetType) -> str:
|
||||
async def confirm_asset_issuer(ctx, asset: StellarAssetType):
|
||||
if asset is None or asset.type == consts.ASSET_TYPE_NATIVE:
|
||||
return
|
||||
text = Text("Confirm issuer", ui.ICON_CONFIRM, ui.GREEN)
|
||||
text.bold("%s issuer:" % asset.code)
|
||||
text.mono(*split(asset.issuer))
|
||||
await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput)
|
||||
await require_confirm_op(
|
||||
ctx,
|
||||
"confirm_issuer",
|
||||
title="Confirm issuer",
|
||||
subtitle="%s issuer:" % asset.code,
|
||||
data=asset.issuer,
|
||||
)
|
||||
|
@ -61,6 +61,7 @@ __all__ = (
|
||||
"confirm_modify_output",
|
||||
"confirm_modify_fee",
|
||||
"confirm_coinjoin",
|
||||
"confirm_timebounds_stellar",
|
||||
)
|
||||
|
||||
|
||||
@ -216,16 +217,18 @@ def _truncate_hex(
|
||||
lines: int = TEXT_MAX_LINES,
|
||||
width: int = MONO_HEX_PER_LINE,
|
||||
middle: bool = False,
|
||||
ellipsis: str = "...", # TODO: cleanup @ redesign
|
||||
) -> Iterator[str]:
|
||||
ell_len = len(ellipsis)
|
||||
if len(hex_data) > width * lines:
|
||||
if middle:
|
||||
hex_data = (
|
||||
hex_data[: lines * width // 2 - 1]
|
||||
+ "..."
|
||||
+ hex_data[-lines * width // 2 + 2 :]
|
||||
hex_data[: lines * width // 2 - (ell_len // 2)]
|
||||
+ ellipsis
|
||||
+ hex_data[-lines * width // 2 + (ell_len - ell_len // 2) :]
|
||||
)
|
||||
else:
|
||||
hex_data = hex_data[: (width * lines - 3)] + "..."
|
||||
hex_data = hex_data[: (width * lines - ell_len)] + ellipsis
|
||||
return chunks_intersperse(hex_data, width)
|
||||
|
||||
|
||||
@ -490,40 +493,55 @@ async def confirm_hex(
|
||||
br_type: str,
|
||||
title: str,
|
||||
data: str,
|
||||
subtitle: str | None = None,
|
||||
description: str | None = None,
|
||||
br_code: ButtonRequestType = ButtonRequestType.Other,
|
||||
icon: str = ui.ICON_SEND, # TODO cleanup @ redesign
|
||||
icon_color: int = ui.GREEN, # TODO cleanup @ redesign
|
||||
font_description: int = ui.NORMAL, # TODO cleanup @ redesign
|
||||
color_description: int = ui.FG, # TODO cleanup @ redesign
|
||||
width: int = MONO_HEX_PER_LINE,
|
||||
width: int | None = MONO_HEX_PER_LINE,
|
||||
width_paginated: int = MONO_HEX_PER_LINE - 2,
|
||||
truncate: bool = False,
|
||||
truncate_middle: bool = False,
|
||||
truncate_ellipsis: str = "...",
|
||||
) -> None:
|
||||
if truncate:
|
||||
text = Text(title, icon, icon_color, new_lines=False)
|
||||
description_lines = 0
|
||||
if description is not None:
|
||||
description_lines = Span(description, 0, font_description).count_lines()
|
||||
text.content.extend(
|
||||
(font_description, color_description, description, ui.FG)
|
||||
)
|
||||
if subtitle is not None:
|
||||
description_lines += Span(subtitle, 0, ui.BOLD).count_lines()
|
||||
text.bold(subtitle)
|
||||
text.br()
|
||||
text.mono(
|
||||
*_truncate_hex(
|
||||
data,
|
||||
lines=TEXT_MAX_LINES - description_lines,
|
||||
width=width,
|
||||
middle=truncate_middle,
|
||||
if description is not None:
|
||||
description_lines += Span(description, 0, ui.NORMAL).count_lines()
|
||||
text.content.extend((ui.NORMAL, color_description, description, ui.FG))
|
||||
text.br()
|
||||
if width is not None:
|
||||
text.mono(
|
||||
*_truncate_hex(
|
||||
data,
|
||||
lines=TEXT_MAX_LINES - description_lines,
|
||||
width=width,
|
||||
middle=truncate_middle,
|
||||
ellipsis=truncate_ellipsis,
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
text.mono(data)
|
||||
content: ui.Layout = Confirm(text)
|
||||
else:
|
||||
width_paginated = min(width, MONO_HEX_PER_LINE - 2)
|
||||
assert color_description == ui.FG # only ethereum uses this and it truncates
|
||||
para = [(font_description, description)] if description is not None else []
|
||||
para.extend((ui.MONO, line) for line in chunks(data, width_paginated))
|
||||
para = []
|
||||
if subtitle is not None:
|
||||
para.append((ui.BOLD, subtitle))
|
||||
if description is not None:
|
||||
assert (
|
||||
color_description == ui.FG
|
||||
) # only ethereum uses this and it truncates
|
||||
para.append((ui.NORMAL, description))
|
||||
if width is not None:
|
||||
para.extend((ui.MONO, line) for line in chunks(data, width_paginated))
|
||||
else:
|
||||
para.append((ui.MONO, data))
|
||||
content = paginate_paragraphs(para, title, icon, icon_color)
|
||||
await raise_if_cancelled(interact(ctx, content, br_type, br_code))
|
||||
|
||||
@ -597,8 +615,9 @@ async def confirm_metadata(
|
||||
br_code: ButtonRequestType = ButtonRequestType.SignTx,
|
||||
hide_continue: bool = False,
|
||||
hold: bool = False,
|
||||
icon: str = ui.ICON_SEND, # TODO cleanup @ redesign
|
||||
) -> None:
|
||||
text = Text(title, ui.ICON_SEND, ui.GREEN, new_lines=False)
|
||||
text = Text(title, icon, ui.GREEN, new_lines=False)
|
||||
text.format_parametrized(content, param if param is not None else "")
|
||||
|
||||
if not hide_continue:
|
||||
@ -732,3 +751,27 @@ async def confirm_signverify(
|
||||
ButtonRequestType.Other,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# TODO cleanup @ redesign
|
||||
async def confirm_timebounds_stellar(
|
||||
ctx: wire.GenericContext, start: int, end: int
|
||||
) -> None:
|
||||
text = Text("Confirm timebounds", ui.ICON_SEND, ui.GREEN)
|
||||
text.bold("Valid from (UTC):")
|
||||
if start:
|
||||
text.normal(str(start))
|
||||
else:
|
||||
text.mono("[no restriction]")
|
||||
|
||||
text.bold("Valid to (UTC):")
|
||||
if end:
|
||||
text.normal(str(end))
|
||||
else:
|
||||
text.mono("[no restriction]")
|
||||
|
||||
await raise_if_cancelled(
|
||||
interact(
|
||||
ctx, Confirm(text), "confirm_timebounds", ButtonRequestType.ConfirmOutput
|
||||
)
|
||||
)
|
||||
|
@ -67,12 +67,15 @@ ADDRESS_N = parse_path(stellar.DEFAULT_BIP32_PATH)
|
||||
NETWORK_PASSPHRASE = "Test SDF Network ; September 2015"
|
||||
|
||||
|
||||
def _create_msg() -> messages.StellarSignTx:
|
||||
def _create_msg(memo=False) -> messages.StellarSignTx:
|
||||
kwargs = {"memo_type": 0}
|
||||
if memo:
|
||||
kwargs = {"memo_type": 1, "memo_text": "hi"}
|
||||
return messages.StellarSignTx(
|
||||
source_account="GAK5MSF74TJW6GLM7NLTL76YZJKM2S4CGP3UH4REJHPHZ4YBZW2GSBPW",
|
||||
fee=100,
|
||||
sequence_number=0x100000000,
|
||||
memo_type=0,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
@ -209,6 +212,131 @@ def test_sign_tx_payment_op_custom_asset12(client):
|
||||
)
|
||||
|
||||
|
||||
# testcase added for UI code coverage, may not normally make sense
|
||||
def test_sign_tx_allow_trust_op(client):
|
||||
|
||||
op = messages.StellarAllowTrustOp()
|
||||
op.is_authorized = True
|
||||
op.trusted_account = "GBOVKZBEM2YYLOCDCUXJ4IMRKHN4LCJAE7WEAEA2KF562XFAGDBOB64V"
|
||||
op.asset_type = 1
|
||||
op.asset_code = "X"
|
||||
tx = _create_msg(memo=True)
|
||||
|
||||
response = stellar.sign_tx(client, tx, [op], ADDRESS_N, NETWORK_PASSPHRASE)
|
||||
|
||||
assert (
|
||||
b64encode(response.signature)
|
||||
== b"JLLwaDvPAomcDnljhlM3LF6WQvlQzI+V/afI95X41HGRYgwbYrxXOplzzdBhbRxd09VxDkb3nQ271l6MEqn/CQ=="
|
||||
)
|
||||
|
||||
|
||||
# testcase added for UI code coverage, may not normally make sense
|
||||
def test_sign_tx_change_trust_op(client):
|
||||
|
||||
op = messages.StellarChangeTrustOp()
|
||||
op.limit = 500111000
|
||||
op.source_account = "GBOVKZBEM2YYLOCDCUXJ4IMRKHN4LCJAE7WEAEA2KF562XFAGDBOB64V"
|
||||
|
||||
op.asset = messages.StellarAssetType(
|
||||
type=2,
|
||||
code="ABCDEFGHIJKL",
|
||||
issuer="GAUYJFQCYIHFQNS7CI6BFWD2DSSFKDIQZUQ3BLQODDKE4PSW7VVBKENC",
|
||||
)
|
||||
tx = _create_msg()
|
||||
|
||||
response = stellar.sign_tx(client, tx, [op], ADDRESS_N, NETWORK_PASSPHRASE)
|
||||
|
||||
assert (
|
||||
b64encode(response.signature)
|
||||
== b"OZdDO/qW8o/xbV6nZaDM/D7z9/fqbrk+P4lSzzCqeD3C8nGOg+Jl33JqHek0zNNOW9Pn+tPpfdoQnuZWJzocCw=="
|
||||
)
|
||||
|
||||
|
||||
# testcase added for UI code coverage, may not normally make sense
|
||||
def test_sign_tx_passive_offer_op(client):
|
||||
|
||||
op = messages.StellarCreatePassiveOfferOp()
|
||||
op.selling_asset = messages.StellarAssetType(
|
||||
type=2,
|
||||
code="ABCDEFGHIJKL",
|
||||
issuer="GAUYJFQCYIHFQNS7CI6BFWD2DSSFKDIQZUQ3BLQODDKE4PSW7VVBKENC",
|
||||
)
|
||||
op.buing_asset = messages.StellarAssetType(
|
||||
type=1,
|
||||
code="X",
|
||||
issuer="GAUYJFQCYIHFQNS7CI6BFWD2DSSFKDIQZUQ3BLQODDKE4PSW7VVBKENC",
|
||||
)
|
||||
op.amount = 4
|
||||
op.price_n = 5
|
||||
op.price_d = 6
|
||||
|
||||
tx = _create_msg()
|
||||
|
||||
response = stellar.sign_tx(client, tx, [op], ADDRESS_N, NETWORK_PASSPHRASE)
|
||||
|
||||
assert (
|
||||
b64encode(response.signature)
|
||||
== b"78/YNz3U4PZmKubPgl+PIm/Jr8omISshJtwJuqXp4rSo9bAxxYRvMSa2IGFIy9PfIe2kDxnqSajTwiUFGOOtAw=="
|
||||
)
|
||||
|
||||
|
||||
# testcase added for UI code coverage, may not normally make sense
|
||||
def test_sign_tx_manage_offer_op(client):
|
||||
|
||||
op = messages.StellarManageOfferOp()
|
||||
op.selling_asset = messages.StellarAssetType(
|
||||
type=2,
|
||||
code="ABCDEFGHIJKL",
|
||||
issuer="GAUYJFQCYIHFQNS7CI6BFWD2DSSFKDIQZUQ3BLQODDKE4PSW7VVBKENC",
|
||||
)
|
||||
op.buing_asset = messages.StellarAssetType(
|
||||
type=1,
|
||||
code="X",
|
||||
issuer="GAUYJFQCYIHFQNS7CI6BFWD2DSSFKDIQZUQ3BLQODDKE4PSW7VVBKENC",
|
||||
)
|
||||
op.amount = 4
|
||||
op.price_n = 5
|
||||
op.price_d = 6
|
||||
op.offer_id = 1337
|
||||
|
||||
tx = _create_msg()
|
||||
|
||||
response = stellar.sign_tx(client, tx, [op], ADDRESS_N, NETWORK_PASSPHRASE)
|
||||
|
||||
assert (
|
||||
b64encode(response.signature)
|
||||
== b"tkCta8G5dmxOVYJ+/mGve1lzNyIKZB83DwWdZH2A6zGI7KF2VYGG8RNHmlMpMqeBFcit9o+/Ss+IAQ86CZBTAw=="
|
||||
)
|
||||
|
||||
|
||||
# testcase added for UI code coverage, may not normally make sense
|
||||
def test_sign_tx_path_payment_op(client):
|
||||
|
||||
op = messages.StellarPathPaymentOp()
|
||||
op.send = messages.StellarAssetType(
|
||||
type=1,
|
||||
code="X",
|
||||
issuer="GAUYJFQCYIHFQNS7CI6BFWD2DSSFKDIQZUQ3BLQODDKE4PSW7VVBKENC",
|
||||
)
|
||||
op.send_max = 50000
|
||||
op.destination_account = "GAUYJFQCYIHFQNS7CI6BFWD2DSSFKDIQZUQ3BLQODDKE4PSW7VVBKENC"
|
||||
op.destination_asset = messages.StellarAssetType(
|
||||
type=1,
|
||||
code="X",
|
||||
issuer="GAUYJFQCYIHFQNS7CI6BFWD2DSSFKDIQZUQ3BLQODDKE4PSW7VVBKENC",
|
||||
)
|
||||
op.destination_amount = 6667
|
||||
|
||||
tx = _create_msg()
|
||||
|
||||
response = stellar.sign_tx(client, tx, [op], ADDRESS_N, NETWORK_PASSPHRASE)
|
||||
|
||||
assert (
|
||||
b64encode(response.signature)
|
||||
== b"KMUKub6SRXS4R86ux74bSSJXtMMQ1tEWhxc71jcAozKRX/db5dJC7oBVSeijSCT+hJOG/wTw6Jd1UBkJC0JgCw=="
|
||||
)
|
||||
|
||||
|
||||
def test_sign_tx_set_options(client):
|
||||
"""Set inflation destination"""
|
||||
|
||||
@ -278,6 +406,30 @@ def test_sign_tx_set_options(client):
|
||||
# db6adf70eaf10621396a4a4db27597f323e000ea5c95b6a356a5d469730d78bd34d29d4e845862d39d2dd7e18d469a123727d5b0918dbed948086a47e24b0301
|
||||
)
|
||||
|
||||
op = messages.StellarSetOptionsOp()
|
||||
op.signer_type = 1
|
||||
op.signer_key = bytes.fromhex(
|
||||
"72187adb879c414346d77c71af8cce7b6eaa57b528e999fd91feae6b6418628e"
|
||||
)
|
||||
op.signer_weight = 0
|
||||
|
||||
tx = _create_msg()
|
||||
response = stellar.sign_tx(client, tx, [op], ADDRESS_N, NETWORK_PASSPHRASE)
|
||||
assert (
|
||||
b64encode(response.signature)
|
||||
== b"fvZ8rzZPkJwa1lsr4/z/wXZLT6cBwFmwY861nmEnhy57Stw1dxCipo5PJeXk/EhyLxlAwL99+m7qWtdW38EZBg=="
|
||||
)
|
||||
|
||||
op = messages.StellarSetOptionsOp()
|
||||
op.clear_flags = 4
|
||||
|
||||
tx = _create_msg()
|
||||
response = stellar.sign_tx(client, tx, [op], ADDRESS_N, NETWORK_PASSPHRASE)
|
||||
assert (
|
||||
b64encode(response.signature)
|
||||
== b"ZObGPxqyavv1fjXnlVQJM35FZu8zkJBAWkuOaI+KqyHTB8rOI4oXb27tohAnxiAzMn7e/tiNfqZwzQQS6RqmBg=="
|
||||
)
|
||||
|
||||
|
||||
def test_sign_tx_timebounds(client):
|
||||
op = messages.StellarSetOptionsOp()
|
||||
|
Loading…
Reference in New Issue
Block a user