Skip to content

Commit f88ed25

Browse files
authored
Merge pull request #55 from kristapsk/wallet-seed-restore
LND wallet restore from seed
2 parents 67d2815 + 2950c3a commit f88ed25

File tree

3 files changed

+128
-3
lines changed

3 files changed

+128
-3
lines changed

src/ui/c/ui.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ extern void nm_keyboard_popon(lv_obj_t *input)
123123
lv_obj_update_layout(tabview); /* make sure sizes are recalculated */
124124
lv_obj_set_height(tabview, NM_DISP_VER - lv_obj_get_height(virt_keyboard));
125125
lv_obj_clear_flag(virt_keyboard, LV_OBJ_FLAG_HIDDEN);
126+
lv_obj_move_foreground(virt_keyboard);
126127
lv_obj_scroll_to_view_recursive(input, LV_ANIM_OFF);
127128
}
128129

src/ui/lightning.zig

Lines changed: 116 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//! all functions assume LVGL is init'ed and ui mutex is locked on entry.
33

44
const std = @import("std");
5+
const ascii = std.ascii;
56

67
const comm = @import("../comm.zig");
78
const lvgl = @import("lvgl.zig");
@@ -88,6 +89,7 @@ var tab: struct {
8889
topwin: lvgl.Window,
8990
arena: *std.heap.ArenaAllocator, // all non-UI elements are alloc'ed here
9091
mnemonic: ?types.StringList = null, // 24 words genseed result
92+
restore_input: ?lvgl.TextArea = null, // restore-from-seed UI bits (optional)
9193
pairing: ?struct {
9294
// app_description key to connection URL.
9395
// keys are static, values are heap-alloc'ed in `setupPairing`.
@@ -183,9 +185,13 @@ pub fn initTabPanel(allocator: std.mem.Allocator, cont: lvgl.Container) !void {
183185
tab.nowallet.resizeToMax();
184186
tab.nowallet.setPad(10, .row, .{});
185187
_ = try lvgl.Label.new(tab.nowallet, "lightning wallet is uninitialized.\ntap the button to start the setup process.", .{});
186-
const btn = try lvgl.TextButton.new(tab.nowallet, "SETUP NEW WALLET");
187-
btn.setWidth(lvgl.sizePercent(50));
188-
_ = btn.on(.click, nm_lnd_setup_click, null);
188+
const btn_new = try lvgl.TextButton.new(tab.nowallet, "SETUP NEW WALLET");
189+
btn_new.setWidth(lvgl.sizePercent(60));
190+
_ = btn_new.on(.click, nm_lnd_setup_click, null);
191+
192+
const btn_restore = try lvgl.TextButton.new(tab.nowallet, "RESTORE FROM SEED");
193+
btn_restore.setWidth(lvgl.sizePercent(60));
194+
_ = btn_restore.on(.click, nm_lnd_restore_click, null);
189195
}
190196

191197
// locked wallet state
@@ -305,6 +311,10 @@ export fn nm_lnd_setup_click(_: *lvgl.LvEvent) void {
305311
startSeedSetup() catch |err| logger.err("startSeedSetup: {any}", .{err});
306312
}
307313

314+
export fn nm_lnd_restore_click(_: *lvgl.LvEvent) void {
315+
startRestoreSetup() catch |err| logger.err("startRestoreSetup: {any}", .{err});
316+
}
317+
308318
export fn nm_lnd_pair_click(_: *lvgl.LvEvent) void {
309319
startPairing() catch |err| logger.err("startPairing: {any}", .{err});
310320
}
@@ -327,6 +337,55 @@ fn startSeedSetup() !void {
327337
try comm.pipeWrite(.lightning_genseed);
328338
}
329339

340+
fn startRestoreSetup() !void {
341+
const win = try lvgl.Window.newTop(60, " " ++ symbol.LightningBolt ++ " RESTORE FROM SEED");
342+
try tab.initSetup(win);
343+
errdefer tab.destroySetup(); // TODO: display an error instead
344+
345+
const wincont = win.content().flex(.column, .{});
346+
347+
_ = try lvgl.Label.new(wincont,
348+
\\enter the 24-word mnemonic seed below, separated by spaces.
349+
\\the seed is case-insensitive, and words may be separated
350+
\\by multiple spaces/newlines.
351+
, .{});
352+
353+
var ta = try lvgl.TextArea.new(wincont, .{ .oneline = false });
354+
ta.setWidth(lvgl.sizePercent(100));
355+
ta.setHeight(lvgl.sizePercent(55));
356+
ta.setPlaceholderText("word1 word2 ... word24");
357+
_ = ta.on(.all, nm_lnd_restore_input_event, null);
358+
tab.seed_setup.?.restore_input = ta;
359+
360+
const btnrow = try lvgl.FlexLayout.new(wincont, .row, .{
361+
.width = lvgl.sizePercent(100),
362+
.height = .content,
363+
.main = .space_between,
364+
});
365+
366+
const cancel_btn = try lvgl.TextButton.new(btnrow, "CANCEL");
367+
cancel_btn.setWidth(lvgl.sizePercent(30));
368+
cancel_btn.addStyle(lvgl.nm_style_btn_red(), .{});
369+
_ = cancel_btn.on(.click, nm_lnd_setup_finish, null);
370+
371+
const proceed_btn = try lvgl.TextButton.new(btnrow, "PROCEED " ++ symbol.Right);
372+
proceed_btn.setWidth(lvgl.sizePercent(30));
373+
_ = proceed_btn.on(.click, nm_lnd_restore_proceed, null);
374+
}
375+
376+
export fn nm_lnd_restore_input_event(e: *lvgl.LvEvent) callconv(.C) void {
377+
const code = e.code();
378+
if (code == .click or code == .press or code == .focus) {
379+
if (tab.seed_setup) |ss| if (ss.restore_input) |ta| widget.keyboardOn(ta);
380+
} else if (code == .defocus or code == .ready or code == .cancel) {
381+
widget.keyboardOff();
382+
}
383+
}
384+
385+
export fn nm_lnd_restore_proceed(_: *lvgl.LvEvent) void {
386+
restoreProceed() catch |err| logger.err("restoreProceed: {any}", .{err});
387+
}
388+
330389
/// similar to `startSeedSetup` but used when seed is already setup,
331390
/// at any time later.
332391
/// reuses tab.seed_setup elements for the same purpose.
@@ -396,6 +455,60 @@ export fn nm_lnd_setup_commit_seed(_: *lvgl.LvEvent) void {
396455
setupCommitSeed() catch |err| logger.err("setupCommitSeed: {any}", .{err});
397456
}
398457

458+
fn restoreProceed() !void {
459+
errdefer tab.destroySetup(); // TODO: display an error instead
460+
if (tab.seed_setup == null) return error.LightningSetupInactive;
461+
const ss = &tab.seed_setup.?;
462+
if (ss.restore_input == null) return error.LightningSetupInactive;
463+
464+
const raw = ss.restore_input.?.text(); // expected: []const u8
465+
466+
// Tokenize by any whitespace; ignore multiple spaces/newlines/tabs.
467+
var it = std.mem.tokenizeAny(u8, raw, " \t\r\n");
468+
469+
// Collect up to 24 words.
470+
var words: [24][]const u8 = undefined;
471+
var n: usize = 0;
472+
while (it.next()) |w| {
473+
if (n >= 24) {
474+
try showRestoreError("too many words: expected 24.");
475+
return;
476+
}
477+
words[n] = w;
478+
n += 1;
479+
}
480+
if (n != 24) {
481+
try showRestoreError("invalid seed length: expected 24 words.");
482+
return;
483+
}
484+
485+
const alloc = ss.arena.allocator();
486+
var normalized = try alloc.alloc([]const u8, 24);
487+
for (words, 0..) |w, i| {
488+
var tmp = try alloc.alloc(u8, w.len);
489+
for (w, 0..) |ch, j| tmp[j] = ascii.toLower(ch);
490+
normalized[i] = tmp;
491+
}
492+
493+
ss.mnemonic = try types.StringList.fromUnowned(alloc, normalized);
494+
495+
widget.keyboardOff();
496+
try setupCommitSeed();
497+
}
498+
499+
fn showRestoreError(msg: [*:0]const u8) !void {
500+
// Reuse modal helper for a simple “OK”.
501+
const ok: [*:0]const u8 = "OK";
502+
widget.modal(" " ++ symbol.Warning ++ " INVALID SEED", msg, &.{ok}, restoreErrModalCallback) catch |err| {
503+
logger.err("showRestoreError: modal: {any}", .{err});
504+
};
505+
}
506+
507+
fn restoreErrModalCallback(_: usize) align(@alignOf(widget.ModalButtonCallbackFn)) void {
508+
// nothing; modal close already handled in widget.modal callback path
509+
preserve_main_active_tab();
510+
}
511+
399512
fn setupCommitSeed() !void {
400513
errdefer tab.destroySetup(); // TODO: display an error instead
401514
if (tab.seed_setup == null) {

src/ui/lvgl.zig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,16 @@ pub const TextArea = struct {
811811
}
812812
}
813813

814+
pub fn setOneLine(self: TextArea, enable: bool) void {
815+
lv_textarea_set_one_line(self.lvobj, enable);
816+
}
817+
818+
/// Placeholder text shown when the textarea is empty.
819+
/// `txt` is heap-duplicated by LVGL and owned by the textarea object.
820+
pub fn setPlaceholderText(self: TextArea, txt: [:0]const u8) void {
821+
lv_textarea_set_placeholder_text(self.lvobj, txt.ptr);
822+
}
823+
814824
/// `text` arg is heap-duplicated by LVGL's alloc and owned by this text area object.
815825
pub fn setText(self: TextArea, txt: [:0]const u8) void {
816826
lv_textarea_set_text(self.lvobj, txt.ptr);
@@ -1267,6 +1277,7 @@ extern fn lv_textarea_set_max_length(obj: *LvObj, n: u32) void;
12671277
extern fn lv_textarea_set_one_line(obj: *LvObj, enable: bool) void;
12681278
extern fn lv_textarea_set_password_mode(obj: *LvObj, enable: bool) void;
12691279
extern fn lv_textarea_set_text(obj: *LvObj, text: [*:0]const u8) void;
1280+
extern fn lv_textarea_set_placeholder_text(obj: *LvObj, text: [*:0]const u8) void;
12701281

12711282
extern fn lv_dropdown_create(parent: *LvObj) ?*LvObj;
12721283
extern fn lv_dropdown_set_text(obj: *LvObj, text: ?[*:0]const u8) void;

0 commit comments

Comments
 (0)