11import lvgl as lv
2+ import platform
3+ from helpers import conv_time
24from .prompt import Prompt
35from ..common import add_label , format_addr
46from ..decorators import on_release
57
6-
78class TransactionScreen (Prompt ):
89 def __init__ (self , title , meta ):
910 self .default_asset = meta .get ("default_asset" , "BTC" )
@@ -54,9 +55,15 @@ def __init__(self, title, meta):
5455 style_warning .text .color = lv .color_hex (0xFF9A00 )
5556 style_warning .text .font = lv .font_roboto_22
5657
58+ style_gray = lv .style_t ()
59+ lv .style_copy (style_gray , self .message .get_style (0 ))
60+ style_gray .text .color = lv .color_hex (0x999999 )
61+ style_gray .text .font = lv .font_roboto_22
62+
5763 self .style = style
5864 self .style_secondary = style_secondary
5965 self .style_warning = style_warning
66+ self .style_gray = style_gray
6067
6168 num_change_outputs = 0
6269 for out in meta ["outputs" ]:
@@ -66,13 +73,14 @@ def __init__(self, title, meta):
6673 continue
6774 obj = self .show_output (out , obj )
6875
69- if meta .get ("fee" ):
76+ fee = meta .get ("fee" )
77+ if fee :
7078 if send_amount > 0 :
71- fee_percent = meta [ " fee" ] * 100 / send_amount
72- fee_txt = "%d satoshi (%.2f%%)" % (meta [ " fee" ] , fee_percent )
79+ fee_percent = fee * 100 / send_amount
80+ fee_txt = "%d satoshi (%.2f%%)" % (fee , fee_percent )
7381 # back to wallet
7482 else :
75- fee_txt = "%d satoshi" % (meta [ " fee" ] )
83+ fee_txt = "%d satoshi" % (fee , )
7684 fee = add_label ("Fee: " + fee_txt , scr = self .page )
7785 fee .set_style (0 , style )
7886 fee .align (obj , lv .ALIGN .OUT_BOTTOM_MID , 0 , 30 )
@@ -85,7 +93,8 @@ def __init__(self, title, meta):
8593 self .warning .set_style (0 , style_warning )
8694 self .warning .align (obj , lv .ALIGN .OUT_BOTTOM_MID , 0 , 30 )
8795
88- lbl = add_label ("%d INPUTS" % len (meta ["inputs" ]), scr = self .page2 )
96+ meta_inputs_len = len (meta ["inputs" ])
97+ lbl = add_label ("%d %s" % (meta_inputs_len , "INPUT" if meta_inputs_len == 1 else "INPUTS" ), scr = self .page2 )
8998 lbl .align (self .page2 , lv .ALIGN .IN_TOP_MID , 0 , 30 )
9099 obj = lbl
91100 for i , inp in enumerate (meta ["inputs" ]):
@@ -101,6 +110,33 @@ def __init__(self, title, meta):
101110 lbl .align (idxlbl , lv .ALIGN .IN_TOP_LEFT , 0 , 0 )
102111 lbl .set_x (60 )
103112
113+ # https://learnmeabitcoin.com/technical/transaction/input/sequence
114+ sequence = inp .get ("sequence" )
115+ if sequence is not None :
116+ seqlbl = lv .label (self .page2 )
117+ is_relative_locktime = False
118+ if sequence == 0xFFFFFFFF :
119+ seq_text = "Locktime disabled"
120+ elif sequence == 0xFFFFFFFE :
121+ seq_text = 'RBF "disabled"'
122+ elif sequence == 0xFFFFFFFD :
123+ seq_text = "RBF enabled"
124+ elif meta ["tx_version" ] >= 2 and sequence <= 0xEFFFFFFF and (sequence | 0x0040FFFF == 0x0040FFFF ):
125+ seq_text = "Relative Locktime"
126+ is_relative_locktime = True
127+ else :
128+ seq_text = "Non-standard"
129+ seqlbl .set_text ("Seq: 0x%08X (%s)" % (sequence , seq_text ))
130+ seqlbl .set_style (0 , style_gray )
131+ seqlbl .align (lbl , lv .ALIGN .OUT_BOTTOM_LEFT , 0 , 5 )
132+ seqlbl .set_x (60 )
133+ lbl = seqlbl
134+ if is_relative_locktime :
135+ rltlbl = lv .label (self .page2 )
136+ rltlbl .set_style (0 , style_gray )
137+ rltlbl .set_text (self .relative_locktime_to_text (sequence ))
138+ rltlbl .align (lbl , lv .ALIGN .OUT_BOTTOM_LEFT , 15 , 5 )
139+ lbl = rltlbl
104140 if inp .get ("sighash" , "" ):
105141 shlbl = lv .label (self .page2 )
106142 shlbl .set_long_mode (lv .label .LONG .BREAK )
@@ -112,7 +148,8 @@ def __init__(self, title, meta):
112148 lbl = shlbl
113149 obj = lbl
114150
115- lbl = add_label ("%d OUTPUTS" % len (meta ["outputs" ]), scr = self .page2 )
151+ meta_outputs_len = len (meta ["outputs" ])
152+ lbl = add_label ("%d %s" % (len (meta ["outputs" ]), "OUTPUT" if meta_outputs_len == 1 else "OUTPUTS" ), scr = self .page2 )
116153 lbl .align (self .page2 , lv .ALIGN .IN_TOP_MID , 0 , 0 )
117154 lbl .set_y (obj .get_y () + obj .get_height () + 30 )
118155 for i , out in enumerate (meta ["outputs" ]):
@@ -149,11 +186,47 @@ def __init__(self, title, meta):
149186 warning .set_x (60 )
150187 lbl = warning
151188
152- if meta . get ( " fee" ) :
189+ if fee :
153190 idxlbl = lv .label (self .page2 )
154191 idxlbl .set_text ("Fee: " + fee_txt )
155192 idxlbl .align (lbl , lv .ALIGN .OUT_BOTTOM_MID , 0 , 30 )
156193 idxlbl .set_x (30 )
194+ lbl = idxlbl
195+
196+ verlbl = lv .label (self .page2 )
197+ verlbl .set_style (0 , style_gray )
198+ verlbl .set_text ("Transaction Version: %d" % meta ["tx_version" ])
199+ # If the fee label is present, we want to be close to it. Otherwise, we want a larger margin.
200+ verlbl .align (lbl , lv .ALIGN .OUT_BOTTOM_LEFT , 0 , 5 if fee else 30 )
201+ verlbl .set_x (30 )
202+ locktime = meta ["locktime" ]
203+ if all (inp ["sequence" ] == 0xFFFFFFFF for inp in meta ["inputs" ]):
204+ # Locktime disabled. See: https://learnmeabitcoin.com/technical/transaction/input/sequence
205+ ltlbl = lv .label (self .page2 )
206+ ltlbl .set_style (0 , style_gray )
207+ ltlbl .set_text ("Locktime: %d" % locktime )
208+ ltlbl .align (verlbl , lv .ALIGN .OUT_BOTTOM_LEFT , 0 , 5 )
209+ ltdiabledlbl = lv .label (self .page2 )
210+ ltdiabledlbl .set_style (0 , style_warning )
211+ ltdiabledlbl .set_text ("All inputs have locktime disabled!" if meta ["inputs" ] else "No inputs!" )
212+ ltdiabledlbl .align (ltlbl , lv .ALIGN .OUT_BOTTOM_LEFT , 15 , 5 )
213+ elif locktime <= 499999999 :
214+ # Block height. See: https://learnmeabitcoin.com/technical/transaction/locktime
215+ ltlbl = lv .label (self .page2 )
216+ ltlbl .set_style (0 , style_gray )
217+ ltlbl .set_text ("Locktime: %d (Block Height)" % locktime )
218+ ltlbl .align (verlbl , lv .ALIGN .OUT_BOTTOM_LEFT , 0 , 5 )
219+ else :
220+ # Block timestamp. See: https://learnmeabitcoin.com/technical/transaction/locktime
221+ ltlbl = lv .label (self .page2 )
222+ ltlbl .set_style (0 , style_gray )
223+ ltlbl .set_text ("Locktime: %d (Timestamp)" % locktime )
224+ ltlbl .align (verlbl , lv .ALIGN .OUT_BOTTOM_LEFT , 0 , 5 )
225+ mp_time = conv_time (locktime )
226+ ltdatelbl = lv .label (self .page2 )
227+ ltdatelbl .set_style (0 , style_gray )
228+ ltdatelbl .set_text ("%04d-%02d-%02d %02d:%02d:%02d UTC" % mp_time [:6 ])
229+ ltdatelbl .align (ltlbl , lv .ALIGN .OUT_BOTTOM_LEFT , 15 , 5 )
157230
158231 self .toggle_details ()
159232
@@ -194,4 +267,30 @@ def show_output(self, out, obj):
194267 warning .set_style (0 , self .style_warning )
195268 warning .align (obj , lv .ALIGN .OUT_BOTTOM_MID , 0 , 10 )
196269 obj = warning
197- return obj
270+ return obj
271+
272+ def relative_locktime_to_text (self , sequence ):
273+ if sequence & 0x00400000 :
274+ # In units of 512 seconds
275+ rlt_total = (sequence & 0xFFFF ) * 512
276+ rlt_parts = [
277+ (amount , unit )
278+ for amount , unit in [
279+ (rlt_total // 86400 , "day" ),
280+ ((rlt_total // 3600 ) % 24 , "hour" ),
281+ ((rlt_total // 60 ) % 60 , "minute" ),
282+ (rlt_total % 60 , "second" ),
283+ ]
284+ if amount > 0
285+ ]
286+ # Break into 2 lines if there are too many parts
287+ rlt_lines_parts = [rlt_parts ] if len (rlt_parts ) < 4 else [rlt_parts [:3 ], rlt_parts [3 :]]
288+ return ",\n " .join (
289+ ", " .join (
290+ "%d %s%s" % (amount , unit , "" if amount == 1 else "s" )
291+ for amount , unit in rlt_line_parts
292+ )
293+ for rlt_line_parts in rlt_lines_parts
294+ )
295+ else :
296+ return "%d %s" % (sequence , "block" if sequence == 1 else "blocks" )
0 commit comments