From 403e145b306282669172ea8e26ef11f73e9653e4 Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 7 Jul 2026 10:49:03 -0500 Subject: [PATCH] feat(wallet): give new full-node wallets a BIP39 seed phrase + backup UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New full-node wallets now get a real 24-word recovery phrase, and users can reveal/back it up from Settings. - Pass -usemnemonic=1 to dragonxd. The daemon reads it only inside GenerateNewSeed() when a wallet has no seed yet, so it is inert on existing wallets (safe to pass unconditionally) and makes every freshly-created wallet mnemonic-backed — its phrase is then exportable via z_exportmnemonic and portable to SDXLite/ObsidianDragonLite. (Existing/legacy wallets are unaffected and keep using the chat identity's z_exportkey fallback.) - App::exportSeedPhrase() wraps z_exportmnemonic on the RPC worker with sodium_memzero wiping on every path; it flags the daemon's "not derived from a mnemonic" error as a legacy wallet rather than a failure. - New "Back up seed phrase" modal (Settings -> Backup & Data, full-node gated): reveals the phrase in a numbered word grid, copy (45s clipboard auto-clear) + save-to-file (0600), wipes the secret on every close path incl. dismiss-mid-fetch. Shared ui/windows/seed_display.h grid helper. - One-time nudge (settings flag seed_backup_reminded) toasts mnemonic-wallet users to back up their phrase; legacy wallets are not nagged. - 15 new strings translated into all 8 languages (additive, 957->972 keys); CJK subset font rebuilt for the new glyphs. Co-Authored-By: Claude Opus 4.8 (1M context) --- res/fonts/NotoSansCJK-Subset.ttf | Bin 522576 -> 532500 bytes res/lang/de.json | 15 +++++ res/lang/es.json | 15 +++++ res/lang/fr.json | 15 +++++ res/lang/ja.json | 15 +++++ res/lang/ko.json | 15 +++++ res/lang/pt.json | 15 +++++ res/lang/ru.json | 15 +++++ res/lang/zh.json | 15 +++++ src/app.cpp | 106 ++++++++++++++++++++++++++++++- src/app.h | 22 +++++++ src/app_network.cpp | 63 ++++++++++++++++++ src/config/settings.cpp | 2 + src/config/settings.h | 5 ++ src/daemon/embedded_daemon.cpp | 5 ++ src/ui/pages/settings_page.cpp | 11 +++- src/ui/windows/seed_display.h | 46 ++++++++++++++ src/util/i18n.cpp | 16 +++++ 18 files changed, 394 insertions(+), 2 deletions(-) create mode 100644 src/ui/windows/seed_display.h diff --git a/res/fonts/NotoSansCJK-Subset.ttf b/res/fonts/NotoSansCJK-Subset.ttf index e24cba4799d492ec1d42ba19829d2358a6ee274e..403b7979f695c6328e61bef9ca22ed7f35005601 100644 GIT binary patch delta 34107 zcmaI82S5~8_x^wG)Ma7my~Dy%r7gYpE-HwEy?4c~fV%eHQL&4?gDqlWFR>_$B5{=td=c` zVlzL>Ciz4Sk!OBZQE_VZ*76dPA3P!*$(Jaz4=SBKuAr?YLbsWLfQVoWy6Ly zPNguCkJn(o`$$6ryWMt>{N5bfUL(=Krfa*8VjB&rzaxfHj!Ct(b+vpjn$*lk#Un=-3jc>0H+Xr3QW`xI4qZ_kl_5X;o}9gE3N&!I+w$e;Ts^#=H*t zV2roJzfBqRw<+C%HyZaFF?eLf$jKwmkGx(L;;a7KsKFsHYOil-C(dBTRIR~`U0N-h zRP{ic1Yo}IeU9EOC-<7{h7rsloD|A=Tt{|daZoBMvnby5( zS{~Qkue(zBW!*udx=nTKv8=6IUbn1nN!^0Fd36)&#-qHTF1{|NPFojPr>Qfpyo5?oi)^%GGx5i_M-5Rkq ze5)@OkF9Q7owhV?xw>WEW);zNL+OUV4FMahH{i-&zi<8C^|kA3)~{SY zW_{)Q?(4g)Z?isUecJlO^|9+C*N3hTTCc%T%60G7y;=8o-PLuw)~#AMdflLPQR|%7 zzF2#0?b)?!*3PcES97Q4IF@5Ihimp?fjl*J(wQp73s+m}3teUo}a#fF2T~_6<%3Kw_ z3VqbdKUY3lIeX;{^qDKiuN=2>Ad1?qL>IX-Y^CE$yOp%!z>1|S7Oa>+w4(ot-YYsT zf3kf3^1x+3E}Op$9rsclXtgw9sZDi5^}Fg%tLIk_t8QN%Rc&7_EqS$M{gM?+mMp1S z(th!)<)w=sV!6Bc>f)~!pILl(@vy~x7W*w$FLqp%T$NpwqN}o9_+a6cg&P(QB3iI= zLD%_x=R420ncFb;)!b)uf1dkr?)AA}%sn*sz?`}{o91+z(`HV}oP;@XbHe8Y%r2f? zG&^>7%xrnq`&pM~?U^-w)}&d+Gf&T)H*@yP2{Xsdte9CkGk2!fOqUto&$vC~{EX!@ z=FX^?(Pl>GjIr(xrktK~Xv)?po2M+E zQZ}Xaua(HD~<$%hrm2E3?D|0HdE3+ztE3GR{E2-jH#gmE$SPoU}#j>Je zPQ~~NT}7|*i{;14mzFOnpHn`yd_uXdd~EsX@{#3b<$cP#mbWi&S6)<}UG6`6)97`h zmyDh>I&`%6Xphl$qkbH9W7O4AJ4dY^HF?yCQM&k%{YJbW@ovN~Bc6}AJ>u4gPq6G5 zv6*PZwh>!KY#y<31d?vV^bwOsj2TfeqI|@_5gkXg8IeRZ{QU6!!+Q_U9rk?K(_v4B zJsCQCXxgWbv=l|3x`q3nLyfwIkI zYs#w2=9kUam5nQ_DC<{NT$WdsT^3O0U1mJ!-JmyvUJY74Xz9S$1CJ98oIP;H!0`iz z4=fwlzyIt0%lhm3NA!;D^=mt;b{6fVc0#GXbbINR(v797OIMaIDxFYTQ98V|PieQ( ztkQ_m@Y0~tfRZ~U$4fpdX;Bhg5>XOX5>O(RaBbhT{k`q8wz_d`ySG&o|6cra@#f+U z#cPX~7cVZJUp%LHX7R-0@x>{{KCQoLeXwkXGPP;8b9xYyMU9{moZ24{WY#u58Zd-^ss~e?9+F{ulYD^H1g<%io#b zF28Mla(-yOCSRRzm2a8%Ti%Pj`+5EHdgpG*U7Nc&cTw*A+&Q_ka&@`oxjwmGx%RoH zxyHGQoL_UE=RC~$A?J9`(VRm$F*%Vr+8lNE?(A*ZmD$6y`(<~_w9B-})EQ+;nUwJ? z1xuIq)SO>lMW}XOR7m)l~kRyIB9Ou?4%h< z6O+azjZV@ec_n!yIVRa9StPzme2{oN@n~XQ;?~62L~Wu^!tR9a32PF@CR8SjP8gEV zJAQgxOq^Ssd7Md{9J?>JCU$vjR;)JWRm_o?12H>d21PeUzm5Ji`eF2i=(EwfL(vt{ z!=k%HXGEJu8%Ham#zwx2{5|qirMA}DOjW`~W7k(`KaCmw6 z$ne48#oCA3Gul(y1KK^>YV888zg7;L95yzrAS^2^JS;RUD9k6!E6g>_IP^v6#n7Xn zi$W)db_>l4%?M2mwF-F~@@>e)kTTN{XQPpda~MCJqjSWnzKXZ!jTmXk++`09Z#Yk#@WaKwlI9nC5PTG&wy*O!6)$XJLoaB#v>FHwA0M~H7c>hB!V$)sr!{>*RkL8#D zx%IK^7|uI^R$Fr7=&Iu>S2$q{YTxI{zy8m`{PX{;|Fy>7!(qKRe$3wEDPM3^?g>^^ zbJ7*M2o}XkS$o!*b!9zRFV=?*Wy9GzMH)N64zf$^3VXz!uvhF2`-A<-8FMPmnzQBX zxz1cyt|!-<8^8_X26IEXDsC~igxkbz;m&bi@?m^9U%`*zC-KwxIs9CHKEH@x!f)U= z@%8+t{6YRGe~drQpAt-Uf`xERxFL=fE5#Y&Y;m5rU91)L5-BPamGY1BL-{Agn7>@W zw><~}xd0<&<^o!SBY28H5r_R8o_?BWkMG^!9uW_OF5oWID*_c@9nhgbassgcPc+g@ z@ELf9RB;8kkIOh%UJH;q3X~~^gL?3e$S4qW1bYF(s8oPZJio+)HeejUK9wUtTc#3- z1(l$YNM{C5%$9)fiOd5)TL8-}FtV_~t-})AmMe&?;Hgz-fG}Hs4qg%2;24`NM7F5I z7Q+iWH3X^x2*0CDc4;}0O|yw^1$gt&2or>l8J)T5s^tmp-?Iem1`4-!n+eiSOC-& z*$JE?is}aL6Gg+B7!U9*QS4(KQQRS-1T&(THPGAtvn!>?GT##VzyqVda#CP-j5;y=+1K{cZjM7VIT}Smu@6lmIqL_JRXB)dvF26X~?|F4E#*A z8VR*#E;vY3gGOt#pavWzT8k`S3&YmIh;<0(dKXZO2a`;qjS4UgPc(3FQwN}1j}Khy zn=Ju4lFj=;Bhi+@0IF>52@sZT?f_nFL)LB|NwmWe^dZ`53s7M#zH7(hIS!51!I8Qr zcm`|*mJ#g^1;~{>MgUIixsFFt)U_A+qT35k_F>1qa-vV{0OZ||Y~R0?sNN5tq62wg z6M$i#eogcl^5U}-LX!vW$`!(YK4RYkF7tuASave@yKSFc^2Hq6FMxtA#i2v;6= zPZ5e|(cmu8b2Rh|>U;sYf7KKHj_+3p$?KCuZ*-94?EnC2|2RkV4jK3-D*6+B!Fv^0 zOw<4m8X&tKmN&Klw=ruqnHUQMSBY``Fl)tuRm6mJaF>|4ftb{vm>f$?VNcA+6#Pa^ zd77B3V{#B3H3vn?iOH=LL~ z8gv*#%yA1brVwx&qJ`aic z1rYPECLIfSK&)9lvEXoGq0Yp#B3Oa>z;?tUupa48EGmdtw1QYnJh9jj#Ntj7i*F#7 z0B;g^5lgBjmJCmmUlU82K`hk_ttS#Ib_X|!wYf#CZ4Krn z2M{ZTO6?$j2L{3bjOpkJJ|WhrH8@YKa~Gg{j+xWJppjVDQN+4w0CslI2WN@($OEXn z=X7Gd62ULTdRGwZgLwCW@qMEK4DE-8`=3@B>`5R$<;;2UB?;LOmT z;3s0ken$L@&!F5Meo_1h6 z+u_9Sz}Rogzzt&G%|QI`qOrTE=-x$Q_Z7r`5QsfsU>va@QPIOtVn4y!M`-+Kc=QC7 zJb6s)>2hMvCK7vIPVAQ}#C|Ox_S-9Bzc&(l`Ht8t^c%17{nnA#AGi$PA*}CFe*;3Q z=fPWIjo*`wql1`#UqKu1n@OBqC2I4I$1Q=X-V|&MTWZ@7}~| z#u4YUMMs?P8RGnYCeHr`aeIIQT>K>B64nrx2nUmf6PH{_T*^Y?QokZDZ2@uVI#`&m-&LYtaZd?Lx~)mkh_GqyivsEmlM~#KXC;;h-)#FxR%)8s-CzaDAPKQxMEb) zW+HKI7ZFziuS&NO*KR&>?Q4naFrB!LQN(p}CayCht_zOuI*7P#CyDFfNt~{y7jeCu zi0d6lTptDag}A=I6W8xCas96nH()z)gRs90QV;%~xFL6l8>%O6I4T?wOWep-#EsfT zTsh>f&=ObKg}5=$!?-93f0&$B1h+90ExFu+)IszbMOD__)3kJy|E{8o4{s-X$!)&H3;$FIs|-9+_sLyZC3$Uw*xt_ z(+)t&T2xx+4dB%-RJeN;aeH9Y-tolkOC;_S0_Ta_UqM{GhPVSz;nR5HKFcBQU=VSK z+=)A^1iOhlGLpEX$cJOK#2pU?I(T%V89>3wjl`XT^`GZ~OT?W%OWYZR3_;g^WtMR<0xfw)UgiMtGsze3p+?Ef0!yNYA3HWGL31#vg5h`VV9_7Hcg zGjX?(Pj}GPcP@zkU3iWW4tIYpaX+je?!jloJ^YTi$MEb)6>(4T{qzUop25TC`-uAm zaeD#Pe*J>D-y!SEI4}&rxL2s>6%2k2h2B81x5%kK5UzL2iTm?8aSgG=>AMlvIEi># zO*~5=9nT&U&rK(u??t@Of_O24c&RJ#ayQ}?nZz44BVIX&c;geqo9rT9HJ5nP;l!Jv z-0Vl<&G!>;DG+a!OuTg-@ix7Qw;fEpT`lqU_lbA-gLo$`@y=_AcezQt>(|7)T_oQ9 z5%HcjI^xx6#A_V!-dBkCF(cl$E%AQ+i1&X&d?5BWn@W7pY2rgv#D}7x(D%e^{fQ6X zOME1bjkX~^<}mSbeTa{@BR&C*Cw@bG()YwCKPNurCGlx6IQssJF{+#CMA&zPkrN9X;TbuIG8;dts%w1%R}D zWB@h#;-G#?aDn*#%ZVSL1(0mu0^$cvBfe}g@q(R;K1tP z#IJz@7#HwsA^p0c#ILU-e#0HacM~k#+>!V#sCX-+-fl$vjv(T977<@Niuk&7#P5O; zdl37*8shP^#D8K!{C-Q~>#K-A@Q(P;P|rb}d-xFXN7oX69NwOQ0jFT#>D9!asU`mG zX5!C*^JwQnJ@H>$C;m&_cH%G2C;rk!;=hU_{tCSOx(D&!Aih^2-}N%$Z@LhF>ucg~ ze?$DYi1l|k?rsI~-yv$o;vXUQk2ewj1a&-ZApZGJ#J~8A_}^gA z?>C8m^%e1NVf-I6i2pMcp=coD^*xDiY#@OZlfbPdfxke4m_dT%OM>h|f})B9qp2h) ze|m>(m-66IFElVFobf^9SjcJbf`66`0F;NVGuqceC+g41FWoUOoH z5?s)h>pK$Mej=UVewzf3&q?t7mIU=TBzUKjpgBW=Z+jB_4w2yR3TjCRXhlL`841nS zkP!5Qgb}Ukd`L*|Mnc9a60)2~$lgdot~m*L(@4nIg6AYOhXDl>NNBN}gqE*KD8&Av zND^AZ%iix7$z>8sH<8c|=d~Y7LI>2<(Tjvm0VH&WI$h9k*M1~)t0AF>83{ed zlh8{Cd3sxt&_^PnuRuaSV-or+Nf=N~!k}OP>B~@F2H6K&0C+NF9SKA6JzND2kT3#` zkE|eJ)NB$)y90!!{1Xx?@Lh@Uj6qn(dVn`1=vI(00ZvUkOTr|CL^ruV2~$RrFm(Y5 z)8OIsaU{$bM8eG8B+MF3!W^E2xl2iy4=-@(2~~b1EZRfDVtBv!XA+itO+q!4TiTO^ zWey}PF90ZCahQaaUy!g0j;w|gYdVooGna(59Z6UhO~QId5;mA1{u|-Nrp_d6E+=8j z7815XLd)nNx0X6gzxi6xNi?$lkmWZgdf2}82FQ}l7vTy$>S3wJaH%CDH7(H z4GGWVN%&dq|!n+6({)AHR%SdP#LV|uP361+m zq_ZS)rX=z)Bnksa6oFJpqI{G@g`PxZ2NI2INmOY`)R~?q(Yz;#mMuxN`iVrFIV9S( zC($9CM8^;koluE$GKqM;7hT(u=(dnVcbwzt0KOzqJ(xtV1QNZCNYqRq(U+0vw}-?4 ztOsV1*es63pjZ-vXOI}GATg{NiCP#C-h#x4=cE&(ib=%KS&XhCF(#12SZjdoxK$*^ zpC&P(oW#U@aD&98r6eW?fcGS(awMiXf-5AZe@kK}*0V}T%-%p^&KeSPw~(0khQ#JL zw_p~DExsnPRS}7Wok=VjM`AJBY7?p>v8_9aB{n3MLb7)7u>D37J3yX}pOe_xmBcP8 z61%37*lj$C-BCf00VMVmK_Bpv#9qTm?43wr9~TnwI4<_mae@~Z z1Fn-eF$UC-I0=nTxyhq|J96W0ufMm1VfI0v}=0t%7-~owq(coMdG4~>g^U%;dNIc&X!07qE zlDGg#wh&4!MEn=NBeALw93*iOlv%V6ydrTiLbUiiiA&({lE;XDwU)$cD6rHIz|y6k zf)^w%a|Z3eHWHWHg4SR?i7R-}A3&{@Jpnvhg%GWR0;{f(xY`ULM5|%&8Z@$I1Bo>r zpqj+B)&PmVb|OG-t#d~F*L4Fwkhs1#Km{8L06f?T2{#S{Ka#j94eTdza|F0T;+AG$ zDS%pA`+;*LZbM_+ZjiVg-fqWv+i#P&18VGm0XyK(&S)?lTqm&>A*@{lbdbId2i4)A zU3LJM$!>h_ZUZ)xxJLmX?DGXXNccz?zYN?Vu^vX& z_W;L9Jb=az93k;j0h9uS`ZEW_|1)IbXFrp8FdN`9IS8u{#RIG#Liu5Y;4nNt;sxfD zc+?(r08r?d5f};(l;33x~1sS*HVK1UvX{way4TO$6a zVaXW{Ks?SO6lY=OIe&nY&LdvuXOeh94GiCU62BM-E|d6W20$ejQvkwuDF>jz%VA(Q ziC-xJ)Vv~tjsOjR4JW={PU1Ik@SD9PUQI&$ui>(|hU@iuF^M;DzzryJ6W=%2k$4Ld z--1WC+k)>%ywjJ&Z(-bb3=9YG>Mm5dhp^m31K*D!@jg_!KLNZY@rSV_KHvbH`7suJ zLE^(6;3pD)T1ny~ohd#L+sE+qF)aGIC1@b=$yO4d_5<+v*=X>D#OFBiIXwLZD*f`1 z#21kB1(f);HGpxyT_ExI7T^YnFV~Rx3ZB0z2hT`+jWE6bg~T^7_Dv&+Z%2Xqi2on( z`VXA=t`)#_{U^Tv{Efu-XyiRIzoC>wJ;I}3Ok$%2m_rgpf>R_hE%=lqt}{465+4CR zBS~lv4v{4Kfh8nKHUQ_z#$XFciURPKB%|e|lavBU%IPE-2ZL)QndE{`NK$nKS4lEW z1HX}ERta8^WIhNqkYs_1ETh44lJIg;vRVmVl4Om>tuK;flLt1FWa|N@fyX4-Wq@rY z*}H?;;5!{j4mn^INscCJ!_bh{7Mvj|d^$-HYOtN8 zNEjS>kff*-fbc|LB1so>jHFn26L*88_!T52EFmfJAxTM|02L-}0Kbxy+#Jj!DFue6 zz_65^B&9k6c$W&PQzw7}B&A_HZ5M#CX$Vs~!kXR%AUx@}NXj7K3o=0u#6M#(R!#$W zk!b`F|ID%A4oO)(Ny_HH=Kx0KxPaCGM&%$(xn{r*pe%PNKv^D+&ua$|f_yhHgQVu> z0O4rcu&+gP#J|Nbu!f|TIH)D!*D3@o0!X^TK!C~$A#dT&Bo)~M$X8SW zb^}P*Ius!Gt>IlUDlOg&kce%@fEw_Kq_zk_+iCzMOM*Zts06y@_&_X5?two@DzyT0 z0g|I#F@WdoBLNKU&<$(?cS-7qgzPu~Y$U0Z31|uSlGNE5Am*LVk<=v_%m+}Vt0|ZW zplY{xfE?-e1LEIJPg3`Cl6rWN)Du$mN+qc`EbD{ox-asgKg#<*A!(pHxJl9=R9uEi z2SbU$^&}0MNzzbU+rw6pG`urOBf`K9l13gQ2@fRF=os)dNfmIS@&HL=VAPmbB#niW zN zouqlKNSc42q=mytS~Q5H#eb5t1ff{kpQL4NNLr3uTCszql~8KcQj%6<9b*Ei<~m7h zk3rUTkYoemvk_8nMx|S#NZQ(kq;1%@BZH)!sIU$(u0vwhp~1QfB<+HiyYfLVFc17h z(rzDs5behH?%O2ofyaB$&|VclU3*`Xw9gY1fIeU%NuTHhd}IPh|H)?nQtwBC?H>pr zS3TrDfMogphl9C9>2oM> zdNlZvq%+eIkb0fCrRhC5dRCP>i!3P#gX)<14-`@wuTCl^rJ{>3;>Twro$w& zCEzZ}Tr(X$%1P#Va1!7EVFJmb4d@H%NtW7@EEB-C{58pn9-xtAqa!3M_mgZqo@A4W zB&$$|3T3!q$z~$xO|m(@&9{(jf&G>oI7qTpbCRv?z$YZz=w{&KHOaOsNw&j@_MTub z$qp4HJEB6z`6N3Pf)^y?Cp5B)H#kPJYcsHmWH+4S)H@Bi?2U4b6M*MF`6T;VgC9xO`TamLW;*47ao{bqDW zl4Q()%Awmx4oe08E@6)gn?ih*askOqBlq)88;s}xdX{5i%3rWhU7Ht%Yf$@FG`uQ`Zo!Why2R0mh{h5)Smj}BS9-7+K zGr&11gt<7mxc~PSarmQ=`)mbo^06hK$jB(UsHu8wQ&VF950 z)63HV8|?mvR-9ej!b8&jYV+e(Rj+>0eR=B1V);q@Y6a>WaA=pq!gkk4;v2|SZOVyEVMRS8?{Z8 zohc{#M6v3xh6^6Lt=#&CjzjqJ_tU~NOLJGOSjSqeUmS0g$~}mSTas(-sqhbPrnI&; z;!OO^McW{MMM!Lj91>xjn>_k?&ca;YLSL(7`*)-tw#qQJvBD1}S+7{P7(1P#xm8TG z(p*f-bF~T!WHZm$VXA}-_8t1$KIEOt|Br!Mo9YEJ=hW@LOB}9=Ewo`q7D8f%Gt8^c^kpl| za3k<)+{G{Fv&e{4NW(B97Ujy>$w8c~wNP7mSXfK4B&jtTA6wg~$jAr_4{t+?TBt4X ziPA)ljg7Sp_BBp4)-+4W$$7@wTiV&SjN?od0l6v@BmE2gx=eE)yDW1rOTkg;n;Fad zy18VgIeIy!XV3C&2;+ynf9mO_R0yX2W(@xssklqst)d2oH?-sITq5mFLsiTp+mS&@hH?9N-XA`5jZPyBDZ?K>+JCGa3k_V`IkIq z>v2IClMRB}jAEz-b;Fft;}OAtx8d6mMg%Z2k}WyFtaZ>1*NR7kHbQL&4bcJp75A{H zu*irAbyM7Ng5jhnTU)zC?hy0VUuB{COJGm3O^T`_621a8@XIQx6qmlDwgxQ;+rGDi4*VOA9v_w>%e@);%ChbmJ%d2|gTuab`=N zq%ox+B!48uQaZHiS$(-BbJrPkk`2DX=f8AAl6hGDt(}!&Z(kJgxyM90MyjG!;rh2MOn(tfV<&X_ z#GquWcvmA2W0&wLnMM}!ora%(Hr~YQK50^qQz_hn6-MGNKHMdJT!4wI(jlYeLzmDF zEnK09V|MWy$K+w1Jxx87u5J2uZ{51y!L!K4)vcHdPDmJ(kf7E7qOY_HZ0DwMlwES! z3Uif*c|wB2^kbVGT@_CCYxJZrG|4VSskminr`dSDsbLBKD*(ao2QXNF!MEM7HpDf$%;rL z2RU?Zf`fmzj@~X_oqOotTkp9UY3{9R_Bq$%eC8>2x^IyFTX#<*BaxZ!q1^YSY>P{y(S;y{Wssf#ieN3HwSXi<{ zke5-qkvh;fGhTm2vNJjSVpVpwK0ABt*l|YFV0Q}aR;c5Qtf$&~v<#Rthg+p8*%2>W ziLvYQYy-M=)HrB5ch|qPU4JUrOr!FtfC zGV5>EaARd_f!uLX&xQ2JRCIf>xRk3GY~gIHpReL1U1U@Q`kbZ&^@#9j8bZJiLr-Xd zo@h`0@ZrOa##$H*WTvubv{Gp_&N{hp(j=C|TIzQhn9)|{Z2GdfeplXBQ#(HUe9lRa zERFsF1XMymKXb+u#!T>TZf18QM_=>oS=OU>CVnNF2Jeta?t9L!dwrWN+^gs~1vxd= z@)tke?Qo4zJS9_S>JEWFb~}d4hyQq2hJ}V68+T@?&Mt8dI?+ZOAp<*GJB(4_6r7Q9 zg&DdkpO5`j6uKars0j8wE7I=-eb_y{``e5T9g4YW`aLOctXoIPz09mE5`sAs**(I@ zVx-lKiVEgXUS7W5KG51jdHE+3U#E1JG>wUaoE_tw>?PS6XGKQ4J}B35mD$;&GczO6 zt>vE2tal1X(0g?cOfhDqA)fk+ZXQMo=|etQGAB*8-xrxLv$Mx#XG_gfJn{qLIpvC8 zZXp&%ZYr-Nw@^zHccpt$%N~7;ty0m;_%wCPAI2%hxPcgyNvAyOMg9I(M~pDYQ~lyB zXH&iEJ|}CM0&4|%nxu-dlWicE)kl#;HI#{r^wCxivE!|DQHFk5-6YMotWaMI`m(!v zkM|i}yOyYWnps#z2jW_E4KXtBVl$$D|El~XgJ9mGf65=a3MdpfP33KC?eWDk6CYKm zXKaqKqnsJ-lA#f8mAUb5K^Detmn%43X6C4jjBx#JmUce#xJSB={ySF>TxO;^WG=2w z**n|sR%XLzCafSKLI15BV>EeG2wRbvIWjkw_iL{9Xx^JswwMr>>S^R=;+^cFwK8@y z^2`YA9nFs}v`J_ZYWLqMWM;UtVGQX<8I;o`lwmjr_>Z@G{x5CR7XJ~(yGalkhebahEr>+eQeKvqAfDk(q8TA%wp_t*=Awxq^y`7JztLeO-NH~TQ?{rm2Y z97^K*zOTN;4(Zd`p@w%{QNvE3Tz3EDNv1eJ*y9sc*#U3 zQB>njem9@nRJ9ptDT?BV43&GMa-Y8r(@8-d3Vztp> z@zF71Sy}y~2SsaRW7y%Cn4OrEhrzW?2Fvufxlu6JV8>r$O8(!*Vx;tcn%fcfHn7)v zB|FyehMSeISLTfUe_Q)6W9t$_*Xz%_y0e~>3ObP%R77nJf6l=on)*v5xnY==$T!^#Y_x{wJ3}W5lQCRv>P20&4v5tx2`aTL7o6bdNO%BUFa}NKppKsp0$%WR) zqHC_R`B+=g#M4ADv9Q4a$j)e!v3sf7&bz=R*(WQ?(pY0^YO7!5re?#OtWDKQtN1*> zWSw(HU1)o6ua4pCZ*}Y#(ROyng>CV!gnSXleS8YKisl%c6cc_=-!wo&4t=MH z8e?;9s#l6bYQwGe?ITL&w4Yy6Qt$UEZ|hRbV}QF0NsWI*0~yj0nPEs*B(aF^2(1Mk zk^eDrYkIVc@PGnMcaaYxUc(L72hV&_YCP?EM5&owb@EEiPKTQgyU?61dq0g;>3_~) zzvSu9*|C{scD7kz7|?4{l*Y2YPHC;YV(Mxh?VD{LCzu(HeAHGRtFTg>ZqB^SU$PCc zR>4kgk=7c!WQ~7GfWp<-ueHW4(bX$EKa$UGmy*)!tFQFG=IO7xcp70S>SJnnG?a{< zxW+i@l-6o2*-ta)JS;D2?(EhNGMI>}K!O;5=h zA5;EsSqwL0wkuWTV8mv;U&8Q@Xe{K-WHT2_EALA=EKzA`r2jUXV|n@q8aBbw&MrTi zGgJ8G8C&bu28F&34YLjMGYMB&4V{tg&ay)s;@$l+9X*{}H4Ew(AlV!F75jT-d$?piZ}R|)W_FH2Rtgu{%-hydZRTy^V{MJ!vi(D=bPVAB zTdR*U;Vz0kQ2S9T&wuAsm=;fs9)ByP@e!+PM%e2NBJIyIfk--tv{be_pXus$B+JVy z>$+1TWwVAn{Q)Po-rUYMKbkj{{d1Mp`dJY_1cci_o(S36L_dO+g@+_*(j7F;1%W}G z0%S*JlQ`~P>)qoZjcufzL7Ij@MPhS^5|!fVnfaG0-$Xd$*@jcOn_#f=WTH!~t3yJx zjkgMk%1w6k(^#p^LM#I8oPw;3+!SWsHju=xxs^|S;x*p0I7=i4^e|uHqB0^23{6_n zI7rjfpBmDKHzbsy-~GG9+ari;$`r$;B%AVhq{jc%yME^Ft2@RE&5AY69Xh8fHKr-W zx1()+6p6CeFUd+Td!@I^(Tz=D+dUJsmKl~&>E0@IeM~equ`p4&+FB;KGh-uJY4 zXjb*OD8WVX;m+e@Q$4{Atk1JxbIXi$Q43#XXBOTToRLwD^e6r=1N}^ z3;V~>=5BJF?8>g21*8U>xhZU9GaECNn~hbH8cKM^s8pu1vAL-@&8)Pu>}gU`U_8dg z$k#Nz#5&hHTZ=zfNw#Sy!_{cv?O+n(5*_cT;LRkJv$;uVjBS=@ao}(pKg+c6%9I@n z3!PD%nTws2%qzr_{;`gSjl;_WRWA80{f$$m1WTqu(AZqdfbL$_UOl-c=YPK*~V&R+_@S4c1jmH@z^kvzI)R%caCVdViIJ=dpWCOeI+BA zH*RJsxY(K%^o?}QoE4*VZ9OsU^k|~qStj$!;)>d&IOsQ~>P#K%*WF<0n zg+=EsvYSb1v8q2?;FDrIz^4Q(p(i{;&Mx)a+7^{t9k4?>tTid3-bAa&fIL5C* zxO1-HeLqf#Mqu_V$uN+Bq?i^JamNrbwTw$0*Kg7XIGfW=b0*kTB=k^z02fv zKQh0EY23RY+lX+R2;0c$Q7JC^wz+2B#=)^3Rv z^r-%m!-rG!$I|Lco(Vh7X$+6#_zg1B#u|T0?u;4C0E(bA!5^{bjiB9 z;#1K|2mA5xjqyL`fIV3Mq@?LFG1C)`+|>#3L?v*F-xf z#ahN%L?!E=RwI%Z*qvjpC-t3qpZ7a>WkYxFfqp>y&DH5jC%MDz3Y%`-mi6et!n$_V zJ88Ug?2Mh2W`4~qy)BysTABJ=1_yJccA@CUJ2$+pZ`?0-kaQxsHtxW@%?D4c$cIw@ z<-x~jc>iradRush#~HPi)-a*@F@zsy5)%0cR+gGNEiP_$vVEw+!^F>qZJeSomyWQj zXOvdTvswB#>6n{MgdJ^yJY z_iJciP)JBnV5t5?hS(}IuT`tO%vNGXntskwv1Rg??NjReg-G2@?PSVq+{d?pg*7}LVX{DuU-XS60 zn&4pl$n?JP@qN?N`XwavOG|CrHWgeueU3St4a-mWQuq~=CIqvuS}oO&TgJ!fqdrfM zj!jF8jZTkOySS;SHYI~oVLBxwU8(k;yrBf=BSEz%SUFS%C&0V+imod~bZkna_#}n=ZSo!yjj;{{6Be_<4#52e0BR})Xrtwr8 zWv*FpytmTB#52nG-2aZKo;^1YOY+1WD!7&7fdOpyhx?SPjb(y|VL%nFGBz{Zro>Hh zGYqI4jN4#9RsL~6)%jloD(yCFI{}@r#r{0n#x*FaWb17^w-~Zq5 zi*5e>zW8Iohxf(){z_YKkEYkfc#ms%r^}^;hopD$^mk4UX0DDd9{;^%cw5Y+;cfAt z|GX`ZkUcdm{=U);Z;K5je;0Id^bf%M;SVp19hw?!dRNS)1sK}E8)>wW)(!8c|FKnn z@~t$CLl4uh@pt|<2GSoh&F&u+d;CAvw>xPxPENkQtb-xr{Mm zWhY$(UT{TWR(>t5!_N?uSmGO_vBr$>2fakN{!DO0yQY1j-b8PLHB%UGgHFJqX)4wO zld<09D}SXB6kZDawJz!q8>OZrjTtYWmoLbd-#Yn|y1^!YO4+4VWh!9j` zg4fK}c;#k~Cj-Ms#1n6lG??G@m#@fIDhU>GG%m#zE3hQfDl93qm9}9lu>(sw)ndt@-E^EX=_FmjtlBp^x=Fq1HkN_(E#0L-^gWg# z^bpHXdPI-W=o5N|W?#@t8clELEsdpj__s!N)QDvw!=RHUF@Xs%Or*h$PiYAFyzw=i z2H#?x`@59K+I{T5PYY2lk*ZO5s_BsLuz9!fseH9@rF^|{7kqAugGxXtXa@#?GBCLD zVp9bZ8$Y8-U@};Y^25N;n4zY7;CpZ%NMwRf6)11Kfo3nFjZ1k(KYS*Pw^>S zswSvXg#()7gchJBPHu(ji@-o^FK#mQJ}SA73^KuyAC7*CqwnL$r#SKk=I5-DZPra^ z%t6^a>|5}6^Z0qy|JT`-!0A-=|Fh0LcV0?Gy+xLaMpP1#zp^I#9#XcfWytncT3lKf z?WwnsMxjh$FtUus&ZvYDqET6T6ocW7G`8w_zrS;AP)NyW8srg;NJfu^;SS=BZy8sTKf{s(6?Hyo(>>z?+M37q$l9 zvHIgFz`T%%91-xRDH-pBR|1i&PDUMvQR-2P#R1soHjh19Sa%<$^>Ph>Qc}eu(THRDVqNb zl#4v-`YTW#h`P)N6`;&9{1s@C0Eh-0xFKX=?LXJ*Ee9uaV$J8r^ajHrW~@Tu21eZz zLY}kb{K<&a+G&<1p-eUhXQ|2gwz)_#aUaC^@=^Mbm5D!k&gNkdiGd8#F>Ydm z)MVK}{DTDa6yuykOkHK8Xkn3Y5%AFD6WRs2VjgH$OaTS(fKUOSqKnUBUFD!;4oc>* zBVg~Sot#N!1^8OG=o|+}BL5~!Jp>{{DFiFTMg+NdfgCx$ols6uwn_J~GqJ(MXFV$K z%f-h68VKeBa;#Ob_SK*wMPdd?)3EIboDT@E6%@$+Zl?;PlVF8-3~EkGBfszrLrBK2>7jba9~lW1-i@aAfcAo@lQ@gH z7K(i%vY>1Y%ukI)$+BqFci3TQcR0wyAjX2mfo6lqhA66D2_pG)@CB3$0p+qy$k+_> zVUon}3#zEj$U+E~&!N1)b)Ul3V>3L5QqVYyz8IZB%hWcHzK?ACFN&q`AOsB(RLDGq zlz&WD#RA%+eE%$#MZl4!(&j0Yl&LG{Ial`2aOOJmoJG#Z&N63(v&vcHtaH{opE?`N zI5Xa)xXnRvKi|26doP<;7&EV#*O)M`8!QH5-ZXQWGV{$MR?aLj>)17BgZZ4*Fq_O~R?}=T zTUaf#)of+8&6nmYR>$lzdssb_Zw|4B=7>4N8k=9-GOUAprF$hC?3Q!Ovt&2Pt;!yD zt6?Ra;qJ}ut!$KA+pWV!yLIt^j6LBtb{pgRRyptmXD$q8UNqZn27yyfE^=~^lNrm& zLrykw(vg$NTqgtf5az;w=S2(5e6X0wN}C0sk3d^kX^0B}D_Y7ysWg-tgL-K;C)4qY zW|@G2JQT{YO@+-Yp=Q7kIb$V z8V$#kjjGg@pG(rPV>bx6Y_38Mb)M@QlyeDqP&+d=vNE=gKoaza#=sPhMzG?0pqsI_IDDa?PnCrwIW=^P7Yw=`4*o$E_ZWCv1SUNCJ%nCCK`HmZ-ju1Z4u2 zijJ5D6fc)X{y)haE+VX=`t!v{9ujIaUey<98S8d&b%f=7+m9b{NB{v(ATjF!U<8-# z9MeZ)>_muHaloK%{D~&49F|G93W%d+Jv-Wnk87w!>hSS1X1cb^&_ylcdW=LKV!$Nm zs*pH!6(pjqzx%9&Xhvd=Ge_eiOz>To%EZ~FZsUXX`zJ|vuOsGswn3RKb(Sm(4y{Q-ftEFEj|Jvu!E{2WU7c#803Cc z>e{7Fg_0S5z=QAx!A;h&rOpuPmBQ_KeBg%v94|T`YC^P+i^bvIK?8tZSSnQ3g0dEaEP2hBXQ zkc~iew1hooJ~r8Gv{_+RuqVuFvz|R^Hkys>X|vgU!JaYO5F<@MjPwnA$?P@Xu~*Ia z<_GpV;w9V>GRMr1Y>GKyPOz!wq&dmnF{iPt#JlDfncuMD^K@6Zg1zr5SFxF{ zaZ9pv_hR=lmf@B`fVBVtRynrB#dkd{%dPBIVapI~C9xIm4Om!zrF)Zm6IjgKcsfxD8k?;;yD_3xdYG**3S8+nQ~6?{T}Z9d2*8cPaK2bcB7hqFeEo zyPZ?>S2#;ObFsP7;3=hf)=W0K3hGuJFKi%phUYL=PhW~EtWK1Zy$)#RDoW{){) zj+;Vr*8J)wxTW2)?$z$~ZdLb2M1lY3-s!e<+qj+Fu0c=vwx^2_64!@ql#KowJrONL zOkOIQj|W5f%#Eg2SmjhsGNnuzlWL}#cM+O?YtFdZy~?fSHgp>W4Pj+gMASd0CZlxO zx6E|&lZ)@k-0SRVfx8>E66^MLaTM(I~C}@WC*y0E5yVt6_`ZX^XeE#krrdD6$wDIUR zwZMPZ{tC2z5w@1Va*7DLv7#sba`1qaO!0V*Qg?(YlZ4cC$}?4CsT*P`EY=#Ft&S8u z-&6)moCc|z!jxxj4wI%vEOiS~e$zE$sas>ITCr5^SgHuio#H(;wyaKbzB3urSHxo>XnF#bBXU1inUFTfG z6M(L58=NdB z+il@21HJ)PfpKl!c5ZvOgWC~k*O@@p?do=O?{&Mo_qq4GJ=~sdFSoaO+C1YdaOOLi z4%WHFb8RwYf(nun78)#x%uDcJ;cjm^2&MwQ-n$qNq3hv3NGmoCGv#}DHarr~h7;I! zXREV~?Zjj79Sk?}a8GQPbHF*kzQ(h5#lFF#@dWm*!9TVe&)%!BJtoQA$-c9qKaGdx zkFv9PaQ-B2j)kJ1RshEyyj=J_KgbXB0)B#@J7YD>4aa8;y3dI?5R{SEOQpg19$`bM-Sz4Br<>mFVs;n+=mbGPFSzk7kjb&4L zw`?ujO0T`_C_Brp@?LqL>?Ql)UB7|yK{;5CmQTnra=d&+PQpjrlko-qbori~Dd)1@?N^7@QT4MrrOv2xDyo%sw5u=C zm+3P43Vo$6tFP7-bR~VQuB@-uHvsd!n{*9*tFEKl==Qpk?x`QZB8J2CDE+8@TtB6s z(<%A|{j#2<|E(wMxAi-Ex_(cm>)Cp)UZ9uh6?&E4sPpuUa&r|q|{8j!se~Z7(r}F9i1D?U>ljN5vCjj2I^-h>6%#Dosom@7ax|W{bIEfmkROi)^u6tQ2d-X0cUl z7hj9rVz2l?92AGeF>z9y5~syE@hk90N=GKjlJa6%#*^h_1(_sokT=R2vW~o6-XR;w zCh{(<2i-=tlO1Fyd5`QSyUY7!Z~1`iCkM%gWU}4G%EK;Jugcf4gVkGj8+wM_$BI6$ zqP?tE$Q*oq_PN}IH;cE)Jo$~>E%(U-@`yYpPspF;X?a%uiaVvA26UCgZdYYgB~@8f zSGTCz>Q2>IHB~KD8`WNQRGrnm>OR#|^-=xRK&(dluzFlQrJhw2)bnbR`nQ^_rl>Ua zj+&-ssQ1+eYL=R#=Bd?cty-@(sI9b*mikuhRo|)a)e&_}6{^$ftoj8vXtg#TVvkF8 zX??l=r@l&;LmX03U!$+nRdkZBrf<|Y>sxd!eVcBpJLt~3m+q?v=;8Wbc4MwFdVH`s z*X#OC{gzJC@9G))ef^=`tm`AaT(8ut^(MVb@7DWtL?6^g^fCRTKAnIi$p{Ma$6$8*han`gU05xsT2T;@Q47-~8ahI+pTQPb(qVAM%J zA4V;TcKJPyng%|Knl6N*rfVEUO*eBdP}H+9>dYz*q^5guAa!Q{F_4=3ERf!0?k5~I zJ#>51)5lTM+w{?ong>i@)6eub1N6&gpc!NybY_}|^mH>=e_)b5Gej?NK8Qo985%;V zGY3$5#)ne%p9{cf+c=E=KTve0MN#kfFuKvg=-&a+5JUeig1#%Jdw&T;zxy*7jiG3@ zzlozBZ3lQ^AkF-rP`dvDVER|E^q%5q>IHE6SHSd>KZn!5ji%mThtq|B2B?73-(e|F zVQc745O2e~@*eyF1fLJ_A^cy6I-f<<`I^Tkn@0Q{K8w%gA0yD*#B=!M=RM=04`v_>e|P2492iHF4t;w3Rf%n_@_I`NtKT;z%!;v2C~ zL`1$gVp@xz#VLTS0!D@&arbej8)Dvoq8mFF9De49Fl6pnGuHIBLRfhUpZBn^vo7&~6-RcK*NEN6P z>Q~JXt5wxCb!}Z&x6}8SUix7@LO-HM>nF9RC+O$(OZrv)hECN}^i(}f&(yPYhMun% z>ny!T7wF^qCtaw|BwUTZNBoz*=-!19nvhJOuLkJ^xBY!I}( zs9v^#%limaybWrj+KecLVwCSyJ|dLkHa58w0Z15nbk_Ilq5m`N2!oDAdI^G!BM351 z=${d2Tts1peQmm77-L{tqwZ|B^@+N?s~2+$a)}B2dH$O92=a&Jd@X!oCoZ>qPw@S~ z_ku?7gBHd7U@Sc0W$Ou_zz;SCevmH;d>_Dce#P&6pn>#S$N{kar{!}pMZObQJ=yzK zYw-u=8CZLwy3AU7O?&Oq(Cq_jZx^%ozJa}uwD#_)r>(ue3R_=i?R<;cuD*tu9|+9+ zH<-C&4IO6g={CizeC!`ud1hec$MwncZCoBIV&$`~mETsx%E`o2_{6}($-r0eH85^6 zZ7-jbZTpt(+jabEAhhgpG232W#Ioa7ohUCaZq?SPn_F`pXU%zPV8&#?+pGy6kk))@ zquENTN)UE8u=d;jPb`?McP-|E3mdLMJe?H8%ztjS)%;-Dx7!O@Z7eosEcN6L=h{jy z$$Z?X$`^Hg;(KvC0Mt+B^L3z)va0XIl1e%Wc}c1Dzj1o;s#%l2amq^GH7jF}gN0pg z$Eo^R-|lld@ze?t$E4m&KNBKOsnjMBXCLL2NWFHyGfMYC$*!qy?01^zDo9qz`f|VX ziqN%?zB#q`0cV0s-BcpV`u+euMX<+}aa=V^rf8IFm3{JiIM{v>ZM=;gkxKl2*+HvQ))UGF-Zdr3qI7>P2 lpVj-MGeM;8FLVYbFpXwJYV%W0HTMLP#8Ks}VW*sZ{J*u$_(uQ$ delta 25178 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 callback); + // Export the wallet's BIP39 seed phrase (z_exportmnemonic). The callback receives + // (ok, noMnemonic, phrase, error): ok+phrase on success; noMnemonic=true when the + // wallet's seed is not mnemonic-derived (legacy wallet). Full-node only; the phrase + // is a secret and is wiped after the callback returns. + void exportSeedPhrase(std::function callback); // Transaction operations void sendTransaction(const std::string& from, const std::string& to, @@ -344,6 +351,7 @@ public: void showImportKeyDialog() { show_import_key_ = true; } void showExportKeyDialog() { show_export_key_ = true; } void showBackupDialog() { show_backup_ = true; } + void showSeedBackupDialog() { show_seed_backup_ = true; } void showAboutDialog() { show_about_ = true; } // Legacy tab compat — maps int to NavPage @@ -599,6 +607,9 @@ private: // variants); derives via deriveChatIdentityFromSecret and wipes the secret. No-op when the // feature is off, already provisioned, in flight, or unavailable. void maybeProvisionChatIdentity(); + // One-time nudge: on a full-node wallet that has a mnemonic, remind the user (once per + // install) to back up their seed phrase. Cheap early-outs keep it idle until it can act. + void maybeRemindSeedBackup(); void provisionChatIdentityFromSecret(std::string secret); std::string chatReplyZaddr(); // a stable (persisted) wallet z-addr for chat std::string generateChatLocalId(const char* prefix, int numBytes) const; // unique echo id / cid @@ -656,6 +667,16 @@ private: bool show_import_key_ = false; bool show_export_key_ = false; bool show_backup_ = false; + bool show_seed_backup_ = false; + + // Seed-phrase backup dialog state. seed_backup_phrase_ holds a SECRET (the revealed + // mnemonic) and is wiped with sodium_memzero when the dialog closes. + std::string seed_backup_phrase_; + std::string seed_backup_status_; + bool seed_backup_fetch_started_ = false; + bool seed_backup_loading_ = false; + bool seed_backup_no_mnemonic_ = false; + bool seed_backup_reminder_in_flight_ = false; // guards the one-time backup nudge probe // Embedded daemon state bool use_embedded_daemon_ = wallet::supportsEmbeddedDaemon(wallet::currentWalletCapabilities()); @@ -918,6 +939,7 @@ private: void renderImportKeyDialog(); void renderExportKeyDialog(); void renderBackupDialog(); + void renderSeedBackupDialog(); // full-node "Back up seed phrase" modal (z_exportmnemonic) void renderFirstRunWizard(); void renderLockScreen(); void renderEncryptWalletDialog(); diff --git a/src/app_network.cpp b/src/app_network.cpp index 2ef10b1..89096bf 100644 --- a/src/app_network.cpp +++ b/src/app_network.cpp @@ -2863,6 +2863,69 @@ void App::importPrivateKey(const std::string& rawKey, std::function callback) +{ + if (!state_.connected || !rpc_ || !worker_) { + if (callback) callback(false, false, std::string(), "Not connected to the daemon."); + return; + } + worker_->post([this, callback]() -> rpc::RPCWorker::MainCb { + std::string phrase; // SECRET — wiped after the callback runs + std::string err; + bool ok = false; + bool noMnemonic = false; + rpc::RPCClient::TraceScope trace("Settings / Export seed phrase"); + try { + auto response = rpc_->call("z_exportmnemonic"); + if (response.contains("mnemonic") && response["mnemonic"].is_string()) { + auto& m = response["mnemonic"].get_ref(); + phrase = m; + if (!m.empty()) sodium_memzero(&m[0], m.size()); // scrub the json's own copy + ok = !phrase.empty(); + } else { + err = "The daemon returned no seed phrase."; + } + } catch (const rpc::RpcError& e) { + err = e.what(); + // The daemon errors with this specific message when the wallet's seed is not + // mnemonic-derived (a legacy wallet). Any other error (e.g. locked) stays generic. + if (err.find("not derived from a mnemonic") != std::string::npos) + noMnemonic = true; + } catch (const std::exception& e) { + err = e.what(); + } + return [callback, ok, noMnemonic, phrase = std::move(phrase), err]() mutable { + if (callback) callback(ok, noMnemonic, phrase, err); + if (!phrase.empty()) sodium_memzero(&phrase[0], phrase.size()); + }; + }); +} + +// One-time nudge to back up the seed phrase on a mnemonic-backed full-node wallet. Fires at +// most one z_exportmnemonic probe per install; the phrase is not retained (the probe only +// learns whether a mnemonic exists). Legacy (non-mnemonic) wallets are not nagged. +void App::maybeRemindSeedBackup() +{ + if (lite_wallet_) return; // lite has its own seed UX + if (!settings_ || settings_->getSeedBackupReminded()) return; + if (!state_.connected || !state_.encryption_state_known) return; + if (state_.isLocked()) return; // wait until unlocked to read it + if (seed_backup_reminder_in_flight_) return; + seed_backup_reminder_in_flight_ = true; + exportSeedPhrase([this](bool ok, bool noMnemonic, const std::string& /*phrase*/, + const std::string& /*error*/) { + seed_backup_reminder_in_flight_ = false; + // Only mark "reminded" once we have a definitive answer, so a transient failure retries + // next session rather than silently suppressing the reminder forever. + if (ok || noMnemonic) { + settings_->setSeedBackupReminded(true); + settings_->save(); + } + if (ok) + ui::Notifications::instance().info(TR("seed_backup_reminder"), 12.0f); + }); +} + void App::backupWallet(const std::string& destination, std::function callback) { if (!state_.connected || !rpc_) { diff --git a/src/config/settings.cpp b/src/config/settings.cpp index 1f0cb78..9dfc2de 100644 --- a/src/config/settings.cpp +++ b/src/config/settings.cpp @@ -199,6 +199,7 @@ bool Settings::load(const std::string& path) } } loadScalar(j, "wizard_completed", wizard_completed_); + loadScalar(j, "seed_backup_reminded", seed_backup_reminded_); loadScalar(j, "auto_lock_timeout", auto_lock_timeout_); loadScalar(j, "unlock_duration", unlock_duration_); loadScalar(j, "pin_enabled", pin_enabled_); @@ -432,6 +433,7 @@ bool Settings::save(const std::string& path) j["address_meta"] = meta_obj; } j["wizard_completed"] = wizard_completed_; + j["seed_backup_reminded"] = seed_backup_reminded_; j["auto_lock_timeout"] = auto_lock_timeout_; j["unlock_duration"] = unlock_duration_; j["pin_enabled"] = pin_enabled_; diff --git a/src/config/settings.h b/src/config/settings.h index 3efea93..ed8b367 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -246,6 +246,10 @@ public: bool getWizardCompleted() const { return wizard_completed_; } void setWizardCompleted(bool v) { wizard_completed_ = v; } + // Whether the one-time "back up your seed phrase" reminder has already been shown. + bool getSeedBackupReminded() const { return seed_backup_reminded_; } + void setSeedBackupReminded(bool v) { seed_backup_reminded_ = v; } + // Security — auto-lock timeout (seconds; 0 = disabled) int getAutoLockTimeout() const { return auto_lock_timeout_; } void setAutoLockTimeout(int seconds) { auto_lock_timeout_ = seconds; } @@ -444,6 +448,7 @@ private: std::set favorite_addresses_; std::map address_meta_; bool wizard_completed_ = false; + bool seed_backup_reminded_ = false; int auto_lock_timeout_ = 900; // 15 minutes int unlock_duration_ = 600; // 10 minutes bool pin_enabled_ = false; diff --git a/src/daemon/embedded_daemon.cpp b/src/daemon/embedded_daemon.cpp index ba21684..af37011 100644 --- a/src/daemon/embedded_daemon.cpp +++ b/src/daemon/embedded_daemon.cpp @@ -212,6 +212,11 @@ std::vector EmbeddedDaemon::getChainParams() "-addnode=node4.dragonx.is", "-experimentalfeatures", "-developerencryptwallet", + // Create fresh wallets from a BIP39 mnemonic so their 24-word phrase can be + // exported (z_exportmnemonic) and is portable to SDXLite/ObsidianDragonLite. + // The daemon reads this ONLY inside GenerateNewSeed() when a wallet has no seed + // yet, so it is inert on existing wallets — safe to pass unconditionally. + "-usemnemonic=1", dbcache_arg }; } diff --git a/src/ui/pages/settings_page.cpp b/src/ui/pages/settings_page.cpp index c3b4079..b2d6fdc 100644 --- a/src/ui/pages/settings_page.cpp +++ b/src/ui/pages/settings_page.cpp @@ -1304,8 +1304,10 @@ void RenderSettingsPage(App* app) { naturalW += ImGui::CalcTextSize(r1[i]).x + btnPadX; float wizW = showFullNodeLifecycleActions ? ImGui::CalcTextSize(wizLabel).x + btnPadX : 0.0f; float bsW = showFullNodeLifecycleActions ? ImGui::CalcTextSize(bsLabel).x + btnPadX : 0.0f; + // Full-node-only "Seed phrase" button trails the Backup button (see below). + float seedW = showFullNodeLifecycleActions ? ImGui::CalcTextSize(TR("seed_backup_button")).x + btnPadX : 0.0f; float totalW = naturalW + sp * 5; - if (showFullNodeLifecycleActions) totalW += wizW + bsW + sp * 2; + if (showFullNodeLifecycleActions) totalW += wizW + bsW + seedW + sp * 3; float scale = (totalW > contentW) ? contentW / totalW : 1.0f; if (scale < 1.0f) ImGui::SetWindowFontScale(scale); @@ -1326,6 +1328,13 @@ void RenderSettingsPage(App* app) { if (TactileButton(r1[3], ImVec2(0, 0), btnFont)) app->showBackupDialog(); if (ImGui::IsItemHovered()) material::Tooltip("%s", t1[3]); + if (showFullNodeLifecycleActions) { + // Back up the wallet's BIP39 seed phrase (full-node mnemonic wallets). + ImGui::SameLine(0, scaledSp); + if (TactileButton(TR("seed_backup_button"), ImVec2(0, 0), btnFont)) + app->showSeedBackupDialog(); + if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_seed_backup")); + } ImGui::SameLine(0, scaledSp); if (TactileButton(r1[4], ImVec2(0, 0), btnFont)) ExportTransactionsDialog::show(); diff --git a/src/ui/windows/seed_display.h b/src/ui/windows/seed_display.h new file mode 100644 index 0000000..f0b2d62 --- /dev/null +++ b/src/ui/windows/seed_display.h @@ -0,0 +1,46 @@ +#pragma once + +// Shared helpers for rendering a BIP39 seed phrase as a numbered word grid and for +// splitting a whitespace-separated phrase into words. Used by the full-node seed-phrase +// backup dialog and the "migrate to a seed wallet" flow so both present the phrase the +// same way the lite welcome wizard does. + +#include +#include +#include + +#include "imgui.h" + +namespace dragonx { +namespace ui { + +// Split a whitespace-separated seed phrase into its individual words. +inline std::vector SplitSeedWords(const std::string& phrase) +{ + std::vector words; + std::string cur; + for (char c : phrase) { + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { + if (!cur.empty()) { words.push_back(cur); cur.clear(); } + } else { + cur.push_back(c); + } + } + if (!cur.empty()) words.push_back(cur); + return words; +} + +// Render the words as a numbered 4-column grid (matches the lite welcome wizard layout). +inline void RenderSeedWordGrid(const std::vector& words, float colSpacingPx = 130.0f) +{ + for (size_t i = 0; i < words.size(); ++i) { + char cell[96]; + std::snprintf(cell, sizeof(cell), "%2zu. %s", i + 1, words[i].c_str()); + ImGui::TextUnformatted(cell); + if ((i % 4) != 3 && i + 1 < words.size()) + ImGui::SameLine(((i % 4) + 1) * colSpacingPx); + } +} + +} // namespace ui +} // namespace dragonx diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index 90f7906..2ccc3ef 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -233,6 +233,22 @@ void I18n::loadBuiltinEnglish() strings_["chat_toast_compose_failed"] = "Could not compose the message (too long?)."; strings_["chat_toast_request_compose_failed"] = "Could not compose the contact request (invalid address / text?)."; strings_["chat_toast_request_queued"] = "Contact request queued."; + // Seed-phrase backup (full-node) + strings_["seed_backup_button"] = "Seed phrase"; + strings_["tt_seed_backup"] = "Show and back up your wallet's 24-word recovery seed phrase"; + strings_["seed_backup_title"] = "Back up seed phrase"; + strings_["seed_backup_intro"] = "These words are the master backup of your wallet. Anyone who has them controls your funds."; + strings_["seed_backup_warning"] = "Write them down in order, store them offline, and never share or photograph them. If you lose them, your funds cannot be recovered."; + strings_["seed_backup_loading"] = "Loading your seed phrase…"; + strings_["seed_backup_locked"] = "Unlock your wallet to reveal its seed phrase."; + strings_["seed_backup_none"] = "This wallet predates seed phrases, so it has no 24-word phrase. Back it up with \"Backup wallet\" or by exporting your private keys instead."; + strings_["seed_backup_load_failed"] = "Could not load the seed phrase."; + strings_["seed_backup_copy"] = "Copy"; + strings_["seed_backup_save"] = "Save to file…"; + strings_["seed_backup_saved"] = "Saved to "; + strings_["seed_backup_save_failed"] = "Could not write "; + strings_["seed_backup_close"] = "Close"; + strings_["seed_backup_reminder"] = "Your wallet has a 24-word recovery seed phrase. Back it up now in Settings → Node & Security."; strings_["contacts_search_placeholder"] = "Search contacts..."; strings_["contacts_search_no_match"] = "No matching contacts"; strings_["address_book_confirm_delete"] = "Confirm delete?";