From 1b9999e9ec70f919e6cae6121a40c46e11f17e55 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Fri, 17 Jul 2026 20:57:52 +0200 Subject: [PATCH 1/9] fix: gate-flow PIN lockout copy + local ticket date (#870) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two small product-copy/display bugs surfaced during the support & PIN golden audit (noted against #816). Two atomic commits. ## 1. `fix(pin)` — dangling 'Forgot PIN?' reference in gate-flow lockout copy The permanent-lock message `pinVerifyLocked` ("…use 'Forgot PIN?' to reset") points at a button that **only exists in the app-lock entry point** (`VerifyPinPage.appLock`, `bottom != null`). In feature-gate flows the button isn't rendered, so the copy referenced nothing — the exact trap already solved for `VerifyPinUnverifiable`. Fix: branch `VerifyPinLocked`'s copy on `widget.bottom`, mirroring `Unverifiable`: - gate flow → new **`pinVerifyLockedGate`**: "…Lock the app and reset the wallet from the lock screen." - app-lock flow → unchanged button-referencing text. The existing `verify_pin_page_locked` golden was the gate variant and **showed the bug** — it's regenerated with the corrected copy, and a new `verify_pin_page_locked_app_lock` golden covers the button-present branch (mirroring the `unverifiable` / `unverifiable_app_lock` pair). Page-test updated + app-lock case added. ## 2. `fix(support)` — ticket list date shown in UTC `ticket.created` is parsed as UTC (`DateTime.parse` of the API's ISO-8601), so the list rendered the UTC calendar date — wrong across midnight for non-UTC users. Converted with `.toLocal()` before formatting (same fix as the chat-bubble timestamp in #820). New regression test fails on the raw-UTC path. The `support_tickets_page_loaded` golden is **unchanged** (its midnight-UTC fixture stays on the same day in the runner timezone — verified). ## Verification (m5me, Flutter 3.41.6, CI-identical) `flutter analyze` clean; `verify_pin_page_test` + `support_tickets_page_test` pass; goldens double-run deterministic; only the two intended pin baselines changed, no other golden drifted. --- assets/languages/strings_de.arb | 3 +- assets/languages/strings_en.arb | 3 +- lib/screens/pin/verify_pin_page.dart | 11 +++--- .../subpages/support_tickets_page.dart | 6 +++- .../goldens/macos/verify_pin_page_locked.png | Bin 23758 -> 28453 bytes .../macos/verify_pin_page_locked_app_lock.png | Bin 0 -> 26394 bytes .../macos/verify_pin_page_unverifiable.png | Bin 29122 -> 31270 bytes .../screens/pin/verify_pin_golden_test.dart | 26 +++++++++++++- test/screens/pin/verify_pin_page_test.dart | 26 ++++++++++++-- .../support/support_tickets_page_test.dart | 33 ++++++++++++++++++ 10 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 test/goldens/screens/pin/goldens/macos/verify_pin_page_locked_app_lock.png diff --git a/assets/languages/strings_de.arb b/assets/languages/strings_de.arb index 3d7b53fc6..d480221dc 100644 --- a/assets/languages/strings_de.arb +++ b/assets/languages/strings_de.arb @@ -204,10 +204,11 @@ "pinVerifyFailed": "Die PIN ist falsch. Versuchen Sie es erneut.", "pinVerifying": "Anmeldung…", "pinVerifyLocked": "Zu viele Fehlversuche. Nutzen Sie 'PIN vergessen?', um zurückzusetzen.", + "pinVerifyLockedGate": "Zu viele Fehlversuche. Schließen Sie die App und starten Sie sie neu; auf dem Sperrbildschirm setzen Sie die Wallet über 'PIN vergessen?' zurück.", "pinVerifyLockedTemporarily": "Zu viele Fehlversuche. Versuchen Sie es in ${remaining} erneut.", "pinVerifySeedDescription": "Geben Sie Ihre PIN ein, um Ihre Seed-Phrase anzuzeigen", "pinVerifyUnverifiable": "Ihre PIN kann nicht überprüft werden (Speicherfehler). Setzen Sie die Wallet über 'PIN vergessen?' zurück und stellen Sie sie mit Ihrer Seed-Phrase wieder her.", - "pinVerifyUnverifiableGate": "Ihre PIN kann nicht überprüft werden (Speicherfehler). Sperren Sie die App und setzen Sie die Wallet über den Sperrbildschirm zurück.", + "pinVerifyUnverifiableGate": "Ihre PIN kann nicht überprüft werden (Speicherfehler). Schließen Sie die App und starten Sie sie neu; auf dem Sperrbildschirm setzen Sie die Wallet über 'PIN vergessen?' zurück.", "pleaseSelect": "Bitte auswählen", "portfolio": "Bestand", "portfolioDevelopment": "Bestandsentwicklung", diff --git a/assets/languages/strings_en.arb b/assets/languages/strings_en.arb index 7e9dcaf86..cccdfd62a 100644 --- a/assets/languages/strings_en.arb +++ b/assets/languages/strings_en.arb @@ -204,10 +204,11 @@ "pinVerifyFailed": "PIN is wrong. Try again.", "pinVerifying": "Signing in…", "pinVerifyLocked": "Too many failed attempts. Use 'Forgot PIN?' to reset.", + "pinVerifyLockedGate": "Too many failed attempts. Close and reopen the app; on the lock screen, reset the wallet via 'Forgot PIN?'.", "pinVerifyLockedTemporarily": "Too many failed attempts. Try again in ${remaining}.", "pinVerifySeedDescription": "Enter your PIN to view your seed phrase", "pinVerifyUnverifiable": "Your PIN cannot be verified (storage error). Reset the wallet via 'Forgot PIN?' and restore it with your seed phrase.", - "pinVerifyUnverifiableGate": "Your PIN cannot be verified (storage error). Lock the app and reset the wallet from the lock screen.", + "pinVerifyUnverifiableGate": "Your PIN cannot be verified (storage error). Close and reopen the app; on the lock screen, reset the wallet via 'Forgot PIN?'.", "pleaseSelect": "Please select", "portfolio": "Portfolio", "portfolioDevelopment": "Portfolio development", diff --git a/lib/screens/pin/verify_pin_page.dart b/lib/screens/pin/verify_pin_page.dart index 702e8a0d6..3cc7ab698 100644 --- a/lib/screens/pin/verify_pin_page.dart +++ b/lib/screens/pin/verify_pin_page.dart @@ -167,11 +167,14 @@ class _VerifyPinViewState extends State { .pinVerifyLockedTemporarily( _formatRemaining(s.lockedUntil), ), - VerifyPinLocked _ => S.of(context).pinVerifyLocked, // The 'Forgot PIN?' button only exists in - // the appLock variant (bottom != null); - // gate flows get a text without that - // dangling button reference. + // the appLock variant (bottom != null); gate + // flows get a text without that dangling + // button reference — both the permanent-lock + // and the unverifiable messages branch on it. + VerifyPinLocked _ => widget.bottom != null + ? S.of(context).pinVerifyLocked + : S.of(context).pinVerifyLockedGate, VerifyPinUnverifiable _ => widget.bottom != null ? S.of(context).pinVerifyUnverifiable : S.of(context).pinVerifyUnverifiableGate, diff --git a/lib/screens/support/subpages/support_tickets_page.dart b/lib/screens/support/subpages/support_tickets_page.dart index b7a09056e..2a8e81691 100644 --- a/lib/screens/support/subpages/support_tickets_page.dart +++ b/lib/screens/support/subpages/support_tickets_page.dart @@ -52,6 +52,10 @@ class SupportTicketsView extends StatelessWidget { separatorBuilder: (_, _) => const SizedBox(height: 12), itemBuilder: (context, index) { final ticket = tickets.elementAt(index); + // `created` is parsed as UTC from the API; render it in + // the device's local time so the date is correct across + // the midnight boundary. + final created = ticket.created.toLocal(); return OutlinedTile( leading: Padding( padding: const .symmetric(vertical: 6.0), @@ -68,7 +72,7 @@ class SupportTicketsView extends StatelessWidget { ), title: ticket.name, subtitle: - '${ticket.created.day}.${ticket.created.month}.${ticket.created.year}', + '${created.day}.${created.month}.${created.year}', onTap: () => context.pushNamed( SupportRoutes.chat, pathParameters: {'uid': ticket.uid}, diff --git a/test/goldens/screens/pin/goldens/macos/verify_pin_page_locked.png b/test/goldens/screens/pin/goldens/macos/verify_pin_page_locked.png index f690278751a72e2a29cd11832fc0f912b667589c..3bd525d9e2478d50201720529a18fef8ab4b67fc 100644 GIT binary patch literal 28453 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 diff --git a/test/goldens/screens/pin/goldens/macos/verify_pin_page_locked_app_lock.png b/test/goldens/screens/pin/goldens/macos/verify_pin_page_locked_app_lock.png new file mode 100644 index 0000000000000000000000000000000000000000..628f19270bf25de4152ebb8fe14a7019f315d690 GIT binary patch literal 26394 zcmeFYWl&sE*EQHU!AWpyLU4D2Yl4ITAq1y^;O?#omOzjY+$FfXYljfDae@bTr)i*( zxjgUpeN#0xRr6=2YG&%?2XZ<0o_o$-d+oLN+5J^RO%Wf53I_xN;VUaCXo5f};UEy2 zDmEr?Mb{pO4)}rYCa0{04Se}yn|}fRMsd?rlmk_c((Hmj^dMygSuO93gC#F_k`HJY zCv8IMa>3}>nPcMEs1tG#tePK6v01UT(Q5)oUPZDh)i5xyqJGYCmpi5hzBxS)jgJeB zGp`G{M4J703-J`0v&UgVco8Ui?fD7(=ny!!yd3!W15cZXn7ANb`T&)PcsxI>M@B&b z6%&n^_z4A`f&!xnFA?$g;Qzk(-y8DZVEF&JBP0;Efx@C?t0G>-A7l$r;cuMvX2Rn8 zP+R0qtD{hyA0%Ap3e5IhdS6JPbwDCgQYa{*fBPHsM|Jq+%}quhJjBq5W;pHprZ7ff z)YR0B?R#(+wSd8~rd0&8A-*1CH30?(^DT8ULp!RbbJFUqil={lGf4J0!WWvH%!amj z?VsR6qp69G zY`nxRnKRi~f*yn$WQ5nCf9T#J9r!@oE9{{9An{T{Nu?= zfB<AnV4UC7p)@QYwmUfx+phD%M2ob{`Ig>9r5uKt#orRUM2p8ZlShCpXV%xYC^ck#Gc z=z;|>HuU=0vuAZifeH%f_qwg<^2XF#?wMZ4%cNW5`xkW6BSwVle|_UgS`C!h*w`p; z26H&&pVxKXVeyjG5|NYd^ONx7V&f^?Vd8^8z-GxQr3-_C&q&$-owL0^?2E00aoNcZ z0R>A1AjzEBz&zq!2hP}GVjg>WZxj^*g&C^_I=oSpTyU*nq8=pI1n zglbuGRz>M~iJm`qAo4h8j-eIbYZCGq=Se;7u5<1bvgL^ zE<@alj5(ipb4W+#N8!kUmBG=UMVWo`(u;m$U~^hDzi9Bct8u z&rEMHat#niEtPhV32z&~L{R~Lmusq5l#3m1SfuRQ3QMgXi7cJGQh@=v-%L{0`Bv7Zb z@UpMCMmjMvE@FWM{R&0*X38P}A6_4m#&Wql(EGI%PB?#-7NozJFfK zq197u3Ac?f1^Zv{LA+0yiYBqpG1um{Tg7OTGZI4Q>a0vRBR9Ec5z9M#6gTtLoP?$O zvsI>3;!aPj7R9~n`$gO=*Mrts{%y)uy&QI1|Bdf27ML+vCk;xG+_zJ`y%2CedTI+! zN=XUTGlta%UGnIYC@U*p`To`+IOn;wK+$h-jCSfo(osb>4b>L$r4RRR|5g`qNnr+n z#sep=YUhTuvnG_7LW^iV_xxzxDllkrTn(u)BMLxNLYuRL(jvftHe6N0f5dLCuT4+JbZ9@X?oD&hVaaRkPV`=;N;$A6-PTCJ=+5u9 z|DmX6@GkF4fr5SE`|IZe@88vFb0Mn_Vk)lBx1aBAy#>iM(MgWAA)*L^A}(!!IkUHT zoaZ$je)`^4{aE@rZCfL1|AV*OEm;FE0Ny~uGxV{>j&I)Rb8sZ`TnUxvq<3w^V>fOC5nEf{6pCHM^<2uf_M}}$- zY;311x%{)Fj}>YDcCA$S)Wz#~j6|zT-E?n~YXa25{0)h&S;&&gpC)eAD9;KC?&^|0 ziQC&bK8_Xh_*FK(C=npEbw?NqD^kl;_*?jvy9oC6aG~+zuM<;l1PUlLTQsC61k-o1 zv&QAl7thb*%TO1zI9-I#rMH|v7&_o;FV7oM*Z&7jUhQV#r&Wg9c$V8H@ zo*Mayhwy1QPs+m&X#J?fWP1w~%mKQ+SV#1th@O}6gq;FW!`o+!BSpd^4NAH4>&beZ ze=PmmNy`x;B8nYe{~p-6smproYRT(pn(z+3k1v;f_TOfR`w1^x8x6#^`j!IlFV=5J zgPuI{HQ;N>=b~*MyZifgUB`F4dlZyDXxpP-kxYl5KH*aTcTZOgYlwA-Q&cCE?I77W|c*0e8QpHukF zvsPiyXac?Q;NH&W_n?ZzLhrT~{CISr*V{~K43nGPZkv;S6VE8ITjQ}$HdU-Qx$7Db z5OBI(+I5&*f*Y6>>$El87#sWLOZWGn*qm2UN!ZeEmPCh_G9?W`t#g(`SrM2^4lxz9B+A#S?V2T>6AJ~5X)`H*y~Qq zUlgF>>J}~E$hZu05x6{$^jNK;odeoW$w>J2=ui`VQCG(+Fa9>Wgg^xwGBXmI-xST7 z9z=jYZpR#6nx4A2=w1w=whQb@#wNF&!AQTvM%4uo5D@&{^PoO(_~bBO|0=?BceO>- z{+U!Z|MWTwcF?Vj)O@`I*2YI^X*Qe@Qu*FHBmuP4(%a_n^)C-3d1_sdjg@qh(~@iR z#sPq&c*eO0j|0vuNuw73Ig1vEe@U$KvFlD*m1suCO1ldh$oYDL$<(`Srs7?vKL*kl z$OVB8#oa@zs;YQL0Cd+H{vN8r`+b{Dp0j_xq|xR{iiW86`b*lZ z2V85P=+raDkXvy-jtCGgeNw~mBX`$5^5J549a)OGF+Dv!fSagPCYcH^&`D9;-(Yh- zn58=34riPu9zMt9siNUQ+`#R6x95yuzYg&52S*iNFB4JaE=`LuDqI~51O~>Fq;Z7T zS`3NdMYVswXSM{e{W&D$@WJqO179U`8E%l(7fI?yq0OM-pLOeK3pLo{dD=8n;TE7S z#yjb|;p$TfzB_nI75O&3X)TJU>**0n5#{+ZYtuZ}S!-pMtEb$%pI^Eb&ao1?qg^)# zMQIVd`xHbJUuG3MKXuKNzdBv(J2y`ad=>fchs%1H>0_V*qVK;=WnRO!t>0P= zKL0kHP(&AD>S=OreHyLb*6$97l6mr#CoMt2$u}3IbVcPl3qI+=a=YGJ+XuM3FLaFY zlmaRx>V}(2BYy_!aU{khug_JqX_aVl_g`OMTZmQ_k>FtXUL1sR=jNuIPU{e;I{|1G zU8rkquPiHab&J9PsbD2~idN?A=~e{>;86)u;1r4Uwsb@xV=e7h*&CJe{_o1QIW)yzuvSJZ#S$cfsXLvYasDPb%j~j+FC2;|vy|oh! z8xBfA`Sj^;twTirrmN@Brb?lKx`n1Z7b~k0#WFw~st>l|{rgD+=5(-qg6QAW>ny6j z3i0>Ds@o1;^KN(;NirpGuL!dji(%?iesh%c-MDtm{LVpgWxnW*M<+NYbpV9WtAH9m zhYyq=N=Z1;hglw|W0!e4Q>-jTSfvr?>&0V>DyYFan1rNKb`AQfG!N29Nlh>FzyvNp zP$3bAZlWF2wj-`S>B5dAjunRO&M2!^zxvlEDm`tdh^jt%9xi?YQkdA;OdYBF6Eu5* zg^i1MgBu&>O@Z=kb&3s@KD){lX6r@SuXo&FPoNT_nEXv$ch}ozoGVP;hK;cG91RR; zRKU&4dho$Y6QTNaRaDJ!?C-svJ_a&M`;~eAfdKGTOkWbE%3Ql|HM=m1Zrjn=}O2k;Q?fPvc0IpH&l0>=*D6iLK_1Y2xfQ|2T(7M9u7m+!sRC}_sNt5;)s zE?*YEFRHxjUtBzWIXi8n?n)ccDLlMO4L^z>Fi(+y#TZTE~e zX{)3C{iti)z@B8|5d{RDWMw|dquvDt74Z^J>`A2{fn~RWEQtkaZNiG3#j8uzlMiL%m zLmR=};aq-hMi8%AS~ng#KExU^bu#@F$4Eyrl9wX-5=Qv+N7$Jq$unxg9|H&P^nm?} zp^-$(AE?2LZN>UJproQ!6U1E{K;iX(QqLx4nlf%JH{9W^l-gSVRzp!CHx*1f*-mq4_GUu0tiy0^p^0ad9Dfr6+YBJu|ibDfoOWmdT#G7H*UJuk$buQ=<;3aQ5inam*GoM^_XSx*!bfK28} zN4|V(HLD7GT^FPqX&U02>pLAO%Kb+5AfSA%p1MEz~U36=eg;L&d-3k;O%t*`vmCryNUqvp=} z^hdp8GRGD~;v2KTV6w*n%J*6w*3Yu(dC*u~o#$+NQTIF&QS3dN|JEx$3BQWqn03ti zMxA@Yd$@??$vf^_KU}@j=rknaw8Z5$j#9XGl?4SS!2}gTTBqq2DzO6Eg|060iIr3* z7#PmTW}KFz`hv>)cSMXwT{{anpeF-6xuScA+#D94RsQ88!WnW>x~Shj15K3@e+zh^ zi6E7kA14ny!U6rem>aq@;Y}R|Ham(gdVx%-rSVww+s9XTlz4RPBt6dmPLvnbm40Cz z6Oi5S1O&8?S3~ek=YRb)FQ03sPOf^@YmhJdht-*Nauzr=7;tsCUNRWOl-bzwbg7v?_JUDhveW4yh6|lcd>1F)ZFBD}+ta zYp|2{)~{|?ngDjZ`j^>rX&P}~XN2)H7KzPgLTw!m8m1U(^)Kd?f$ZF9*h4gG%UeZ_e6`&VvC7Z|d$ zOsqVhZ$EcU2sq4Ci5_V#ZQ2XooRkHR5l8T7W)mLF(>CmdBJAHR-;DYZ_e%53{TP!uuM!NLvRlIaN!r5>Y zjmeaV*!#fou{#Qeb8ZaW|K@&iqk6HvY|EyOEYGXyvX>M_*j28Ok0~@>t0&)t=vgn- zsOK~6pbKM99JKIbwrcoMhy}{h9#R?`^G?EPh`X>2Uq}*%p^R(_#^9bhGSxvj+=iN48UJ99eYr!(ZPE zo0#Nq$)TEW2MT%BVs~Z0!}Fi7%-a8Uyr_h9?Ox#aB_f|FE2)t=ePU#?`F=t_cP|PM zRoRTA;;!Z=?-rPwM%f45dVAU#`}t_Aw5k3mNwL+5MC|hA>Y+n;Z*%}fcHEZ&4v`*n zVYBY2TsUyBU@X0#=A9=OsvID1mD+EoI13ok#wPZ zPPwHnUWXc7n#f^tlkw_h;Gl3c%Mhz}J$7ei7tJ_gEpvIH5-@eEr(-mi_(RXVJj)n> zrFudeRAaDH5qzoPSX!$}pV&xzVN~-CIvMl;oWyXpwM4Vz7v$$HU98n8zcG^k>tR6Vi$DoGa~b6SQJs z{H5=jVN7!Rk`yPfj&S@v3qsSnzR&qAqqzpwU+#wg0>-@C>flZ10;-q`p025Qi5y9N zVMD~$;0Y3dzH)pMBTvrZufKMGd>L@b&z^wW+s7fI!2$i?c$)X>kUfp=-+dBIwEH!2>-Da*1a|$1I^74SRDiZ>$N#TeP055(nPeA`J`Aa{P zm^?i2%QAsLuS3r3Y*SUkg}JEnU7+)S{Z3d3=tRTTi$={Z(s`g=U#fU3gc^v2^wK|r z$t$1!Uh4FN9h$Hb>ki+Cp@J+_l7EE_&4$?wq9+o`^-Ovc8GT?)#7W>X!ja6f+n?EN zRb__eNEz{2E%P5NKY+y=T_gmC|2%I0Z=U4(2olC9CfK|%C`ZLv6!}oJq*n5-LGz~i zDas`WrB_QG;r>)PgJJ4YbPO7ZMwBdO(#pXB!$t?nS97Wco+$p1&ks-oc2wWzC>@5e zIy-TY%#!{G7xvqyK-nO&`3l|L5%Dd64lot4oOS(QTtMLX74nNQW#niYHhH(BYUqzD z?6?%+nEPC`^)2Ej2T9LklyxkGOrXk`oaTAk>4HJs8J5 zvTu{E)JM^OLe8x8=%m4`bw&GXcK(+N8YMFI4sgN7W2tZ99(LxU6>Ui77X_ND7t6km zj8?ww&p?qk;&g;I4k%f!R$$Ba08~)x)Iw@QDVF%K@ze@zm)};qD)jBb<@(CH)g#B3 zwH^-UKn`sIb5f{fwP1sKzxGtUnO%K7MO$yTgbugWkp}>CfZT4L+_c&n2z38#+pvZp zP1oqOC(;*kN;tg8LWcq5jxIm_@9b~;Iz2a}!Adj^DFxxL$eNneZ8m<=KSz>vg@}7Zv)Sy#O49 zc1uR{BK-pb zhv)oz+?wvb3jM#Hz*Izv-_xoTpaMMKfBk0Gl$VOwLmZP_lX_-?<^~}clPyM=xg6}! z=74xbJzbRw?7}bL39EZ>Rz_wU7vzy-`AR$a>-;ApRI_ek0cBQMy$#e&)ef`IC6rX< zG;7He)w4p>vLEoR2+fv&A_bbUFM(H{87-uAkX8Ef?bUVJmFrPe?~ny$A_4oSKgHtB z?wgovN7CIu9E`mnSFOo=rBUNgc_8Xcq8O9)NzF+HKz2;laBD#R>uDA5xD-x9wn5NT zIoY?L<`sWC0XYVQ6~xDNQ(g*}n*%1~hcNNQxmYID$hE~jJEig6%+6{G+Gi0CZG^dY z+cPIK)+T~!p;+Rmk;wHGv(-m+NG)<~@|J-59_$si7Aed9W3@rHquTMDjz-EC)4+?i zHun4+-ncReKhreZ7)!3cts2p7_YVn3i*ZQL)!sAn1m>?ulMd^eISyrXhycoJUCTxq z67a7qUP&k*eyg|1+*Im3#?d5$h5#ZVd*f_(l!vP44Y)R_nUGi`!C zbm@Bw<54HWck+GDBAL{o_a$+ACH!r)L$eIWH=e8Mm=~bC-t%P?=|Zm|GS5o^{p7xSWY@VW&Mi{ z+6Rtg#ilV~JvA#~1K{BD-)6z3w-Lt3#~OOQbW|s&X!$mMNVR?i01P83qwSdT%Y5?& z1~z5f$4MpqHDroJWC}EWB_066z(jioNfmdZ#3bAqZ7SIP+G|43z#yAz?a$1>fZ(I} z@tFv8dIwqa!@C$W zjQY|Gh;kwiajH)!uf|)9+ysjaXr1$m+5k_`t#_xrz?R z3&_;(2^pW2!Lh#pN6#eCDf`e;$}$e^=D_r21tibOP<+D)eB8wO!I9OVmGS<{uI_Go zyl#J&n>U9-PJdo()`zKqr80Q!{-w_rLktbBBEo9HWa75fDuM%)AZhUJ?ZS%E%-$kh;2K`M}Z^wP*3to~)@OZu= zY&Cy1ZpgyBAV}no1^;6XP)A<5b}{$nva{m*TXioRS{e);R#3QcZ6lil&~CL&gV zcAF@D1}!^%vvf7jICdC%UY4bB{k2DiFig!UxGJ`33|9xp9cTl;aTiH0VUY1ex|l_* zcCB001RzWS@knq7!EQh#qp(OTawQMZ>oGMA6=<<%+|L)*tzLgn&%6C)k`s}qH2|Ey zbwvW=I%08Yl+LhPmvMD_G7$$uU(g}=QF7-o+0~fAh@Vq1FgWE~?j2t)`2FL^nn5#O zPUMhIQq&^)d!3xx_f73Y`hMKCCXa<$Ua zQAl%S@d6+4TYP+c?j&{2=u}YOceB+mF<(P^tbYNB`GhBVviYjztTjskBU%UVLgTSS z5#a0b8{p?h#TPd(zC4Dq6t-Ktw_9oc6*N_>{iICfpmz_y?c^qo96&ZEkZPG{*Za@W zUj*@KrF1w}lwMihyDG(RjG-AfZn}ER9Dn>T`?n5OSHX&8AvI4y-M%kSZ`2>KMiA2SbcU&$CZf6ogU|57`-4ebXm1e9p|Dmi zwf-XaeY_F%D0&{_PrwMA08v)N-`^(`yj=RAk)Jl){eExLFxPvp|Hot|yb_qh5d<a^KDELw=|$wZ5@QZf(f%tcmG1!CPaIx2wGLURj&Q+|4?w0m7X=~7D9 zu_|PvpH(&K?<|d%tscBGE)Z<^J%G|_>GO0B!apE=26~ccCfQET6}DFLTMuT>-|M2+qC`f~m5#f<7S?N0<&rH6*X*JJucz)pz&O+YbaMA5!JxQ_jwoIT zq=R`%9uiV(=SA;poK`|)2fN90?N^?p>8;?Lr_J1A3`rwbB_C#KlFO?3 zDh%+U+t29F;b9-j!Z=y6)7Xq<$A40OlMW!X>kY8(Ond)ta}Z$?m&p30D<}pRI*jy` ze;CkZ_3KLD+1K*ee_~Na6nk$X!NjA3A{FHHM~mpgr;2QQJY%)6JP(6ELrFtRuFU?Z zRcw>F-by_tqBr1gQRH$0X50yfVjx%1pKr*Y%+mST^ZJ{kKdgWVS0&2u^oVQ9QKBu zL;E&2vCL$iLXZk6T{eyl{br*<=ra=7ohnk{)jVx-jo{V96eA>nCcigHxenu1jcg82 zST=fY)P;F2jm&+x3NURKwMTP@LOu!qWV-;zgp>HZ7-HCV_hlsw*5|taF;|JgxmuJ9 z7RQ;7D;$faz!~bfBdB|0r6w=(vDhKtnN;}Z)w|fetfiZB#7Fx{NDJPbpA$oi3$1R< zJ+9KzZ|6ZWgB$_h7n=z*eZ9hL?x+?To!uUklDXv`R;{h&)8wcpNWw)0!cGg}c5xF` zBK5F_2-6vzxID)#$8@Va>v5{c8+_B0%NA$lEIpO3wk$7Ux`RB#a-U+PJXnoBjMS^%QcIVAVIj>MWKFpr*@RA^~0B z{7DNNg4#Iqg;u3NGIO0TaC3<+W=Q91J$kD3VDDgUhY?Sg7Bj_*Bem#CqfM z8yhPXskItnf{G&uB1|W|b-@yUTw4WOBm+l~h;CjR2>>oHEdIS23m3$=@a?t^b~%+p zo|XkvN@})7$RXzGk?nf-MvEP2Q1a{-1%Aa2w{a+d6OVe*)Xp8*YzkEvha@Do$}f}% z;R$qAFI}S3ycz(u=jWGUz8m6~rZYbo zmEL{tb)QKfQ6e7|H44v@eUXyH1Hin2P{3&cHH9kgiw@BmExEYZ+hfo^H))F0^3-B%4UO)mcBP4zfv|0UBGgIcMz=WMANJv z!v}LNqIotQ%~iraesX_-+A0DGYlonD!wj%}qV;)?7tkgrR{p_9DXwlCQ1-q%$wN9n zR}eTJfOjn!-JzI0ROGl-%9A)ir^#SWg+JvKjS4DWp~D{`7s$0B?zQfLccJ~{OkhUiM9J0T`*fcqSVTXVr!VB^QvH9CpYN$?Vm{U2+J8SjVU`2 zbmMEIhNs+kDLzQ%VeVxS;?~XYTiFeMXuH10?7~j3&q75s4ymCU8mQFt1w98DXgz}x z$SbOJ9Ak|u$N)Ee#93(|TJH6YflU4!+;Q$0+c!Dl}00V4|W*tcDUfOxywO&+Y}#*GV!OYVPVCaPmYoT?7>Z zIZ5Mv633BaJ%P7XVG5WL*8`q*Iq*R~LSEATr^8Hqfyr_m?#v(RxapGQ{MNRPPARmU zK*fNb-FlNFP=}so<`Rx!8d`f;93j2KPm~s?<8|HWZsn#fMg&oI2#T7^NItS z4=eS7_&=$MPwr01LBX2`l)7qk5wQ7@IGu9zS7k&1rS!J@ywK`^s2aOZTKyARbz684 zvB8{d?Q($IO$_H!0RHREql*XFV9e77cN#Y!m+FjagOpv2%Tui>JMR@ogsG+$BWM48 zh`TUN;oJS^xHzBlTqZe1l2s2es?=HJMb193%D0IbRaP%cybP?+=W4eKzdt|8&TQFV4r$LlZJ7x+7K$+bd^*4$!H?p<5uT`AjJ?e)c_BRyIq$Rzq}xZ_`w zKl@XkW*3f8KpP**ELynE-+#gg5#l2bhxOw&#Yr%opMEOc#^sH02Mip~@r2Djw*Z1gN+b@fjd~&8%bW57{UhaB6Hl4Vzhy5kt7!YGWNW zc`m5?G9ppCzy4p1E-0+`+~FwHcgHzJ#3|JDh9n|V(V8RgjxYN$JH7^>}*u9B?=o_xuKTg|*uX zS$AHE4Rz&y)->rc$+$~|a{VmVeOA|LRdO&-+xoq#*H@Zut0FzudScioB%iUTbQ-W4 zcLL?8Wdg0ZJfz6+s7RfO0V7G%S80sHn`<{0 z=a2O0kR^Z&SQR>|Q)D2~(C9H)?ji>ww&9ALW{EvasVGYlHFzWMR496FmpKjty}R9y zWjI}=+iqBqn_27cXm|8$WWJXk&&!BOD|tu>|FG4J+_n9zgr5p~S?jiK06S@dQJd`b zf!mn+Em$>*eExG!^j&_j5qaS>hh(|NvZEVFW6BA8y1R#0m5LCEF}*KSt9Z&06D!o; z^Bg0Or+zL)3#Nu%s|HmByK^sI{;>=>i-I(;`)2~oxPdYwllb@H{T1VSG>$?al;W|HyOaxfZ^v18C;=AjT zZHkxUP~iqn@eN+^X}^<6bZpZ~oBQkO2aA*@r%yJ!PSwAc>oQ9Y7MTTuH!W|bMkAHBx*(DYIs5f{ z-H(1fY+_VN?^zZrH9Z#=>utmCv?JQ4oH}u@u6tb9Qg{s^`DChgI`Watn7h3`6kS90 zhCI=SQFbu3ZhK5TU#0INCReWPQ{b48;i@l($`l>T5HCF$5MEtp=w{c8>OJ?kN<-JV z>H~uF<)vzxMMAp^Bi;Ob)8*PHfRK`(Zi}7VJwHoI${7d>F~pf<7`YmS7k;j+iS0%nYX2@@&-ziC;jNJ%TNV`Aq6>) zYfwu|+_4Vc#C2Wu@BRg3 zMt~{-$r)U#==8_`ZbH%AHdwo33JjEcg(!X12WlWu z=;bw|QVrs+BZbY9r#S_=>TAo+zhs$gE^S-b{}Hu~K2d<%yIwFTe7Fi9cnF>fpE$UT z%`<%g*&p_z0W>yUc z)YPA=%bZy(z@qC-Nv-;qY77ay94s(#+(!zWXMCi0{5#Br}t$3x)emAvNH>J^6>* zNt^_)X3xI}_yhB|7SwoB!s%wQ4PtGlvhW(YL5 z1eFugfpA(5hsFBCJZnbazPfLj|3rFMtLl_Z*gk(>X4Lh?TCh7U0Z;FA&GDG0Obu(t zz0o0I0ofOi0oYTf#QouFXx;46)D>}+==kb+fgky!{_GRr(zI%nlfL1S$qlx=L^yKI zmcDH~Z0mj+W4x^Q?Tvx4V`G~TH$m;kK0`WzB!QwFYJN~B@*!k+R-}IjR)QK}C}hB| zcOND4*AWjH>A*E`d|# zacgC&GPoues823Ojd~{jkd7M54b_5t{D!4e9l>3MPwgkMFy^rIe#D!!xT6e`RV+a& zhi3_$PYbf9UoN1xEF8^^*a6buCfW;!k>uo$5g!q;kBs!HSqfBG=2Xi}_Ze8G)sqy+ zhjOnS13zWQNj`oC^g^MUnOROOh%Aor&tO8s9S*8b=zmHto>TJwdo}CgZ{6c4a)O_C zuC@fem{UP7=U4Bj@_J)>WMUHwi~HMcGLh%u*|pL~SFAO*4FmzNiAWO^ z_a!kI2wB@aBk}?iLDiC3JBf8(gmkZxf(W|nElJxaA%7XzW$?n~)PVQ9sX6ZTJHvty!|c;Q zszT$SPn#?9kLnm6XR1+;Q&Yj=02X#coji^*dD~2AVXaeqL+Co!k46f?3-2AZ4h2%G z>XgX{L|H&yuA78)`q`-IQm@V2+O z)(DGrZyIaj^Y)0ThG!jIYg3KTW#+GL%Dp~{UTW>hjWf+giL|Lhbm#iXIql4qx31Yl zkmNKk!Y*aD{BtP`#41IFwR^_&=RH#M&mZ1%+?3c`wCp|(P#A-kh44-;7>=1B=?nE6 z*VKKlX(H;gc+c#n4>R8L>X(sRmz>$cUN=3XMqi~3z3j^<5hy(Hy^qI84ClP591F>~3*+jbyKw=_!Ow_*_0@2} zMzWB#`^w+2@h}IUyo$cyq43!(T_fzX5sXVN1C@<>#F~R1^QA3(yVW||j&tzdknJ6p z{`OT<$V8yFXc;&ATcCs?dP^uc+2wiDSgvR>W1SP|#QBIwpLMZfO#HA~2XO;qP|JbK z$O%*gWO9u9RCM>6Gd=6q7vQ|fs{HF$0w_>53)a8gwN4Eh4djFYDmMHp7`lHKTYEV6 zTaPdhkbQ`mAGspJei|01>t++F@8f1Gu-5Z18jqAaCW6X&L@cxkDKRaOYP?)`_<-xm z0p+mvF60x9Qi&s%5!VChlq9Zj#4$&xTGiswHD_$lm$)od0Hxe;mZA#8(VVzivRd|V z@(0S!3{)R@ng+gqUoM{LVGmCLM5w&U=}8Ds(Uch<^PJ(T(q9rj_{EnTUB1T$mYZut z)AW_Zf{N7rgTM0)!foNIYyZgP9gV~*mAZSZ*8o*Px$$zJOUa-C^IZptlJa@;9TtCd zpp=_aJS$_UE_Feo6h^*c#=0?a^IW(>7su{EQkUQ;!ezk8r<3k6-EdmLsd{*e{*Bq- z*y{C*e>Q}HB|xUt)9#1G$r97!iy#1$0ujXd$Mw}?iMr6DYr*$uf*I<%hh9#(Y!p}0 zL66Ic0%&U93SG?eqs$iWx)14i=ZzzcoouSi0HxT!&)jVpi(cH?DC!H)M1qCMrc1)7L>tOc3H(ZAh26E370p*gJ zg{B-ZJpAxdHNPmAPHgc~>jV2pa-GSQJB{YeMfL2Q-<)wADHroad7TLN)9J29(eFyUUpZ(|P)ZXm>c#8Ue zdbIlA7yl1u&Hue2|GgpqtMTCf5)3;Jhl@UjA3W~+PZ1av$*^OKxgfdZDcwx{9;8v9U8r_)w9(J=#dOzr62zubJf5=4n6c=M+7@8gfd$ zxO{%I?7P+S4)~-C+xeWc&9z#QHuu9@V}Ie(JZWil9v;cd4S_xBoh|xA9s1M1 ze0TesBiee-(X0FpbM>;Z%Rz+iii77Hk4?CY7b+r_{U6XqQpAn3{qGYo zmt4~u9Ihw&A{V+~g={W-xzW8B{aD~6Mtn*!)pcgo8NzdNn;94`e2?0RquP_!ZpCv9%?q%jEe-<9o zy9)gV+}E=4R3bJXxsXU?@~T!M)+`0LyoU1^NDxv|lt$CMOxQCvq|9F3y}n`br=FXE zmF(^a@)XOi#i(Dcaa8jiarn?wt&nW-H{nX-vY+c;HCHpa@a3rIC7*IXUp;wl@v~^( zT|X^`g>I5ZdSrU^aN%cfP(Jjiec_0hgRvJxqVneLbemH~8}9BnOV-vM>*iv_ac4zE zx(H<~u!vnSPgminQQMrb&Z~YQOc_m2?WETa=RO6DEN{YQ>6@`SKMoH68ACe7jt|?s zPfSm@L%bw?s)$#7<_gOgPth@+igDruD*)PJ^VW48nv~Dem=m@UWCr5dz6t_B|x6&}~#; zZ|^u=B9zva8PWHQ4IipEU83r^Kv-44zZ(1Jhnt!ZyZh;N_cUO{ZDFaXBco2Wi2fW8-~aI zc3pl2v-j`1+b;eUX)y1p(p(h^LrA5`kg$KrSA1IUbi5qpXne8nzt4=SY&+l=trGnL zQ%m$&e^5?X*R6d8^5$mm2jOzp&i#vr)4avSr6FrE606zELn?90dO7N_5<3K4DK!m) zw6t`lit_kY^nSxxlf_JDW~+OHQ~`)dkwRnhV7UdVEA1yT(I;`fcpjQd6B)Zub=7=F zzO>w8Clc*z_+SF$IArUcSZzQDCEi zx@9OpAk1D6h?fWx6#R)71o~f$h4wh^|2sqeL$Io9K-)e%a)nWOSxRbesi|w}#VP|p ztJW*alF5rKtUt5#8*B_L1y;{2EX}RgZ#=}}opsp;SQ3Bycmd9)0`ABLOgAS936rO& zjK2lCejhF=9Q3=?;z?+p%25o~0H2JT`1YXIu^}}JZ~4xv1rjKFJH4G9=-T6ui%$)> ztPF5p_SUV*E0oQZlJ`XibSOHkE4x!0Pu7L(emDJI&w@ZSSKpy?6Ph;T5cKui-6D_Y zB{5Z2wOnf(ZkygcXdQ}qL>@fTsMDrY51UB27*M=1-EuGC#f<&tt=ZwY^CFu{h*5b? zPtVoEqrqn|@++@lZhN~#G_8Ps{05jPC*d68tPxo7GXp8=(b<@Oi-u~SiB<5!m%T6Y zGh@fe-u@ooDmAff5fS)5J`gM>x`$cbBL27Tt~08sEZmOcppK{v3NnJ=paCfYDov5% zD2M^+Akt+NrAY6gIXcJy0um|G1Zg2ah)5D30U|IUMQTVw3n40mB!(VJD0zo@zutOl zy`OKb_kQ23oO8aj&))m{?!Djj=`iGGLG(7}z5+@*t`q9|tM>+fTu0bspYhzQ6LnmA-TITh+`jS5HsbIz(oyk&f#AnZz5D%JjFfa? z20*c1u7*gRuR47tGbHCj-G|j#AN1{b#4DLNzJE_o4<_nD#`XCebxIK6YMBO~H>nUv z7pd12kX)rhgz!<~mMa-4^^lX_@E)v?n?_R>f1xM0S=yNrVt}ban64i^Qz<7L zi}kwUY~o!RX*|}2IHO%$)KxHX=j6uMTY?P(^jLLs=^!R}OUs8?``}g;bY=Fi?`Rh#(%s~08cK;)@U;v-A=t5TM;!FM0#n<$kfel@ThUNVGQr*Uei3r<=-JGo>> z;v=k&ERc216$UOzS=sRYEIwmyB1!f(;b-W@%Xp{K2_?K4K|?Zt0IX944~gxsGX zB7K3hskA@!R}-)HaWZ9j73t;@LIiZ*tm|D0@%kb`uWzZb30FE|trdI$kPB`xz- zv?*#K_6~K6C;47$BJk2x49P>w$)iBI0Ar zjoJs4pxe*+9y)FwA4P8}HZRQ|)CrbnKAQjso`sb#hQ`;wPmNuXhFS+S!#s4(sj7ro zyac37{tYviyqj41mG9m2(5FCTNIA~g*?9!9@Izmoo+*5lLGb6Ktn0r@Hf>#KM39D8 zV;Ft1a9iTOKsA*%OOup5jm9}#9aRR>O(NO!IT>aQ*(#)9tL)hv#X?2hv(~6_j16u2 zx3aQw@2_}lrwep<-##`iJ2`7+h#NlT8}H7UHG}bo5=J;L^n=y|sGkL~4_qSrhN~O* zoX`qJT*Uo)ra0CIQ5?o?n$`j?Z0Bnxb@NDBkkS>1VTr2@s{SzjAzoYFG3r5a^7r-C zE@tH8Estr*cDru@`CDQ5hT+NON~bhe_O*aPp?V1qFuDa8LUUGjICqT;bL1bYx=)26uR zFH*#0L^lOGCsf^{Cu6y^6D9k%*1K7s?Lng@@o|Ma4AJzUK*oCcvMTU8ME|jBAlf>C zXQ5bcA*BzYQrIW7&iNrF4S*mYd(=pFp@q`YE--q=__HT4VG?5}WkiwLu5mVd6T6$&2(?Lx$dv6`$EfbiQ~PE(U=0HuAfB8KO{iW$fRh zinI{yOL{`%7f%#Kny2t5NE8@Vrn$%Sa)vFKS(d8feUxCQ!~S2qhK7>g&MD~a%-w{S z0s}gJ;sojuL*+^SNrAF~f!mp8K`M6YU9g1S&Ud=_FP1iW+BMXTI3fc4)I{fP1zJC^ zT6AEWOo>!J3jzUPg*2xXCHv9e`=ww{gCP2jW7SH!Ev>Bx;SRm-RE<3^j^cq4Gy)cX z=_}MWhGTp=C)+Ih^L5ip-&D}78R=6M*|(}*pwST4_`KeXO}jmiLs@99o=>?9?0aBi zBVj@(*r**@TJ>Z$j#!(OK#x&FzIoSdA_HIPP8KEz$Wp40#0i?n{o zpraoN*DM}i=ZNzw>AK5IWrxp?-Ys9TJMb4nDJd~^7>CL$YokAe(FQ-wrw8l<^0aq0 z;TbvU?*`6CS65fxnPDNibZ#-oC8NmkZ` z;QrqKMk8z%n)Pmx5V0P5Yls9v^}F^3429YEwk+E?We<~PHeamgqZUQK()uo05wNj+ z-oU#lfE}R0Tp#-iS9Gg+m34@=+g*(7f8x^^^QpfxUM<7GR6=FMKUo4Ak4Tw<32u@_ zj%Gq=qXPM69$$ISWlK-QQmJmyH3?^;+GZ1%h|%67gQFt)ln+M2*x*lE(z*fHk4kFU zKJ2A<(~)(ZyFM7Kn(;!J@vk+O=3%&&o!LqMxAjMf?UYE%CY3yG$KLjZ7Amk6aG0(&Km`djWog7oEU*+D;@2=!f;SzcTm0k5sS zoH&E#PPKPJ11|xsaNOv%sFV_`W|L_l%ZBi~Xy+Q9&t0c2-9@Hor`L^))9wvFsv`=v zHjrG89D#H7EY!$=VA`|@PJ04msF3(HAcXyCNH zD9+7WYb4+H`RS4SLL{uQ1t=Z7OuO88BefLsJ(`g4!}pAw9OMJzw5|?@8>c}$c}X)S zSE6qNXdd<#Mb3(fR@xK)q?PQ$1wzU^m=k(G-A~9Nm9tkY3>v3#=LI^}&;6wxXVn=! z9o%HQk(vT_IO%?4LxAl7fYBxde@klV2>;zfTbC(+oliA;`Pj z@x7#jcFykhH!-_Z(!bddil@JGz;j$ASd+ty$1k)RSAQ}Ptez;i8r)f~Xypj{`jP7_ z^yHy^^FOgg-IyQ}xW>S*WBZ{-R^R%EKVW>l_iq-s>3UYdgjGnj3*5*O6v3U+N&X4}UIQfb3<}X;E$LR{TRhupY7(;^d0wrvnsRR~J> zq*(@Rp~M1p<#WDP<`rkz-OiF8+{DG zlnucgG<+$Oj#D@f({#{K?`_nDCZmTreeue{TF2n49p4hi@m?hW2=^u@;*}5&vTMMs ztih)3)kP~@u8GT7{qpn#6v|&BoT9Wy>x9<^T4&t-DsQIY-eaGXIF15-X-v;Nz5`KJ zY?8aWl#r|W)d{)y;F6zYP?fH(E{`*?60&i>l~o36q^mQl3z6+&m8aXDDk-X~+n`^< z%qMVuEpz&yqKHd{JgxleVo^^|f6wqgOJ_WJJu|@n;$-}3xaK`Cq3JK`&WV2Na%lf= zdltA1fvEDM{RaH?nBKAb%6kQq00MYy03f5LG1&QU>tbyAB{i=B#8e1*l< zBaaP1MUbg^@8FldOG#$8hr2OHu)fw7w6pCa@BPd`2~h~Z_^hzjEyejc=+|1o_6Kw7 zUq7*TMC%ocUS!wO%?i%hP0)?U9tZJumg#cP6)Kl9A~A1MR9Me@3@I?7uJ^{-ySnPT zHzd6+)HSwR`c%!+B&_+gZK{6};^V=2q98c5-(-esAYx_t6dm@D^pJM4mXek% z)F#%U>ak#uFpBQ$>K{5Ee0Off;F!W|hpG?82gB|EFv-Q=`m4K_W@dn1^uWiv1RF`vK9Vn*KmUVk@+dO||;`&QN7s43&pBtawm2cwCR( zu#nAKA8p5@-5%2haY)|AI4v;uN_b{28C?=LNwYV9+_D$273zZZlN)eJA@az_eo@KT zm}`xAF=vygtqCKYptE)*3uh`UnAO9>sHl*egmTz!zf|TMc)5^N2`1X9y7)^P5=TsH z(wl@pUD~8riB3^jb63MUrO9eiVO}?Fn~t03TbC5&vSr5ZiHNY3>?I^rrcRg4Ossp; zYWBlR-%sb_8-0{%BXT$38E-RD-3F+kahE`os|84KiT{A!&pvrd9@-PE!&@YfLVOL+ zVf{`QBZY;(Ox8eScz$$SZJbDUyKJZ@<>Q9_njiJf!hmz-YIXkc-BPQ}z@<~!+&g}6 zA;=d9=o^tlrn&b*_%4+!hRQzn7-D3#C)fMEeFgutPD^S$d}(wZ$?Eqs+;B3JGNq@ zc^{vunhbolnjCt4`0%0~bMb{7EVa~z1q*8v$!+!~NG6O7r%2~2>nR>jTqeJB)n7 zxYRlHtPh9>n{xRMH;+u+J$F9z!c{wXMjmtI??818pN<8c*V%|H_tx=K;kG6614HbB zEjeiac65h4gq3F`#oI2u3Q!IwrXlWfClOTQu3fU0vG}IU?A3zYi&13|hd=kJ#-BTo zVPfw0HdPMcMg6lR7w=gDLH23#l6LSfzpLZ1#>#z8gqyWLLvwKtU(YYl%SO>0GGDTJ zDFt&$#}$eg7N29HFfX5|(D26a5rT_N#~D*fS``ioea&kO{?q}?QX^^UE1VocfR=HN>-IQ z;~far^Th6+u6`>38b0;;^O-LJT{(ql(y1|c>D==~cqzd59P1qw3jZ+ht%vnF8?u8B ztpAew);7Y^CFU&S^eiYc7wvr`fS-Ns+m|nc%f7X+z#d(*93rkOnXf`~^7`kM%yLLD zDN0?!r=Kl7{);raMY;_Yvz%|PQ9bXgX=UF`uqytrh&%sL{i`?mV;7r#@!iX8d=$3LGL3LA89a=-L-`(TxO z{r0WNVI!>%I`ltj%Kiy21##nz5)qu5Dd~|ap5`)7^-6KllV2}D`F@*s3vQcI&%m%Y zz3=aH2F7aze?t@p2b_ep#J6V)b7F46#ex-v4tF{1T*JM}j%sD?v1x7{ECFs_Cbbof zy??V+u?}a(Yt!5L=2{T9tRNF-YN>>hqT2Yz`D-3KW=rB@$u-a&>*i5It;556AzZ8E zO0Mr**1Ix2{s-~VedeJZOc-JoO-D_@FhkE$gw%4iLsK&#a^vMW?lQUxbe0-lygsubHV#nxR=#Q)INzY22yEl(#ZCs6R*3)39h0oDWC5wy4 z-9bk$)d9+%BO4sGt$P1U=wT6Z1%dDVWzxoNPnHDY)=hd^0r3fu+g=oNO0J)vk6jG*O80bLS@UJ2RS2{Snl!fk%4JP*yZt3 zC0GD;_usKeG@{+Tzi)VNDk|;1*`u@0xd?!8X)Bf94 z-N{PM9aqrAutTsSJ=z%W?BbmGqe*=7fI@JKs|=s>UJ3$LkDIXmzI!YoB$%27FI_U& z1@5-d$&_PWG<%eN8;)DBumnV~-K2*SH0e%OlWt^R_L}mB4r3$)rJMdKgI$c65ykDv z0vox=RQ1Nlpjl=wuj<{g=y*3nZP`V*F?)t zP-{a5vK&ZZ??iiZSFm%cd7o6J$34rR*M90}P!Ek3yL}4ibE&&Iu_hi@RR!U3N1abF zQN#TWB@}9*xOK0^{%gjbyL=b?PUZlaXw&e_hwnvChjg5m=Tuq6U053kS8N(w+-S(D zbR$g96-7SRVUzKR<621jV&*7@Su~Z?aF5c#4xZ)b<}HkqZh2)SJwfLdMyf{&}{H2tt8;Rw|38YTdP^dHSX}OW72HQ zAG7B#xtYMrn<+)?K3xi zG6e&O4p4~##{6mR&gM0LTNO8dm9gU`Zrb?-j}@E@Y9No>W&saTqUjjDj@G%^jZiOb zD|x<<@`yno4z!RafF0OF2v={S2ZgV?!8tIOkU1e1TroXg`pPjD zkB#!7QPFik>9Ps>5zArC+QEqwu1?XZUfjL7{;~C|&Y#B?3VS2zT`Lxv;_Ev?{r}cv z)mme($EOGBfF-tUN9&r1VJ^|PE8VmWS$p%PA7cbn%~t}6i_v+U8S9KS6QPfgVN0s> zSC?1HGFFdxFux(qpQbI{K($R6Ta`TPo(CqLK|y@dR=B7 zGlQ7M^oY@`ln3bxD`U+lVC5|+_~Fdh-6&XVyq8ElTL<=GIt8?VJXi@CU<7pGYXW3z zR8=dvOq=g_jwP62+ybL@X{PTb?t}bl+0S#rRz1si=;p#58&0QekN)c_pb>lOQO6Gj z3Pn1;t$2N=6$!D8j5J4N`6rW;xV1?JSr@=Sl_#c4R2eBDE_w2DcongVJA3yZKW_bR e+@stTIgk2!AzmZW7JS_12dI&`Va<*E&;A2Fx4qT? literal 0 HcmV?d00001 diff --git a/test/goldens/screens/pin/goldens/macos/verify_pin_page_unverifiable.png b/test/goldens/screens/pin/goldens/macos/verify_pin_page_unverifiable.png index 725b699012f288cf420375645df8a8327a6f9e27..04502f733c8ade820f8de47e2067e9e5b2ac9010 100644 GIT binary patch literal 31270 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 a87J0PTI+7~f^IPZe|p-6k192vMg9+iWE3F) diff --git a/test/goldens/screens/pin/verify_pin_golden_test.dart b/test/goldens/screens/pin/verify_pin_golden_test.dart index adb2655c0..2170c1ea5 100644 --- a/test/goldens/screens/pin/verify_pin_golden_test.dart +++ b/test/goldens/screens/pin/verify_pin_golden_test.dart @@ -211,8 +211,10 @@ void main() { }, ); + // Gate flow (no `bottom`): the button-free lockout copy, since there is no + // Forgot-PIN button to point at. goldenTest( - 'permanently locked disables the pad and shows the lockout message', + 'permanently locked in a gate flow disables the pad and shows the button-free message', fileName: 'verify_pin_page_locked', constraints: const BoxConstraints.tightFor(width: 390, height: 844), builder: () { @@ -228,6 +230,28 @@ void main() { }, ); + // App-lock entry point (`bottom` present): the copy points at the + // Forgot-PIN button, which is rendered below the pad. + goldenTest( + 'permanently locked in the app-lock flow offers recovery via the forgot-pin action', + fileName: 'verify_pin_page_locked_app_lock', + constraints: const BoxConstraints.tightFor(width: 390, height: 844), + builder: () { + final cubit = _MockVerifyPinCubit(); + when(() => cubit.state).thenReturn(const VerifyPinLocked(failedAttempts: 10)); + when(() => cubit.checkBiometricAvailability()).thenAnswer((_) async {}); + return wrapForGolden( + BlocProvider.value( + value: cubit, + child: VerifyPinView( + onAuthenticated: () {}, + bottom: const _ForgotPinButton(), + ), + ), + ); + }, + ); + goldenTest( 'unverifiable app lock offers recovery via the forgot-pin action', fileName: 'verify_pin_page_unverifiable_app_lock', diff --git a/test/screens/pin/verify_pin_page_test.dart b/test/screens/pin/verify_pin_page_test.dart index ec591b45f..789d6deac 100644 --- a/test/screens/pin/verify_pin_page_test.dart +++ b/test/screens/pin/verify_pin_page_test.dart @@ -131,7 +131,7 @@ void main() { expect(tester.widget(find.byType(NumberPad)).inputEnabled, isFalse); }); - testWidgets('shows error message and locks permanently when pin is reaches threshold', ( + testWidgets('permanently locked in a gate flow shows the button-free lockout message', ( tester, ) async { when(() => verifyPinCubit.state).thenReturn(const VerifyPinLocked(failedAttempts: 9)); @@ -140,10 +140,32 @@ void main() { buildSubject(VerifyPinView(onAuthenticated: onAuthenticated)), ); - expect(find.text(S.current.pinVerifyLocked), findsOne); + // No `bottom` (no Forgot-PIN button) → the gate-variant text is shown, + // not the one pointing at a button that isn't there. + expect(find.text(S.current.pinVerifyLockedGate), findsOne); + expect(find.text(S.current.pinVerifyLocked), findsNothing); expect(tester.widget(find.byType(NumberPad)).inputEnabled, isFalse); }); + testWidgets('permanently locked in the app-lock flow references the forgot-PIN reset', ( + tester, + ) async { + when(() => verifyPinCubit.state).thenReturn(const VerifyPinLocked(failedAttempts: 9)); + + await tester.pumpApp( + buildSubject( + VerifyPinView( + onAuthenticated: onAuthenticated, + bottom: const SizedBox(), + ), + ), + ); + + // A `bottom` widget (the Forgot-PIN button lives here) → the text that + // points at it is valid. + expect(find.text(S.current.pinVerifyLocked), findsOne); + }); + testWidgets('shows a loading indicator and hides the number pad while verifying', ( tester, ) async { diff --git a/test/screens/support/support_tickets_page_test.dart b/test/screens/support/support_tickets_page_test.dart index f6fcfbb70..601d5f88c 100644 --- a/test/screens/support/support_tickets_page_test.dart +++ b/test/screens/support/support_tickets_page_test.dart @@ -98,6 +98,39 @@ void main() { expect(find.byType(OutlinedTile), findsNWidgets(tickets.length)); }); + testWidgets('renders the ticket date in local time, not UTC', (tester) async { + // 23:30 UTC crosses midnight only in a positive-offset zone, where the + // local calendar date differs from the UTC one — the case that tells the + // fix apart from the old raw-UTC rendering. On a zero/negative-offset + // runner (e.g. the GitHub-hosted UTC CI job) the two coincide, so skip + // rather than pass vacuously and masquerade as a real regression guard. + final createdUtc = DateTime.utc(2024, 1, 15, 23, 30); + final local = createdUtc.toLocal(); + if (local.day == createdUtc.day) { + markTestSkipped( + 'runner timezone does not push 23:30 UTC across midnight; ' + 'the local-vs-UTC date regression is not exercised here', + ); + return; + } + final tickets = [ + SupportIssue( + uid: '123', + created: createdUtc, + messages: [], + name: 'name', + reason: SupportIssueReason.other, + state: SupportIssueState.created, + type: SupportIssueType.genericIssue, + ), + ]; + when(() => supportTicketsCubit.state).thenReturn(SupportTicketsLoaded(tickets)); + + await tester.pumpApp(buildSubject(const SupportTicketsView())); + + expect(find.text('${local.day}.${local.month}.${local.year}'), findsOne); + }); + testWidgets('renders correctly when successfully loaded but no tickets', (tester) async { final tickets = []; when(() => supportTicketsCubit.state).thenReturn(SupportTicketsLoaded(tickets)); From 91b9301b19cbce9bf152a56844d3b0f85eded82b Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Sat, 18 Jul 2026 09:29:12 +0200 Subject: [PATCH 2/9] fix(transactions): show transaction dates/times in local time (#872) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the ticket-date fix (#870): the same UTC-display bug class in the transaction rows. ## The bug The DFX history API returns transaction timestamps as UTC (`Z`-suffixed → parsed to a UTC `DateTime`). The dashboard and history rows formatted `transaction.timestamp` directly, so they showed **UTC** — off by the device's offset (e.g. +02:00), and the date could be wrong across midnight. ## The fix `.toLocal()` at the display sites — `transaction_row.dart` (×2), `transaction_history_row.dart`, and `pending_transaction_row.dart` — so every transaction date/time renders in the device's local time. Consistent with the chat-bubble and ticket-date fixes. Five goldens shifted from UTC to local and were regenerated (dashboard recent/hidden-amounts, history list + two receipt-row states); verified they show the +02:00-converted times, consistent across dashboard and history. The pending-row golden is unchanged (its fixture doesn't cross midnight). ## Not changed (learned during review) - The **date-range filter** was left as-is: `DateTime.isBefore`/`isAfter` compare the absolute instant regardless of the UTC/local flag, so a `.toLocal()` there is a no-op and changes no filtering. (A separate, pre-existing local-date-boundary question is out of scope.) - The stale determinism comment in `transaction_history_states_golden_test.dart` was updated — it wrongly claimed the row was timezone-independent. ## Verification (self-hosted-equivalent runner, Flutter 3.41.6, TZ +0200) `flutter analyze` clean; dashboard + transaction_history tests pass (incl. the unchanged filter cubit); goldens double-run deterministic; only the five transaction-showing goldens changed. --- .../widgets/pending_transaction_row.dart | 2 +- .../dashboard/widgets/transaction_row.dart | 4 ++-- .../widgets/transaction_history_row.dart | 2 +- .../macos/dashboard_hidden_amounts.png | Bin 52063 -> 51954 bytes .../macos/dashboard_recent_transactions.png | Bin 56031 -> 55894 bytes .../macos/transaction_history_page_list.png | Bin 28877 -> 28741 bytes ...ransaction_history_row_receipt_failure.png | Bin 12945 -> 12938 bytes ...ransaction_history_row_receipt_loading.png | Bin 9049 -> 9040 bytes ...ransaction_history_states_golden_test.dart | 9 +++++---- 9 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/screens/dashboard/widgets/pending_transaction_row.dart b/lib/screens/dashboard/widgets/pending_transaction_row.dart index 6fb45f789..6cc57035f 100644 --- a/lib/screens/dashboard/widgets/pending_transaction_row.dart +++ b/lib/screens/dashboard/widgets/pending_transaction_row.dart @@ -71,7 +71,7 @@ class PendingTransactionRow extends StatelessWidget { ), if (transaction.date != null) Text( - DateFormat('MMM dd, yyyy').format(transaction.date!), + DateFormat('MMM dd, yyyy').format(transaction.date!.toLocal()), textAlign: .end, maxLines: 1, overflow: .ellipsis, diff --git a/lib/screens/dashboard/widgets/transaction_row.dart b/lib/screens/dashboard/widgets/transaction_row.dart index fc96b81ea..cb2b412c4 100644 --- a/lib/screens/dashboard/widgets/transaction_row.dart +++ b/lib/screens/dashboard/widgets/transaction_row.dart @@ -87,7 +87,7 @@ class TransactionRow extends StatelessWidget { ), ), Text( - DateFormat('MMM dd, yyyy | H:mm').format(transaction.timestamp), + DateFormat('MMM dd, yyyy | H:mm').format(transaction.timestamp.toLocal()), style: const TextStyle( fontSize: 12, height: 16 / 12, @@ -187,7 +187,7 @@ class SavingsTransactionRow extends StatelessWidget { Row( children: [ Text( - DateFormat('MMM dd, yyyy').format(transaction.timestamp), + DateFormat('MMM dd, yyyy').format(transaction.timestamp.toLocal()), style: _secondRowTextStyle, ), ], diff --git a/lib/screens/transaction_history/widgets/transaction_history_row.dart b/lib/screens/transaction_history/widgets/transaction_history_row.dart index 26517ab6e..da3382ea6 100644 --- a/lib/screens/transaction_history/widgets/transaction_history_row.dart +++ b/lib/screens/transaction_history/widgets/transaction_history_row.dart @@ -109,7 +109,7 @@ class TransactionHistoryRowView extends StatelessWidget { ), ), Text( - DateFormat('MMM dd, yyyy | H:mm').format(transaction.timestamp), + DateFormat('MMM dd, yyyy | H:mm').format(transaction.timestamp.toLocal()), style: const TextStyle( fontSize: 12, height: 16 / 12, diff --git a/test/goldens/screens/dashboard/goldens/macos/dashboard_hidden_amounts.png b/test/goldens/screens/dashboard/goldens/macos/dashboard_hidden_amounts.png index b183d7dd22b6e3395fc7ed442914b454315268cd..48ddfea2156abaf3dd6e42d524447e1e151329b3 100644 GIT binary patch delta 16347 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 F{~y}mu;u^& delta 16519 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�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 Xyb&RMFGI8Y00000NkvXXu0mjf8~P;| delta 20680 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

Rr*&qDA0*~?Q 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 K>0ur|ef>YJnck8B literal 12945 zcmeI3=T}o()UWXUy{A{$R2ae^;^%JnQJE60IYSEgNK8SjqNN* zTOGp2b}WL8?f8?wPqIdyzT)6#y`AuS3^M+k^$Py`MHK7$m={FrF^ZN&o+#At@ZoNeBJD^}Xd_>uppZR&3PGN2%9!uIKL}UEeGt4Dt$u>-;RVv0 zoMUWXzlGmnV>{E%#&+lL<7{8ApJ8Kr_iqefLvnBNMQh8qeXQe-Fur<5{A#?&<#=tl zB{*^t9NFTAL>@QW~I0Cua$ka4QQ4T*MJ2x^MS%IZ5Jd?KcJ zr6Fj{A%`KGhe0vb@21V2vIUEyEp{xIO#f>8N5<&<(2eDPbiB=8O{}-jRQl+P7C!64 zhWe_TpZK^cogr`0Mc^bL@y>4rz(9+6;gjQRJ^2vA)weTIsqQ-_FE9H>o$N&ZRw;Y4 zy8a_A^5t+drNs8#NuJ%qQXOsWC}o+g?183SGXZI*DTw(F?=p%(Sl*T@`b64W^Q#4~ zkEn?=55;|Le8KU5E*ueIc|a%P;ZD=sdQh$JngR%dDW#F;-IS>l6pYy@jO}Y%agB6D zl=0>8o{8|8)0#EUOZDSi{Pzxu3vstXDzpT`AS`e4NW&Tihlcv*5WxzDr>{sF6E5i+ zfWZ|V`uoOn!ojG7jy7bzf*%|xVb$~mbHr$-tAwov%ou3LrqMqS4_^ii9`5kgz!v&F zhJSV$Kp^l*H=K&(F*f55I;XK6ldrdqxG+Uunw7$y@_HTbc_P8&C1*8yNjq(E!wZ@z zEmm>70LZIP+&N*Uc$8^M=cVj^fZud!y;e)Uds{ZwX*H$s8n?m$!N^i8Wd5INo()(I zz*kr`Pi}Lvag{^b&)-%6*4}vsMlg6A~IbV{~6o4MRW7OqH-GlCY=~lNC0D*Vy-_!t^e&ahj%FjM$Ms7T)VW z{A+PFcqhcU2Iw?sWF}q>b)LkX)wl^oTrwb{{_#9xWoc^a+Dhx6Xj&X6*ZOx$1Yy0`Ci})@ z@cDkawmTG!a2oNOPLs=_QCcVX?%V{qpW&?tKh0fYm>32w-7b8;&6x}g> z?{7#++3Za*AFlc$Vgtpi9rU)d0CO(G#fBxepJZGkI^qzy@c_lkr?I$LZgQM0HU$oc z%-G6(6r+3K`D}_mYc<*+Lv~V*hTE(xT*qt7nIRh&K!anB*`gpGxg~k!?bT;Ha@%}; znV8&BFGar<5m3_}S>|hKnVR*-!ZDI-s##S^a$Q;*GN{61G3oP8*4B^z>D8KnVnl6CJxkJVqb#kux{8(TbQcefyz(W}Uo|QAs zBV{FOJTecMzN*)Dh<7Qv@RKS#?}1LkOMY|Ek>jES0Z0yJ|J0;Xn0BMkq>Z^)u+iWY zSZ-oxDc{O*I3+pTJLteAR|7&ToqitKnx-#u$$hMIi;J+ucEn|D z?#SwZK&DApPLmWdljqnn^J-1_aD~;xXvp&Nvh!%}8`Cn&k{w=|;?UMWPkpbsB0b1J z=!Yt3t#hqI=yv(Nd(KN`_rZjZGH9nMI{RV3=EYjM&jIP3^{OvuZ&y@;^RhUdW(&)I z9w9NU1~X9)mQ~A*g(|nCvW;ep{$5?^3n1ht=y-LOUeaj^bJr4Ia~-Flbf8d6tbBI= z)61{lb$9t`x(qEY&5U6fht22j9EPXJ_PTY z<0=!=_HBm=X@=bmgob#u#UV+0m~R|03PN%nrn z!g5DLLc@OKAd^#4bETx*^WEGfeihM|sQ2K?8Nuc*c<_X(p@IEsuq0)ylSv*r$>8>H zwH6W<7MGVdcdOTROH=e!6csIbGN;hA1i7OqqlZVC=!!KtjB>tt_%JrMf1x{OZcr*D zaBr|&cDmRItO8RF=JvD`1c4F7gU3N6L?QuIO%Ih9Q93>%!XebIkOdb5UdgxxTg$)kW(duK*CqD&mS&3B19F1wcyXE zeFjk`y`w%Ivzr_5_H^MnL5v?ypUxUSeOi3iE}wKYj!)%Dk}x>xhtgN1ZjC$%GGHv>ML}d)7ZXk(M;^IxHiSzrp6)!(gUaGa{gFDFKV9%_wf-ct(|e;gipLuf!WQjK=B>KLh} zqbIkI{eAAlytiDRzyarshkCdQ2A$qXmT zguH4gD8C71o)0a-uIjb)^0R*5G#;Sfsg|>IDq#;L9lO!&WrO|dlHKNxS$PALxVwrK zI2H04q89;xVp+S|_U+xtp1Iv6tfzp>Se5D@r5@2{Rf3e#kTq7k1WUZWpjO-nE9rVL zo9*X)5GI+Dzw{T)g-{V2g6#fdrJd>rH%(SFbc~*B*`RO#aaOD9?s2O@7>T3& z-MxiMrUYBJ5^WZ1BlIS=hu7-NfS@Eqo8~o>IgCLz5U=fC#$+&wY7D6XC+BC?T}vew9Pqt>Z-CKuZ)J@cWKqKHx-RkKA~Hh(;l6A8719A zB$3OUrPihqZjGot>uF%GV*i?aK%mP{paesr32G@Nu9mj5QgP_>c>;o+iPHR#T}BCm z-bD81VR>bLy>26=CeXF_Ox+6EISBX*v$ON>!;M4V`ETU z-w|AK5S!L9wU1j1EdwjmS-qm;Fq?Uy7Iiv#9#1=K-1Bs$l;XBei5uLxIymBL!Nn1t zY+k*{KYix-x7@0F$ISe22ItMhC>gi$g5l)LLc(eEnS!#qD<`Bq7HtkgydLqX=}O#7 zlK#CEh{`bz&@wgY`Q%a*^A!{KacRZUI zA&=^Ht|5nJ4Bj=RHyzqI_OAIljef#WXB=(F5#o{`fvwUG@YHh4{8R3&uMi`D`F8b5 z{YsLGi!V=Cuh~9l=gyL<`n-qSyxx;vwvoel`&O_K3~si~ErsvJs*cHDM zxvuh!fsDjsV$=hl=_GfQ$##b`0R?NTrXjl4d`lcOH7&7$lj|6(%f{aGBkKfFn|dV&PtpW-Mavi$dn%ZDR3n{N-tAdzdTb`2HQtFI7i%E zGT3Q$&0P7ZY~B!fwP0*Jlt^w#^UHE7=~h6~Hg%p*yB1(5tKK4L z+r1{9wQcqX1=2%cZL>tcD^~pLHaCS~3hhW&=NwtX1en*=+di z`Lt;aX~0gI^OXlUTVln{*t3R57K&v8J$`u{(2waI?Fwi6dvDsoks*ruHo?kT6JA@% z);^uWqftC~90Xr;`KV^&Tw&3z?qyi9#?b8awN@-k;w!P&R9D_uNG6po4IV2I1&Q6B z47i$s<35tVd!sFTc>zE|)$;C4lI`#m`2FPc3&dBf;FB`1H@p)f^$~lc)@4kcFu9Y( zfL7nA^J==8;xi_IDMhdtbmiy}nG-grhA**F+o^IEyZh}@Pz8|dO6t*)B%5JMTX`qh z6J=gx*8LBcY{{^CS_YmfmiKlU{1ZNOZt1yLxAFxx@hS{mhxcz5UBkq$>+qucq?9xaCZ=Rc#La{Dsq>{wa@mdCc0j40$awb=_I`v3j z7YNehNns{4mB+TLB6qrz&3jnPO?8!rHU_G#G6GbYd23?rOPTSH9TVHSL)V&B*R$vCetCI7jByRMDZ2j!NG-oVFV! z6wL&}7;&5>HWn<+^T^FBv;U=t{uRJj?(E!E%^ECh4Lz_cJUK~w3oP-PaB!Z^xWl$! zC@S3WgH@Fd-7^{6Y-c* zt&<4`&8d1mhuzt|$_x*=+X&TSAf4(?biK%26 z#6C?z$3bvM(nN)rr~5rO#esTk!fSBXo?76CC`8 zRgC}F%J2V5P5-}#{(m?)`OnG!Ir-l{ocm8>|7q+$js2_V-~Zz+CinuA!^U<|>Q67g zzjP-5Kep*VJN}Kp- z%$|3i0Qa9f+p84+y|=<(e119QUKl)?-hN&PLk}#BV?LW(9zs1`9X!k22bfkm*dZY07YT+qquvg+p@x@%)_zHRQ|jz02mWZR z0*dgl+H_5sGsz=VpPh76uu)aWC6^XtmP6=GH__73jw1RRqi;B_m5r_)uf4tL;V}GQ zo5MKbs9sd`yn%0|Y0vVY`9b|u?ncCpjvVtbtL!-DUcG_&8Z=?0vozK`h)g2v+4MBm z=!eJQf@sRHdRH6Ux!9nll#M_@vHda5D>VB!TeC!ZFgr3t$i_JX&hM|@{~?2eOL^i*}@yCltHjJpKfPI};xYBFI8?Vp= zblvJ({3Zn{<$gV?>&LlN4qk3v)73WU(M5OTKNrr!L|dLWZdJ5cCc0V(ft+mmk}0(} zbQ(5p%9CXcSf=+-ruC;LD5<0xewcBISU8qL!UENz!fiZCu2pO4h06qFyxm?z{oc*A z!_dCB9T{&d9p`uwRq>{t?6DVk(w8k z$sdc8XY%KN7MFb8PcW9-zb3zeH(O~I&|ZIO0l=fW%`tD3l>3dJ^^mq&cbY6CA3z4b zlAh1`6d?cXEqU@t3ZxYQQ1lC0=+%SV z5`xh!BGY7akB`5X5OpR0H`S>Jvk5)04guwbZ;MlR#&1m7|KmP+-7m7ga)O1H)JZp? zEW&+XMCsTC4k?Q((=a4!deC!?<25Pn^BbeEf);s5f76cfetY#+0`1}YE}-w$7k`_f z@8UFZc$yit*+LR$P7=w(VRrYSzY(s?^1J>@sI9fE?0T&2f{kCwWlmiUMAPMHk$OTS ze~WUmC$D1dvTLOvHwIBS|K?pUBg27Z=G*F3 zN`;#6fmXupquQ^kq0`#Hz(4^e!h0mdEf-0Soo26aiM{3CXNR!i_^87+_pIUd-fjcV9~B!G%iM?m;={ z7hQ_KZ-p~0i`u{8R{0#O7a_pUzX^&7F&x1vcX=Z6D?p!Vecv({WKa-lr>x?`WKP-7K;_hEK`++HPSbuMcJ)BUM=-w5}+CK(| z?|TjcQ#77UMpo)Y!W4ue)xysa(epRlyYO{ZREEdXZv^eFzo$h~dj@|J|5|B$}rvVbdX*6y&tv>zo?J+K;-3@-nfyAsb?Ok7a zUg^|kJJM*|G0}^}y5?S9)9(UND#AiPZri<})hUAot6XsjX^!T5J__-uf&A8+7WJtr zZTQkzej8Ah^gSRb??>!U-dvy;RsY4%Ekz!i9(!GoSEegq$#!6^^xN+o1bGki=Jh!gI1?V_H4gOI z=2Qsl!XJmxtrs<>#rGsJss5=x0&Dv(mX|{hCGXyTr#+Bq^89(pyZ2Wlqd1%FbsJN$ zstz@DJ5BNBO62_Vh*~_Anth|Riy$6vpIGqgvj%JHN@_vibc&WN;3W6Yv}QG7c~oJq zsK56_&%>Vq zFT!zWlYs|Qd}8u^IskdibxGl#baP#no4Z0*H-+LQ@7STTYMsRcer>~~oy15t;^tc! z4)XV$b>Ne03YxMOg(iul;dPx=)rTya?kXj`d42he7&C*JBzV`bTW$Kol(N%j*2r?( zsf98lS%q?5_r$l4*u|a=eXqEgDcg(xobo4cEQSbG*b_-kZewL#b>&-BIki8AB&L_V z-wy0C$2A;zur+y?>fY*nNYV^d&l z2o|~6X985U$=MFnWC~>cG65wh8QwrIuR}WH)AmLbd#QZ%)-}wX21}i53QxMxEfw!$ zndKWJ8NSbxYaDuAQY}jF{Qdsi`RVw-7K|~9XFw>VZ zx~HR2_sVoi+N{g4wF%Ox4_O)Agy^8Q-1&30Lqnstxm%@LCS054Q3a#}Yu=lYgi@9P z$%iw-o)Rw6y;W(;t13^{e<-uUgXvZ-ce2c;ZUCiCHSp$WysfTfTej-STOnd*L)a7< zuR1t?aM|~<9_d%}4j8 z-b(D<-krb{Ds=T8(LYsaV@!Aei>QBP=uaFU-M-8z4te@gX5oANmOK1y;=65A=gu6(pU=_b$14_P^Nhkjo^RaU-c9@BHY%N*%O{j`cNjlq25;vE%b zX^;izXh1$ip(-0ql5+#WM%;6|QYC`;R2k-+J$F{c zS>&(O8aF+Q&436r-Tip0amYg{Os-j0WR!FD*ylh diff --git a/test/goldens/screens/transaction_history/goldens/macos/transaction_history_row_receipt_loading.png b/test/goldens/screens/transaction_history/goldens/macos/transaction_history_row_receipt_loading.png index 4ca646463959802c6470f6bd00ff098c002f2060..369aae9065e7ddc4b9bec146c6bdbdd8a5f409b4 100644 GIT binary patch literal 9040 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 Rd?m&Naysb8`jeM${V$0*8r}c^ literal 9049 zcmeHN`BPI@-^Ws=7L@j}6#;=-5FTZTvKk;sEfpFSX{E|elr6FZlmH<>5-k<#qksef zAwX&=P-T@ZY)KR>Ye+iv{|M0{cj|akqWL^c*?xK)ifu!(Q)t;4OpJ~~=*-32F(rUEn)%xz3p4M0Y zt3y42`KxhG#MPNzS{dn-_;6ChSub;4HzO#%h+Ez2>{68wi`*wg4E;f|jZW*l`qg{l zMc>M?8$FM7gD1xZ3t9{!25^~7a+uLxO>SV6dUR@=7SMd8@-NW2!LIS-{AA;Xv z!T>XIS#O;$7Xt9JYE*(Y$O)sFU}GzL(6b4N`@;Yf+4f0t>@4 zH@~y=?r|aq!rF7r+~!od_BS<+4JsX`MUxI@DJ^tnta!+cf0U;AnC~ABuCK;Uj)eL?mw=X<3)RGoA!#n zhis2Qf#PE7@IHPc(O1p0TAuAjF~bkh&sZx=*XQDTraB&kCR_w>=I1R+i`7ru)$ctH zjy7})N>1p5-X^mhTB0JEvl1G3Z4LqzZ6EUCAYo^|%>zkN@tlVq5PS$~#sS4f`s9%h z-oEmDy8VdJ(4YUVqj(%a=o+EdpSH)Hsardl7udyl!{#j|m=@g8*5T0h*F+9JJhr%T zAB(!B0^bUo6LDQ4C+em;-}kZ?f7Qhmv&+P_s~vq3C|)5Xly$FlT8U@4C{SbgeDCh| z>K#4v4OH~%kWbX`BVO<6uFqu}4ZXb!z+?>5CacT`!J74KW`ALt*@m zbcK)D_VemA0rLeg;(%$oyOnbNRbAUepfy>j1wyp_cKzj_$DUe;UH9^`yo9xZ+GG31 zqN9bX(1xcPeBV${v^H>lnus2+Mk!SV1wKQ26oTSC+VbLPPd&4WZq+T6rB6kfp+m*+ zYCubYg+mn0|7qkWJ(uf?PPw111({f%iMb(nKqA*kjPv+lxxm9AarDkCR@m!l?n8@V zxMrrK!dH&xn3Atz(ft6?d>+=lK-gpLg;@xaamQf%w4Hn})6uip8UH?HHCJI{6gV9s zo}RuMQ+^v44^u5@L-CD*E{^Ggcl85(yI_9+_lt{tVns_$IXe+p=@)o4?5nY&g#ma$ zjtgDtvmKf;i(u^R7}UV?X4w@kf@UP1Ggz5Re|3+N`Dzifiq0GH z%6Ou81d8{)1hU;->*;Xk!TD@V+&!L61v@ldm|q@BUAW9s)WV1b_YNg6_9)f}w*7g4 zyb#PN5jnM34Cj@zAJ4K??U9uJ!m%5FdC{4Hh9J`{Ai@M4&8QN-y^&;>oh<@o>{WPgL@e#i^=?$6_s^soC4+|Kyd)@1Pd`CDsb>Ea__pP&z_0~0XnZ}TiMopGJG{R>k73Aq({$qn

wF*Cfgi9+Sm{WG% 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 O=($TjG<<*U*Z%?ifqyjs diff --git a/test/goldens/screens/transaction_history/transaction_history_states_golden_test.dart b/test/goldens/screens/transaction_history/transaction_history_states_golden_test.dart index faccdc1ee..e5cd51d65 100644 --- a/test/goldens/screens/transaction_history/transaction_history_states_golden_test.dart +++ b/test/goldens/screens/transaction_history/transaction_history_states_golden_test.dart @@ -30,10 +30,11 @@ import '../../../helper/helper.dart'; // multi-receipt spinner, the receipt-failure SnackBar and the date picker. // // Determinism note — dates: `TransactionHistoryRow` formats via -// `DateFormat('MMM dd, yyyy | H:mm').format(transaction.timestamp)` -// (`transaction_history_row.dart:112`) — no `toLocal()`, no `now()`, no -// relative time. With fixed UTC instants the rendered text is host- and -// timezone-independent. `TransactionHistoryView` itself reads `clock.now()` +// `DateFormat('MMM dd, yyyy | H:mm').format(transaction.timestamp.toLocal())` +// (`transaction_history_row.dart:112`). The fixed UTC instants are rendered in +// the runner's local time, so these baselines are timezone-dependent — they are +// generated and validated on the same Europe/Zurich self-hosted runner (like +// the chat-bubble goldens). `TransactionHistoryView` itself reads `clock.now()` // for the date-picker bounds (`transaction_history_page.dart:36`), so its // builders are pinned with `withClock`, matching the base file. From 1579367cb20f2adf28d8fc6689c53bc15415c96c Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Sat, 18 Jul 2026 09:53:58 +0200 Subject: [PATCH 3/9] feat(error-handling): catch unhandled async errors via PlatformDispatcher.onError (#867) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What `FlutterError.onError` only sees errors raised inside a Flutter callback — build, layout, paint. Unhandled errors from a `Future`, `Stream` or `Timer` callback in the root isolate reach **no handler at all** today: `lib/main.dart` installs `FlutterError.onError` and `ErrorWidget.builder`, and nothing else. There is no `PlatformDispatcher.onError` and no `runZonedGuarded`. This installs `PlatformDispatcher.onError` alongside the existing handlers and extracts the whole installation out of `main.dart` into `lib/setup/error_handling/error_handlers.dart`, next to the `RealUnitErrorView` it already uses — which is what makes the contract testable at all. ## Scope — please read before assuming this fixes field diagnostics This does **not** deliver release-mode evidence, and the PR should not be merged under that belief. `developer.log` writes to the VM service stream, so these calls surface in the DevTools Logging view under the `WalletApp` tag in **debug and profile builds only** — the VM service is absent from a release build. What this PR actually delivers: - async errors become **reachable where a developer is already attached**, where today they reach nothing; - the handler returns **`false`**, so the engine's own fallback reporting keeps running in release instead of being suppressed. Returning `true` would claim the error as handled while our log is a no-op in release — reported by nobody at all; - the handler body is now **the hook a real sink plugs into**. Getting evidence off a customer's device still needs a crash reporter or a persisted log sink. That is a separate change and the one that would actually close a silent-field-crash report. ## Why `false` and not `true` This is the one design decision in the diff, so it is pinned by an assertion in the test rather than left to prose. `true` = "handled, stop reporting"; `false` = "log it, then let the engine report as it always did". Since the log is compiled out of release, `true` would convert a reported crash into total silence — the opposite of the intent. ## Test `test/setup/error_handling/error_handlers_test.dart` — 4 cases: the async handler is installed and returns `false`; it tolerates an empty stack trace; the Flutter handler still delegates to the previously installed one (the default reporting path stays intact); the on-brand error widget builder is installed. Process-wide statics are captured in `setUp` and restored in `tearDown` so nothing leaks into other suites. `// @no-integration-test:` annotation added per CONTRIBUTING:205 — the engine-side fallback reporting that runs after the handler returns `false` is embedder behaviour and is not observable from a Dart test. ## Verification Draft PRs skip CI in this repo, so both runs are local and this is the only gate: - `flutter analyze` → 1 issue, pre-existing, in generated/gitignored code (`lib/generated/i18n.dart:55` `override_on_non_overriding_member`). - `flutter test` → `+3019 -4`. The 4 failures are **pre-existing golden failures** in `settings_user_data`, baseline-proven at `+3015 -4` without this change. Isolated: `flutter test test/setup/error_handling/` → `+6: All tests passed!` ## Context Came out of a customer case where an Android BitBox setup failed with a repeated crash and we had **zero** diagnostic evidence to work from. The transport half of that case is #866. The missing crash reporting is the other half — this PR is the groundwork for it, not the fix. --- lib/main.dart | 25 +------ lib/setup/error_handling/error_handlers.dart | 58 +++++++++++++++ .../error_handling/error_handlers_test.dart | 73 +++++++++++++++++++ 3 files changed, 133 insertions(+), 23 deletions(-) create mode 100644 lib/setup/error_handling/error_handlers.dart create mode 100644 test/setup/error_handling/error_handlers_test.dart diff --git a/lib/main.dart b/lib/main.dart index 996c5ed0a..cb13b4100 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,3 @@ -import 'dart:developer' as developer; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -11,7 +9,7 @@ import 'package:realunit_wallet/screens/home/bloc/home_bloc.dart'; import 'package:realunit_wallet/screens/pin/bloc/auth/pin_auth_cubit.dart'; import 'package:realunit_wallet/screens/settings/bloc/settings_bloc.dart'; import 'package:realunit_wallet/setup/di.dart'; -import 'package:realunit_wallet/setup/error_handling/realunit_error_view.dart'; +import 'package:realunit_wallet/setup/error_handling/error_handlers.dart'; import 'package:realunit_wallet/setup/lifecycle_initializer.dart'; import 'package:realunit_wallet/setup/routing/boot_navigation.dart'; import 'package:realunit_wallet/setup/routing/router_config.dart'; @@ -20,7 +18,7 @@ import 'package:realunit_wallet/styles/themes.dart'; Future main() async { final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); - _installErrorHandlers(); + installErrorHandlers(); // only preserve splash screen for 3 seconds for release version if (kReleaseMode) { @@ -41,25 +39,6 @@ Future _initialize() async { ); } -/// Defense-in-depth against uncaught build/paint exceptions. Without this, a -/// single throw inside a widget's `build` (e.g. the empty-BitBox-address -/// `EthereumAddress.fromHex("")` crash) surfaces in release as a bare grey -/// [ErrorWidget] with no branding and no signal to the user. We log every such -/// error and replace the grey box with a minimal on-brand surface. -void _installErrorHandlers() { - final defaultOnError = FlutterError.onError; - FlutterError.onError = (details) { - developer.log( - 'uncaught Flutter error: ${details.exceptionAsString()}', - name: 'WalletApp', - error: details.exception, - stackTrace: details.stack, - ); - defaultOnError?.call(details); - }; - ErrorWidget.builder = (details) => RealUnitErrorView(details: details); -} - Future _initializeWithSplashDuration() async { await Future.wait([ _initialize(), diff --git a/lib/setup/error_handling/error_handlers.dart b/lib/setup/error_handling/error_handlers.dart new file mode 100644 index 000000000..a38ae7448 --- /dev/null +++ b/lib/setup/error_handling/error_handlers.dart @@ -0,0 +1,58 @@ +import 'dart:developer' as developer; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:realunit_wallet/setup/error_handling/realunit_error_view.dart'; + +/// Wires up the three process-wide error surfaces. Called once from `main` +/// before any other bootstrap step, so errors thrown during setup are covered. +/// +/// - [FlutterError.onError] catches errors raised inside a Flutter callback +/// (build, layout, paint). It logs and then delegates to the previously +/// installed handler, which keeps the framework's own reporting intact. +/// - [ErrorWidget.builder] replaces the bare grey error box with an on-brand +/// surface; see [RealUnitErrorView]. +/// - [PlatformDispatcher.onError] catches everything with no Flutter callback +/// on the stack — unhandled `Future`, `Stream` and `Timer` errors in the root +/// isolate — which the two handlers above never see. +/// +/// Both handlers log and then hand the error on rather than absorbing it, so +/// nothing that reported an error before reports less of one now. +/// +/// Scope, deliberately narrow: [developer.log] writes to the VM service stream, +/// so these calls surface in the DevTools Logging view under the `WalletApp` +/// tag in debug and profile builds only — the VM service is absent from a +/// release build, where the call is compiled out. Returning `false` from +/// [PlatformDispatcher.onError] is what carries release builds: it keeps the +/// engine's own reporting running instead of suppressing it (returning `true` +/// would claim the error as handled while our log is a no-op, i.e. total +/// silence). So this makes async errors *reachable where a developer is already +/// attached*; it adds no release-mode visibility on its own. Getting evidence +/// off a customer's device needs a crash reporter or a persisted log sink — +/// this handler body is the hook such a sink plugs into. +/// +/// @no-integration-test: the engine-side fallback reporting that runs after +/// [PlatformDispatcher.onError] returns false is embedder behaviour and is not +/// observable from a Dart test; the handler contract itself is unit-tested. +void installErrorHandlers() { + final defaultOnError = FlutterError.onError; + FlutterError.onError = (details) { + developer.log( + 'uncaught Flutter error: ${details.exceptionAsString()}', + name: 'WalletApp', + error: details.exception, + stackTrace: details.stack, + ); + defaultOnError?.call(details); + }; + ErrorWidget.builder = (details) => RealUnitErrorView(details: details); + PlatformDispatcher.instance.onError = (error, stack) { + developer.log( + 'uncaught async error: $error', + name: 'WalletApp', + error: error, + stackTrace: stack, + ); + return false; + }; +} diff --git a/test/setup/error_handling/error_handlers_test.dart b/test/setup/error_handling/error_handlers_test.dart new file mode 100644 index 000000000..dbbef6f5b --- /dev/null +++ b/test/setup/error_handling/error_handlers_test.dart @@ -0,0 +1,73 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:realunit_wallet/setup/error_handling/error_handlers.dart'; +import 'package:realunit_wallet/setup/error_handling/realunit_error_view.dart'; + +void main() { + // `installErrorHandlers` mutates process-wide statics that the test binding + // also relies on, so every handler is captured before and restored after each + // test — otherwise a failure here would leak into unrelated suites. + late FlutterExceptionHandler? originalFlutterOnError; + late ErrorWidgetBuilder originalErrorWidgetBuilder; + late ErrorCallback? originalPlatformOnError; + + setUp(() { + originalFlutterOnError = FlutterError.onError; + originalErrorWidgetBuilder = ErrorWidget.builder; + originalPlatformOnError = PlatformDispatcher.instance.onError; + }); + + tearDown(() { + FlutterError.onError = originalFlutterOnError; + ErrorWidget.builder = originalErrorWidgetBuilder; + PlatformDispatcher.instance.onError = originalPlatformOnError; + }); + + group('installErrorHandlers', () { + test('installs an async error handler that reports the error onwards', () { + installErrorHandlers(); + + final handler = PlatformDispatcher.instance.onError; + expect(handler, isNotNull, reason: 'async errors must not go unhandled'); + + // The contract that matters: the handler returns false, so the engine's + // fallback reporting still runs after we log. Returning true would claim + // the error as handled and suppress that reporting — and since our + // developer.log is compiled out of release builds, that would leave a + // release crash reported by nobody at all. + expect(handler!(Exception('async boom'), StackTrace.current), isFalse); + }); + + test('async handler tolerates an empty stack trace', () { + installErrorHandlers(); + + expect( + () => PlatformDispatcher.instance.onError!(Exception('boom'), StackTrace.empty), + returnsNormally, + ); + }); + + test('Flutter error handler delegates to the previously installed handler', () { + final received = []; + FlutterError.onError = received.add; + + installErrorHandlers(); + final details = FlutterErrorDetails(exception: Exception('sync boom')); + FlutterError.onError!(details); + + expect(received, [details], reason: 'the default reporting path must stay intact'); + }); + + test('installs the on-brand error widget builder', () { + installErrorHandlers(); + + expect( + ErrorWidget.builder(FlutterErrorDetails(exception: Exception('boom'))), + isA(), + ); + }); + }); +} From c45489709b88979e9ff0619066dd99f686c9368c Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Wed, 22 Jul 2026 23:00:53 +0200 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20OCP=20pay-flow=20(RealU=E2=86=92ZCH?= =?UTF-8?q?F=E2=86=92Open=20CryptoPay)=20(#674)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Phase 2 **Open CryptoPay (OCP) pay flow** client: scan a POS payment QR, swap REALU → ZCHF (proceeds stay in the user wallet), then pay that ZCHF to the OCP recipient — all orchestrated through `api.dfx.swiss`. Flow (Page + Cubit per step, separate state files): 1. **Scan** — `mobile_scanner` QR scan → decode the `lightning=LNURL1…` param (LUD-01 bech32) / `app.dfx.swiss`→`api.dfx.swiss` host fallback → extract the `pl_…` id. 2. **Quote** — `GET /v1/lnurlp/:id` shows the requested CHF amount + the exact ZCHF needed (read from the API `transferAmounts` Ethereum/ZCHF entry; never computed locally). The **mainnet-only environment gate is checked up-front here** so the irreversible swap can never start where the pay leg cannot settle. Expired quote / no-ZCHF-method / unsupported-environment are typed states. 3. **Process** (on confirm) — **assert the environment can settle (before any on-chain action)** → check ETH gas (faucet + poll) → `PUT /swap` (targetAmount = ZCHF + headroom buffer) → `/swap/:id/unsigned-transaction` → sign → `/swap/:id/broadcast` → **re-fetch the OCP quote** (fresh `quoteId`, guards expiry between swap and pay) → `/pay/unsigned-transaction` → sign → `/pay/submit` → poll `/pay/:id/status` until terminal. Signing uses the unified raw-payload path (`signToSignature` → r/s/v) for **both** software and BitBox wallets — the flow is not branched on `walletType`; only the genuine non-signing capability gap (debug wallet) is gated and surfaced as a dedicated failure state. Typed failures are rendered as states — no error-string parsing drives control flow. ## Fund-safety semantics (two irreversible legs) The REALU→ZCHF swap is irreversible, so the flow is hardened so the user can never be stranded and a failed pay never double-converts REALU: - **Environment gate before the swap.** The mainnet-only capability is environment-static (`ApiConfig.networkMode`) and is now evaluated at the very start of both the quote and process steps. The swap is never signed/broadcast on an environment where `pay/*` cannot settle. The service keeps `assertPaySupported()` on the `pay/*` calls as defense-in-depth. - **Pay-only retry after a successful swap.** Once the swap is broadcast the cubit records that ZCHF was acquired; any subsequent pay-leg failure surfaces the `PayProcessPayRetry` state whose recovery (`retryPay()`) re-quotes + signs + submits **without ever re-swapping**. A failed pay no longer forces a re-scan → re-swap. Mirrors the sell flow's two-leg `SellBitboxDepositRetry`. - **Genuine expiry vs. transient errors are distinct.** Only an explicit `expiration.isBefore(now)` is treated as expiry; transient fetch/submit/settlement errors route to the pay-only retry, not to a re-scan. - **Slippage boundary.** The swap target uses a documented 3% headroom (was 1%). If the freshly re-fetched settlement amount still exceeds the acquired ZCHF, a typed `PayRetryReason.insufficientZchf` retry state is surfaced (re-quote may land within the held ZCHF; the leftover ZCHF stays in the wallet) instead of an opaque server-side failure. ## API / decision authority Consumes **DFXswiss/api#3819** (`feat/realunit-ocp-pay`) — pair-PR, backend lands first. App renders API-signaled fields (`isValid`/`error`, `requestedAmount`, `transferAmounts`, quote expiration, payment status) and does not duplicate backend limit/eligibility logic. **Mainnet-only limitation:** the OCP payment-link engine settles on mainnet only; on `dev.api.dfx.swiss` (Sepolia) `pay/*` fails fast. The client mirrors this as a typed `PayUnsupportedEnvironmentException` keyed off `ApiConfig.networkMode` (a local environment capability gate, not error-string parsing), surfaced before the swap as a dedicated state. ## Parsing robustness - `lnurlp` DTO: optional transfer-asset `amount` is parsed as nullable (the non-priced display path emits amount-less entries), and the dead `recipient` field is removed (a backend object, never read, that threw a `TypeError` when populated). - The dead `RealUnitSwapDto.fromAmount` constructor (and its coverage-ignore) is removed; the flow only uses `fromTargetAmount`. ## Tests - LNURL bech32 + `app`→`api` decode (unit), all pay DTOs `fromJson`/`toJson` (incl. nullable amount + object-recipient), the pay service (mocked `http` client, incl. `isPaySupportedEnvironment`), every step Cubit (success + each typed failure, `fake_async` for the ETH/status polling timers). - New fund-safety cases: env-unsupported fails before any swap, pay-only retry after a successful swap, transient-fetch error → retry (not re-scan), insufficient-ZCHF-after-swap typed state, and `retryPay` never re-swaps. - Typed exceptions enumerated in `exception_surface_test.dart`; i18n `payRetry*` keys in both ARB files. - Dashboard golden (third **Pay** action button) unchanged and green. Issue: RealUnitCH/app#666 --- assets/languages/strings_de.arb | 38 +- assets/languages/strings_en.arb | 38 +- ios/Runner/Info.plist | 2 +- .../dfx/eip1559_unsigned_tx_decoder.dart | 317 ++++ .../exceptions/payment/pay_exceptions.dart | 47 + lib/packages/service/dfx/lnurl_decoder.dart | 195 +++ .../payment/pay/dto/lnurlp_payment_dto.dart | 164 +++ .../pay/dto/real_unit_ocp_pay_dto.dart | 14 + .../pay/dto/real_unit_ocp_pay_result_dto.dart | 11 + .../pay/dto/real_unit_ocp_pay_status_dto.dart | 56 + .../pay/dto/real_unit_ocp_pay_submit_dto.dart | 30 + ...unit_ocp_pay_unsigned_transaction_dto.dart | 28 + .../payment/pay/dto/real_unit_swap_dto.dart | 21 + .../dto/real_unit_swap_payment_info_dto.dart | 74 + ...al_unit_swap_unsigned_transaction_dto.dart | 12 + .../models/payment/pay/swap_payment_info.dart | 54 + .../service/dfx/real_unit_pay_service.dart | 152 ++ lib/packages/utils/plain_decimal.dart | 124 ++ .../widgets/sections/dashboard_actions.dart | 43 +- .../cubits/pay_process/pay_process_cubit.dart | 619 ++++++++ .../cubits/pay_process/pay_process_state.dart | 118 ++ .../pay/cubits/pay_quote/pay_quote_cubit.dart | 80 + .../pay/cubits/pay_quote/pay_quote_state.dart | 71 + .../pay/cubits/pay_scan/pay_scan_cubit.dart | 28 + .../pay/cubits/pay_scan/pay_scan_state.dart | 30 + lib/screens/pay/pay_process_page.dart | 213 +++ lib/screens/pay/pay_quote_page.dart | 210 +++ lib/screens/pay/pay_scan_page.dart | 80 + lib/setup/di.dart | 4 + lib/setup/routing/router_config.dart | 7 + lib/setup/routing/routes/app_routes.dart | 1 + pubspec.lock | 8 + pubspec.yaml | 1 + .../macos/dashboard_hidden_amounts.png | Bin 51954 -> 53493 bytes .../macos/dashboard_recent_transactions.png | Bin 55894 -> 57659 bytes .../goldens/macos/dashboard_with_balance.png | Bin 25894 -> 27711 bytes .../pay_process_page_awaiting_settlement.png | Bin 0 -> 8160 bytes .../macos/pay_process_page_pay_retry.png | Bin 0 -> 8621 bytes .../macos/pay_process_page_swapping.png | Bin 0 -> 8868 bytes .../goldens/macos/pay_quote_page_expired.png | Bin 0 -> 12327 bytes .../goldens/macos/pay_quote_page_loading.png | Bin 0 -> 3647 bytes .../goldens/macos/pay_quote_page_ready.png | Bin 0 -> 19799 bytes .../pay_quote_page_ready_with_merchant.png | Bin 0 -> 35963 bytes .../goldens/macos/pay_scan_page_scanning.png | Bin 0 -> 16972 bytes .../screens/pay/pay_process_golden_test.dart | 78 + .../screens/pay/pay_quote_golden_test.dart | 108 ++ .../screens/pay/pay_scan_golden_test.dart | 45 + test/helper/golden_plugin_stubs.dart | 43 + test/helper/responsive_surface_catalog.dart | 8 +- .../dfx/eip1559_unsigned_tx_decoder_test.dart | 291 ++++ .../exceptions/exception_surface_test.dart | 4 + .../service/dfx/lnurl_decoder_test.dart | 110 ++ .../dfx/models/payment/pay/pay_dtos_test.dart | 493 +++++++ .../dfx/real_unit_pay_service_test.dart | 341 +++++ test/packages/utils/plain_decimal_test.dart | 68 + .../sections/dashboard_actions_test.dart | 111 ++ test/screens/pay/pay_process_cubit_test.dart | 1288 +++++++++++++++++ test/screens/pay/pay_process_page_test.dart | 274 ++++ test/screens/pay/pay_process_state_test.dart | 64 + test/screens/pay/pay_quote_cubit_test.dart | 155 ++ test/screens/pay/pay_quote_page_test.dart | 187 +++ .../pay/pay_quote_responsive_matrix_test.dart | 124 ++ test/screens/pay/pay_scan_cubit_test.dart | 51 + test/screens/pay/pay_scan_page_test.dart | 145 ++ test/setup/di_pay_service_test.dart | 51 + test/setup/routing/pay_route_test.dart | 65 + 66 files changed, 6974 insertions(+), 20 deletions(-) create mode 100644 lib/packages/service/dfx/eip1559_unsigned_tx_decoder.dart create mode 100644 lib/packages/service/dfx/exceptions/payment/pay_exceptions.dart create mode 100644 lib/packages/service/dfx/lnurl_decoder.dart create mode 100644 lib/packages/service/dfx/models/payment/pay/dto/lnurlp_payment_dto.dart create mode 100644 lib/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_dto.dart create mode 100644 lib/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_result_dto.dart create mode 100644 lib/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_status_dto.dart create mode 100644 lib/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_submit_dto.dart create mode 100644 lib/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_unsigned_transaction_dto.dart create mode 100644 lib/packages/service/dfx/models/payment/pay/dto/real_unit_swap_dto.dart create mode 100644 lib/packages/service/dfx/models/payment/pay/dto/real_unit_swap_payment_info_dto.dart create mode 100644 lib/packages/service/dfx/models/payment/pay/dto/real_unit_swap_unsigned_transaction_dto.dart create mode 100644 lib/packages/service/dfx/models/payment/pay/swap_payment_info.dart create mode 100644 lib/packages/service/dfx/real_unit_pay_service.dart create mode 100644 lib/packages/utils/plain_decimal.dart create mode 100644 lib/screens/pay/cubits/pay_process/pay_process_cubit.dart create mode 100644 lib/screens/pay/cubits/pay_process/pay_process_state.dart create mode 100644 lib/screens/pay/cubits/pay_quote/pay_quote_cubit.dart create mode 100644 lib/screens/pay/cubits/pay_quote/pay_quote_state.dart create mode 100644 lib/screens/pay/cubits/pay_scan/pay_scan_cubit.dart create mode 100644 lib/screens/pay/cubits/pay_scan/pay_scan_state.dart create mode 100644 lib/screens/pay/pay_process_page.dart create mode 100644 lib/screens/pay/pay_quote_page.dart create mode 100644 lib/screens/pay/pay_scan_page.dart create mode 100644 test/goldens/screens/pay/goldens/macos/pay_process_page_awaiting_settlement.png create mode 100644 test/goldens/screens/pay/goldens/macos/pay_process_page_pay_retry.png create mode 100644 test/goldens/screens/pay/goldens/macos/pay_process_page_swapping.png create mode 100644 test/goldens/screens/pay/goldens/macos/pay_quote_page_expired.png create mode 100644 test/goldens/screens/pay/goldens/macos/pay_quote_page_loading.png create mode 100644 test/goldens/screens/pay/goldens/macos/pay_quote_page_ready.png create mode 100644 test/goldens/screens/pay/goldens/macos/pay_quote_page_ready_with_merchant.png create mode 100644 test/goldens/screens/pay/goldens/macos/pay_scan_page_scanning.png create mode 100644 test/goldens/screens/pay/pay_process_golden_test.dart create mode 100644 test/goldens/screens/pay/pay_quote_golden_test.dart create mode 100644 test/goldens/screens/pay/pay_scan_golden_test.dart create mode 100644 test/packages/service/dfx/eip1559_unsigned_tx_decoder_test.dart create mode 100644 test/packages/service/dfx/lnurl_decoder_test.dart create mode 100644 test/packages/service/dfx/models/payment/pay/pay_dtos_test.dart create mode 100644 test/packages/service/dfx/real_unit_pay_service_test.dart create mode 100644 test/packages/utils/plain_decimal_test.dart create mode 100644 test/screens/dashboard/widgets/sections/dashboard_actions_test.dart create mode 100644 test/screens/pay/pay_process_cubit_test.dart create mode 100644 test/screens/pay/pay_process_page_test.dart create mode 100644 test/screens/pay/pay_process_state_test.dart create mode 100644 test/screens/pay/pay_quote_cubit_test.dart create mode 100644 test/screens/pay/pay_quote_page_test.dart create mode 100644 test/screens/pay/pay_quote_responsive_matrix_test.dart create mode 100644 test/screens/pay/pay_scan_cubit_test.dart create mode 100644 test/screens/pay/pay_scan_page_test.dart create mode 100644 test/setup/di_pay_service_test.dart create mode 100644 test/setup/routing/pay_route_test.dart diff --git a/assets/languages/strings_de.arb b/assets/languages/strings_de.arb index d480221dc..acaa47814 100644 --- a/assets/languages/strings_de.arb +++ b/assets/languages/strings_de.arb @@ -57,8 +57,7 @@ "connectBitboxContentIos": "Bitte verbinden Sie Ihre BitBox mit Ihrem Smartphone und aktivieren Sie zusätzlich Bluetooth.", "connectBitboxFailed": "Es ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.", "connectBitboxNotInitialized": "Auf dieser BitBox ist noch keine Wallet eingerichtet. Bitte richten Sie über die BitBox-App eine Wallet auf dem Gerät ein oder stellen Sie eine wieder her und versuchen Sie es erneut.", - "connectBitboxNotInitializedTitle": "BitBox noch nicht eingerichtet", - "connectBitboxSignatureCapturing": "Bitte bestätigen Sie die Anmeldeanfrage auf Ihrem BitBox-Gerät. Diese Signatur wird einmalig erfasst, damit künftige Käufe Ihre BitBox nicht erneut benötigen.", + "connectBitboxNotInitializedTitle": "BitBox noch nicht eingerichtet", "connectBitboxSignatureCapturing": "Bitte bestätigen Sie die Anmeldeanfrage auf Ihrem BitBox-Gerät. Diese Signatur wird einmalig erfasst, damit künftige Käufe Ihre BitBox nicht erneut benötigen.", "connectBitboxSignatureCapturingTitle": "Anmeldung bestätigen", "connectBitboxSignatureFailed": "Ihre Anmeldesignatur konnte nicht erfasst werden. Sie können es erneut versuchen oder trotzdem fortfahren – Ihre BitBox wird dann möglicherweise für Ihren ersten Kauf erneut benötigt.", "connectBitboxSignatureFailedTitle": "Anmeldung nicht abgeschlossen", @@ -175,10 +174,45 @@ "or": "Oder", "originalPdf": "Original-PDF", "pay": "Bezahlen", + "payAwaitingSettlement": "Zahlung wird abgeschlossen", + "payConfirmButton": "Bezahlen", + "payFailureBitboxRequired": "Bitte verbinden Sie Ihre BitBox, um fortzufahren.", + "payFailureGeneric": "Bei der Zahlung ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.", + "payFailureInsufficientEth": "Es konnten nicht genügend ETH für die Netzwerkgebühren bereitgestellt werden. Bitte versuchen Sie es später erneut.", + "payFailureInsufficientZchf": "Ihr REALU-Bestand reicht für diesen Betrag nicht aus.", + "payFailureQuoteExpired": "Das Zahlungsangebot ist abgelaufen. Bitte scannen Sie den Code erneut.", + "payFailureSignatureUnsupported": "Diese Wallet kann keine Transaktionen signieren. Wechseln Sie zu einer Software- oder BitBox-Wallet.", + "payFailureTitle": "Zahlung fehlgeschlagen", "paymentInformationFailed": "Beim Abrufen der Zahlungsinformationen ist ein Fehler aufgetreten.", "paymentInformationFailedDescription": "Bitte versuchen Sie es später erneut. Wenn der Fehler weiterhin besteht, wenden Sie sich an unseren Support.", "payoutAccountAdd": "Auszahlungskonto hinzufügen", "payoutAccountSelect": "Auszahlungskonto auswählen", + "payPaying": "Zahlung wird gesendet", + "payPreparingSwap": "Tausch wird vorbereitet", + "payQuoteMerchant": "Empfänger", + "payQuoteRealuAmount": "Zu verkaufende REALU-Anteile", + "payQuoteRealuEstimated": "Erwarteter Erlös", + "payQuoteRealuFees": "Gebühren", + "payQuoteRequested": "Geforderter Betrag", + "payQuoteSummary": "Sie bezahlen ${amount} ${asset}", + "payQuoteTitle": "Zahlung bestätigen", + "payQuoteUnavailable": "Für diesen Zahlungscode ist keine ZCHF-Zahlung verfügbar.", + "payQuoteZchfNeeded": "Benötigte ZCHF", + "payRefreshingQuote": "Angebot wird aktualisiert", + "payRetryButton": "Zahlung erneut versuchen", + "payRetryInsufficientZchf": "Der Preis hat sich geändert und die getauschten ZCHF decken diese Zahlung nicht mehr. Ihre ZCHF bleiben in Ihrer Wallet – versuchen Sie es erneut, um ein neues Angebot zu erhalten.", + "payRetryQuoteExpired": "Das Zahlungsangebot ist vor dem Abschluss abgelaufen. Ihre getauschten ZCHF bleiben in Ihrer Wallet – versuchen Sie es erneut, um ein neues Angebot zu erhalten und zu bezahlen.", + "payRetryTitle": "Schließen Sie Ihre Zahlung ab", + "payRetryTransient": "Die Zahlung konnte nicht abgeschlossen werden, aber Ihre getauschten ZCHF bleiben in Ihrer Wallet. Versuchen Sie es erneut, um ohne erneuten Tausch zu bezahlen.", + "payRetryUnsignedTxMismatch": "Die vom Server gelieferten Zahlungsdetails stimmten nicht mit der Anfrage überein. Ihre getauschten ZCHF bleiben in Ihrer Wallet – bitte versuchen Sie es erneut.", + "payScanCameraPermissionDenied": "Für das Scannen eines Zahlungscodes wird Kamerazugriff benötigt. Bitte aktivieren Sie den Kamerazugriff für diese App in den Geräteeinstellungen.", + "payScanCameraUnavailable": "Die Kamera konnte nicht gestartet werden. Bitte versuchen Sie es erneut.", + "payScanInvalid": "Dies ist kein gültiger RealUnit-Zahlungscode.", + "payScanTitle": "Zahlungscode scannen", + "paySuccess": "Zahlung erfolgreich", + "paySuccessDescription": "Ihre Zahlung wurde abgeschlossen.", + "paySwapping": "REALU wird in ZCHF getauscht", + "payWaitingForEth": "Netzwerkgebühren werden angefordert", "pdf": "PDF", "pendingTransactions": "Ausstehende Transaktionen", "personalData": "Persönliche Daten", diff --git a/assets/languages/strings_en.arb b/assets/languages/strings_en.arb index cccdfd62a..9a546b20a 100644 --- a/assets/languages/strings_en.arb +++ b/assets/languages/strings_en.arb @@ -57,8 +57,7 @@ "connectBitboxContentIos": "Please connect your BitBox with your Smartphone and activate Bluetooth.", "connectBitboxFailed": "Something went wrong. Please try to connect again.", "connectBitboxNotInitialized": "This BitBox has no wallet set up yet. Set up or restore a wallet on the device using the BitBox app, then try again.", - "connectBitboxNotInitializedTitle": "BitBox not set up yet", - "connectBitboxSignatureCapturing": "Please confirm the sign-in request on your BitBox device. This signature is captured once so future purchases won't need your BitBox again.", + "connectBitboxNotInitializedTitle": "BitBox not set up yet", "connectBitboxSignatureCapturing": "Please confirm the sign-in request on your BitBox device. This signature is captured once so future purchases won't need your BitBox again.", "connectBitboxSignatureCapturingTitle": "Confirm sign-in", "connectBitboxSignatureFailed": "We couldn't capture your sign-in signature. You can retry, or continue anyway – your BitBox may then be needed again for your first purchase.", "connectBitboxSignatureFailedTitle": "Sign-in not completed", @@ -175,10 +174,45 @@ "or": "Or", "originalPdf": "Original PDF", "pay": "Pay", + "payAwaitingSettlement": "Completing payment", + "payConfirmButton": "Pay", + "payFailureBitboxRequired": "Please connect your BitBox to continue.", + "payFailureGeneric": "Something went wrong with the payment. Please try again.", + "payFailureInsufficientEth": "Could not provision enough ETH for network fees. Please try again later.", + "payFailureInsufficientZchf": "Your REALU holdings are not enough for this amount.", + "payFailureQuoteExpired": "The payment quote expired. Please scan the code again.", + "payFailureSignatureUnsupported": "This wallet cannot sign transactions. Switch to a software or BitBox wallet.", + "payFailureTitle": "Payment failed", "paymentInformationFailed": "An error occurred while getting the payment information.", "paymentInformationFailedDescription": "Please try again later. If the error persists, contact our support team.", "payoutAccountAdd": "Add payout account", "payoutAccountSelect": "Select payout account", + "payPaying": "Sending payment", + "payPreparingSwap": "Preparing swap", + "payQuoteMerchant": "Paying to", + "payQuoteRealuAmount": "REALU shares to sell", + "payQuoteRealuEstimated": "Estimated proceeds", + "payQuoteRealuFees": "Fees", + "payQuoteRequested": "Requested amount", + "payQuoteSummary": "You pay ${amount} ${asset}", + "payQuoteTitle": "Confirm payment", + "payQuoteUnavailable": "No ZCHF payment is available for this payment code.", + "payQuoteZchfNeeded": "ZCHF needed", + "payRefreshingQuote": "Refreshing quote", + "payRetryButton": "Retry payment", + "payRetryInsufficientZchf": "The price moved and the swapped ZCHF no longer covers this payment. Your ZCHF stays in your wallet — retry to fetch a new quote.", + "payRetryQuoteExpired": "The payment quote expired before settling. Your swapped ZCHF stays in your wallet — retry to fetch a new quote and pay.", + "payRetryTitle": "Finish your payment", + "payRetryTransient": "The payment could not be completed, but your swapped ZCHF stays in your wallet. Retry to pay without swapping again.", + "payRetryUnsignedTxMismatch": "The payment details returned by the server did not match what was requested. Your swapped ZCHF stays in your wallet — please try again.", + "payScanCameraPermissionDenied": "Camera access is required to scan a payment code. Please enable camera access for this app in your device settings.", + "payScanCameraUnavailable": "The camera could not be started. Please try again.", + "payScanInvalid": "This is not a valid RealUnit payment code.", + "payScanTitle": "Scan payment code", + "paySuccess": "Payment successful", + "paySuccessDescription": "Your payment has been completed.", + "paySwapping": "Swapping REALU to ZCHF", + "payWaitingForEth": "Requesting network fees", "pdf": "PDF", "pendingTransactions": "Pending transactions", "personalData": "Personal data", diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 421d96889..60b32b0c3 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -44,7 +44,7 @@ LSRequiresIPhoneOS NSCameraUsageDescription - This app needs camera access to verify your identity + This app needs camera access to verify your identity and to scan payment codes NSLocationWhenInUseUsageDescription Please provide us with your geolocation data to prove your current location NSMicrophoneUsageDescription diff --git a/lib/packages/service/dfx/eip1559_unsigned_tx_decoder.dart b/lib/packages/service/dfx/eip1559_unsigned_tx_decoder.dart new file mode 100644 index 000000000..b7a561302 --- /dev/null +++ b/lib/packages/service/dfx/eip1559_unsigned_tx_decoder.dart @@ -0,0 +1,317 @@ +/// Minimal, purpose-built decoder for the unsigned EIP-1559 (type 2) ZCHF-transfer +/// (pay-leg) transaction the OCP pay backend hands the app to sign +/// (`PUT /v1/realunit/pay/unsigned-transaction`). It exists so the app can verify — +/// BEFORE signing the pay leg — that the raw ERC20-transfer bytes match the DTO's +/// accompanying metadata (`tokenAddress`, `recipient`, `amountWei`, `chainId`) and +/// that gas/fee fields stay within local caps, rather than blindly signing whatever +/// bytes come back. See `PayProcessCubit._validatePayUnsignedTx`. +/// +/// Scope is the pay leg only. The earlier REALU→ZCHF swap leg +/// (`RealUnitSwapUnsignedTransactionDto`) is signed blind today: that DTO carries only +/// a raw `swap` hex string with no comparable recipient/amount/chainId metadata, so this +/// decoder is not applied to it (closing that gap needs a backend DTO extension). +/// +/// This is intentionally narrow: it only understands the exact shape the backend produces +/// for the pay leg (a type-2 tx whose `data` is a plain ERC20 `transfer(address,uint256)` +/// call) and rejects (fail-closed) anything that does not match that shape, rather than +/// being a general-purpose Ethereum RLP/ABI library. +library; + +import 'dart:typed_data'; + +import 'package:convert/convert.dart' as convert; +import 'package:realunit_wallet/packages/service/dfx/exceptions/payment/pay_exceptions.dart'; + +/// A decoded (still unsigned) EIP-1559 transaction — the fields this app needs to +/// cross-check against `RealUnitOcpPayUnsignedTransactionDto` and to apply local +/// gas/fee caps before signing the pay leg. +class DecodedEip1559Transaction { + final BigInt chainId; + + /// EIP-1559 max priority fee per gas (wei), from RLP field index 2. + final BigInt maxPriorityFeePerGas; + + /// EIP-1559 max fee per gas (wei), from RLP field index 3. + final BigInt maxFeePerGas; + + /// Gas limit, from RLP field index 4. + final BigInt gasLimit; + + /// The tx's `to` address, normalized to `0x` + 40 lowercase hex chars. + final String to; + + /// The tx's native ETH value (wei). An ERC20 transfer must carry 0. + final BigInt value; + + /// The tx's calldata, unparsed. + final Uint8List data; + + const DecodedEip1559Transaction({ + required this.chainId, + required this.maxPriorityFeePerGas, + required this.maxFeePerGas, + required this.gasLimit, + required this.to, + required this.value, + required this.data, + }); +} + +/// Decodes the RLP-encoded, unsigned, type-2 (EIP-1559) transaction hex the OCP pay backend +/// returns for the pay leg. Throws `PayUnsignedTxMismatchException` — never silently +/// truncates/pads/skips — on any structural anomaly, since a transaction the app cannot +/// fully parse is one it must refuse to sign. +abstract final class Eip1559UnsignedTxDecoder { + static const _typeByte = 0x02; + static const _fieldCount = 9; + static const _chainIdFieldIndex = 0; + static const _nonceFieldIndex = 1; + static const _maxPriorityFeePerGasFieldIndex = 2; + static const _maxFeePerGasFieldIndex = 3; + static const _gasLimitFieldIndex = 4; + static const _toFieldIndex = 5; + static const _valueFieldIndex = 6; + static const _dataFieldIndex = 7; + static const _accessListFieldIndex = 8; + + static DecodedEip1559Transaction decode(String rawTransaction) { + final hex = rawTransaction.startsWith('0x') + ? rawTransaction.substring(2) + : rawTransaction; + final Uint8List bytes; + try { + bytes = Uint8List.fromList(convert.hex.decode(hex)); + } on FormatException { + throw const PayUnsignedTxMismatchException('unsigned tx is not valid hex'); + } + + if (bytes.isEmpty || bytes[0] != _typeByte) { + throw const PayUnsignedTxMismatchException( + 'unsigned tx is not an EIP-1559 (type 2) transaction', + ); + } + + final decoded = _decodeItem(bytes, 1); + if (decoded.end != bytes.length) { + throw const PayUnsignedTxMismatchException( + 'unsigned tx has trailing bytes after RLP decode', + ); + } + final root = decoded.value; + if (root is! List) { + throw const PayUnsignedTxMismatchException('unsigned tx RLP root is not a list'); + } + if (root.length != _fieldCount) { + throw PayUnsignedTxMismatchException( + 'unsigned tx has ${root.length} RLP fields, expected $_fieldCount', + ); + } + + final to = root[_toFieldIndex]; + if (to is! Uint8List || to.length != 20) { + throw const PayUnsignedTxMismatchException('unsigned tx "to" is not a 20-byte address'); + } + final data = root[_dataFieldIndex]; + if (data is! Uint8List) { + throw const PayUnsignedTxMismatchException('unsigned tx "data" is not a byte string'); + } + // This narrow decoder only ever handles the plain ERC20-transfer shape the backend + // produces for the pay leg, which never carries an access list. Reject non-empty lists + // fail-closed rather than signing payload bytes we have not inspected. + final accessList = root[_accessListFieldIndex]; + if (accessList is! List || accessList.isNotEmpty) { + throw const PayUnsignedTxMismatchException( + 'unsigned tx "accessList" must be an empty RLP list', + ); + } + + // Canonical RLP for the six quantity fields (including nonce, which is part of the + // signed payload even though its decoded value is not exposed). Leading-zero-byte + // integers are non-canonical and rejected before any BigInt conversion. + _requireCanonicalInteger(root[_chainIdFieldIndex], 'chainId'); + _requireCanonicalInteger(root[_nonceFieldIndex], 'nonce'); + _requireCanonicalInteger(root[_maxPriorityFeePerGasFieldIndex], 'maxPriorityFeePerGas'); + _requireCanonicalInteger(root[_maxFeePerGasFieldIndex], 'maxFeePerGas'); + _requireCanonicalInteger(root[_gasLimitFieldIndex], 'gasLimit'); + _requireCanonicalInteger(root[_valueFieldIndex], 'value'); + + return DecodedEip1559Transaction( + chainId: _asBigInt(root[_chainIdFieldIndex]), + maxPriorityFeePerGas: _asBigInt(root[_maxPriorityFeePerGasFieldIndex]), + maxFeePerGas: _asBigInt(root[_maxFeePerGasFieldIndex]), + gasLimit: _asBigInt(root[_gasLimitFieldIndex]), + to: '0x${convert.hex.encode(to)}', + value: _asBigInt(root[_valueFieldIndex]), + data: data, + ); + } + + /// Canonical RLP integers: empty byte string = 0; otherwise no leading zero byte. + /// Zero itself must be the empty string (`0x80`), never `0x00`. + static void _requireCanonicalInteger(Object field, String name) { + if (field is! Uint8List) { + throw PayUnsignedTxMismatchException( + 'unsigned tx "$name" is not a byte string', + ); + } + if (field.isNotEmpty && field[0] == 0) { + throw PayUnsignedTxMismatchException( + 'unsigned tx "$name" has a non-canonical leading zero byte', + ); + } + } + + static BigInt _asBigInt(Object field) { + if (field is! Uint8List) { + throw const PayUnsignedTxMismatchException('unsigned tx field is not a byte string'); + } + if (field.isEmpty) return BigInt.zero; + return BigInt.parse(convert.hex.encode(field), radix: 16); + } + + /// Decodes a single RLP item starting at [pos]. Returns the decoded value (`Uint8List` for a + /// byte string, `List` for a list) and the index immediately after the item. + static ({Object value, int end}) _decodeItem(Uint8List data, int pos) { + if (pos >= data.length) { + throw const PayUnsignedTxMismatchException('RLP: unexpected end of data'); + } + final prefix = data[pos]; + if (prefix < 0x80) { + return (value: Uint8List.fromList([prefix]), end: pos + 1); + } else if (prefix < 0xb8) { + final length = prefix - 0x80; + final start = pos + 1; + _requireBytes(data, start, length); + final value = data.sublist(start, start + length); + if (length == 1 && value[0] < 0x80) { + throw const PayUnsignedTxMismatchException( + 'RLP: non-canonical single-byte short string (must be the bare byte, not a length-1 string)', + ); + } + return (value: value, end: start + length); + } else if (prefix < 0xc0) { + final lengthOfLength = prefix - 0xb7; + final start = pos + 1; + _requireBytes(data, start, lengthOfLength); + final length = _bytesToLength(data.sublist(start, start + lengthOfLength)); + // Non-minimal long-form string: lengths ≤ 55 must use the short-form prefix. + if (length <= 55) { + throw const PayUnsignedTxMismatchException( + 'RLP: non-minimal long-form string length encoding', + ); + } + final start2 = start + lengthOfLength; + _requireBytes(data, start2, length); + return (value: data.sublist(start2, start2 + length), end: start2 + length); + } else if (prefix < 0xf8) { + final length = prefix - 0xc0; + final start = pos + 1; + _requireBytes(data, start, length); + return _decodeListBody(data, start, start + length); + } else { + final lengthOfLength = prefix - 0xf7; + final start = pos + 1; + _requireBytes(data, start, lengthOfLength); + final length = _bytesToLength(data.sublist(start, start + lengthOfLength)); + // Non-minimal long-form list: lengths ≤ 55 must use the short-form prefix. + if (length <= 55) { + throw const PayUnsignedTxMismatchException( + 'RLP: non-minimal long-form list length encoding', + ); + } + final start2 = start + lengthOfLength; + _requireBytes(data, start2, length); + return _decodeListBody(data, start2, start2 + length); + } + } + + static ({Object value, int end}) _decodeListBody(Uint8List data, int start, int end) { + final items = []; + var pos = start; + while (pos < end) { + final item = _decodeItem(data, pos); + items.add(item.value); + pos = item.end; + } + if (pos != end) { + throw const PayUnsignedTxMismatchException('RLP: list body length mismatch'); + } + return (value: items, end: end); + } + + static void _requireBytes(Uint8List data, int start, int length) { + if (start < 0 || length < 0 || start + length > data.length) { + throw const PayUnsignedTxMismatchException('RLP: declared length exceeds available data'); + } + } + + static int _bytesToLength(Uint8List bytes) { + // Cap length-of-length at 4 bytes so `start + length` in long-form RLP branches + // cannot overflow a 64-bit signed int (lengthOfLength=8 can encode ~2^63 and wrap + // past `_requireBytes`, letting a raw RangeError escape from `sublist`). + if (bytes.length > 4) { + throw const PayUnsignedTxMismatchException( + 'RLP: declared length-of-length exceeds practical transaction size', + ); + } + // Minimal length-of-length encoding never needs a leading zero byte. + if (bytes.length > 1 && bytes[0] == 0) { + throw const PayUnsignedTxMismatchException( + 'RLP: non-minimal length-of-length encoding (leading zero byte)', + ); + } + var value = 0; + for (final b in bytes) { + value = (value << 8) | b; + } + return value; + } +} + +/// A decoded ERC20 `transfer(address,uint256)` call. +class DecodedErc20Transfer { + /// Normalized to `0x` + 40 lowercase hex chars. + final String recipient; + final BigInt amountWei; + + const DecodedErc20Transfer({required this.recipient, required this.amountWei}); +} + +/// Decodes ERC20 `transfer(address,uint256)` calldata — the only shape the OCP pay backend ever +/// asks the app to sign for the pay leg. Anything else (wrong selector, wrong length, a +/// non-zero-padded address slot) is rejected, never partially parsed. +abstract final class Erc20TransferCalldataDecoder { + static const _selector = [0xa9, 0x05, 0x9c, 0xbb]; // transfer(address,uint256) + static const _calldataLength = 68; // 4 (selector) + 32 (address) + 32 (amount) + + static DecodedErc20Transfer decode(Uint8List data) { + if (data.length != _calldataLength) { + throw PayUnsignedTxMismatchException( + 'unsigned tx calldata is ${data.length} bytes, expected $_calldataLength ' + '(ERC20 transfer)', + ); + } + for (var i = 0; i < _selector.length; i++) { + if (data[i] != _selector[i]) { + throw const PayUnsignedTxMismatchException( + 'unsigned tx calldata is not an ERC20 transfer(address,uint256) call', + ); + } + } + + final addressWord = data.sublist(4, 36); + for (var i = 0; i < 12; i++) { + if (addressWord[i] != 0) { + throw const PayUnsignedTxMismatchException( + 'unsigned tx calldata recipient slot is not zero-padded', + ); + } + } + final recipient = '0x${convert.hex.encode(addressWord.sublist(12, 32))}'; + + final amountWord = data.sublist(36, 68); + final amountWei = BigInt.parse(convert.hex.encode(amountWord), radix: 16); + + return DecodedErc20Transfer(recipient: recipient, amountWei: amountWei); + } +} diff --git a/lib/packages/service/dfx/exceptions/payment/pay_exceptions.dart b/lib/packages/service/dfx/exceptions/payment/pay_exceptions.dart new file mode 100644 index 000000000..8e4a0f8a8 --- /dev/null +++ b/lib/packages/service/dfx/exceptions/payment/pay_exceptions.dart @@ -0,0 +1,47 @@ +// Typed failures for the OCP pay flow (scan → swap → pay). Each one renders a +// human-readable string (see `exception_surface_test.dart`) so it can surface +// cleanly in logs, Sentry, and user-facing error states instead of the Dart +// default `Instance of '...'`. + +/// The scanned QR / pasted code is not a DFX Open CryptoPay payment link. +class InvalidPaymentLinkException implements Exception { + final String reason; + + const InvalidPaymentLinkException(this.reason); + + @override + String toString() => 'InvalidPaymentLinkException: $reason'; +} + +/// The loaded wallet cannot produce EIP-1559 signatures (today: the debug +/// wallet). The pay flow needs to sign the swap and pay transactions locally, +/// so it cannot proceed in this wallet mode. +class PaySignatureUnsupportedException implements Exception { + // Only ever thrown / constructed as a const expression, so the zero-arg + // body never registers a runtime line hit; toString() below is exercised. + const PaySignatureUnsupportedException(); // coverage:ignore-line + + @override + String toString() => + 'PaySignatureUnsupportedException: this wallet mode cannot sign transactions'; +} + +/// The unsigned pay-leg (ZCHF ERC20-transfer) transaction the backend returned for signing does +/// not match its accompanying security metadata (`tokenAddress`, `recipient`, `amountWei`, +/// `chainId`), exceeds local gas/fee caps, mismatches the app's locally configured chainId, or +/// could not be parsed as the expected EIP-1559 ERC20-transfer shape. Thrown by +/// Eip1559UnsignedTxDecoder / Erc20TransferCalldataDecoder and +/// PayProcessCubit._validatePayUnsignedTx BEFORE any pay-leg signing happens, so a +/// malformed/compromised backend pay response can never be blindly signed. +/// +/// Scope is the pay leg only. The earlier REALU→ZCHF swap leg +/// (`RealUnitSwapUnsignedTransactionDto`) is signed without this validation today (that DTO +/// has no comparable metadata); closing that gap needs a backend DTO extension. +class PayUnsignedTxMismatchException implements Exception { + final String reason; + + const PayUnsignedTxMismatchException(this.reason); + + @override + String toString() => 'PayUnsignedTxMismatchException: $reason'; +} diff --git a/lib/packages/service/dfx/lnurl_decoder.dart b/lib/packages/service/dfx/lnurl_decoder.dart new file mode 100644 index 000000000..6b85fe0e2 --- /dev/null +++ b/lib/packages/service/dfx/lnurl_decoder.dart @@ -0,0 +1,195 @@ +/// Decodes an Open CryptoPay POS QR into the DFX lnurlp payment-link id and the +/// API URL the app must read the quote from. +/// +/// Two encodings are supported, both pointing at the single allowed DFX host: +/// 1. A LUD-01 bech32 `LNURL1...` string (carried in the `lightning` query +/// parameter of an `https://app.dfx.swiss/pl/?lightning=LNURL1...` QR). +/// Decoding the bech32 yields the wrapped `https://api.dfx.swiss/v1/lnurlp/pl_...` +/// URL directly. +/// 2. A plain `https://app.dfx.swiss/v1/lnurlp/pl_...` (or `/pl/?...`) URL, +/// where the `app` host is rewritten to `api` as a fallback. +/// +/// Only `app.dfx.swiss` / `api.dfx.swiss` (and their `dev.` testnet twins) are +/// accepted — any other host is rejected so a malicious QR cannot redirect the +/// authenticated quote read to a third party. +library; + +import 'package:realunit_wallet/packages/service/dfx/exceptions/payment/pay_exceptions.dart'; + +class DecodedPaymentLink { + /// Fully-qualified `https:///v1/lnurlp/` URL the app reads the + /// OCP quote from. + final Uri lnurlpUrl; + + /// The payment-link id (e.g. `pl_...` / `plp_...`). + final String id; + + const DecodedPaymentLink({required this.lnurlpUrl, required this.id}); +} + +abstract final class LnurlDecoder { + static const _allowedHosts = { + 'api.dfx.swiss', + 'app.dfx.swiss', + 'dev.api.dfx.swiss', + 'dev.app.dfx.swiss', + }; + + // bech32 character set (BIP-173). Index in this string is the 5-bit value. + static const _charset = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; + + /// Decodes [raw] — the full scanned QR payload — into a [DecodedPaymentLink]. + /// + /// Throws [InvalidPaymentLinkException] when the payload is neither a + /// DFX-hosted lnurlp URL nor a bech32 LNURL wrapping one. + static DecodedPaymentLink decode(String raw) { + final input = raw.trim(); + if (input.isEmpty) { + throw const InvalidPaymentLinkException('Empty payment code'); + } + + final lightning = _extractLightningParam(input); + final target = lightning != null ? _decodeBech32(lightning) : input; + + final uri = _parseHttpUri(target); + final apiUri = _toApiUri(uri); + final id = _extractId(apiUri); + + return DecodedPaymentLink(lnurlpUrl: apiUri, id: id); + } + + /// Pulls the `lightning=` value out of a wrapper URL/URI, or returns the raw + /// bech32 when the scan is a bare `lightning:LNURL1...` / `LNURL1...` string. + static String? _extractLightningParam(String input) { + final upper = input.toUpperCase(); + if (upper.startsWith('LNURL1')) return input; + if (upper.startsWith('LIGHTNING:')) return input.substring('lightning:'.length); + + final uri = Uri.tryParse(input); + final value = uri?.queryParameters['lightning']; + if (value != null && value.toUpperCase().startsWith('LNURL1')) return value; + return null; + } + + static Uri _parseHttpUri(String value) { + final uri = Uri.tryParse(value); + if (uri == null || (uri.scheme != 'http' && uri.scheme != 'https')) { + throw InvalidPaymentLinkException('Not a payment link: $value'); + } + return uri; + } + + /// Rewrites an allowed `app.dfx.swiss` host to its `api.dfx.swiss` twin and + /// forces https. Rejects any non-DFX host. + static Uri _toApiUri(Uri uri) { + if (!_allowedHosts.contains(uri.host)) { + throw InvalidPaymentLinkException('Unsupported payment host: ${uri.host}'); + } + final apiHost = uri.host.replaceFirst('app.dfx.swiss', 'api.dfx.swiss'); + return uri.replace(scheme: 'https', host: apiHost); + } + + /// Extracts the `pl_...` / `plp_...` id from the lnurlp path. + static String _extractId(Uri uri) { + final segments = uri.pathSegments.where((s) => s.isNotEmpty).toList(); + final lnurlpIndex = segments.indexOf('lnurlp'); + final candidate = (lnurlpIndex != -1 && lnurlpIndex + 1 < segments.length) + ? segments[lnurlpIndex + 1] + : (segments.isNotEmpty ? segments.last : null); + if (candidate != null && _hasValidPaymentLinkPrefix(candidate)) return candidate; + throw InvalidPaymentLinkException('No payment id in: $uri'); + } + + static bool _hasValidPaymentLinkPrefix(String id) => + id.startsWith('pl_') || id.startsWith('plp_'); + + /// Decodes a LUD-01 bech32 `LNURL1...` string to its wrapped UTF-8 URL. + /// + /// LUD-01 deliberately drops the 90-char BIP-173 length limit, so only the + /// charset, the 1-byte-per-char separation, and the 6-char checksum are + /// validated here. + static String _decodeBech32(String bech) { + final lower = bech.toLowerCase(); + final sepIndex = lower.lastIndexOf('1'); + if (sepIndex < 1 || sepIndex + 7 > lower.length) { + throw InvalidPaymentLinkException('Malformed LNURL: $bech'); + } + + final hrp = lower.substring(0, sepIndex); + final dataPart = lower.substring(sepIndex + 1); + + final data = []; + for (final char in dataPart.split('')) { + final value = _charset.indexOf(char); + if (value == -1) { + throw InvalidPaymentLinkException('Invalid LNURL character: $char'); + } + data.add(value); + } + + if (!_verifyChecksum(hrp, data)) { + throw const InvalidPaymentLinkException('Invalid LNURL checksum'); + } + + // Drop the 6-symbol checksum, then regroup 5-bit → 8-bit. + final payload = data.sublist(0, data.length - 6); + final bytes = _convertBitsTo8(payload); + return String.fromCharCodes(bytes); + } + + /// Regroups 5-bit bech32 symbols into 8-bit bytes (no padding — the LNURL + /// payload is always a whole number of bytes). Rejects leftover bits that + /// cannot form a full byte, which signals a corrupt data section. + static List _convertBitsTo8(List data) { + const from = 5; + const to = 8; + var acc = 0; + var bits = 0; + final result = []; + const maxv = (1 << to) - 1; + for (final value in data) { + acc = (acc << from) | value; + bits += from; + while (bits >= to) { + bits -= to; + result.add((acc >> bits) & maxv); + } + } + // Defensive bech32 invariant: a checksum-valid LNURL payload always + // regroups into whole bytes, so this only trips on corrupt-yet-checksum- + // passing input, which the preceding checksum verify already rules out. + if (bits >= from || ((acc << (to - bits)) & maxv) != 0) { + throw const InvalidPaymentLinkException('Invalid LNURL padding'); // coverage:ignore-line + } + return result; + } + + static int _polymod(List values) { + const generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; + var chk = 1; + for (final value in values) { + final top = chk >> 25; + chk = ((chk & 0x1ffffff) << 5) ^ value; + for (var i = 0; i < 5; i++) { + if (((top >> i) & 1) == 1) chk ^= generator[i]; + } + } + return chk; + } + + static List _hrpExpand(String hrp) { + final result = []; + for (final c in hrp.codeUnits) { + result.add(c >> 5); + } + result.add(0); + for (final c in hrp.codeUnits) { + result.add(c & 31); + } + return result; + } + + static bool _verifyChecksum(String hrp, List data) { + return _polymod([..._hrpExpand(hrp), ...data]) == 1; + } +} diff --git a/lib/packages/service/dfx/models/payment/pay/dto/lnurlp_payment_dto.dart b/lib/packages/service/dfx/models/payment/pay/dto/lnurlp_payment_dto.dart new file mode 100644 index 000000000..06eb2d85d --- /dev/null +++ b/lib/packages/service/dfx/models/payment/pay/dto/lnurlp_payment_dto.dart @@ -0,0 +1,164 @@ +/// Public payment-link read response of `GET /v1/lnurlp/:id` (on api.dfx.swiss, +/// no auth). Carries the requested fiat amount and the active quote the app +/// needs to size the swap and later settle the payment. Only the fields the pay +/// flow consumes are mapped. +/// +/// The backend `recipient` field is mapped as [LnurlpRecipientDto?] (nullable +/// structured object). Only `name` and `city` are carried — the fields the +/// pay-quote screen displays. Everything else on the backend +/// `PaymentLinkRecipientDto` object is intentionally left unmapped. When the +/// merchant configured no recipient the field is absent and parses to null. +class LnurlpPaymentDto { + final LnurlpRequestedAmountDto requestedAmount; + final LnurlpQuoteDto quote; + + /// Per-method/chain transfer amounts. The Ethereum entry lists the exact ZCHF + /// amount the app must transfer; the app does not compute it locally. + final List transferAmounts; + + final LnurlpRecipientDto? recipient; + + const LnurlpPaymentDto({ + required this.requestedAmount, + required this.quote, + required this.transferAmounts, + this.recipient, + }); + + factory LnurlpPaymentDto.fromJson(Map json) { + final transfersRaw = json['transferAmounts']; + if (transfersRaw is! List) { + throw FormatException( + 'transferAmounts is required and must be a list, got: $transfersRaw', + ); + } + return LnurlpPaymentDto( + requestedAmount: LnurlpRequestedAmountDto.fromJson( + json['requestedAmount'] as Map, + ), + quote: LnurlpQuoteDto.fromJson(json['quote'] as Map), + transferAmounts: transfersRaw + .map((e) => LnurlpTransferAmountDto.fromJson(e as Map)) + .toList(), + recipient: json['recipient'] == null + ? null + : LnurlpRecipientDto.fromJson(json['recipient'] as Map), + ); + } +} + +/// Merchant/recipient fields surfaced on the pay-quote screen. Only `name` and +/// `city` (from nested `address`) are mapped; other backend fields are unused. +class LnurlpRecipientDto { + final String? name; + final String? city; + + const LnurlpRecipientDto({this.name, this.city}); + + factory LnurlpRecipientDto.fromJson(Map json) { + final address = json['address'] as Map?; + return LnurlpRecipientDto( + name: json['name'] as String?, + city: address?['city'] as String?, + ); + } +} + +class LnurlpRequestedAmountDto { + final String asset; + final double amount; + + const LnurlpRequestedAmountDto({required this.asset, required this.amount}); + + factory LnurlpRequestedAmountDto.fromJson(Map json) { + return LnurlpRequestedAmountDto( + asset: json['asset'] as String, + amount: _parseAmount('requestedAmount.amount', json['amount']), + ); + } +} + +class LnurlpQuoteDto { + final String id; + final DateTime expiration; + + const LnurlpQuoteDto({required this.id, required this.expiration}); + + factory LnurlpQuoteDto.fromJson(Map json) { + return LnurlpQuoteDto( + id: json['id'] as String, + expiration: DateTime.parse(json['expiration'] as String), + ); + } +} + +class LnurlpTransferAmountDto { + final String method; + final List assets; + + const LnurlpTransferAmountDto({required this.method, required this.assets}); + + factory LnurlpTransferAmountDto.fromJson(Map json) { + final assetsRaw = json['assets']; + if (assetsRaw is! List) { + throw FormatException( + 'transferAmounts.assets is required and must be a list, got: $assetsRaw', + ); + } + return LnurlpTransferAmountDto( + method: json['method'] as String, + assets: assetsRaw + .map((e) => LnurlpTransferAssetDto.fromJson(e as Map)) + .toList(), + ); + } +} + +class LnurlpTransferAssetDto { + final String asset; + + /// Optional on the backend (`amount?`): the non-priced display path emits + /// amount-less asset entries. Parsed as nullable so reading the whole quote + /// never throws — the pay flow only requires the amount for the asset it + /// actually transfers (ZCHF on Ethereum), filtered before it is read. + final double? amount; + + /// Exact JSON amount string as received (before `double` conversion), when + /// present. Used by the pay-process settlement guard for plain-decimal exact + /// comparison so binary-double drift cannot flip a boundary check. Additive — + /// callers that only read [amount] (e.g. [PayQuoteCubit]) are unaffected. + final String? rawAmount; + + const LnurlpTransferAssetDto({ + required this.asset, + this.amount, + this.rawAmount, + }); + + factory LnurlpTransferAssetDto.fromJson(Map json) { + final raw = json['amount']; + if (raw == null) { + return LnurlpTransferAssetDto(asset: json['asset'] as String); + } + final rawAmount = raw.toString(); + return LnurlpTransferAssetDto( + asset: json['asset'] as String, + amount: _parseAmount('transferAmounts.assets.amount', raw), + rawAmount: rawAmount, + ); + } +} + +/// Parses a money amount from JSON. Rejects NaN, ±Infinity and negatives fail- +/// closed via [FormatException] (same type [double.parse] already throws on +/// non-numeric input — no new exception class). +double _parseAmount(String fieldName, Object? raw) { + final value = double.parse(raw.toString()); + if (!value.isFinite) { + throw FormatException('$fieldName is not a finite number: $raw'); + } + if (value.isNegative) { + throw FormatException('$fieldName must not be negative: $raw'); + } + return value; +} diff --git a/lib/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_dto.dart b/lib/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_dto.dart new file mode 100644 index 000000000..7393a5628 --- /dev/null +++ b/lib/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_dto.dart @@ -0,0 +1,14 @@ +/// Request body for `PUT /v1/realunit/pay/unsigned-transaction`. References the +/// scanned payment link and its active quote so the backend resolves recipient +/// and exact ZCHF amount. +class RealUnitOcpPayDto { + final String paymentLinkId; + final String quoteId; + + const RealUnitOcpPayDto({required this.paymentLinkId, required this.quoteId}); + + Map toJson() => { + 'paymentLinkId': paymentLinkId, + 'quoteId': quoteId, + }; +} diff --git a/lib/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_result_dto.dart b/lib/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_result_dto.dart new file mode 100644 index 000000000..1a90a2b46 --- /dev/null +++ b/lib/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_result_dto.dart @@ -0,0 +1,11 @@ +/// Response of `PUT /v1/realunit/pay/submit` — the blockchain transaction id of +/// the submitted ZCHF payment. +class RealUnitOcpPayResultDto { + final String txId; + + const RealUnitOcpPayResultDto({required this.txId}); + + factory RealUnitOcpPayResultDto.fromJson(Map json) { + return RealUnitOcpPayResultDto(txId: json['txId'] as String); + } +} diff --git a/lib/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_status_dto.dart b/lib/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_status_dto.dart new file mode 100644 index 000000000..a16bc1d49 --- /dev/null +++ b/lib/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_status_dto.dart @@ -0,0 +1,56 @@ +import 'dart:developer' as developer; + +/// Mirrors the backend `PaymentLinkPaymentStatus` enum 1:1 (type-safe DTO +/// mirroring, not local business logic). The backend remains the authority on +/// the payment status; the app renders it and uses [isTerminal] / [isCompleted] +/// only to decide when to stop polling and which UI state to show. +enum OcpPaymentStatus { + pending('Pending'), + completed('Completed'), + cancelled('Cancelled'), + expired('Expired'), + unknown('') + ; + + final String value; + + const OcpPaymentStatus(this.value); + + /// Mirrors the backend status 1:1. An unrecognised value is deliberately + /// mapped to `unknown` (forward-compat, not a bug) rather than crashing — + /// but the raw value is logged so the drift stays visible instead of being + /// silently swallowed. The `unknown` sentinel (`unknown('')`) is never matched + /// by value inside the known-value loop — only via the fall-through log+return + /// path — so an empty/unrecognised backend status always hits the log. + static OcpPaymentStatus fromValue(String value) { + for (final status in OcpPaymentStatus.values) { + if (status == OcpPaymentStatus.unknown) continue; + if (status.value == value) return status; + } + developer.log( + 'Unrecognised OcpPaymentStatus "$value" — mapped to unknown (forward-compat).', + name: 'OcpPaymentStatus', + ); + return OcpPaymentStatus.unknown; + } + + /// Polling stops once the payment reaches a final state. + /// `unknown` is intentionally terminal so an unrecognised status can never cause an unbounded poll. + bool get isTerminal => + this == completed || this == cancelled || this == expired || this == unknown; + + bool get isCompleted => this == completed; +} + +/// Response of `GET /v1/realunit/pay/:id/status`. +class RealUnitOcpPayStatusDto { + final OcpPaymentStatus status; + + const RealUnitOcpPayStatusDto({required this.status}); + + factory RealUnitOcpPayStatusDto.fromJson(Map json) { + return RealUnitOcpPayStatusDto( + status: OcpPaymentStatus.fromValue(json['status'] as String), + ); + } +} diff --git a/lib/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_submit_dto.dart b/lib/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_submit_dto.dart new file mode 100644 index 000000000..acd5740bb --- /dev/null +++ b/lib/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_submit_dto.dart @@ -0,0 +1,30 @@ +/// Request body for `PUT /v1/realunit/pay/submit`. The signed-tx envelope +/// (`unsignedTx` + `r`/`s`/`v`) mirrors the sell/swap broadcast shape, plus the +/// payment-link/quote references so the backend forwards the hex into the +/// lnurlp settlement path. +class RealUnitOcpPaySubmitDto { + final String unsignedTx; + final String r; + final String s; + final int v; + final String paymentLinkId; + final String quoteId; + + const RealUnitOcpPaySubmitDto({ + required this.unsignedTx, + required this.r, + required this.s, + required this.v, + required this.paymentLinkId, + required this.quoteId, + }); + + Map toJson() => { + 'unsignedTx': unsignedTx, + 'r': r, + 's': s, + 'v': v, + 'paymentLinkId': paymentLinkId, + 'quoteId': quoteId, + }; +} diff --git a/lib/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_unsigned_transaction_dto.dart b/lib/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_unsigned_transaction_dto.dart new file mode 100644 index 000000000..e887938fb --- /dev/null +++ b/lib/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_unsigned_transaction_dto.dart @@ -0,0 +1,28 @@ +/// Response of `PUT /v1/realunit/pay/unsigned-transaction` — the serialized +/// unsigned EIP-1559 ZCHF transfer transaction to the OCP recipient, plus the +/// metadata the app shows / can sanity-check before signing. +class RealUnitOcpPayUnsignedTransactionDto { + final String unsignedTx; + final String tokenAddress; + final String recipient; + final String amountWei; + final int chainId; + + const RealUnitOcpPayUnsignedTransactionDto({ + required this.unsignedTx, + required this.tokenAddress, + required this.recipient, + required this.amountWei, + required this.chainId, + }); + + factory RealUnitOcpPayUnsignedTransactionDto.fromJson(Map json) { + return RealUnitOcpPayUnsignedTransactionDto( + unsignedTx: json['unsignedTx'] as String, + tokenAddress: json['tokenAddress'] as String, + recipient: json['recipient'] as String, + amountWei: json['amountWei'] as String, + chainId: json['chainId'] as int, + ); + } +} diff --git a/lib/packages/service/dfx/models/payment/pay/dto/real_unit_swap_dto.dart b/lib/packages/service/dfx/models/payment/pay/dto/real_unit_swap_dto.dart new file mode 100644 index 000000000..e16c95782 --- /dev/null +++ b/lib/packages/service/dfx/models/payment/pay/dto/real_unit_swap_dto.dart @@ -0,0 +1,21 @@ +/// Request body for `PUT /v1/realunit/swap`. The backend enforces the `amount` +/// XOR `targetAmount` rule; the app sends exactly one. `amount` is in REALU +/// shares, `targetAmount` is in ZCHF. IBAN-free by design (proceeds stay in the +/// user wallet). +class RealUnitSwapDto { + /// Amount of REALU shares to swap. + final int? amount; + + /// Target amount in ZCHF (alternative to [amount]). + final double? targetAmount; + + // The OCP pay flow always sizes the swap by ZCHF target (fromTargetAmount); + // `amount` stays in the body only to document the backend's XOR contract and + // is always null on the wire here. + const RealUnitSwapDto.fromTargetAmount(double this.targetAmount) : amount = null; + + Map toJson() => { + if (amount != null) 'amount': amount, + if (targetAmount != null) 'targetAmount': targetAmount, + }; +} diff --git a/lib/packages/service/dfx/models/payment/pay/dto/real_unit_swap_payment_info_dto.dart b/lib/packages/service/dfx/models/payment/pay/dto/real_unit_swap_payment_info_dto.dart new file mode 100644 index 000000000..a40714ae1 --- /dev/null +++ b/lib/packages/service/dfx/models/payment/pay/dto/real_unit_swap_payment_info_dto.dart @@ -0,0 +1,74 @@ +/// Response of `PUT /v1/realunit/swap` — the REALU → ZCHF swap quote. The +/// backend is the authority on validity, limits, fees and the ZCHF estimate; +/// the app renders these fields and never recomputes them. +class RealUnitSwapPaymentInfoDto { + final int id; + final String uid; + final int routeId; + final DateTime timestamp; + final double amount; + final double estimatedAmount; + final String targetAsset; + final double minVolume; + final double maxVolume; + final double minVolumeTarget; + final double maxVolumeTarget; + final double ethBalance; + final double requiredGasEth; + final bool isValid; + final String? error; + final RealUnitSwapFeeDto? fees; + + const RealUnitSwapPaymentInfoDto({ + required this.id, + required this.uid, + required this.routeId, + required this.timestamp, + required this.amount, + required this.estimatedAmount, + required this.targetAsset, + required this.minVolume, + required this.maxVolume, + required this.minVolumeTarget, + required this.maxVolumeTarget, + required this.ethBalance, + required this.requiredGasEth, + required this.isValid, + this.error, + this.fees, + }); + + factory RealUnitSwapPaymentInfoDto.fromJson(Map json) { + return RealUnitSwapPaymentInfoDto( + id: json['id'] as int, + uid: json['uid'] as String, + routeId: json['routeId'] as int, + timestamp: DateTime.parse(json['timestamp'] as String), + amount: (json['amount'] as num).toDouble(), + estimatedAmount: (json['estimatedAmount'] as num).toDouble(), + targetAsset: json['targetAsset'] as String, + minVolume: (json['minVolume'] as num).toDouble(), + maxVolume: (json['maxVolume'] as num).toDouble(), + minVolumeTarget: (json['minVolumeTarget'] as num).toDouble(), + maxVolumeTarget: (json['maxVolumeTarget'] as num).toDouble(), + ethBalance: (json['ethBalance'] as num).toDouble(), + requiredGasEth: (json['requiredGasEth'] as num).toDouble(), + isValid: json['isValid'] as bool, + error: json['error'] as String?, + fees: json['fees'] == null + ? null + : RealUnitSwapFeeDto.fromJson(json['fees'] as Map), + ); + } +} + +/// Fee breakdown from the swap quote. Only `total` is mapped for display. +class RealUnitSwapFeeDto { + final double total; + + const RealUnitSwapFeeDto({required this.total}); + + factory RealUnitSwapFeeDto.fromJson(Map json) { + return RealUnitSwapFeeDto(total: (json['total'] as num).toDouble()); + } +} diff --git a/lib/packages/service/dfx/models/payment/pay/dto/real_unit_swap_unsigned_transaction_dto.dart b/lib/packages/service/dfx/models/payment/pay/dto/real_unit_swap_unsigned_transaction_dto.dart new file mode 100644 index 000000000..5e0fe5b69 --- /dev/null +++ b/lib/packages/service/dfx/models/payment/pay/dto/real_unit_swap_unsigned_transaction_dto.dart @@ -0,0 +1,12 @@ +/// Response of `PUT /v1/realunit/swap/:id/unsigned-transaction` — the +/// serialized unsigned EIP-1559 REALU `transferAndCall` swap transaction hex +/// (no deposit sweep; ZCHF lands in the user wallet). +class RealUnitSwapUnsignedTransactionDto { + final String swap; + + const RealUnitSwapUnsignedTransactionDto({required this.swap}); + + factory RealUnitSwapUnsignedTransactionDto.fromJson(Map json) { + return RealUnitSwapUnsignedTransactionDto(swap: json['swap'] as String); + } +} diff --git a/lib/packages/service/dfx/models/payment/pay/swap_payment_info.dart b/lib/packages/service/dfx/models/payment/pay/swap_payment_info.dart new file mode 100644 index 000000000..b0d237812 --- /dev/null +++ b/lib/packages/service/dfx/models/payment/pay/swap_payment_info.dart @@ -0,0 +1,54 @@ +import 'package:equatable/equatable.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_swap_payment_info_dto.dart'; + +/// Domain model for an IBAN-free REALU → ZCHF swap quote. The backend decides +/// validity, limits and the ZCHF estimate; this model only carries those fields +/// for the flow's cubits to render and to drive the ETH-balance / swap steps. +class SwapPaymentInfo extends Equatable { + final int id; + final double amount; + final double estimatedAmount; + final String targetAsset; + final double ethBalance; + final double requiredGasEth; + final bool isValid; + final String? error; + final double? feesTotal; + + const SwapPaymentInfo({ + required this.id, + required this.amount, + required this.estimatedAmount, + required this.targetAsset, + required this.ethBalance, + required this.requiredGasEth, + required this.isValid, + this.error, + this.feesTotal, + }); + + factory SwapPaymentInfo.fromDto(RealUnitSwapPaymentInfoDto dto) => SwapPaymentInfo( + id: dto.id, + amount: dto.amount, + estimatedAmount: dto.estimatedAmount, + targetAsset: dto.targetAsset, + ethBalance: dto.ethBalance, + requiredGasEth: dto.requiredGasEth, + isValid: dto.isValid, + error: dto.error, + feesTotal: dto.fees?.total, + ); + + @override + List get props => [ + id, + amount, + estimatedAmount, + targetAsset, + ethBalance, + requiredGasEth, + isValid, + error, + feesTotal, + ]; +} diff --git a/lib/packages/service/dfx/real_unit_pay_service.dart b/lib/packages/service/dfx/real_unit_pay_service.dart new file mode 100644 index 000000000..33a085ac5 --- /dev/null +++ b/lib/packages/service/dfx/real_unit_pay_service.dart @@ -0,0 +1,152 @@ +import 'dart:convert'; + +import 'package:realunit_wallet/packages/config/api_config.dart'; +import 'package:realunit_wallet/packages/service/dfx/dfx_auth_service.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/api_exception.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/lnurlp_payment_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_result_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_status_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_submit_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_unsigned_transaction_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_swap_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_swap_payment_info_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_swap_unsigned_transaction_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/swap_payment_info.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/sell/dto/broadcast_transaction_request_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/sell/dto/broadcast_transaction_response_dto.dart'; + +/// Backend client for the Open CryptoPay pay flow (DFXswiss/api #3819, all under +/// `/v1/realunit/...`). Subclasses [DFXAuthService] for the JWT handshake + +/// retry-on-401 the sell flow already uses; the public lnurlp read is the only +/// unauthenticated call. +class RealUnitPayService extends DFXAuthService { + static const _lnurlpPath = '/v1/lnurlp'; + static const _swapPath = '/v1/realunit/swap'; + static String _swapUnsignedTxPath(int id) => '/v1/realunit/swap/$id/unsigned-transaction'; + static String _swapBroadcastPath(int id) => '/v1/realunit/swap/$id/broadcast'; + static const _payUnsignedTxPath = '/v1/realunit/pay/unsigned-transaction'; + static const _paySubmitPath = '/v1/realunit/pay/submit'; + static String _payStatusPath(String id) => '/v1/realunit/pay/$id/status'; + + static const _httpTimeout = Duration(seconds: 20); + + RealUnitPayService(super.appStore, super.walletService); + + /// Public OCP payment-link read (no auth). Returns the requested fiat amount, + /// the active quote (id + expiration) and the per-method transfer amounts. + Future getPaymentDetails(String id) async { + final uri = buildUri(host, '$_lnurlpPath/$id'); + final response = await appStore.httpClient + .get(uri, headers: {'accept': 'application/json'}) + .timeout(_httpTimeout); + + if (response.statusCode != 200) { + _throwApi(response.body, response.statusCode); + } + return LnurlpPaymentDto.fromJson(jsonDecode(response.body) as Map); + } + + // --- Swap (REALU → ZCHF, proceeds stay in the user wallet) --- + + Future getSwapPaymentInfo(RealUnitSwapDto dto) async { + final uri = buildUri(host, _swapPath); + final response = await authenticatedPut( + uri, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(dto.toJson()), + ).timeout(_httpTimeout); + + if (response.statusCode != 200 && response.statusCode != 201) { + _throwApi(response.body, response.statusCode); + } + final responseDto = RealUnitSwapPaymentInfoDto.fromJson( + jsonDecode(response.body) as Map, + ); + return SwapPaymentInfo.fromDto(responseDto); + } + + Future createSwapUnsignedTransaction(int id) async { + final uri = buildUri(host, _swapUnsignedTxPath(id)); + final response = await authenticatedPut( + uri, + headers: {'Content-Type': 'application/json'}, + ).timeout(_httpTimeout); + + if (response.statusCode != 200 && response.statusCode != 201) { + _throwApi(response.body, response.statusCode); + } + return RealUnitSwapUnsignedTransactionDto.fromJson( + jsonDecode(response.body) as Map, + ); + } + + Future broadcastSwapTransaction(int id, BroadcastTransactionRequestDto dto) async { + final uri = buildUri(host, _swapBroadcastPath(id)); + final response = await authenticatedPut( + uri, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(dto.toJson()), + ).timeout(_httpTimeout); + + if (response.statusCode != 200 && response.statusCode != 201) { + _throwApi(response.body, response.statusCode); + } + return BroadcastTransactionResponseDto.fromJson( + jsonDecode(response.body) as Map, + ).txHash; + } + + // --- OCP pay (settle a ZCHF payment-link quote via the lnurlp flow) --- + + Future createPayUnsignedTransaction( + RealUnitOcpPayDto dto, + ) async { + final uri = buildUri(host, _payUnsignedTxPath); + final response = await authenticatedPut( + uri, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(dto.toJson()), + ).timeout(_httpTimeout); + + if (response.statusCode != 200 && response.statusCode != 201) { + _throwApi(response.body, response.statusCode); + } + return RealUnitOcpPayUnsignedTransactionDto.fromJson( + jsonDecode(response.body) as Map, + ); + } + + Future submitPay(RealUnitOcpPaySubmitDto dto) async { + final uri = buildUri(host, _paySubmitPath); + final response = await authenticatedPut( + uri, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(dto.toJson()), + ).timeout(_httpTimeout); + + if (response.statusCode != 200 && response.statusCode != 201) { + _throwApi(response.body, response.statusCode); + } + return RealUnitOcpPayResultDto.fromJson( + jsonDecode(response.body) as Map, + ).txId; + } + + Future getPayStatus(String id) async { + final uri = buildUri(host, _payStatusPath(id)); + final response = await authenticatedGet(uri).timeout(_httpTimeout); + + if (response.statusCode != 200) { + _throwApi(response.body, response.statusCode); + } + return RealUnitOcpPayStatusDto.fromJson( + jsonDecode(response.body) as Map, + ); + } + + Never _throwApi(String body, int statusCode) { + final errorJson = jsonDecode(body) as Map; + throw ApiException.fromJson(errorJson, httpStatusCode: statusCode); + } +} diff --git a/lib/packages/utils/plain_decimal.dart b/lib/packages/utils/plain_decimal.dart new file mode 100644 index 000000000..9716ee996 --- /dev/null +++ b/lib/packages/utils/plain_decimal.dart @@ -0,0 +1,124 @@ +/// Exact comparison of plain (non-scientific) decimal number strings via +/// [BigInt], without floating-point conversion. Dependency-free (`dart:core` +/// only) — used where money amounts must be ordered without binary-double drift. +/// +/// Accepts an optional leading `+`/`-`, an integer part, an optional fractional +/// part, and nothing else. Scientific notation (`1e3`, `1E-2`) and any other +/// malformed input throw [FormatException] — fail closed, never coerced. +library; + +/// Returns a negative value if [a] < [b], zero if equal, positive if [a] > [b]. +int comparePlainDecimalStrings(String a, String b) { + final parsedA = _parsePlainDecimal(a); + final parsedB = _parsePlainDecimal(b); + + // Different signs: negative is always smaller (and -0 == +0 is handled below + // once both sides are scaled to integer magnitude). + if (parsedA.negative != parsedB.negative) { + final aZero = parsedA.integerDigits == '0' && parsedA.fractionDigits.isEmpty; + final bZero = parsedB.integerDigits == '0' && parsedB.fractionDigits.isEmpty; + if (aZero && bZero) return 0; + return parsedA.negative ? -1 : 1; + } + + final scale = parsedA.fractionDigits.length > parsedB.fractionDigits.length + ? parsedA.fractionDigits.length + : parsedB.fractionDigits.length; + final scaledA = _toScaledInteger(parsedA, scale); + final scaledB = _toScaledInteger(parsedB, scale); + + final cmp = scaledA.compareTo(scaledB); + // Same sign: if both negative, the larger magnitude is the smaller number. + return parsedA.negative ? -cmp : cmp; +} + +({bool negative, String integerDigits, String fractionDigits}) _parsePlainDecimal( + String raw, +) { + var s = raw.trim(); + if (s.isEmpty) { + throw FormatException('plain decimal is empty: $raw'); + } + // Reject scientific notation and any non-decimal characters up front. + if (s.contains('e') || s.contains('E')) { + throw FormatException('plain decimal must not use scientific notation: $raw'); + } + + var negative = false; + if (s.startsWith('-')) { + negative = true; + s = s.substring(1); + } else if (s.startsWith('+')) { + s = s.substring(1); + } + if (s.isEmpty) { + throw FormatException('plain decimal has no digits: $raw'); + } + + final parts = s.split('.'); + if (parts.length > 2) { + throw FormatException('plain decimal has too many decimal points: $raw'); + } + final integerPart = parts[0]; + final fractionPart = parts.length == 2 ? parts[1] : ''; + + // Integer part may be empty only when a fraction is present (".5"); fraction + // may be empty ("42." / "42"). At least one side must carry a digit. + if (integerPart.isEmpty && fractionPart.isEmpty) { + throw FormatException('plain decimal has no digits: $raw'); + } + if (integerPart.isNotEmpty && !_isAllDigits(integerPart)) { + throw FormatException('plain decimal has non-digit characters: $raw'); + } + if (fractionPart.isNotEmpty && !_isAllDigits(fractionPart)) { + throw FormatException('plain decimal has non-digit characters: $raw'); + } + + // Normalize integer digits: strip leading zeros but keep a single "0" when + // the integer magnitude is zero (including ".5" → integer "0"). + final integerDigits = integerPart.isEmpty + ? '0' + : (BigInt.parse(integerPart).toString()); // drops leading zeros + // Strip trailing zeros from the fraction so "1.50" and "1.5" compare equal + // without needing a shared scale for zero-equality of the fractional tail. + final fractionDigits = _stripTrailingZeros(fractionPart); + + // -0 and +0: treat as non-negative zero so sign handling above is clean. + if (integerDigits == '0' && fractionDigits.isEmpty) { + return (negative: false, integerDigits: '0', fractionDigits: ''); + } + + return ( + negative: negative, + integerDigits: integerDigits, + fractionDigits: fractionDigits, + ); +} + +BigInt _toScaledInteger( + ({bool negative, String integerDigits, String fractionDigits}) parsed, + int scale, +) { + final fraction = parsed.fractionDigits.padRight(scale, '0'); + final combined = '${parsed.integerDigits}$fraction'; + // combined is digits-only (possibly empty only if both parts empty — already + // rejected). BigInt.parse('0' * n) is fine; empty cannot happen here. + return BigInt.parse(combined.isEmpty ? '0' : combined); +} + +bool _isAllDigits(String s) { + for (var i = 0; i < s.length; i++) { + final c = s.codeUnitAt(i); + if (c < 0x30 || c > 0x39) return false; + } + return true; +} + +String _stripTrailingZeros(String fraction) { + if (fraction.isEmpty) return fraction; + var end = fraction.length; + while (end > 0 && fraction.codeUnitAt(end - 1) == 0x30) { + end--; + } + return fraction.substring(0, end); +} diff --git a/lib/screens/dashboard/widgets/sections/dashboard_actions.dart b/lib/screens/dashboard/widgets/sections/dashboard_actions.dart index 9b68eed66..2fb38a1f0 100644 --- a/lib/screens/dashboard/widgets/sections/dashboard_actions.dart +++ b/lib/screens/dashboard/widgets/sections/dashboard_actions.dart @@ -13,23 +13,38 @@ class DashboardActions extends StatelessWidget { return Row( spacing: 10, children: [ - ActionButton( - icon: Icon( - Icons.add_circle_rounded, - color: RealUnitColors.basic.white, - size: 20, + Expanded( + child: ActionButton( + icon: Icon( + Icons.add_circle_rounded, + color: RealUnitColors.basic.white, + size: 20, + ), + label: S.of(context).buy, + onPressed: () => context.pushNamed(AppRoutes.buy), ), - label: S.of(context).buy, - onPressed: () => context.pushNamed(AppRoutes.buy), ), - ActionButton( - icon: Icon( - Icons.do_not_disturb_on_rounded, - color: RealUnitColors.basic.white, - size: 20, + Expanded( + child: ActionButton( + icon: Icon( + Icons.do_not_disturb_on_rounded, + color: RealUnitColors.basic.white, + size: 20, + ), + label: S.of(context).sell, + onPressed: () => context.pushNamed(AppRoutes.sell), + ), + ), + Expanded( + child: ActionButton( + icon: Icon( + Icons.qr_code_scanner_rounded, + color: RealUnitColors.basic.white, + size: 20, + ), + label: S.of(context).pay, + onPressed: () => context.pushNamed(AppRoutes.pay), ), - label: S.of(context).sell, - onPressed: () => context.pushNamed(AppRoutes.sell), ), ], ); diff --git a/lib/screens/pay/cubits/pay_process/pay_process_cubit.dart b/lib/screens/pay/cubits/pay_process/pay_process_cubit.dart new file mode 100644 index 000000000..f8f4993cd --- /dev/null +++ b/lib/screens/pay/cubits/pay_process/pay_process_cubit.dart @@ -0,0 +1,619 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:convert/convert.dart' as convert; +import 'package:equatable/equatable.dart'; +import 'package:flutter/foundation.dart' show visibleForTesting; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:web3dart/crypto.dart'; +import 'package:realunit_wallet/packages/service/app_store.dart'; +import 'package:realunit_wallet/packages/service/dfx/dfx_blockchain_api_service.dart'; +import 'package:realunit_wallet/packages/service/dfx/dfx_faucet_service.dart'; +import 'package:realunit_wallet/packages/service/dfx/eip1559_unsigned_tx_decoder.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/bitbox_exception.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/payment/pay_exceptions.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/lnurlp_payment_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_submit_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_unsigned_transaction_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_swap_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/swap_payment_info.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/sell/dto/broadcast_transaction_request_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_pay_service.dart'; +import 'package:realunit_wallet/packages/service/wallet_service.dart'; +import 'package:realunit_wallet/packages/utils/plain_decimal.dart'; +import 'package:realunit_wallet/packages/wallet/wallet.dart'; + +part 'pay_process_state.dart'; + +/// Orchestrates the on-chain half of the OCP pay flow after the user confirms a +/// quote: check ETH gas → swap REALU→ZCHF (sign + broadcast) → re-fetch the OCP +/// quote (fresh quoteId, guards expiry between swap and pay) → pay (sign + +/// submit) → poll status until terminal. +/// +/// Signing uses the unified raw-payload path (`signToSignature` → r/s/v) for +/// BOTH software and BitBox wallets — the backend returns the unsigned txs, the +/// app signs them the same way regardless of wallet mode. The flow is NOT +/// branched on `walletType`; only the genuine capability gap (a debug wallet +/// that cannot sign) is gated, surfacing [PaySignaturePending] → +/// [PaySignatureUnsupportedException]. +class PayProcessCubit extends Cubit { + final RealUnitPayService _payService; + final DfxFaucetService _faucetService; + final DfxBlockchainApiService _blockchainService; + final WalletService _walletService; + final AppStore _appStore; + + final String _paymentLinkId; + final double _zchfNeeded; + + SwapPaymentInfo? _swap; + + /// Set once the REALU→ZCHF swap has been broadcast successfully. From this + /// point the user holds ZCHF and recovery must NEVER re-swap — the pay leg is + /// retried on its own via [retryPay]. + bool _swapCompleted = false; + + /// Guards overlapping ETH-poll ticks from each calling [_executeSwap]. Set + /// synchronously before the first await in a tick; released in `finally` on + /// every path out of a tick (success, max-attempts, isClosed, transient + /// error) so a later polling cycle never starts with a stuck `true`. + bool _swapInFlight = false; + + /// Test-only visibility into the ETH-poll re-entrancy guard — lets tests prove it is released on + /// every abort/end path of a poll tick (see [_startEthPolling]) instead of only inferring it + /// indirectly from timer behavior. + @visibleForTesting + bool get debugSwapInFlight => _swapInFlight; + + /// ZCHF acquired by the (completed) swap — the backend `estimatedAmount` of + /// the swap quote. Used to detect when a freshly re-fetched settlement amount + /// can no longer be covered by what we actually hold. + double _acquiredZchf = 0; + + Timer? _ethPollingTimer; + Timer? _statusPollingTimer; + + /// 24 attempts * 5s interval ≈ 2 minutes, mirrors the status-poll budget. + /// Bounds ETH-balance polling so a faucet that never funds (or a hung + /// balance RPC) cannot leave [_swapInFlight] wedged indefinitely. + static const _ethPollMaxAttempts = 24; + + /// Per-request cap on [DfxBlockchainApiService.getEthBalance] so a hung HTTP + /// layer always resolves (as [TimeoutException], treated as transient by the + /// catch path) instead of blocking every subsequent poll tick. + static const _ethPollTimeout = Duration(seconds: 10); + + int _ethPollAttempts = 0; + + /// 40 attempts * 3s interval = 2 minutes. Bounds the poll so a status that + /// never turns terminal (or a backend that keeps erroring) cannot poll + /// forever; beyond this the swap already left the user holding ZCHF, so it + /// surfaces the existing pay-only retry state rather than a new failure mode. + static const _statusPollMaxAttempts = 40; + + /// Bumped every [_startStatusPolling] call. A tick captures the generation + /// active when it starts; if that generation is stale by the time its + /// (possibly slow) request returns, the tick is a leftover from an earlier + /// polling cycle and must not act on the timer/state of a newer cycle. + int _statusPollGeneration = 0; + int _statusPollAttempts = 0; + bool _statusPollInFlight = false; + + /// Headroom over the OCP ZCHF amount when sizing the swap target. The swap is + /// quoted/broadcast against the ORIGINAL OCP quote, but the pay step settles + /// the EXACT amount of a FRESHLY re-fetched quote; in between, the OCP price + /// (CHF→ZCHF) and the swap rate can both move. A 1% buffer left no margin for + /// the common case (a few minutes of drift + the OCP/swap fees), so any + /// adverse move stranded the user in ZCHF that could not cover settlement. + /// 3% is a pragmatic headroom that absorbs ordinary drift while keeping the + /// over-swap small (leftover ZCHF simply stays in the wallet); a larger move + /// is caught explicitly and surfaced as a retryable + /// [PayRetryReason.insufficientZchf] rather than a server-side failure. + static const _slippageBuffer = 1.03; + + /// Local cap on the pay-leg tx's gasLimit. An ERC20 `transfer` costs roughly + /// 60–80k gas; 200k gives >2× headroom so a legitimate tx is never rejected + /// while still bounding a compromised backend that would set an absurd limit. + static const _maxGasLimit = 200000; + + /// Local cap on the pay-leg tx's declared max total fee + /// (`maxFeePerGas * gasLimit`), in wei (0.05 ETH). Combined with + /// [_maxGasLimit] this still tolerates fee spikes up to ~250 gwei/gas, well + /// above ordinary mainnet congestion, while ensuring a compromised backend + /// can never make the app commit to burning more than 0.05 ETH in fees. + /// `static final` (not `const`): BigInt is not const-constructible in Dart. + static final _maxTotalFeeWei = BigInt.parse('50000000000000000'); + + static const _ethPollInterval = Duration(seconds: 5); + static const _statusPollInterval = Duration(seconds: 3); + + PayProcessCubit({ + required RealUnitPayService payService, + required DfxFaucetService faucetService, + required DfxBlockchainApiService blockchainService, + required WalletService walletService, + required AppStore appStore, + required String paymentLinkId, + required double zchfNeeded, + }) : _payService = payService, + _faucetService = faucetService, + _blockchainService = blockchainService, + _walletService = walletService, + _appStore = appStore, + _paymentLinkId = paymentLinkId, + _zchfNeeded = zchfNeeded, + super(const PayProcessInitial()); + + /// Entry point — called by the view once the user confirms the quote. + Future start() async { + // Capability gate — checked BEFORE any on-chain action: the debug wallet + // cannot produce EIP-1559 signatures, so the irreversible REALU→ZCHF swap + // must never run on it. The backend settles OCP on every environment + // (Sepolia off-PRD, mainnet+L2 on PRD), so there is no environment gate + // here — the flow requests the real quote and surfaces a typed backend + // error if one ever comes back. + if (_appStore.wallet.walletType == WalletType.debug) { + emit(const PayProcessFailure(PayProcessFailureReason.signatureUnsupported)); + return; + } + await _requestSwapQuote(); + } + + Future _requestSwapQuote() async { + try { + emit(const PayProcessPreparingSwap()); + final swap = await _payService.getSwapPaymentInfo( + RealUnitSwapDto.fromTargetAmount(_zchfNeeded * _slippageBuffer), + ); + if (isClosed) return; + _swap = swap; + + // The API is the authority on whether the swap is fundable; render its + // signal rather than recomputing limits locally. + if (!swap.isValid) { + emit(const PayProcessFailure(PayProcessFailureReason.insufficientZchf)); + return; + } + + await _checkEthBalance(swap); + } catch (e) { + if (isClosed) return; + emit(PayProcessFailure(PayProcessFailureReason.generic, message: e.toString())); + } + } + + Future _checkEthBalance(SwapPaymentInfo swap) async { + if (swap.ethBalance >= swap.requiredGasEth) { + await _executeSwap(); + return; + } + await _requestFaucet(swap); + } + + Future _requestFaucet(SwapPaymentInfo swap) async { + try { + emit(const PayProcessWaitingForEth()); + await _faucetService.requestFaucet(); + if (isClosed) return; + _startEthPolling(swap); + } catch (e) { + if (isClosed) return; + emit(PayProcessFailure(PayProcessFailureReason.insufficientEth, message: e.toString())); + } + } + + void _startEthPolling(SwapPaymentInfo swap) { + _ethPollingTimer?.cancel(); + _ethPollAttempts = 0; + _ethPollingTimer = Timer.periodic(_ethPollInterval, (_) async { + if (_swapInFlight) return; + _swapInFlight = true; + _ethPollAttempts++; + try { + final balance = await _blockchainService + .getEthBalance(_appStore.primaryAddress) + .timeout(_ethPollTimeout); + if (isClosed) return; + if (balance >= swap.requiredGasEth) { + _ethPollingTimer?.cancel(); + await _executeSwap(); + } else if (_ethPollAttempts >= _ethPollMaxAttempts) { + _ethPollingTimer?.cancel(); + emit( + const PayProcessFailure( + PayProcessFailureReason.insufficientEth, + message: 'eth balance polling exceeded max attempts', + ), + ); + } + // else: balance still short — falls through to `finally`, which releases + // `_swapInFlight` so the next tick can retry. + } catch (_) { + if (isClosed) return; + if (_ethPollAttempts >= _ethPollMaxAttempts) { + _ethPollingTimer?.cancel(); + emit( + const PayProcessFailure( + PayProcessFailureReason.insufficientEth, + message: 'eth balance polling exceeded max attempts', + ), + ); + return; + } + // keep polling on transient errors (including per-request timeout) — `finally` below + // releases `_swapInFlight`. + } finally { + // Every path out of this tick (success+swap-triggered, max-attempts emit, isClosed + // abort, transient-error retry) must release the guard — otherwise a stuck `true` from + // one polling cycle silently wedges every tick of a LATER cycle (`_startEthPolling` + // never resets this itself). Safe even on the swap-triggered success path: the timer for + // THIS cycle is already cancelled above before `_executeSwap()` runs. + _swapInFlight = false; + } + }); + } + + Future _executeSwap() async { + final swap = _swap; + if (swap == null) return; + try { + emit(const PayProcessSwapping()); + final unsigned = await _payService.createSwapUnsignedTransaction(swap.id); + if (isClosed) return; + final signed = await _signTransaction(unsigned.swap); + if (isClosed) return; + await _payService.broadcastSwapTransaction(swap.id, signed); + if (isClosed) return; + // The swap is now irreversible — the user holds ZCHF. From here every + // recovery path retries the PAY leg only; the swap is never redone. + _swapCompleted = true; + _acquiredZchf = swap.estimatedAmount; + await _refreshQuoteAndPay(); + } on PaySignatureUnsupportedException { + if (isClosed) return; + emit(const PayProcessFailure(PayProcessFailureReason.signatureUnsupported)); + } on BitboxNotConnectedException { + if (isClosed) return; + emit(const PayProcessFailure(PayProcessFailureReason.bitboxRequired)); + } catch (e) { + if (isClosed) return; + emit(PayProcessFailure(PayProcessFailureReason.generic, message: e.toString())); + } + } + + /// Retries the pay leg ONLY, after a successful swap. Re-fetches the OCP quote + /// and re-runs sign + submit; it never re-swaps (guarded by [_swapCompleted]), + /// so the ZCHF already in the wallet is reused and REALU is never + /// double-converted. Wired to the retry action on [PayProcessPayRetry]. + Future retryPay() async { + if (!_swapCompleted) return; + await _refreshQuoteAndPay(); + } + + /// Re-reads the OCP quote so the pay step uses a fresh quoteId — the swap may + /// have taken longer than the original quote's validity window. Runs both on + /// the first pay attempt (right after the swap) and on every [retryPay]. + /// + /// A GENUINE expiry (the explicit `expiration.isBefore(now)` check) and a + /// TRANSIENT fetch error are kept distinct: both are recoverable by retrying + /// the pay leg, so neither forces a re-scan → re-swap. + Future _refreshQuoteAndPay() async { + final LnurlpPaymentDto details; + try { + emit(const PayProcessRefreshingQuote()); + details = await _payService.getPaymentDetails(_paymentLinkId); + } catch (e) { + // Transient/network error fetching the quote — NOT a genuine expiry. + // Retry the pay leg; the swapped ZCHF stays in the wallet. + if (isClosed) return; + emit(PayProcessPayRetry(PayRetryReason.transient, message: e.toString())); + return; + } + + if (isClosed) return; + + if (details.quote.expiration.isBefore(DateTime.now())) { + emit(const PayProcessPayRetry(PayRetryReason.quoteExpired)); + return; + } + + // Guard the slippage boundary: the swap acquired [_acquiredZchf], but the + // fresh quote may now demand more ZCHF than that. Settling it would fail + // server-side AFTER the irreversible swap, so surface a typed, retryable + // state (re-quote may land within the held ZCHF) instead of an opaque + // failure. The leftover ZCHF stays in the wallet. + // + // Comparison prefers exact plain-decimal strings on the fresh side + // ([LnurlpTransferAssetDto.rawAmount]) vs. [_acquiredZchf].toString(). Note + // that [_acquiredZchf] is still ultimately double-derived upstream + // (swap.estimatedAmount via RealUnitSwapPaymentInfoDto — out of this fix's + // scope); this only removes binary-comparison artifacts at the comparison + // site itself and gives exact precision on the freshZchf side, without + // pretending end-to-end exactness. When the raw string is missing or a plain-decimal + // comparison cannot be performed (parsing throws), the comparison is fail-closed: + // [_settlementExceedsAcquired] returns `true` (= exceeds acquired), triggering the + // [PayRetryReason.insufficientZchf] retry — never treated as "covered". + final freshZchf = _zchfTransferAmount(details); + if (freshZchf != null && + _settlementExceedsAcquired(freshZchf.amount, freshZchf.raw, _acquiredZchf)) { + emit( + PayProcessPayRetry( + PayRetryReason.insufficientZchf, + message: + 'fresh settlement ${freshZchf.amount} ZCHF exceeds acquired $_acquiredZchf ZCHF', + ), + ); + return; + } + + await _executePay(details.quote.id); + } + + Future _executePay(String quoteId) async { + try { + emit(const PayProcessPaying()); + final RealUnitOcpPayUnsignedTransactionDto unsigned = await _payService + .createPayUnsignedTransaction( + RealUnitOcpPayDto(paymentLinkId: _paymentLinkId, quoteId: quoteId), + ); + if (isClosed) return; + _validatePayUnsignedTx(unsigned); + final signed = await _signTransaction(unsigned.unsignedTx); + if (isClosed) return; + final txId = await _payService.submitPay( + RealUnitOcpPaySubmitDto( + unsignedTx: signed.unsignedTx, + r: signed.r, + s: signed.s, + v: signed.v, + paymentLinkId: _paymentLinkId, + quoteId: quoteId, + ), + ); + if (isClosed) return; + emit(PayProcessAwaitingSettlement(txId)); + _startStatusPolling(); + } on PayUnsignedTxMismatchException catch (e) { + // The backend's own unsigned tx does not match its own metadata — never sign it. The swap + // already happened; recovery retries the pay leg, which re-fetches AND re-validates a fresh + // unsigned tx from scratch, so a bad tx can never slip through on retry. + if (isClosed) return; + emit(PayProcessPayRetry(PayRetryReason.unsignedTxMismatch, message: e.toString())); + } catch (e) { + // The swap already happened; the user holds ZCHF. Any pay-leg failure here (signing + // dropped, BitBox disconnect, transient submit error, settlement rejected) is recoverable + // by retrying the pay leg — never by re-swapping. Surface the retryable state rather than a + // terminal failure. + if (isClosed) return; + emit(PayProcessPayRetry(PayRetryReason.transient, message: e.toString())); + } + } + + /// The ZCHF amount listed for the Ethereum transfer method in a fresh quote, + /// or null if the link no longer offers a priced Ethereum/ZCHF method. Mirrors + /// [PayQuoteCubit]'s selection — the app never computes the amount locally. + /// Also surfaces the raw JSON amount string when present for exact decimal + /// comparison (see [_settlementExceedsAcquired]). + static ({double amount, String? raw})? _zchfTransferAmount(LnurlpPaymentDto details) { + for (final transfer in details.transferAmounts) { + if (transfer.method.toLowerCase() != 'ethereum') continue; + for (final asset in transfer.assets) { + if (asset.asset.toUpperCase() != 'ZCHF') continue; + final amount = asset.amount; + if (amount == null) return null; + return (amount: amount, raw: asset.rawAmount); + } + } + return null; + } + + /// True when [freshAmount] strictly exceeds [acquired] — or when an exact plain-decimal + /// comparison cannot be established at all. Never falls back to a rounding-prone double `>` + /// comparison: doing so could wrongly report "not exceeding" (falsely "covered") when the true + /// decimal values differ. Fail-closed: any inability to prove exact coverage is treated as + /// "exceeds acquired", which routes the caller into the existing retryable + /// [PayRetryReason.insufficientZchf] path rather than risking an under-swapped settlement. + static bool _settlementExceedsAcquired( + double freshAmount, + String? freshRaw, + double acquired, + ) { + if (freshRaw == null) { + return true; + } + final acquiredRaw = acquired.toString(); + try { + return comparePlainDecimalStrings(freshRaw, acquiredRaw) > 0; + } on FormatException { + // Not a plain decimal on one/both sides (e.g. scientific notation from double.toString) — + // cannot prove exact coverage. Fail closed rather than falling back to a rounding-prone + // double comparison that could wrongly say "covered". + return true; + } + } + + /// Locally re-derives the security-relevant fields of the pay-leg [unsigned] raw tx (the ZCHF + /// ERC20-transfer `to`/recipient/amount/chainId/gas/fees) from the RLP bytes themselves and + /// checks them against the DTO metadata, the app's locally configured chainId + /// (`apiConfig.asset.chainId`), and local gas/fee caps BEFORE the pay tx is signed. Scope is + /// the pay leg only — the earlier REALU→ZCHF swap (`RealUnitSwapUnsignedTransactionDto`) is + /// signed without an equivalent decode+validate step today (that DTO has no comparable + /// metadata; closing the gap needs a backend extension). The backend is untrusted for this: a + /// compromised/buggy backend must never be able to make the app sign a pay transfer to the + /// wrong token, recipient, amount, chain, or with unbounded fees. Throws + /// [PayUnsignedTxMismatchException] fail-closed on any mismatch or structural anomaly. + void _validatePayUnsignedTx(RealUnitOcpPayUnsignedTransactionDto unsigned) { + final tx = Eip1559UnsignedTxDecoder.decode(unsigned.unsignedTx); + + // Network + fee sanity (DTO self-consistency, local trusted chain, gas/fee caps) + // before content checks (to / recipient / amount). + final expectedChainId = BigInt.from(unsigned.chainId); + if (tx.chainId != expectedChainId) { + throw PayUnsignedTxMismatchException( + 'chainId mismatch: tx=${tx.chainId} dto=${unsigned.chainId}', + ); + } + + // Independent of the DTO: bind against the chainId baked into the app build + // (same value `_signTransaction` passes to `signToSignature`). A compromised + // backend that returns a self-consistent but wrong chainId cannot pass this. + final localChainId = BigInt.from(_appStore.apiConfig.asset.chainId); + if (tx.chainId != localChainId) { + throw PayUnsignedTxMismatchException( + 'chainId ${tx.chainId} does not match locally configured chain $localChainId ' + '(apiConfig.asset.chainId) — refusing to sign for an unexpected network', + ); + } + + if (tx.gasLimit > BigInt.from(_maxGasLimit)) { + throw PayUnsignedTxMismatchException( + 'unsigned tx gasLimit ${tx.gasLimit} exceeds local cap $_maxGasLimit', + ); + } + final totalFeeWei = tx.maxFeePerGas * tx.gasLimit; + if (totalFeeWei > _maxTotalFeeWei) { + throw PayUnsignedTxMismatchException( + 'unsigned tx max total fee $totalFeeWei wei exceeds local cap $_maxTotalFeeWei wei', + ); + } + + if (tx.value != BigInt.zero) { + throw PayUnsignedTxMismatchException( + 'unsigned tx sends native value ${tx.value}, expected 0', + ); + } + + final expectedToken = _normalizeAddress(unsigned.tokenAddress, 'tokenAddress'); + if (tx.to != expectedToken) { + throw PayUnsignedTxMismatchException( + 'tokenAddress mismatch: tx.to=${tx.to} dto.tokenAddress=${unsigned.tokenAddress}', + ); + } + + final transfer = Erc20TransferCalldataDecoder.decode(tx.data); + + final expectedRecipient = _normalizeAddress(unsigned.recipient, 'recipient'); + if (transfer.recipient != expectedRecipient) { + throw PayUnsignedTxMismatchException( + 'recipient mismatch: calldata=${transfer.recipient} dto.recipient=${unsigned.recipient}', + ); + } + + final BigInt expectedAmount; + try { + expectedAmount = BigInt.parse(unsigned.amountWei); + } on FormatException { + throw PayUnsignedTxMismatchException('amountWei is not a valid integer: ${unsigned.amountWei}'); + } + if (transfer.amountWei != expectedAmount) { + throw PayUnsignedTxMismatchException( + 'amount mismatch: calldata=${transfer.amountWei} dto.amountWei=${unsigned.amountWei}', + ); + } + } + + /// Normalizes an address DTO field to `0x` + 40 lowercase hex chars, or throws + /// [PayUnsignedTxMismatchException] if it isn't one — refusing to compare against a malformed + /// address is safer than silently truncating/padding it. + static String _normalizeAddress(String raw, String fieldName) { + final hex = (raw.startsWith('0x') || raw.startsWith('0X')) ? raw.substring(2) : raw; + if (!RegExp(r'^[0-9a-fA-F]{40}$').hasMatch(hex)) { + throw PayUnsignedTxMismatchException('$fieldName is not a valid 20-byte address: $raw'); + } + return '0x${hex.toLowerCase()}'; + } + + void _startStatusPolling() { + _statusPollingTimer?.cancel(); + final generation = ++_statusPollGeneration; + _statusPollAttempts = 0; + _statusPollInFlight = false; + _statusPollingTimer = Timer.periodic(_statusPollInterval, (_) async { + if (generation != _statusPollGeneration || _statusPollInFlight) return; + _statusPollInFlight = true; + try { + final status = await _payService.getPayStatus(_paymentLinkId); + if (isClosed || generation != _statusPollGeneration) return; + _statusPollAttempts++; + if (!status.status.isTerminal) { + if (_statusPollAttempts >= _statusPollMaxAttempts) { + _statusPollingTimer?.cancel(); + emit( + const PayProcessPayRetry( + PayRetryReason.transient, + message: 'status polling exceeded max attempts', + ), + ); + return; + } + _statusPollInFlight = false; + return; + } + _statusPollingTimer?.cancel(); + if (status.status.isCompleted) { + emit(const PayProcessSuccess()); + } else { + emit(const PayProcessPayRetry(PayRetryReason.transient)); + } + } catch (_) { + if (isClosed || generation != _statusPollGeneration) return; + _statusPollAttempts++; + if (_statusPollAttempts >= _statusPollMaxAttempts) { + _statusPollingTimer?.cancel(); + emit( + const PayProcessPayRetry( + PayRetryReason.transient, + message: 'status polling exceeded max attempts', + ), + ); + return; + } + _statusPollInFlight = false; + } + }); + } + + /// Signs a serialized unsigned EIP-1559 tx with the active wallet credentials + /// and returns the broadcast envelope (`unsignedTx` + r/s/v). Works for + /// software and BitBox; a debug wallet's `signToSignature` throws + /// [UnsupportedError], normalised here to [PaySignatureUnsupportedException]. + Future _signTransaction(String rawTransaction) async { + await _walletService.ensureCurrentWalletUnlocked(); + try { + final credentials = _appStore.wallet.currentAccount.primaryAddress; + final payload = Uint8List.fromList( + convert.hex.decode( + rawTransaction.startsWith('0x') ? rawTransaction.substring(2) : rawTransaction, + ), + ); + final MsgSignature sig; + try { + sig = await credentials.signToSignature( + payload, + chainId: _appStore.apiConfig.asset.chainId, + isEIP1559: true, + ); + } on UnsupportedError { + throw const PaySignatureUnsupportedException(); + } + final r = sig.r.toRadixString(16).padLeft(64, '0'); + final s = sig.s.toRadixString(16).padLeft(64, '0'); + return BroadcastTransactionRequestDto( + unsignedTx: rawTransaction, + r: '0x$r', + s: '0x$s', + v: sig.v, + ); + } finally { + await _walletService.lockCurrentWallet(); + } + } + + @override + Future close() { + _ethPollingTimer?.cancel(); + _statusPollingTimer?.cancel(); + return super.close(); + } +} diff --git a/lib/screens/pay/cubits/pay_process/pay_process_state.dart b/lib/screens/pay/cubits/pay_process/pay_process_state.dart new file mode 100644 index 000000000..9f501ff68 --- /dev/null +++ b/lib/screens/pay/cubits/pay_process/pay_process_state.dart @@ -0,0 +1,118 @@ +part of 'pay_process_cubit.dart'; + +/// Why the pay flow failed. Each reason maps to a localized, user-facing +/// message in the view — the cubit carries the reason, not the copy. +enum PayProcessFailureReason { + /// The swap quote came back invalid (e.g. not fundable for the requested + /// ZCHF amount after the slippage buffer). + insufficientZchf, + + /// Not enough ETH to cover gas and the faucet top-up did not arrive. + insufficientEth, + + /// The active wallet mode cannot sign transactions (debug wallet). + signatureUnsupported, + + /// A BitBox is required but not connected. + bitboxRequired, + + /// Any other unexpected error. + generic, +} + +/// Why the pay leg failed AFTER the REALU→ZCHF swap already succeeded. The user +/// holds ZCHF, so recovery must retry the pay leg ONLY (re-quote + sign + +/// submit) — never the swap. Each reason maps to a localized message. +enum PayRetryReason { + /// The OCP quote expired between the swap and the pay step. Re-quoting is + /// safe — the swapped ZCHF stays in the wallet. + quoteExpired, + + /// A transient/network error while re-fetching the quote or settling. Not a + /// genuine expiry; retrying the pay leg is the correct recovery. + transient, + + /// The freshly re-fetched settlement amount exceeds the ZCHF acquired by the + /// swap (price moved more than the swap headroom buffer). Re-quoting may land + /// within the held ZCHF; the leftover ZCHF stays in the wallet meanwhile. + insufficientZchf, + + /// The unsigned tx the backend returned for signing did not match its own security metadata + /// (token/recipient/amount/chain) — see [PayUnsignedTxMismatchException]. Never signed. Retrying + /// re-fetches AND re-validates a fresh unsigned tx from scratch. + unsignedTxMismatch, +} + +sealed class PayProcessState extends Equatable { + const PayProcessState(); + + @override + List get props => []; +} + +class PayProcessInitial extends PayProcessState { + const PayProcessInitial(); +} + +class PayProcessPreparingSwap extends PayProcessState { + const PayProcessPreparingSwap(); +} + +class PayProcessWaitingForEth extends PayProcessState { + const PayProcessWaitingForEth(); +} + +class PayProcessSwapping extends PayProcessState { + const PayProcessSwapping(); +} + +class PayProcessRefreshingQuote extends PayProcessState { + const PayProcessRefreshingQuote(); +} + +class PayProcessPaying extends PayProcessState { + const PayProcessPaying(); +} + +/// Pay tx submitted; polling `/pay/:id/status` until it settles. +class PayProcessAwaitingSettlement extends PayProcessState { + final String txId; + + const PayProcessAwaitingSettlement(this.txId); + + @override + List get props => [txId]; +} + +class PayProcessSuccess extends PayProcessState { + const PayProcessSuccess(); +} + +/// The swap succeeded (ZCHF is in the wallet) but the pay leg failed. Recoverable +/// by retrying the pay leg ONLY — the view calls [PayProcessCubit.retryPay], +/// which re-quotes + signs + submits without ever re-swapping. This is the key +/// fund-safety state: a failed pay no longer forces a re-scan → re-swap (which +/// would double-convert REALU). +class PayProcessPayRetry extends PayProcessState { + final PayRetryReason reason; + + /// Diagnostic detail for logs — not the user-facing copy. + final String? message; + + const PayProcessPayRetry(this.reason, {this.message}); + + @override + List get props => [reason, message]; +} + +class PayProcessFailure extends PayProcessState { + final PayProcessFailureReason reason; + + /// Diagnostic detail for logs — not the user-facing copy. + final String? message; + + const PayProcessFailure(this.reason, {this.message}); + + @override + List get props => [reason, message]; +} diff --git a/lib/screens/pay/cubits/pay_quote/pay_quote_cubit.dart b/lib/screens/pay/cubits/pay_quote/pay_quote_cubit.dart new file mode 100644 index 000000000..4e0d7e07d --- /dev/null +++ b/lib/screens/pay/cubits/pay_quote/pay_quote_cubit.dart @@ -0,0 +1,80 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/lnurlp_payment_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_swap_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_pay_service.dart'; + +part 'pay_quote_state.dart'; + +/// Reads the public OCP payment-link quote (`GET /v1/lnurlp/:id`) and surfaces +/// the requested fiat amount + the exact ZCHF amount the Ethereum method +/// requires. The amount comes from the API `transferAmounts` (ZCHF on the +/// Ethereum entry) — the app never computes it. An expired quote surfaces as a +/// typed state so the view can prompt a re-scan. +class PayQuoteCubit extends Cubit { + final RealUnitPayService _payService; + final String _paymentLinkId; + + PayQuoteCubit(this._payService, this._paymentLinkId) : super(const PayQuoteLoading()); + + Future load() async { + emit(const PayQuoteLoading()); + + try { + final details = await _payService.getPaymentDetails(_paymentLinkId); + if (isClosed) return; + + if (details.quote.expiration.isBefore(DateTime.now())) { + emit(const PayQuoteExpired()); + return; + } + + final zchfAmount = _zchfTransferAmount(details); + if (zchfAmount == null) { + emit(const PayQuoteUnavailable()); + return; + } + + // Preview-only swap quote using the plain bill ZCHF amount (no slippage + // buffer). Shown on the pay-quote screen so the user sees expected REALU + // sold, ZCHF proceeds and fees for this bill before confirming. The + // actual swap executed later in PayProcessCubit re-requests its own quote + // independently with its own slippage buffer and remains the sole source + // of truth for what is actually swapped. + final swap = await _payService.getSwapPaymentInfo( + RealUnitSwapDto.fromTargetAmount(zchfAmount), + ); + if (isClosed) return; + + emit( + PayQuoteReady( + paymentLinkId: _paymentLinkId, + quoteId: details.quote.id, + fiatAsset: details.requestedAmount.asset, + fiatAmount: details.requestedAmount.amount, + zchfAmount: zchfAmount, + merchantName: details.recipient?.name, + merchantCity: details.recipient?.city, + realuAmount: swap.amount, + realuEstimatedZchf: swap.estimatedAmount, + realuFeesTotal: swap.feesTotal, + ), + ); + } catch (e) { + if (isClosed) return; + emit(PayQuoteError(e.toString())); + } + } + + /// The ZCHF amount listed for the Ethereum transfer method, or null if the + /// payment link does not offer an Ethereum/ZCHF method. + static double? _zchfTransferAmount(LnurlpPaymentDto details) { + for (final transfer in details.transferAmounts) { + if (transfer.method.toLowerCase() != 'ethereum') continue; + for (final asset in transfer.assets) { + if (asset.asset.toUpperCase() == 'ZCHF') return asset.amount; + } + } + return null; + } +} diff --git a/lib/screens/pay/cubits/pay_quote/pay_quote_state.dart b/lib/screens/pay/cubits/pay_quote/pay_quote_state.dart new file mode 100644 index 000000000..8a62dc347 --- /dev/null +++ b/lib/screens/pay/cubits/pay_quote/pay_quote_state.dart @@ -0,0 +1,71 @@ +part of 'pay_quote_cubit.dart'; + +sealed class PayQuoteState extends Equatable { + const PayQuoteState(); + + @override + List get props => []; +} + +class PayQuoteLoading extends PayQuoteState { + const PayQuoteLoading(); +} + +class PayQuoteReady extends PayQuoteState { + final String paymentLinkId; + final String quoteId; + final String fiatAsset; + final double fiatAmount; + final double zchfAmount; + final String? merchantName; + final String? merchantCity; + final double? realuAmount; + final double? realuEstimatedZchf; + final double? realuFeesTotal; + + const PayQuoteReady({ + required this.paymentLinkId, + required this.quoteId, + required this.fiatAsset, + required this.fiatAmount, + required this.zchfAmount, + this.merchantName, + this.merchantCity, + this.realuAmount, + this.realuEstimatedZchf, + this.realuFeesTotal, + }); + + @override + List get props => [ + paymentLinkId, + quoteId, + fiatAsset, + fiatAmount, + zchfAmount, + merchantName, + merchantCity, + realuAmount, + realuEstimatedZchf, + realuFeesTotal, + ]; +} + +/// The quote attached to the scanned link has expired — the user must re-scan. +class PayQuoteExpired extends PayQuoteState { + const PayQuoteExpired(); +} + +/// The payment link offers no Ethereum/ZCHF transfer method. +class PayQuoteUnavailable extends PayQuoteState { + const PayQuoteUnavailable(); +} + +class PayQuoteError extends PayQuoteState { + final String message; + + const PayQuoteError(this.message); + + @override + List get props => [message]; +} diff --git a/lib/screens/pay/cubits/pay_scan/pay_scan_cubit.dart b/lib/screens/pay/cubits/pay_scan/pay_scan_cubit.dart new file mode 100644 index 000000000..d40c3cf68 --- /dev/null +++ b/lib/screens/pay/cubits/pay_scan/pay_scan_cubit.dart @@ -0,0 +1,28 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/payment/pay_exceptions.dart'; +import 'package:realunit_wallet/packages/service/dfx/lnurl_decoder.dart'; + +part 'pay_scan_state.dart'; + +/// Decodes a scanned OCP QR into a DFX payment-link id + lnurlp URL. Pure +/// decode — no network. The view advances to the quote step on +/// [PayScanDecoded]; a malformed code keeps the scanner open with an error. +class PayScanCubit extends Cubit { + PayScanCubit() : super(const PayScanScanning()); + + /// Called once per detected barcode. Guards against re-entry after a + /// successful decode so a continuously-detecting scanner does not re-emit. + void onCodeDetected(String raw) { + if (state is PayScanDecoded) return; + try { + final decoded = LnurlDecoder.decode(raw); + emit(PayScanDecoded(decoded)); + } on InvalidPaymentLinkException catch (e) { + emit(PayScanInvalid(e.reason)); + } + } + + /// Dismiss an error and resume scanning. + void reset() => emit(const PayScanScanning()); +} diff --git a/lib/screens/pay/cubits/pay_scan/pay_scan_state.dart b/lib/screens/pay/cubits/pay_scan/pay_scan_state.dart new file mode 100644 index 000000000..f3552e5f4 --- /dev/null +++ b/lib/screens/pay/cubits/pay_scan/pay_scan_state.dart @@ -0,0 +1,30 @@ +part of 'pay_scan_cubit.dart'; + +sealed class PayScanState extends Equatable { + const PayScanState(); + + @override + List get props => []; +} + +class PayScanScanning extends PayScanState { + const PayScanScanning(); +} + +class PayScanInvalid extends PayScanState { + final String reason; + + const PayScanInvalid(this.reason); + + @override + List get props => [reason]; +} + +class PayScanDecoded extends PayScanState { + final DecodedPaymentLink link; + + const PayScanDecoded(this.link); + + @override + List get props => [link.id, link.lnurlpUrl]; +} diff --git a/lib/screens/pay/pay_process_page.dart b/lib/screens/pay/pay_process_page.dart new file mode 100644 index 000000000..23ac7f8ff --- /dev/null +++ b/lib/screens/pay/pay_process_page.dart @@ -0,0 +1,213 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/packages/service/app_store.dart'; +import 'package:realunit_wallet/packages/service/dfx/dfx_blockchain_api_service.dart'; +import 'package:realunit_wallet/packages/service/dfx/dfx_faucet_service.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_pay_service.dart'; +import 'package:realunit_wallet/packages/service/wallet_service.dart'; +import 'package:realunit_wallet/screens/pay/cubits/pay_process/pay_process_cubit.dart'; +import 'package:realunit_wallet/setup/di.dart'; +import 'package:realunit_wallet/styles/colors.dart'; + +class PayProcessPage extends StatelessWidget { + final String paymentLinkId; + final double zchfNeeded; + + const PayProcessPage({ + super.key, + required this.paymentLinkId, + required this.zchfNeeded, + }); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => PayProcessCubit( + payService: getIt(), + faucetService: getIt(), + blockchainService: getIt(), + walletService: getIt(), + appStore: getIt(), + paymentLinkId: paymentLinkId, + zchfNeeded: zchfNeeded, + )..start(), + child: const PayProcessView(), + ); + } +} + +class PayProcessView extends StatelessWidget { + const PayProcessView({super.key}); + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listenWhen: (previous, current) => + current is PayProcessSuccess || + current is PayProcessFailure || + current is PayProcessPayRetry, + listener: (context, state) async { + if (state is PayProcessSuccess) { + await _showResultSheet( + context, + icon: Icons.check_circle_rounded, + title: S.of(context).paySuccess, + description: S.of(context).paySuccessDescription, + ); + } else if (state is PayProcessPayRetry) { + // The swap already succeeded — offer to retry the PAY leg only. The + // ZCHF stays in the wallet; this never re-swaps. + await _showRetrySheet(context, state.reason); + } else if (state is PayProcessFailure) { + await _showResultSheet( + context, + icon: Icons.error_rounded, + title: S.of(context).payFailureTitle, + description: _failureMessage(context, state.reason), + ); + } + }, + builder: (context, state) { + return Scaffold( + appBar: AppBar(title: Text(S.of(context).pay)), + body: SafeArea( + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + spacing: 24, + children: [ + const CupertinoActivityIndicator(radius: 16), + Text( + _progressLabel(context, state), + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyLarge, + ), + ], + ), + ), + ), + ); + }, + ); + } + + String _progressLabel(BuildContext context, PayProcessState state) => switch (state) { + PayProcessInitial() || PayProcessPreparingSwap() => S.of(context).payPreparingSwap, + PayProcessWaitingForEth() => S.of(context).payWaitingForEth, + PayProcessSwapping() => S.of(context).paySwapping, + PayProcessRefreshingQuote() => S.of(context).payRefreshingQuote, + PayProcessPaying() => S.of(context).payPaying, + PayProcessAwaitingSettlement() => S.of(context).payAwaitingSettlement, + PayProcessSuccess() => S.of(context).paySuccess, + PayProcessPayRetry() => S.of(context).payRetryTitle, + PayProcessFailure() => S.of(context).payFailureTitle, + }; + + String _failureMessage(BuildContext context, PayProcessFailureReason reason) => switch (reason) { + PayProcessFailureReason.insufficientZchf => S.of(context).payFailureInsufficientZchf, + PayProcessFailureReason.insufficientEth => S.of(context).payFailureInsufficientEth, + PayProcessFailureReason.signatureUnsupported => S.of(context).payFailureSignatureUnsupported, + PayProcessFailureReason.bitboxRequired => S.of(context).payFailureBitboxRequired, + PayProcessFailureReason.generic => S.of(context).payFailureGeneric, + }; + + String _retryMessage(BuildContext context, PayRetryReason reason) => switch (reason) { + PayRetryReason.quoteExpired => S.of(context).payRetryQuoteExpired, + PayRetryReason.transient => S.of(context).payRetryTransient, + PayRetryReason.insufficientZchf => S.of(context).payRetryInsufficientZchf, + PayRetryReason.unsignedTxMismatch => S.of(context).payRetryUnsignedTxMismatch, + }; + + Future _showResultSheet( + BuildContext context, { + required IconData icon, + required String title, + required String description, + }) async { + await showModalBottomSheet( + context: context, + isDismissible: false, + builder: (_) => SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24), + child: Column( + mainAxisSize: MainAxisSize.min, + spacing: 24, + children: [ + Icon(icon, color: RealUnitColors.realUnitBlue, size: 64), + Text(title, style: Theme.of(context).textTheme.headlineMedium), + Text( + description, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: RealUnitColors.neutral500, + ), + ), + FilledButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(S.of(context).close), + ), + ], + ), + ), + ), + ); + if (context.mounted) Navigator.of(context).pop(); + } + + /// Recovery sheet shown after a successful swap when the pay leg failed. The + /// primary action retries the PAY leg only ([PayProcessCubit.retryPay]) — the + /// swap is never redone, so the ZCHF already held is reused. Dismissing leaves + /// that ZCHF safely in the wallet. + Future _showRetrySheet(BuildContext context, PayRetryReason reason) async { + final cubit = context.read(); + // The sheet returns true when the user retries (keep the page) and false + // when they close (leave the flow); a barrier dismissal yields null. + final retry = await showModalBottomSheet( + context: context, + isDismissible: false, + builder: (sheetContext) => SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24), + child: Column( + mainAxisSize: MainAxisSize.min, + spacing: 24, + children: [ + const Icon(Icons.replay_rounded, color: RealUnitColors.realUnitBlue, size: 64), + Text( + S.of(sheetContext).payRetryTitle, + style: Theme.of(sheetContext).textTheme.headlineMedium, + ), + Text( + _retryMessage(sheetContext, reason), + textAlign: TextAlign.center, + style: Theme.of(sheetContext).textTheme.bodyMedium?.copyWith( + color: RealUnitColors.neutral500, + ), + ), + FilledButton( + onPressed: () => Navigator.of(sheetContext).pop(true), + child: Text(S.of(sheetContext).payRetryButton), + ), + TextButton( + onPressed: () => Navigator.of(sheetContext).pop(false), + child: Text(S.of(sheetContext).close), + ), + ], + ), + ), + ), + ); + + if (retry == true) { + // Retry the PAY leg only — never re-swaps. Keep the page so the next + // attempt surfaces its own result. + await cubit.retryPay(); + } else if (context.mounted) { + // Closed: leave the flow. The swapped ZCHF stays safely in the wallet. + Navigator.of(context).pop(); + } + } +} diff --git a/lib/screens/pay/pay_quote_page.dart b/lib/screens/pay/pay_quote_page.dart new file mode 100644 index 000000000..df9c33703 --- /dev/null +++ b/lib/screens/pay/pay_quote_page.dart @@ -0,0 +1,210 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_pay_service.dart'; +import 'package:realunit_wallet/packages/utils/default_assets.dart'; +import 'package:realunit_wallet/screens/pay/cubits/pay_quote/pay_quote_cubit.dart'; +import 'package:realunit_wallet/screens/pay/pay_process_page.dart'; +import 'package:realunit_wallet/setup/di.dart'; +import 'package:realunit_wallet/styles/colors.dart'; +import 'package:realunit_wallet/widgets/scrollable_actions_layout.dart'; + +class PayQuotePage extends StatelessWidget { + final String paymentLinkId; + + const PayQuotePage({super.key, required this.paymentLinkId}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => PayQuoteCubit(getIt(), paymentLinkId)..load(), + child: const PayQuoteView(), + ); + } +} + +class PayQuoteView extends StatelessWidget { + const PayQuoteView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(S.of(context).payQuoteTitle)), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), + child: BlocBuilder( + builder: (context, state) => switch (state) { + PayQuoteLoading() => const Center(child: CupertinoActivityIndicator()), + PayQuoteReady() => _PayQuoteReadyView(state: state), + PayQuoteExpired() => _PayQuoteMessage(message: S.of(context).payFailureQuoteExpired), + PayQuoteUnavailable() => _PayQuoteMessage(message: S.of(context).payQuoteUnavailable), + PayQuoteError() => _PayQuoteMessage( + message: S.of(context).payFailureGeneric, + onRetry: () => context.read().load(), + ), + }, + ), + ), + ), + ); + } +} + +class _PayQuoteReadyView extends StatefulWidget { + final PayQuoteReady state; + + const _PayQuoteReadyView({required this.state}); + + @override + State<_PayQuoteReadyView> createState() => _PayQuoteReadyViewState(); +} + +class _PayQuoteReadyViewState extends State<_PayQuoteReadyView> { + bool _navigating = false; + + @override + Widget build(BuildContext context) { + final state = widget.state; + return ScrollableActionsLayout( + centerBody: true, + body: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + spacing: 24, + children: [ + Text( + S + .of(context) + .payQuoteSummary( + state.fiatAmount.toStringAsFixed(2), + state.fiatAsset, + ), + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headlineMedium, + ), + if (state.merchantName != null) ...[ + _AmountRow( + label: S.of(context).payQuoteMerchant, + value: state.merchantCity != null + ? '${state.merchantName}, ${state.merchantCity}' + : state.merchantName!, + ), + ], + _AmountRow( + label: S.of(context).payQuoteRequested, + value: '${state.fiatAmount.toStringAsFixed(2)} ${state.fiatAsset}', + ), + _AmountRow( + label: S.of(context).payQuoteZchfNeeded, + value: '${state.zchfAmount.toStringAsFixed(2)} ZCHF', + ), + if (state.realuAmount != null) ...[ + _AmountRow( + label: S.of(context).payQuoteRealuAmount, + value: '${state.realuAmount!.toStringAsFixed(0)} ${realUnitAsset.symbol}', + ), + ], + if (state.realuEstimatedZchf != null) ...[ + _AmountRow( + label: S.of(context).payQuoteRealuEstimated, + value: '${state.realuEstimatedZchf!.toStringAsFixed(2)} ZCHF', + ), + ], + if (state.realuFeesTotal != null) ...[ + _AmountRow( + label: S.of(context).payQuoteRealuFees, + value: '${state.realuFeesTotal!.toStringAsFixed(2)} ${realUnitAsset.symbol}', + ), + ], + ], + ), + actions: [ + FilledButton( + onPressed: _navigating + ? null + : () { + setState(() => _navigating = true); + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => PayProcessPage( + paymentLinkId: state.paymentLinkId, + zchfNeeded: state.zchfAmount, + ), + ), + ); + }, + child: Text(S.of(context).payConfirmButton), + ), + ], + ); + } +} + +class _AmountRow extends StatelessWidget { + final String label; + final String value; + + const _AmountRow({required this.label, required this.value}); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 16, + children: [ + Flexible( + child: Text( + label, + softWrap: true, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: RealUnitColors.neutral500, + ), + ), + ), + Flexible( + child: Text( + value, + textAlign: TextAlign.end, + softWrap: true, + style: Theme.of(context).textTheme.bodyLarge, + ), + ), + ], + ); + } +} + +class _PayQuoteMessage extends StatelessWidget { + final String message; + final VoidCallback? onRetry; + + const _PayQuoteMessage({required this.message, this.onRetry}); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + message, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: RealUnitColors.neutral500, + ), + ), + if (onRetry != null) ...[ + const SizedBox(height: 16), + OutlinedButton( + key: const ValueKey('payQuoteRetryButton'), + onPressed: onRetry, + child: Text(S.of(context).retry), + ), + ], + ], + ), + ); + } +} diff --git a/lib/screens/pay/pay_scan_page.dart b/lib/screens/pay/pay_scan_page.dart new file mode 100644 index 000000000..71c6be737 --- /dev/null +++ b/lib/screens/pay/pay_scan_page.dart @@ -0,0 +1,80 @@ +// @no-integration-test: the QR scanner is camera/MethodChannel-coupled +// (mobile_scanner) and can only be exercised on a real device with a live +// camera. The decode logic it feeds is unit-tested in lnurl_decoder_test.dart +// and the cubit behaviour in pay_scan_cubit_test.dart; the camera preview +// itself is out of scope for widget tests. +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:mobile_scanner/mobile_scanner.dart'; +import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/screens/pay/cubits/pay_scan/pay_scan_cubit.dart'; +import 'package:realunit_wallet/screens/pay/pay_quote_page.dart'; +import 'package:realunit_wallet/styles/colors.dart'; + +class PayScanPage extends StatelessWidget { + const PayScanPage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => PayScanCubit(), + child: const PayScanView(), + ); + } +} + +class PayScanView extends StatelessWidget { + const PayScanView({super.key}); + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) { + if (state is PayScanDecoded) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => PayQuotePage(paymentLinkId: state.link.id), + ), + ); + // Reset so returning to the scanner re-arms detection. + context.read().reset(); + } + if (state is PayScanInvalid) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(S.of(context).payScanInvalid), + backgroundColor: RealUnitColors.status.red600, + ), + ); + context.read().reset(); + } + }, + builder: (context, state) { + return Scaffold( + appBar: AppBar(title: Text(S.of(context).payScanTitle)), + body: MobileScanner( + onDetect: (capture) { + final raw = capture.barcodes.firstOrNull?.rawValue; + if (raw != null) context.read().onCodeDetected(raw); + }, + errorBuilder: (context, error, child) { + final message = error.errorCode == MobileScannerErrorCode.permissionDenied + ? S.of(context).payScanCameraPermissionDenied + : S.of(context).payScanCameraUnavailable; + return Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Text( + message, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: RealUnitColors.neutral500), + ), + ), + ); + }, + ), + ); + }, + ); + } +} diff --git a/lib/setup/di.dart b/lib/setup/di.dart index 3b8ee3632..a3c54ef27 100644 --- a/lib/setup/di.dart +++ b/lib/setup/di.dart @@ -31,6 +31,7 @@ import 'package:realunit_wallet/packages/service/dfx/dfx_widget_service.dart'; import 'package:realunit_wallet/packages/service/dfx/real_unit_account_service.dart'; import 'package:realunit_wallet/packages/service/dfx/real_unit_buy_payment_info_service.dart'; import 'package:realunit_wallet/packages/service/dfx/real_unit_legal_service.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_pay_service.dart'; import 'package:realunit_wallet/packages/service/dfx/real_unit_pdf_service.dart'; import 'package:realunit_wallet/packages/service/dfx/real_unit_registration_service.dart'; import 'package:realunit_wallet/packages/service/dfx/real_unit_sell_payment_info_service.dart'; @@ -200,6 +201,9 @@ void setupServices() { getIt.registerFactory( () => RealUnitLegalService(getIt(), getIt()), ); + getIt.registerFactory( + () => RealUnitPayService(getIt(), getIt()), + ); getIt.registerFactory( () => RealUnitPdfService(getIt(), getIt()), ); diff --git a/lib/setup/routing/router_config.dart b/lib/setup/routing/router_config.dart index 88073f096..e7d3559b8 100644 --- a/lib/setup/routing/router_config.dart +++ b/lib/setup/routing/router_config.dart @@ -13,6 +13,7 @@ import 'package:realunit_wallet/screens/kyc/kyc_page_manager.dart'; import 'package:realunit_wallet/screens/legal/legal_disclaimer_page.dart'; import 'package:realunit_wallet/screens/legal/subpages/legal_document_page.dart'; import 'package:realunit_wallet/screens/onboarding/onboarding_completed_page.dart'; +import 'package:realunit_wallet/screens/pay/pay_scan_page.dart'; import 'package:realunit_wallet/screens/pin/setup_pin_page.dart'; import 'package:realunit_wallet/screens/pin/verify_pin_page.dart'; import 'package:realunit_wallet/screens/receive/receive_page.dart'; @@ -174,6 +175,12 @@ final GoRouter routerConfig = GoRouter( builder: (_, state) => SellBitboxPage(paymentInfo: state.extra as SellPaymentInfo), ), + GoRoute( + name: AppRoutes.pay, + path: '/pay', + builder: (_, _) => const PayScanPage(), + ), + GoRoute( name: LegalRoutes.disclaimer, path: '/legalDisclaimer', diff --git a/lib/setup/routing/routes/app_routes.dart b/lib/setup/routing/routes/app_routes.dart index c8b54cf71..e7cd66a96 100644 --- a/lib/setup/routing/routes/app_routes.dart +++ b/lib/setup/routing/routes/app_routes.dart @@ -6,6 +6,7 @@ abstract final class AppRoutes { static const buyPaymentDetails = 'buyPaymentDetails'; static const sell = 'sell'; static const sellBitbox = 'sellBitbox'; + static const pay = 'pay'; static const kyc = 'kyc'; static const receive = 'receive'; static const bitboxAddressRecovery = 'bitboxAddressRecovery'; diff --git a/pubspec.lock b/pubspec.lock index 795c516be..5c204b0f5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -951,6 +951,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + mobile_scanner: + dependency: "direct main" + description: + name: mobile_scanner + sha256: d234581c090526676fd8fab4ada92f35c6746e3fb4f05a399665d75a399fb760 + url: "https://pub.dev" + source: hosted + version: "5.2.3" mocktail: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index 13998da87..67fd1d5ce 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -65,6 +65,7 @@ dependencies: http: ^1.1.0 intl: any local_auth: ^3.0.0 + mobile_scanner: ^5.2.3 no_screenshot: ^1.1.0 open_file: ^3.5.11 path: ^1.9.0 diff --git a/test/goldens/screens/dashboard/goldens/macos/dashboard_hidden_amounts.png b/test/goldens/screens/dashboard/goldens/macos/dashboard_hidden_amounts.png index 48ddfea2156abaf3dd6e42d524447e1e151329b3..2e8d0176b0c6c9cf690182f2735900eb992fe38a 100644 GIT binary patch delta 36213 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 delta 34662 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 LS`_h8>&yQE=8&8Y diff --git a/test/goldens/screens/dashboard/goldens/macos/dashboard_recent_transactions.png b/test/goldens/screens/dashboard/goldens/macos/dashboard_recent_transactions.png index 88e5d2810920a8178f6e19df3b9f4f06f2360edb..f4b55f0d8cb666e3fadcd345774af03707457be3 100644 GIT binary patch delta 39826 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
p<49lGXOxY2Dk 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 delta 38165 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 KMNUMnLSTYtgCxxW diff --git a/test/goldens/screens/dashboard/goldens/macos/dashboard_with_balance.png b/test/goldens/screens/dashboard/goldens/macos/dashboard_with_balance.png index 4aed8b45bdefdf1d58ee75ad70552627cd1a0e9c..066064d918074d73bba09ceccb100ae848b64b3b 100644 GIT binary patch literal 27711 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

m0<$^y8ysFQ2DqHntlpR7diw-#?oj=# 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 diff --git a/test/goldens/screens/send/goldens/macos/send_amount_page_empty.png b/test/goldens/screens/send/goldens/macos/send_amount_page_empty.png new file mode 100644 index 0000000000000000000000000000000000000000..9aa47c72e92a7803ffcf71619e06e1b9359de8fa GIT binary patch literal 13405 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 PL|tUdy5_ELQB!cs=b9Esamz7s%WV# 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 I(_0V!1+zJ!rvLx| literal 0 HcmV?d00001 diff --git a/test/goldens/screens/send/goldens/macos/send_process_page_signing.png b/test/goldens/screens/send/goldens/macos/send_process_page_signing.png new file mode 100644 index 0000000000000000000000000000000000000000..fc553f68808727b928b383ada508aa8b2ca99674 GIT binary patch literal 10320 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 uwNsv+t>|bnQYljC1f4j z*v1&L3J2NlvFtZ zQ&;Y|en!#$)Tzg(i>NapLZfWYNa@hyXAd`ZvmZVA8;|r`%6hetttcy*4nvO7?|Y{A!isEoUi?4V7Pydk>UHziwq2r_Zb*2{wprvS8d{*McM-iqx!@}YaR z)=Uh{SE3}Rae+7+Tp7dc@?Ojqkh5_#X5AXYbP2|75u=mW~MUUb2STl))JO`ZWV&byRC;XsD;BXZTOYN65nOgjHLy+^I%+ zpZ$XDs7qW+Gfj{(?4obu$DZC^Kd-6UZsix>UxV%RKv{?t<8{&RA4Xgi&zwHp0Dqot zc#o-QvMsDxQA7kWHJ-@|AAxQQ@12q(QkI9*YMjxg{Tb4t{*8To&nYV-nq_9l%4!JX zfWSYeBF=%}Mm|11%$F~#mi9V3I+i|;WKsxNGcQ7Nc^A)>2prxDrC`j2c+Rk|6~g5G zoQf)0y^H_wFb)KBSbM)&>d!P=QaU=IrU~??OTznT`@8F9?=EnduI`_5=A3yjTTcCX zLBe>d8WwR()3&%A$0}^Vw~jOqd#)ns*O%v}+|;!S20e zZ*+Aa*ucPG3KbENPHs8aXih8)A+^?zLVbOG+1S~O8X6k3?KEef8?ECPx;_}0M89uwpJ+K4918+NHeX& zEnTnrje&G?cpSSDjKN!gh8)5ivM z7*B5Z&igh(HlMw%uJ-Gr374+K=sde86Jh-%-*9-i)p9pthNjVL>XjfMc&BKUBoS`> z=b`9G-o$B-$tt}`d3iyQDtDmq*?58F*@V-A+&U_yM7bb=$H35E=t)V^KKp-#Ngt0J z*@Dyy3yX-r{e|sSnxbKont$R}$fRN-9TsNh75>mxBnV{D>5Dh)xw5-HRS(Rhk2WRJ%B5DMb!||~(o@4r=;~tnm)&8MIo2l-(&C(rWj+8?l zFviY8BtI~%8Sh5x;C+mqwEYCbrM9olgu_6U%#!So%d7o_4Kw{0m$VE)o|Ukm6^GdC zV|I}eYX%$D4lEra!h#sT$-M%to`C^MFiyq`U&cgPy<2YD?@Qp3bxmCovw&w!TceLA z1Vjv}_<)q3YHCX4Bl6;dCEqvFfBE~Mc$-28p^oDCMcQtt71Lf%Ez)&Uc-;{ zl)RJ1Ba95HwmxWUgA`YKb3V0Lr~Qe)BP^-dJe+pa|0_L4HMKCo>~bo~?s#N0Vb3A> zV1$n{%o^z0In6%RNPT_0HQ1jLj$?n|)vXXFA08!-^zdg@-cGxcSQy`RT%N~>#%$gS zD|5Z12TJ*8F3HD;otHBoPA6T%oWE=Wwv5{4mGd3bc$`>#_|5z*i_r3ivtEWZ8kdqY z)5`j>RD{L+eq!MpLZA_Kzyl$`oVb&giTvDa6GrL@4a)|PHMEU+O|G0*3joChY$)H= zvaIoe@9!+%)S9X~cr8%sfL($+;${x)t2f%9=8Z4+_Pg*>z(jH3?fMYXv}GFzZa>dT z8JZ9dK8}^3tnnAH8A|?^q>GKS3_+Erh=U2ID=bkqNyQSz#z#%7Q&o;1dIJG75|hqP zwK(K>HtyPIkEmEmM>4WUNUhYWmgaO@i>JwuCg4~kAHp-+5<1}Zw}mEB)h`Fkbau7r zq=`txexXzWyLUZ5x}d#ls|BQ%x(e#cNsI z5EqjN9JY8Y=b(S@f10tuPW5vuo~PjiOiu2;cm!cj-3q znPDRp3jMb6X()bV$t|$&rMkvHD?K04X7PSgca`~dB-)PZP9k&w&dNSZf{R9HA&1EDMOp>$$czSm;3fr*k9Aib%||^9-h?i0D75-l{wj%AJTDDeb((k7;;W z7!Ip5ZrWkWM;wxDl(o`OUU&!dwuK~=pL$1a(1I-RP^)=7!a3Y{c&H9 z!B^Q=(uI~@WU1VcHHnT}K#u2LXo7~>mCX834vBWFd5Xv(v-t?j%#r5K_jNPCh}U4f zr0Ed-oG|J~zΤ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 E0*SBAV*mgE literal 0 HcmV?d00001 diff --git a/test/goldens/screens/send/send_golden_test.dart b/test/goldens/screens/send/send_golden_test.dart new file mode 100644 index 000000000..71c63cbff --- /dev/null +++ b/test/goldens/screens/send/send_golden_test.dart @@ -0,0 +1,161 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/models/balance.dart'; +import 'package:realunit_wallet/packages/utils/default_assets.dart'; +import 'package:realunit_wallet/screens/sell/cubits/sell_balance/sell_balance_cubit.dart'; +import 'package:realunit_wallet/screens/send/cubits/send_amount/send_amount_cubit.dart'; +import 'package:realunit_wallet/screens/send/cubits/send_process/send_process_cubit.dart'; +import 'package:realunit_wallet/screens/send/cubits/send_recipient/send_recipient_cubit.dart'; +import 'package:realunit_wallet/screens/send/send_amount_page.dart'; +import 'package:realunit_wallet/screens/send/send_confirm_page.dart'; +import 'package:realunit_wallet/screens/send/send_process_page.dart'; +import 'package:realunit_wallet/screens/send/send_recipient_page.dart'; + +import '../../../helper/helper.dart'; + +class _MockSendRecipientCubit extends MockCubit implements SendRecipientCubit {} + +class _MockSellBalanceCubit extends MockCubit implements SellBalanceCubit {} + +class _MockSendAmountCubit extends MockCubit implements SendAmountCubit {} + +class _MockSendProcessCubit extends MockCubit implements SendProcessCubit {} + +Balance _balance(int shares) => Balance( + chainId: realUnitAsset.chainId, + contractAddress: realUnitAsset.address, + walletAddress: '0xwallet', + balance: BigInt.from(shares), + asset: realUnitAsset, +); + +void main() { + setUpAll(() { + registerFallbackValue(BigInt.zero); + stubMobileScannerChannel(); + }); + + group('$SendRecipientView', () { + late _MockSendRecipientCubit recipientCubit; + + setUp(() { + recipientCubit = _MockSendRecipientCubit(); + when(() => recipientCubit.state).thenReturn(const SendRecipientEmpty()); + }); + + goldenTest( + 'scan + manual-entry state', + fileName: 'send_recipient_page_empty', + constraints: phoneConstraints, + // The camera preview never reaches an isInitialized frame headlessly, so + // pumpAndSettle would await a settle that never comes. pumpOnce captures + // the deterministic placeholder frame. + pumpBeforeTest: pumpOnce, + builder: () => wrapForGolden( + BlocProvider.value( + value: recipientCubit, + child: const SendRecipientView(), + ), + ), + ); + }); + + group('$SendAmountView', () { + late _MockSellBalanceCubit balanceCubit; + late _MockSendAmountCubit amountCubit; + + setUp(() { + balanceCubit = _MockSellBalanceCubit(); + amountCubit = _MockSendAmountCubit(); + when(() => balanceCubit.state).thenReturn(_balance(42)); + when(() => amountCubit.availableShares).thenReturn(BigInt.from(42)); + when(() => amountCubit.availableSharesChanged(any())).thenReturn(null); + when(() => amountCubit.state).thenReturn(const SendAmountState()); + }); + + goldenTest( + 'amount entry with available balance', + fileName: 'send_amount_page_empty', + constraints: phoneConstraints, + builder: () => wrapForGolden( + MultiBlocProvider( + providers: [ + BlocProvider.value(value: balanceCubit), + BlocProvider.value(value: amountCubit), + ], + child: const SendAmountView(recipient: '0xRecipient'), + ), + ), + ); + + goldenTest( + 'over-balance amount shows the insufficient error', + fileName: 'send_amount_page_insufficient', + constraints: phoneConstraints, + builder: () { + when(() => amountCubit.state).thenReturn( + const SendAmountState( + text: '99', + amount: 99, + status: SendAmountStatus.insufficientBalance, + ), + ); + return wrapForGolden( + MultiBlocProvider( + providers: [ + BlocProvider.value(value: balanceCubit), + BlocProvider.value(value: amountCubit), + ], + child: const SendAmountView(recipient: '0xRecipient'), + ), + ); + }, + ); + }); + + group('$SendConfirmPage', () { + goldenTest( + 'transfer summary', + fileName: 'send_confirm_page', + constraints: phoneConstraints, + builder: () => wrapForGolden( + const SendConfirmPage( + recipient: '0x9F5713DEacB8e9CAB6c2d3FaE1AFc2715F8D2D71', + amount: 5, + ), + ), + ); + }); + + group('$SendProcessView', () { + late _MockSendProcessCubit processCubit; + + setUp(() { + processCubit = _MockSendProcessCubit(); + when(() => processCubit.state).thenReturn(const SendProcessInitial()); + }); + + // Terminal states (success/failure) are surfaced via a modal sheet from the + // listener, not the build tree — exercised in the widget test. The build + // tree shows the in-progress indicator with a per-state label. + goldenTest( + 'in-progress signing state', + fileName: 'send_process_page_signing', + constraints: phoneConstraints, + // The CupertinoActivityIndicator animates forever; pumpOnce captures the + // first frame. + pumpBeforeTest: pumpOnce, + builder: () { + when(() => processCubit.state).thenReturn(const SendProcessSigning()); + return wrapForGolden( + BlocProvider.value( + value: processCubit, + child: const SendProcessView(), + ), + ); + }, + ); + }); +} diff --git a/test/helper/responsive_surface_catalog.dart b/test/helper/responsive_surface_catalog.dart index c529c93d3..f45b7dfd3 100644 --- a/test/helper/responsive_surface_catalog.dart +++ b/test/helper/responsive_surface_catalog.dart @@ -192,6 +192,31 @@ const kResponsiveSurfaceCatalog = [ matrixTestPath: 'test/screens/sell/sell_responsive_matrix_test.dart', productionPath: 'lib/screens/sell/sell_page.dart', ), + ResponsiveSurface( + id: 'send_amount_page', + description: 'Send amount page (next CTA)', + matrixTestPath: 'test/screens/send/send_amount_responsive_matrix_test.dart', + productionPath: 'lib/screens/send/send_amount_page.dart', + ), + ResponsiveSurface( + id: 'send_confirm_page', + description: 'Send confirmation page (confirm CTA)', + matrixTestPath: 'test/screens/send/send_confirm_responsive_matrix_test.dart', + productionPath: 'lib/screens/send/send_confirm_page.dart', + ), + ResponsiveSurface( + id: 'send_process_result_sheet', + description: 'Send process terminal result sheet (shrinkWrap mode)', + matrixTestPath: + 'test/screens/send/send_process_result_sheet_responsive_matrix_test.dart', + productionPath: 'lib/screens/send/send_process_page.dart', + ), + ResponsiveSurface( + id: 'send_recipient_page', + description: 'Send recipient manual-entry block (next CTA)', + matrixTestPath: 'test/screens/send/send_recipient_responsive_matrix_test.dart', + productionPath: 'lib/screens/send/send_recipient_page.dart', + ), ResponsiveSurface( id: 'setup_pin_page', description: 'Setup PIN page (sticky numeric keypad as actions)', @@ -234,7 +259,7 @@ const kResponsiveSurfaceCatalog = [ matrixTestPath: 'test/screens/pin/pin_sheets_responsive_matrix_test.dart', productionPath: 'lib/screens/pin/widgets/enable_biometric_bottom_sheet.dart', ), - // Migration covers 30 surfaces total (bitbox_connect_sheet + 29 above). No + // Migration covers 34 surfaces total (bitbox_connect_sheet + 33 above). No // further known candidates remain from the prior sweep. welcome_page was // reviewed and found safe (scrolls end-to-end, no separate sticky CTA) — not // a migration candidate. Not exhaustive — review responsibility for every diff --git a/test/packages/service/dfx/exceptions/exception_surface_test.dart b/test/packages/service/dfx/exceptions/exception_surface_test.dart index fea010bba..55387f3cb 100644 --- a/test/packages/service/dfx/exceptions/exception_surface_test.dart +++ b/test/packages/service/dfx/exceptions/exception_surface_test.dart @@ -5,6 +5,7 @@ import 'package:realunit_wallet/packages/service/dfx/exceptions/bitbox_exception import 'package:realunit_wallet/packages/service/dfx/exceptions/payment/buy_exceptions.dart'; import 'package:realunit_wallet/packages/service/dfx/exceptions/payment/pay_exceptions.dart'; import 'package:realunit_wallet/packages/service/dfx/exceptions/payment/sell_exceptions.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/payment/transfer_exceptions.dart'; import 'package:realunit_wallet/packages/service/dfx/exceptions/registration_rejected_exception.dart'; import 'package:realunit_wallet/packages/storage/secure_storage.dart'; import 'package:realunit_wallet/packages/wallet/exceptions/signing_cancelled_exception.dart'; @@ -36,6 +37,11 @@ void main() { const InvalidPaymentLinkException('test'), const PaySignatureUnsupportedException(), const PayUnsignedTxMismatchException('test'), + const InvalidRecipientAddressException('test'), + const TransferSignatureUnsupportedException(), + const TransferGasFundingUnavailableException(), + const TransferConfirmMismatchException(), + const TransferAlreadyConfirmedException(code: 'TEST', message: 'test'), ]; for (final ex in exceptions) { diff --git a/test/packages/service/dfx/models/payment/transfer/transfer_dtos_test.dart b/test/packages/service/dfx/models/payment/transfer/transfer_dtos_test.dart new file mode 100644 index 000000000..961fd12ec --- /dev/null +++ b/test/packages/service/dfx/models/payment/transfer/transfer_dtos_test.dart @@ -0,0 +1,118 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_eip7702_data_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_payment_info_dto.dart'; + +Map _eip7702Json() => { + 'relayerAddress': '0xRelayer', + 'delegationManagerAddress': '0xManager', + 'delegatorAddress': '0xDelegator', + 'userNonce': 7, + 'domain': { + 'name': 'DelegationManager', + 'version': '1', + 'chainId': 11155111, + 'verifyingContract': '0xManager', + }, + 'types': { + 'Delegation': [ + {'name': 'delegate', 'type': 'address'}, + ], + 'Caveat': [ + {'name': 'enforcer', 'type': 'address'}, + ], + }, + 'message': { + 'delegate': '0xRelayer', + 'delegator': '0xSender', + 'authority': '0xRoot', + 'caveats': [], + 'salt': 3, + }, + 'tokenAddress': '0xRealu', + 'amountWei': '5', + 'recipient': '0xRecipient', +}; + +void main() { + group('RealUnitTransferDto', () { + test('toJson carries toAddress + amount', () { + const dto = RealUnitTransferDto(toAddress: '0xRecipient', amount: 5); + + expect(dto.toJson(), {'toAddress': '0xRecipient', 'amount': 5}); + }); + }); + + group('RealUnitTransferEip7702DataDto', () { + test('fromJson parses the recipient (transfer) shape', () { + final data = RealUnitTransferEip7702DataDto.fromJson(_eip7702Json()); + + expect(data.relayerAddress, '0xRelayer'); + expect(data.delegatorAddress, '0xDelegator'); + expect(data.userNonce, 7); + expect(data.domain.chainId, 11155111); + expect(data.types.delegation.first.name, 'delegate'); + expect(data.types.caveat.first.type, 'address'); + expect(data.message.delegator, '0xSender'); + expect(data.message.salt, 3); + expect(data.tokenAddress, '0xRealu'); + expect(data.amountWei, '5'); + expect(data.recipient, '0xRecipient'); + }); + + test('toEip7702Data maps recipient into the shared signer DTO depositAddress', () { + final data = RealUnitTransferEip7702DataDto.fromJson(_eip7702Json()); + + final shared = data.toEip7702Data(); + + // The recipient flows through depositAddress (the signers never read it), + // while every signed field is preserved verbatim. + expect(shared.depositAddress, '0xRecipient'); + expect(shared.relayerAddress, '0xRelayer'); + expect(shared.delegatorAddress, '0xDelegator'); + expect(shared.userNonce, 7); + expect(shared.domain.chainId, 11155111); + expect(shared.message.delegator, '0xSender'); + expect(shared.message.salt, 3); + expect(shared.tokenAddress, '0xRealu'); + expect(shared.amountWei, '5'); + }); + }); + + group('RealUnitTransferPaymentInfoDto', () { + test('fromJson parses the full prepare response', () { + final dto = RealUnitTransferPaymentInfoDto.fromJson({ + 'id': 99, + 'uid': 'RTabc', + 'toAddress': '0xRecipient', + 'amount': 5, + 'tokenAddress': '0xRealu', + 'chainId': 11155111, + 'eip7702': _eip7702Json(), + }); + + expect(dto.id, 99); + expect(dto.uid, 'RTabc'); + expect(dto.toAddress, '0xRecipient'); + expect(dto.amount, 5); + expect(dto.tokenAddress, '0xRealu'); + expect(dto.chainId, 11155111); + expect(dto.eip7702.recipient, '0xRecipient'); + expect(dto.eip7702.amountWei, '5'); + }); + + test('fromJson tolerates a numeric (double) amount from the API', () { + final dto = RealUnitTransferPaymentInfoDto.fromJson({ + 'id': 1, + 'uid': 'RTx', + 'toAddress': '0xRecipient', + 'amount': 5.0, + 'tokenAddress': '0xRealu', + 'chainId': 1, + 'eip7702': _eip7702Json(), + }); + + expect(dto.amount, 5); + }); + }); +} diff --git a/test/packages/service/dfx/real_unit_transfer_service_test.dart b/test/packages/service/dfx/real_unit_transfer_service_test.dart new file mode 100644 index 000000000..475bffe31 --- /dev/null +++ b/test/packages/service/dfx/real_unit_transfer_service_test.dart @@ -0,0 +1,462 @@ +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:web3dart/web3dart.dart'; +import 'package:realunit_wallet/packages/config/api_config.dart'; +import 'package:realunit_wallet/packages/config/network_mode.dart'; +import 'package:realunit_wallet/packages/repository/cache_repository.dart'; +import 'package:realunit_wallet/packages/service/app_store.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/api_exception.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/payment/transfer_exceptions.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_payment_info_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_transfer_service.dart'; +import 'package:realunit_wallet/packages/service/session_cache.dart'; +import 'package:realunit_wallet/packages/service/wallet_service.dart'; +import 'package:realunit_wallet/packages/wallet/wallet.dart'; +import 'package:realunit_wallet/packages/wallet/wallet_account.dart'; + +class _MockAppStore extends Mock implements AppStore {} + +class _MockWallet extends Mock implements AWallet {} + +class _MockAccount extends Mock implements AWalletAccount {} + +class _MockCacheRepository extends Mock implements CacheRepository {} + +class _MockWalletService extends Mock implements WalletService {} + +// Deterministic test private key — a real EthPrivateKey credential the +// EIP-712 / EIP-7702 signers accept directly (they reject anything that isn't +// BitboxCredentials or EthPrivateKey). +const _testPrivateKeyHex = 'fb1ace12f9801e85f3db1b3935dd47d9f064f98152466f47c701b5e12680e612'; + +final _privKey = EthPrivateKey.fromHex(_testPrivateKeyHex); +final _walletAddress = _privKey.address.hexEip55; + +const _metaMaskDelegator = '0x63c0c19a282a1b52b07dd5a65b58948a07dae32b'; +const _delegationManager = '0xdb9b1e94b5b69df7e401ddbede43491141047db3'; + +const _confirmedRecipient = '0xRecipient'; +const _confirmedAmount = 5; + +/// Debug-wallet style credential: it is neither BitboxCredentials nor +/// EthPrivateKey, so `Eip712Signer.signDelegation` hits its `_ => throw +/// UnsupportedError(...)` branch — exactly the debug-wallet capability gap. +class _UnsupportedCreds extends Fake implements CredentialsWithKnownAddress { + @override + EthereumAddress get address => _privKey.address; +} + +Map _eip7702Json({int chainId = 1, String recipient = _confirmedRecipient}) => { + 'relayerAddress': '0xrelay', + 'delegationManagerAddress': _delegationManager, + 'delegatorAddress': _metaMaskDelegator, + 'userNonce': 7, + 'domain': { + 'name': 'RealUnit', + 'version': '1', + 'chainId': chainId, + 'verifyingContract': _delegationManager, + }, + 'types': { + 'Delegation': >[], + 'Caveat': >[], + }, + 'message': { + 'delegate': '0xrelay', + 'delegator': _walletAddress, + 'authority': '0xauth', + 'caveats': >[], + 'salt': 0, + }, + // The asset address the mainnet RealUnit token resolves to in this fixture. + 'tokenAddress': '0x553C7f9C780316FC1D34b8e14ac2465Ab22a090B', + 'amountWei': '5', + 'recipient': recipient, +}; + +RealUnitTransferPaymentInfoDto _info({ + String toAddress = _confirmedRecipient, + int amount = _confirmedAmount, + String eip7702Recipient = _confirmedRecipient, +}) => RealUnitTransferPaymentInfoDto.fromJson({ + 'id': 42, + 'uid': 'RTabc', + 'toAddress': toAddress, + 'amount': amount, + 'tokenAddress': '0xRealu', + 'chainId': 1, + 'eip7702': _eip7702Json(recipient: eip7702Recipient), +}); + +/// Happy-path confirm with matching user-confirmed recipient/amount. +Future _confirm( + RealUnitTransferService service, + RealUnitTransferPaymentInfoDto info, { + String confirmedRecipient = _confirmedRecipient, + int confirmedAmount = _confirmedAmount, +}) { + return service.confirmTransfer( + info, + confirmedRecipient: confirmedRecipient, + confirmedAmount: confirmedAmount, + ); +} + +void main() { + late _MockAppStore appStore; + late _MockWallet wallet; + late _MockAccount account; + late _MockWalletService walletService; + late SessionCache session; + + setUp(() { + appStore = _MockAppStore(); + wallet = _MockWallet(); + account = _MockAccount(); + walletService = _MockWalletService(); + session = SessionCache(_MockCacheRepository()); + session.setAuthToken('jwt-1'); + + when(() => appStore.apiConfig).thenReturn(const ApiConfig(networkMode: NetworkMode.mainnet)); + when(() => appStore.sessionCache).thenReturn(session); + when(() => appStore.wallet).thenReturn(wallet); + when(() => wallet.currentAccount).thenReturn(account); + when(() => account.primaryAddress).thenReturn(_privKey); + when(() => walletService.ensureCurrentWalletUnlocked()).thenAnswer((_) async {}); + when(() => walletService.lockCurrentWallet()).thenAnswer((_) async {}); + }); + + RealUnitTransferService build(http.Client client) { + when(() => appStore.httpClient).thenReturn(client); + return RealUnitTransferService(appStore, walletService); + } + + group('prepareTransfer', () { + test('200 → parses the payment-info DTO and PUTs toAddress + amount', () async { + Uri? sentUri; + Map? body; + final client = MockClient((request) async { + sentUri = request.url; + body = jsonDecode(request.body) as Map; + return http.Response( + jsonEncode({ + 'id': 42, + 'uid': 'RTabc', + 'toAddress': '0xRecipient', + 'amount': 5, + 'tokenAddress': '0xRealu', + 'chainId': 1, + 'eip7702': _eip7702Json(), + }), + 200, + ); + }); + + final info = await build(client).prepareTransfer( + const RealUnitTransferDto(toAddress: '0xRecipient', amount: 5), + ); + + expect(sentUri!.path, '/v1/realunit/transfer'); + expect(body, {'toAddress': '0xRecipient', 'amount': 5}); + expect(info.id, 42); + expect(info.eip7702.recipient, '0xRecipient'); + }); + + test('503 → TransferGasFundingUnavailableException', () async { + final client = MockClient( + (_) async => http.Response( + jsonEncode({'statusCode': 503, 'message': 'W2W gas funding temporarily unavailable'}), + 503, + ), + ); + + expect( + () => build(client).prepareTransfer( + const RealUnitTransferDto(toAddress: '0xRecipient', amount: 5), + ), + throwsA(isA()), + ); + }); + + test('400 (invalid recipient / insufficient REALU) → ApiException', () async { + final client = MockClient( + (_) async => http.Response( + jsonEncode({'statusCode': 400, 'code': 'X', 'message': 'Invalid recipient address'}), + 400, + ), + ); + + expect( + () => build(client).prepareTransfer( + const RealUnitTransferDto(toAddress: 'bad', amount: 5), + ), + throwsA(isA()), + ); + }); + }); + + group('confirmTransfer (software wallet happy path)', () { + test('signs delegation + authorization, PUTs the envelope, returns txHash', () async { + Uri? sentUri; + Map? body; + final client = MockClient((request) async { + sentUri = request.url; + body = jsonDecode(request.body) as Map; + return http.Response(jsonEncode({'txHash': '0xdeadbeef'}), 200); + }); + + final txHash = await _confirm(build(client), _info()); + + expect(txHash, '0xdeadbeef'); + expect(sentUri!.path, '/v1/realunit/transfer/42/confirm'); + + final envelope = body!; + expect(envelope.containsKey('delegation'), isTrue); + expect(envelope.containsKey('authorization'), isTrue); + + final delegation = envelope['delegation'] as Map; + expect(delegation['delegate'], '0xrelay'); + expect(delegation['delegator'], _walletAddress); + expect(delegation['authority'], '0xauth'); + expect(delegation['salt'], '0'); + expect((delegation['signature'] as String).length, 132); + + final authorization = envelope['authorization'] as Map; + expect(authorization['chainId'], 1); + expect(authorization['address'], _metaMaskDelegator); + expect(authorization['nonce'], 7); + // r/s are always full 32-byte (64 hex char) big-endian values. + expect((authorization['r'] as String).substring(2).length, 64); + expect((authorization['s'] as String).substring(2).length, 64); + expect(authorization['yParity'], anyOf(0, 1)); + }); + + test('locks the wallet after signing (key never left resident)', () async { + final client = MockClient((_) async => http.Response(jsonEncode({'txHash': '0x1'}), 200)); + + await _confirm(build(client), _info()); + + verify(() => walletService.ensureCurrentWalletUnlocked()).called(1); + verify(() => walletService.lockCurrentWallet()).called(1); + }); + + test('debug-wallet credentials → TransferSignatureUnsupportedException', () async { + when(() => account.primaryAddress).thenReturn(_UnsupportedCreds()); + final client = MockClient((_) async => http.Response('{}', 200)); + + expect( + () => _confirm(build(client), _info()), + throwsA(isA()), + ); + }); + + test('503 on confirm → TransferGasFundingUnavailableException', () async { + final client = MockClient( + (_) async => http.Response(jsonEncode({'message': 'unavailable'}), 503), + ); + + expect( + () => _confirm(build(client), _info()), + throwsA(isA()), + ); + }); + + test('4xx on confirm → ApiException', () async { + final client = MockClient( + (_) async => + http.Response(jsonEncode({'statusCode': 409, 'code': 'X', 'message': 'no'}), 409), + ); + + expect( + () => _confirm(build(client), _info()), + throwsA(isA()), + ); + }); + + // Every pinned contract/field is rejected before signing, mirroring the + // sell software-confirm guard. Each case mutates one field of the otherwise + // valid eip7702 payload and asserts validation throws WITHOUT any PUT. + group('eip7702 validation pins (throw before any PUT)', () { + RealUnitTransferPaymentInfoDto infoWith(Map Function() mutate) => + RealUnitTransferPaymentInfoDto.fromJson({ + 'id': 42, + 'uid': 'RTabc', + 'toAddress': _confirmedRecipient, + 'amount': _confirmedAmount, + 'tokenAddress': '0xRealu', + 'chainId': 1, + 'eip7702': mutate(), + }); + + Future expectRejected(RealUnitTransferPaymentInfoDto info) async { + var called = false; + final client = MockClient((_) async { + called = true; + return http.Response('{}', 200); + }); + await expectLater(() => _confirm(build(client), info), throwsException); + expect(called, isFalse, reason: 'no PUT must happen when validation rejects the payload'); + } + + test('wrong delegator (MetaMask delegator) contract', () async { + await expectRejected(infoWith(() => _eip7702Json()..['delegatorAddress'] = '0xWrong')); + }); + + test('wrong delegation manager contract', () async { + await expectRejected( + infoWith(() => _eip7702Json()..['delegationManagerAddress'] = '0xWrong'), + ); + }); + + test('verifying contract != delegation manager', () async { + await expectRejected( + infoWith(() { + final json = _eip7702Json(); + (json['domain'] as Map)['verifyingContract'] = '0xWrong'; + return json; + }), + ); + }); + + test('message delegator != wallet address', () async { + await expectRejected( + infoWith(() { + final json = _eip7702Json(); + (json['message'] as Map)['delegator'] = '0xSomeoneElse'; + return json; + }), + ); + }); + + test('chain id mismatch', () async { + await expectRejected(infoWith(() => _eip7702Json(chainId: 999))); + }); + + test('message delegate != relayer address', () async { + await expectRejected( + infoWith(() { + final json = _eip7702Json(); + (json['message'] as Map)['delegate'] = '0xOtherRelayer'; + return json; + }), + ); + }); + + test('token address != RealUnit token', () async { + await expectRejected(infoWith(() => _eip7702Json()..['tokenAddress'] = '0xNotRealu')); + }); + + test('amount-wei mismatch', () async { + await expectRejected(infoWith(() => _eip7702Json()..['amountWei'] = '6')); + }); + + test('unparseable amount-wei', () async { + await expectRejected(infoWith(() => _eip7702Json()..['amountWei'] = 'not-a-number')); + }); + }); + + // User-confirmed recipient/amount must match the prepare response before + // any signature is produced (blind-sign guard). + group('user-confirmed recipient/amount guard (throw before any PUT)', () { + Future expectMismatch( + RealUnitTransferPaymentInfoDto info, { + String confirmedRecipient = _confirmedRecipient, + int confirmedAmount = _confirmedAmount, + }) async { + var called = false; + final client = MockClient((_) async { + called = true; + return http.Response('{}', 200); + }); + await expectLater( + () => _confirm( + build(client), + info, + confirmedRecipient: confirmedRecipient, + confirmedAmount: confirmedAmount, + ), + throwsA(isA()), + ); + expect(called, isFalse, reason: 'no PUT must happen when user-confirm guard rejects'); + } + + test('toAddress mismatch → TransferConfirmMismatchException', () async { + await expectMismatch(_info(toAddress: '0xOtherRecipient')); + }); + + test('eip7702.recipient mismatch → TransferConfirmMismatchException', () async { + await expectMismatch(_info(eip7702Recipient: '0xOtherRecipient')); + }); + + test('amount mismatch → TransferConfirmMismatchException', () async { + await expectMismatch(_info(amount: 99)); + }); + + test('case-insensitive recipient match is accepted (no throw)', () async { + final client = MockClient( + (_) async => http.Response(jsonEncode({'txHash': '0xok'}), 200), + ); + final txHash = await _confirm( + build(client), + _info(toAddress: '0xrecipient', eip7702Recipient: '0xRECIPIENT'), + confirmedRecipient: '0xReCiPiEnT', + ); + expect(txHash, '0xok'); + }); + }); + }); + + group('confirm 409 mapping', () { + test('409 "already confirmed" → TransferAlreadyConfirmedException', () async { + final client = MockClient( + (_) async => http.Response( + jsonEncode({ + 'statusCode': 409, + 'message': 'Transaction request is already confirmed', + 'error': 'Conflict', + 'txHash': '0xfrom409', + }), + 409, + ), + ); + + await expectLater( + _confirm(build(client), _info()), + throwsA( + isA() + .having((e) => e.txHash, 'txHash', '0xfrom409') + .having( + (e) => e.message.toLowerCase().contains('already confirmed'), + 'message mentions already confirmed', + isTrue, + ), + ), + ); + }); + + test('any other 409 conflict stays a plain ApiException', () async { + final client = MockClient( + (_) async => http.Response( + jsonEncode({ + 'statusCode': 409, + 'message': 'Transaction request is in an invalid state', + 'error': 'Conflict', + }), + 409, + ), + ); + + await expectLater( + _confirm(build(client), _info()), + throwsA( + predicate((e) => e is ApiException && e is! TransferAlreadyConfirmedException), + ), + ); + }); + }); +} diff --git a/test/screens/dashboard/widgets/sections/dashboard_actions_test.dart b/test/screens/dashboard/widgets/sections/dashboard_actions_test.dart index 43eb39755..0192a2e36 100644 --- a/test/screens/dashboard/widgets/sections/dashboard_actions_test.dart +++ b/test/screens/dashboard/widgets/sections/dashboard_actions_test.dart @@ -14,9 +14,9 @@ void main() { pushedRoutes = []; }); - // Routes the three action buttons can push. Each target records the - // pushed route name so the `onPressed` closures are both executed and - // asserted, instead of only painted. + // Routes the four action buttons can push. Each target records the pushed + // route name so the `onPressed` closures are both executed and asserted, + // instead of only painted. GoRouter buildRouter() { GoRoute target(String name, String path) => GoRoute( name: name, @@ -37,6 +37,7 @@ void main() { target(AppRoutes.buy, '/buy'), target(AppRoutes.sell, '/sell'), target(AppRoutes.pay, '/pay'), + target(AppRoutes.send, '/send'), ], ); } @@ -62,15 +63,16 @@ void main() { ); group('$DashboardActions', () { - testWidgets('renders the buy, sell and pay action buttons', (tester) async { + testWidgets('renders the buy, sell, pay and send action buttons', (tester) async { await pumpActions(tester); expect(actionButtonByLabel(S.current.buy), findsOneWidget); expect(actionButtonByLabel(S.current.sell), findsOneWidget); expect(actionButtonByLabel(S.current.pay), findsOneWidget); - // Each button is laid out inside an Expanded so the row divides - // the available width into three equal slots. - expect(find.byType(Expanded), findsNWidgets(3)); + expect(actionButtonByLabel(S.current.send), findsOneWidget); + // Each button is laid out inside an Expanded so the row divides the + // available width into four equal slots. + expect(find.byType(Expanded), findsNWidgets(4)); }); testWidgets('renders the expected icons for each action', (tester) async { @@ -79,6 +81,7 @@ void main() { expect(find.byIcon(Icons.add_circle_rounded), findsOneWidget); expect(find.byIcon(Icons.do_not_disturb_on_rounded), findsOneWidget); expect(find.byIcon(Icons.qr_code_scanner_rounded), findsOneWidget); + expect(find.byIcon(Icons.send_rounded), findsOneWidget); }); testWidgets('buy button pushes the buy route', (tester) async { @@ -107,5 +110,14 @@ void main() { expect(pushedRoutes, [AppRoutes.pay]); }); + + testWidgets('send button pushes the send route', (tester) async { + await pumpActions(tester); + + await tester.tap(actionButtonByLabel(S.current.send)); + await tester.pumpAndSettle(); + + expect(pushedRoutes, [AppRoutes.send]); + }); }); } diff --git a/test/screens/send/cubits/send_amount_cubit_test.dart b/test/screens/send/cubits/send_amount_cubit_test.dart new file mode 100644 index 000000000..f9b528255 --- /dev/null +++ b/test/screens/send/cubits/send_amount_cubit_test.dart @@ -0,0 +1,87 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:realunit_wallet/screens/send/cubits/send_amount/send_amount_cubit.dart'; + +void main() { + group('SendAmountCubit', () { + test('starts empty and not valid', () { + final cubit = SendAmountCubit(availableShares: BigInt.from(10)); + expect(cubit.state.status, SendAmountStatus.empty); + expect(cubit.state.isValid, isFalse); + }); + + blocTest( + 'empty text → empty status', + build: () => SendAmountCubit(availableShares: BigInt.from(10)), + act: (cubit) => cubit.amountChanged(' '), + verify: (cubit) => expect(cubit.state.status, SendAmountStatus.empty), + ); + + blocTest( + 'a whole number within balance is valid', + build: () => SendAmountCubit(availableShares: BigInt.from(10)), + act: (cubit) => cubit.amountChanged('5'), + verify: (cubit) { + expect(cubit.state.status, SendAmountStatus.valid); + expect(cubit.state.amount, 5); + expect(cubit.state.isValid, isTrue); + }, + ); + + blocTest( + 'zero is invalid', + build: () => SendAmountCubit(availableShares: BigInt.from(10)), + act: (cubit) => cubit.amountChanged('0'), + verify: (cubit) => expect(cubit.state.status, SendAmountStatus.invalid), + ); + + blocTest( + 'a non-integer is invalid', + build: () => SendAmountCubit(availableShares: BigInt.from(10)), + act: (cubit) => cubit.amountChanged('1.5'), + verify: (cubit) => expect(cubit.state.status, SendAmountStatus.invalid), + ); + + blocTest( + 'more than the available balance → insufficientBalance', + build: () => SendAmountCubit(availableShares: BigInt.from(10)), + act: (cubit) => cubit.amountChanged('11'), + verify: (cubit) { + expect(cubit.state.status, SendAmountStatus.insufficientBalance); + expect(cubit.state.isValid, isFalse); + }, + ); + + blocTest( + 'with unknown balance the over-balance guard is skipped (API validates)', + build: SendAmountCubit.new, + act: (cubit) => cubit.amountChanged('999999'), + verify: (cubit) => expect(cubit.state.status, SendAmountStatus.valid), + ); + + blocTest( + 'useMax fills the full available balance', + build: () => SendAmountCubit(availableShares: BigInt.from(42)), + act: (cubit) => cubit.useMax(), + verify: (cubit) { + expect(cubit.state.text, '42'); + expect(cubit.state.amount, 42); + expect(cubit.state.status, SendAmountStatus.valid); + }, + ); + + blocTest( + 'useMax is a no-op when the balance is zero', + build: () => SendAmountCubit(availableShares: BigInt.zero), + act: (cubit) => cubit.useMax(), + expect: () => [], + ); + + blocTest( + 'useMax is a no-op when the balance is unknown', + build: SendAmountCubit.new, + act: (cubit) => cubit.useMax(), + expect: () => [], + ); + }); +} diff --git a/test/screens/send/cubits/send_process/send_process_state_test.dart b/test/screens/send/cubits/send_process/send_process_state_test.dart new file mode 100644 index 000000000..7bc8137dd --- /dev/null +++ b/test/screens/send/cubits/send_process/send_process_state_test.dart @@ -0,0 +1,161 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:realunit_wallet/screens/send/cubits/send_process/send_process_cubit.dart'; + +/// Equatable-`props` surface tests for `SendProcessState`. +void main() { + group('SendProcessState equality (Equatable props)', () { + test('payload-less states expose empty props and compare by type', () { + // Reading `.props` directly evaluates the inherited base getter; const + // canonicalization would otherwise let `==` short-circuit via identical + // and skip the getter line in the coverage report. + expect(const SendProcessInitial().props, isEmpty); + expect(const SendProcessPreparing().props, isEmpty); + expect(const SendProcessSigning().props, isEmpty); + + // Same type compares equal (base `props => []`). + expect(const SendProcessInitial(), const SendProcessInitial()); + expect(const SendProcessPreparing(), const SendProcessPreparing()); + expect(const SendProcessSigning(), const SendProcessSigning()); + + // Different payload-less subclasses are unequal. + expect( + const SendProcessInitial(), + isNot(equals(const SendProcessPreparing())), + ); + expect( + const SendProcessPreparing(), + isNot(equals(const SendProcessSigning())), + ); + expect( + const SendProcessSigning(), + isNot(equals(const SendProcessInitial())), + ); + }); + + test('SendProcessSuccess is keyed on txHash', () { + expect(const SendProcessSuccess('h'), const SendProcessSuccess('h')); + expect( + const SendProcessSuccess('h'), + isNot(equals(const SendProcessSuccess('g'))), + ); + expect(const SendProcessSuccess('h').props, ['h']); + }); + + test('SendProcessFailure is keyed on reason + message + canRetry', () { + // Equal when reason, message, and canRetry match. + expect( + const SendProcessFailure( + SendProcessFailureReason.invalidRequest, + message: 'm', + ), + const SendProcessFailure( + SendProcessFailureReason.invalidRequest, + message: 'm', + ), + ); + + // Unequal when the reason differs. + expect( + const SendProcessFailure( + SendProcessFailureReason.invalidRequest, + message: 'm', + ), + isNot( + equals( + const SendProcessFailure( + SendProcessFailureReason.generic, + message: 'm', + ), + ), + ), + ); + + // Unequal when the message differs. + expect( + const SendProcessFailure( + SendProcessFailureReason.invalidRequest, + message: 'm', + ), + isNot( + equals( + const SendProcessFailure( + SendProcessFailureReason.invalidRequest, + message: 'n', + ), + ), + ), + ); + + // Unequal when canRetry differs (same reason + message). + expect( + const SendProcessFailure( + SendProcessFailureReason.generic, + message: 'm', + canRetry: true, + ), + isNot( + equals( + const SendProcessFailure( + SendProcessFailureReason.generic, + message: 'm', + ), + ), + ), + ); + expect( + const SendProcessFailure( + SendProcessFailureReason.generic, + message: 'm', + canRetry: true, + ), + const SendProcessFailure( + SendProcessFailureReason.generic, + message: 'm', + canRetry: true, + ), + ); + + // The default (no `message`, canRetry false) variant is equal to itself + // and unequal to the same reason carrying a message — both evaluate `props`. + expect( + const SendProcessFailure(SendProcessFailureReason.signatureCancelled), + const SendProcessFailure(SendProcessFailureReason.signatureCancelled), + ); + expect( + const SendProcessFailure(SendProcessFailureReason.signatureCancelled), + isNot( + equals( + const SendProcessFailure( + SendProcessFailureReason.signatureCancelled, + message: 'm', + ), + ), + ), + ); + + expect( + const SendProcessFailure( + SendProcessFailureReason.invalidRequest, + message: 'm', + ).props, + [SendProcessFailureReason.invalidRequest, 'm', false], + ); + expect( + const SendProcessFailure(SendProcessFailureReason.signatureCancelled).props, + [SendProcessFailureReason.signatureCancelled, null, false], + ); + expect( + const SendProcessFailure( + SendProcessFailureReason.generic, + message: 'x', + canRetry: true, + ).props, + [SendProcessFailureReason.generic, 'x', true], + ); + expect( + const SendProcessFailure(SendProcessFailureReason.confirmMismatch).props, + [SendProcessFailureReason.confirmMismatch, null, false], + ); + }); + }); +} diff --git a/test/screens/send/cubits/send_process_cubit_test.dart b/test/screens/send/cubits/send_process_cubit_test.dart new file mode 100644 index 000000000..72cf02f0b --- /dev/null +++ b/test/screens/send/cubits/send_process_cubit_test.dart @@ -0,0 +1,886 @@ +import 'dart:async'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/packages/service/app_store.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/api_exception.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/bitbox_exception.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/payment/buy_exceptions.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/payment/transfer_exceptions.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_payment_info_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_transfer_service.dart'; +import 'package:realunit_wallet/packages/wallet/exceptions/signing_cancelled_exception.dart'; +import 'package:realunit_wallet/packages/wallet/wallet.dart'; +import 'package:realunit_wallet/screens/send/cubits/send_process/send_process_cubit.dart'; + +class _MockTransferService extends Mock implements RealUnitTransferService {} + +class _MockAppStore extends Mock implements AppStore {} + +class _MockWallet extends Mock implements AWallet {} + +RealUnitTransferPaymentInfoDto _info({int id = 42}) => RealUnitTransferPaymentInfoDto.fromJson({ + 'id': id, + 'uid': 'RTabc', + 'toAddress': '0xRecipient', + 'amount': 5, + 'tokenAddress': '0xRealu', + 'chainId': 1, + 'eip7702': { + 'relayerAddress': '0xrelay', + 'delegationManagerAddress': '0xmanager', + 'delegatorAddress': '0xdelegator', + 'userNonce': 0, + 'domain': {'name': 'd', 'version': '1', 'chainId': 1, 'verifyingContract': '0xmanager'}, + 'types': { + 'Delegation': >[], + 'Caveat': >[], + }, + 'message': { + 'delegate': '0xrelay', + 'delegator': '0xsender', + 'authority': '0xroot', + 'caveats': >[], + 'salt': 0, + }, + 'tokenAddress': '0xRealu', + 'amountWei': '5', + 'recipient': '0xRecipient', + }, +}); + +void main() { + late _MockTransferService service; + late _MockAppStore appStore; + late _MockWallet wallet; + + setUpAll(() { + registerFallbackValue(const RealUnitTransferDto(toAddress: '0x', amount: 1)); + registerFallbackValue(_info()); + }); + + setUp(() { + service = _MockTransferService(); + appStore = _MockAppStore(); + wallet = _MockWallet(); + when(() => appStore.wallet).thenReturn(wallet); + when(() => wallet.walletType).thenReturn(WalletType.software); + }); + + SendProcessCubit build() => SendProcessCubit( + transferService: service, + appStore: appStore, + recipient: '0xRecipient', + amount: 5, + ); + + /// Stubs `confirmTransfer` with the required named user-confirmed params. + void stubConfirm([Object? answerOrThrow]) { + Future invocation() => service.confirmTransfer( + any(), + confirmedRecipient: any(named: 'confirmedRecipient'), + confirmedAmount: any(named: 'confirmedAmount'), + ); + if (answerOrThrow is Exception) { + when(invocation).thenThrow(answerOrThrow); + } else { + when(invocation).thenAnswer((_) async => (answerOrThrow as String?) ?? '0xdeadbeef'); + } + } + + void verifyConfirmCalled({int times = 1}) { + verify( + () => service.confirmTransfer( + any(), + confirmedRecipient: any(named: 'confirmedRecipient'), + confirmedAmount: any(named: 'confirmedAmount'), + ), + ).called(times); + } + + void wireHappyPath() { + when(() => service.prepareTransfer(any())).thenAnswer((_) async => _info()); + stubConfirm(); + } + + test('debug wallet → signatureUnsupported before any network call', () async { + when(() => wallet.walletType).thenReturn(WalletType.debug); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessFailure; + expect(state.reason, SendProcessFailureReason.signatureUnsupported); + verifyNever(() => service.prepareTransfer(any())); + await cubit.close(); + }); + + test('bitbox wallet → signatureUnsupported before any network call', () async { + when(() => wallet.walletType).thenReturn(WalletType.bitbox); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessFailure; + expect(state.reason, SendProcessFailureReason.signatureUnsupported); + verifyNever(() => service.prepareTransfer(any())); + await cubit.close(); + }); + + test('happy path: prepare → confirm → success with txHash', () async { + wireHappyPath(); + RealUnitTransferDto? sentDto; + when(() => service.prepareTransfer(any())).thenAnswer((invocation) async { + sentDto = invocation.positionalArguments.first as RealUnitTransferDto; + return _info(); + }); + + final cubit = build(); + await cubit.start(); + + expect(sentDto!.toAddress, '0xRecipient'); + expect(sentDto!.amount, 5); + final state = cubit.state as SendProcessSuccess; + expect(state.txHash, '0xdeadbeef'); + verify( + () => service.confirmTransfer( + any(), + confirmedRecipient: '0xRecipient', + confirmedAmount: 5, + ), + ).called(1); + await cubit.close(); + }); + + test('emits Preparing then Signing then Success', () async { + wireHappyPath(); + + final cubit = build(); + final emitted = []; + final sub = cubit.stream.listen(emitted.add); + await cubit.start(); + // Let the final Success microtask flush before cancelling the subscription. + await Future.delayed(Duration.zero); + await sub.cancel(); + + expect(emitted.map((s) => s.runtimeType).toList(), [ + SendProcessPreparing, + SendProcessSigning, + SendProcessSuccess, + ]); + await cubit.close(); + }); + + test('service-reported unsupported signature → signatureUnsupported', () async { + when(() => service.prepareTransfer(any())).thenAnswer((_) async => _info()); + stubConfirm(const TransferSignatureUnsupportedException()); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessFailure; + expect(state.reason, SendProcessFailureReason.signatureUnsupported); + expect(state.canRetry, isFalse); + await cubit.close(); + }); + + test('gas funding unavailable exception → gasFundingUnavailable', () async { + when( + () => service.prepareTransfer(any()), + ).thenThrow(const TransferGasFundingUnavailableException()); + + final cubit = build(); + await cubit.start(); + + expect( + (cubit.state as SendProcessFailure).reason, + SendProcessFailureReason.gasFundingUnavailable, + ); + await cubit.close(); + }); + + test('prepare-phase TransferSignatureUnsupportedException → signatureUnsupported', () async { + when( + () => service.prepareTransfer(any()), + ).thenThrow(const TransferSignatureUnsupportedException()); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessFailure; + expect(state.reason, SendProcessFailureReason.signatureUnsupported); + expect(state.canRetry, isFalse); + verifyNever( + () => service.confirmTransfer( + any(), + confirmedRecipient: any(named: 'confirmedRecipient'), + confirmedAmount: any(named: 'confirmedAmount'), + ), + ); + await cubit.close(); + }); + + test('prepare-phase SigningCancelledException → signatureCancelled', () async { + when(() => service.prepareTransfer(any())).thenThrow(const SigningCancelledException()); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessFailure; + expect(state.reason, SendProcessFailureReason.signatureCancelled); + expect(state.canRetry, isFalse); + verifyNever( + () => service.confirmTransfer( + any(), + confirmedRecipient: any(named: 'confirmedRecipient'), + confirmedAmount: any(named: 'confirmedAmount'), + ), + ); + await cubit.close(); + }); + + test('prepare-phase BitboxNotConnectedException → signatureUnsupported', () async { + when(() => service.prepareTransfer(any())).thenThrow(const BitboxNotConnectedException()); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessFailure; + expect(state.reason, SendProcessFailureReason.signatureUnsupported); + expect(state.canRetry, isFalse); + verifyNever( + () => service.confirmTransfer( + any(), + confirmedRecipient: any(named: 'confirmedRecipient'), + confirmedAmount: any(named: 'confirmedAmount'), + ), + ); + await cubit.close(); + }); + + test('signing cancelled → signatureCancelled', () async { + when(() => service.prepareTransfer(any())).thenAnswer((_) async => _info()); + stubConfirm(const SigningCancelledException()); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessFailure; + expect(state.reason, SendProcessFailureReason.signatureCancelled); + expect(state.canRetry, isFalse); + await cubit.close(); + }); + + test('bitbox not connected (defensive) → signatureUnsupported', () async { + when(() => service.prepareTransfer(any())).thenAnswer((_) async => _info()); + stubConfirm(const BitboxNotConnectedException()); + + final cubit = build(); + await cubit.start(); + + expect( + (cubit.state as SendProcessFailure).reason, + SendProcessFailureReason.signatureUnsupported, + ); + await cubit.close(); + }); + + test('API 400 (invalid recipient / insufficient REALU) → invalidRequest', () async { + when(() => service.prepareTransfer(any())).thenThrow( + const ApiException(statusCode: 400, code: 'X', message: 'Invalid recipient address'), + ); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessFailure; + expect(state.reason, SendProcessFailureReason.invalidRequest); + expect(state.message, 'Invalid recipient address'); + expect(state.canRetry, isFalse); + await cubit.close(); + }); + + test('API 404 → invalidRequest', () async { + when( + () => service.prepareTransfer(any()), + ).thenThrow(const ApiException(statusCode: 404, code: 'X', message: 'not found')); + + final cubit = build(); + await cubit.start(); + + expect((cubit.state as SendProcessFailure).reason, SendProcessFailureReason.invalidRequest); + await cubit.close(); + }); + + test('API 503 → gasFundingUnavailable', () async { + when( + () => service.prepareTransfer(any()), + ).thenThrow(const ApiException(statusCode: 503, code: 'X', message: 'unavailable')); + + final cubit = build(); + await cubit.start(); + + expect( + (cubit.state as SendProcessFailure).reason, + SendProcessFailureReason.gasFundingUnavailable, + ); + await cubit.close(); + }); + + test('API 500 → generic', () async { + when( + () => service.prepareTransfer(any()), + ).thenThrow(const ApiException(statusCode: 500, code: 'X', message: 'boom')); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessFailure; + expect(state.reason, SendProcessFailureReason.generic); + // Prepare-phase failures are never retryable (no transfer id stored). + expect(state.canRetry, isFalse); + await cubit.close(); + }); + + test('RegistrationRequiredException → registrationOrKycRequired with message', () async { + when(() => service.prepareTransfer(any())).thenThrow( + const RegistrationRequiredException( + code: 'REGISTRATION_REQUIRED', + message: 'Please register first', + ), + ); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessFailure; + expect(state.reason, SendProcessFailureReason.registrationOrKycRequired); + expect(state.message, 'Please register first'); + await cubit.close(); + }); + + test('KycLevelRequiredException → registrationOrKycRequired with message', () async { + when(() => service.prepareTransfer(any())).thenThrow( + const KycLevelRequiredException( + code: 'KYC_LEVEL_REQUIRED', + message: 'KYC level 2 required', + requiredLevel: 2, + currentLevel: 1, + ), + ); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessFailure; + expect(state.reason, SendProcessFailureReason.registrationOrKycRequired); + expect(state.message, 'KYC level 2 required'); + await cubit.close(); + }); + + test('API 403 → registrationOrKycRequired', () async { + when(() => service.prepareTransfer(any())).thenThrow( + const ApiException(statusCode: 403, code: 'X', message: 'forbidden'), + ); + + final cubit = build(); + await cubit.start(); + + expect( + (cubit.state as SendProcessFailure).reason, + SendProcessFailureReason.registrationOrKycRequired, + ); + await cubit.close(); + }); + + test('closing the cubit before prepareTransfer resolves does not throw and does not emit', () async { + final completer = Completer(); + when(() => service.prepareTransfer(any())).thenAnswer((_) => completer.future); + + final cubit = build(); + final future = cubit.start(); + await cubit.close(); + completer.complete(_info()); + await future; // must not throw StateError, and must not attempt to emit after close + }); + + test('closing the cubit before prepareTransfer rejects does not throw and does not emit', () async { + final completer = Completer(); + when(() => service.prepareTransfer(any())).thenAnswer((_) => completer.future); + + final cubit = build(); + final emitted = []; + final sub = cubit.stream.listen(emitted.add); + final future = cubit.start(); + await cubit.close(); + completer.completeError(Exception('boom')); + await future; // must not throw StateError, and must not attempt to emit after close + await Future.delayed(Duration.zero); + await sub.cancel(); + + expect(emitted.map((s) => s.runtimeType).toList(), [SendProcessPreparing]); + }); + + test('closing the cubit before confirmTransfer resolves does not throw and does not emit', () async { + when(() => service.prepareTransfer(any())).thenAnswer((_) async => _info()); + final completer = Completer(); + when( + () => service.confirmTransfer( + any(), + confirmedRecipient: any(named: 'confirmedRecipient'), + confirmedAmount: any(named: 'confirmedAmount'), + ), + ).thenAnswer((_) => completer.future); + + final cubit = build(); + final future = cubit.start(); + await cubit.close(); + completer.complete('0xdeadbeef'); + await future; // must not throw StateError, and must not attempt to emit after close + }); + + test( + 'closing the cubit while confirmTransfer is in flight → does not emit regardless of outcome', + () async { + when(() => service.prepareTransfer(any())).thenAnswer((_) async => _info()); + final completer = Completer(); + when( + () => service.confirmTransfer( + any(), + confirmedRecipient: any(named: 'confirmedRecipient'), + confirmedAmount: any(named: 'confirmedAmount'), + ), + ).thenAnswer((_) => completer.future); + + final cubit = build(); + final emitted = []; + final sub = cubit.stream.listen(emitted.add); + final future = cubit.start(); + // Let prepare resolve and start() reach the in-flight confirm await + // (SendProcessSigning) before closing. + await Future.delayed(Duration.zero); + await Future.delayed(Duration.zero); + expect(cubit.state, isA()); + + final countAtClose = emitted.length; + await cubit.close(); + // Representative reject path — centralized isClosed guard must suppress + // emit for every catch branch, not only success. + completer.completeError(Exception('socket hung up')); + await future; // must not throw StateError + // Let any residual microtask that might have emitted flush. + await Future.delayed(Duration.zero); + await sub.cancel(); + + // No state after close (Signing was already emitted before close). + expect(emitted.length, countAtClose); + expect(emitted.map((s) => s.runtimeType).toList(), [ + SendProcessPreparing, + SendProcessSigning, + ]); + }, + ); + + test( + 'closing the cubit while confirmTransfer is in flight, then it resolves successfully → does not emit but logs the missed success', + () async { + when(() => service.prepareTransfer(any())).thenAnswer((_) async => _info()); + final completer = Completer(); + when( + () => service.confirmTransfer( + any(), + confirmedRecipient: any(named: 'confirmedRecipient'), + confirmedAmount: any(named: 'confirmedAmount'), + ), + ).thenAnswer((_) => completer.future); + + final cubit = build(); + final emitted = []; + final sub = cubit.stream.listen(emitted.add); + final future = cubit.start(); + // Let prepare resolve and start() reach the in-flight confirm await + // (SendProcessSigning) before closing. + await Future.delayed(Duration.zero); + await Future.delayed(Duration.zero); + expect(cubit.state, isA()); + + final countAtClose = emitted.length; + await cubit.close(); + // Direct success path after close — isClosed guard must suppress emit + // and take the developer.log branch for the missed txHash. + completer.complete('0xdeadbeef'); + await future; // must not throw StateError + // Let any residual microtask that might have emitted flush. + await Future.delayed(Duration.zero); + await sub.cancel(); + + // No state after close (Signing was already emitted before close). + expect(emitted.length, countAtClose); + expect(emitted.map((s) => s.runtimeType).toList(), [ + SendProcessPreparing, + SendProcessSigning, + ]); + }, + ); + + test('unexpected error on prepare → generic (non-retryable)', () async { + when(() => service.prepareTransfer(any())).thenThrow(Exception('weird')); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessFailure; + expect(state.reason, SendProcessFailureReason.generic); + expect(state.canRetry, isFalse); + await cubit.close(); + }); + + test( + 'unclassified/transport error on confirm → retryable failure; prepare called once', + () async { + when(() => service.prepareTransfer(any())).thenAnswer((_) async => _info()); + stubConfirm(Exception('socket hung up')); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessFailure; + expect(state.reason, SendProcessFailureReason.generic); + expect(state.canRetry, isTrue); + verify(() => service.prepareTransfer(any())).called(1); + verifyConfirmCalled(); + await cubit.close(); + }, + ); + + test( + 'retryConfirm re-invokes confirmTransfer with the same stored info/id (no new prepare)', + () async { + final prepared = _info(id: 42); + when(() => service.prepareTransfer(any())).thenAnswer((_) async => prepared); + + var confirmCalls = 0; + RealUnitTransferPaymentInfoDto? confirmedInfo; + when( + () => service.confirmTransfer( + any(), + confirmedRecipient: any(named: 'confirmedRecipient'), + confirmedAmount: any(named: 'confirmedAmount'), + ), + ).thenAnswer((invocation) async { + confirmCalls++; + confirmedInfo = invocation.positionalArguments.first as RealUnitTransferPaymentInfoDto; + if (confirmCalls == 1) { + throw Exception('transport lost'); + } + return '0xretryhash'; + }); + + final cubit = build(); + await cubit.start(); + + expect((cubit.state as SendProcessFailure).canRetry, isTrue); + + await cubit.retryConfirm(); + + expect(cubit.state, isA()); + expect((cubit.state as SendProcessSuccess).txHash, '0xretryhash'); + expect(confirmCalls, 2); + expect(confirmedInfo!.id, 42); + // prepareTransfer must never have been called again on retry. + verify(() => service.prepareTransfer(any())).called(1); + await cubit.close(); + }, + ); + + test('409 already confirmed on start() → SendProcessSuccess', () async { + when(() => service.prepareTransfer(any())).thenAnswer((_) async => _info()); + stubConfirm( + const TransferAlreadyConfirmedException( + statusCode: 409, + code: 'CONFLICT', + message: 'Transaction request is already confirmed', + txHash: '0xalready', + ), + ); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessSuccess; + expect(state.txHash, '0xalready'); + await cubit.close(); + }); + + test('409 already confirmed on retryConfirm() → SendProcessSuccess', () async { + when(() => service.prepareTransfer(any())).thenAnswer((_) async => _info()); + + var confirmCalls = 0; + when( + () => service.confirmTransfer( + any(), + confirmedRecipient: any(named: 'confirmedRecipient'), + confirmedAmount: any(named: 'confirmedAmount'), + ), + ).thenAnswer((_) async { + confirmCalls++; + if (confirmCalls == 1) { + throw Exception('timeout after server accepted'); + } + throw const TransferAlreadyConfirmedException( + statusCode: 409, + code: 'CONFLICT', + message: 'Transaction request is already confirmed', + ); + }); + + final cubit = build(); + await cubit.start(); + expect((cubit.state as SendProcessFailure).canRetry, isTrue); + + await cubit.retryConfirm(); + + // No txHash on the 409 body → empty string (success sheet does not show it). + expect(cubit.state, isA()); + expect((cubit.state as SendProcessSuccess).txHash, ''); + await cubit.close(); + }); + + test('retryConfirm with nothing prepared → throws StateError', () async { + final cubit = build(); + + expect(() => cubit.retryConfirm(), throwsA(isA())); + await cubit.close(); + }); + + test('retryConfirm after a non-retryable failure → throws StateError', () async { + when(() => service.prepareTransfer(any())).thenAnswer((_) async => _info()); + stubConfirm(const TransferSignatureUnsupportedException()); + + final cubit = build(); + await cubit.start(); + + expect((cubit.state as SendProcessFailure).canRetry, isFalse); + expect(() => cubit.retryConfirm(), throwsA(isA())); + await cubit.close(); + }); + + test('retryConfirm while a confirm is already in flight → throws StateError', () async { + when(() => service.prepareTransfer(any())).thenAnswer((_) async => _info()); + final completer = Completer(); + when( + () => service.confirmTransfer( + any(), + confirmedRecipient: any(named: 'confirmedRecipient'), + confirmedAmount: any(named: 'confirmedAmount'), + ), + ).thenAnswer((_) => completer.future); + + final cubit = build(); + final startFuture = cubit.start(); + // Let prepare resolve and start() reach the in-flight confirm await (SendProcessSigning). + await Future.delayed(Duration.zero); + await Future.delayed(Duration.zero); + + expect(cubit.state, isA()); + expect(() => cubit.retryConfirm(), throwsA(isA())); + + completer.complete('0xdone'); + await startFuture; + await cubit.close(); + }); + + test( + 'concurrent start while confirm is already in flight → throws StateError', + () async { + when(() => service.prepareTransfer(any())).thenAnswer((_) async => _info()); + final completer = Completer(); + when( + () => service.confirmTransfer( + any(), + confirmedRecipient: any(named: 'confirmedRecipient'), + confirmedAmount: any(named: 'confirmedAmount'), + ), + ).thenAnswer((_) => completer.future); + + final cubit = build(); + final firstStart = cubit.start(); + await Future.delayed(Duration.zero); + await Future.delayed(Duration.zero); + expect(cubit.state, isA()); + + // Second start re-prepares then hits _confirmPrepared's in-flight guard. + await expectLater(cubit.start(), throwsA(isA())); + + completer.complete('0xdone'); + await firstStart; + await cubit.close(); + }, + ); + + test( + 'TransferConfirmMismatchException → confirmMismatch, non-retryable', + () async { + when(() => service.prepareTransfer(any())).thenAnswer((_) async => _info()); + stubConfirm(const TransferConfirmMismatchException('toAddress mismatch')); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessFailure; + expect(state.reason, SendProcessFailureReason.confirmMismatch); + expect(state.canRetry, isFalse); + expect(() => cubit.retryConfirm(), throwsA(isA())); + await cubit.close(); + }, + ); + + test( + 'confirm-phase TransferGasFundingUnavailableException → gasFundingUnavailable', + () async { + when(() => service.prepareTransfer(any())).thenAnswer((_) async => _info()); + stubConfirm(const TransferGasFundingUnavailableException()); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessFailure; + expect(state.reason, SendProcessFailureReason.gasFundingUnavailable); + expect(state.canRetry, isFalse); + await cubit.close(); + }, + ); + + test( + 'confirm-phase RegistrationRequiredException → registrationOrKycRequired', + () async { + when(() => service.prepareTransfer(any())).thenAnswer((_) async => _info()); + stubConfirm( + const RegistrationRequiredException( + code: 'REGISTRATION_REQUIRED', + message: 'Please register first', + ), + ); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessFailure; + expect(state.reason, SendProcessFailureReason.registrationOrKycRequired); + expect(state.message, 'Please register first'); + expect(state.canRetry, isFalse); + await cubit.close(); + }, + ); + + test( + 'confirm-phase KycLevelRequiredException → registrationOrKycRequired', + () async { + when(() => service.prepareTransfer(any())).thenAnswer((_) async => _info()); + stubConfirm( + const KycLevelRequiredException( + code: 'KYC_LEVEL_REQUIRED', + message: 'KYC level 2 required', + requiredLevel: 2, + currentLevel: 1, + ), + ); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessFailure; + expect(state.reason, SendProcessFailureReason.registrationOrKycRequired); + expect(state.message, 'KYC level 2 required'); + expect(state.canRetry, isFalse); + await cubit.close(); + }, + ); + + test( + 'confirm-phase API 500 → generic (retryable via _isDefinitiveApiFailure)', + () async { + when(() => service.prepareTransfer(any())).thenAnswer((_) async => _info()); + stubConfirm(const ApiException(statusCode: 500, code: 'X', message: 'boom')); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessFailure; + expect(state.reason, SendProcessFailureReason.generic); + expect(state.message, 'boom'); + expect(state.canRetry, isTrue); + await cubit.close(); + }, + ); + + test( + 'confirm-phase API 400 → invalidRequest (non-retryable definitive failure)', + () async { + when(() => service.prepareTransfer(any())).thenAnswer((_) async => _info()); + stubConfirm( + const ApiException(statusCode: 400, code: 'X', message: 'bad request'), + ); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessFailure; + expect(state.reason, SendProcessFailureReason.invalidRequest); + expect(state.message, 'bad request'); + expect(state.canRetry, isFalse); + await cubit.close(); + }, + ); + + test( + 'confirm-phase API 403 → registrationOrKycRequired (non-retryable)', + () async { + when(() => service.prepareTransfer(any())).thenAnswer((_) async => _info()); + stubConfirm(const ApiException(statusCode: 403, code: 'X', message: 'forbidden')); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessFailure; + expect(state.reason, SendProcessFailureReason.registrationOrKycRequired); + expect(state.canRetry, isFalse); + await cubit.close(); + }, + ); + + test( + 'confirm-phase API 404 → invalidRequest (non-retryable)', + () async { + when(() => service.prepareTransfer(any())).thenAnswer((_) async => _info()); + stubConfirm(const ApiException(statusCode: 404, code: 'X', message: 'not found')); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessFailure; + expect(state.reason, SendProcessFailureReason.invalidRequest); + expect(state.canRetry, isFalse); + await cubit.close(); + }, + ); + + test( + 'confirm-phase API 503 → gasFundingUnavailable (non-retryable)', + () async { + when(() => service.prepareTransfer(any())).thenAnswer((_) async => _info()); + stubConfirm( + const ApiException(statusCode: 503, code: 'X', message: 'unavailable'), + ); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as SendProcessFailure; + expect(state.reason, SendProcessFailureReason.gasFundingUnavailable); + expect(state.canRetry, isFalse); + await cubit.close(); + }, + ); +} diff --git a/test/screens/send/cubits/send_recipient_cubit_test.dart b/test/screens/send/cubits/send_recipient_cubit_test.dart new file mode 100644 index 000000000..8bd3ae82d --- /dev/null +++ b/test/screens/send/cubits/send_recipient_cubit_test.dart @@ -0,0 +1,144 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:realunit_wallet/screens/send/cubits/send_recipient/send_recipient_cubit.dart'; + +void main() { + // A valid EVM address and its EIP-55 checksum form. + const lowercase = '0x9f5713deacb8e9cab6c2d3fae1afc2715f8d2d71'; + const checksummed = '0x9F5713DEacB8e9CAB6c2d3FaE1AFc2715F8D2D71'; + + group('SendRecipientCubit', () { + test('starts empty', () { + expect(SendRecipientCubit().state, isA()); + }); + + blocTest( + 'a valid address is normalized to its EIP-55 checksum', + build: SendRecipientCubit.new, + act: (cubit) => cubit.submit(lowercase), + verify: (cubit) { + final state = cubit.state as SendRecipientValid; + expect(state.address, checksummed); + }, + ); + + blocTest( + 'trims surrounding whitespace before validating', + build: SendRecipientCubit.new, + act: (cubit) => cubit.submit(' $checksummed '), + expect: () => [const SendRecipientValid(checksummed)], + ); + + blocTest( + 'strips an ethereum: EIP-681 scheme and @chainId / query suffix', + build: SendRecipientCubit.new, + act: (cubit) => cubit.submit('ethereum:$lowercase@1?value=0'), + expect: () => [const SendRecipientValid(checksummed)], + ); + + blocTest( + 'strips ethereum: pay- EIP-681 payment-request marker before the address', + build: SendRecipientCubit.new, + act: (cubit) => cubit.submit('ethereum:pay-$lowercase@1?value=100'), + expect: () => [const SendRecipientValid(checksummed)], + ); + + blocTest( + 'strips a query suffix when no @chainId is present (simple form)', + build: SendRecipientCubit.new, + act: (cubit) => cubit.submit('ethereum:$lowercase?value=0'), + expect: () => [const SendRecipientValid(checksummed)], + ); + + blocTest( + 'EIP-681 ERC-20 transfer URI extracts recipient from address= query param', + build: SendRecipientCubit.new, + act: (cubit) => cubit.submit( + 'ethereum:0x1111111111111111111111111111111111111111/transfer' + '?address=$lowercase&uint256=1000000000000000000', + ), + expect: () => [const SendRecipientValid(checksummed)], + ); + + blocTest( + 'EIP-681 function-call with unrecognized function name fails closed', + build: SendRecipientCubit.new, + act: (cubit) => cubit.submit('ethereum:$lowercase/approve?spender=0xSpender&uint256=1'), + expect: () => [isA()], + ); + + blocTest( + 'EIP-681 function-call without a query string fails closed', + build: SendRecipientCubit.new, + act: (cubit) => cubit.submit('ethereum:$lowercase/transfer'), + expect: () => [isA()], + ); + + blocTest( + 'EIP-681 /transfer URI without address query param fails closed', + build: SendRecipientCubit.new, + act: (cubit) => cubit.submit('ethereum:$lowercase/transfer?uint256=1'), + expect: () => [isA()], + ); + + blocTest( + 'EIP-681 /transfer URI with empty address= query param fails closed', + build: SendRecipientCubit.new, + act: (cubit) => cubit.submit('ethereum:$lowercase/transfer?address=&uint256=1'), + expect: () => [isA()], + ); + + blocTest( + 'EIP-681 /transfer URI with duplicate address= query keys fails closed', + build: SendRecipientCubit.new, + act: (cubit) => cubit.submit( + 'ethereum:0x1111111111111111111111111111111111111111/transfer' + '?address=$lowercase&address=0x2222222222222222222222222222222222222222', + ), + expect: () => [isA()], + ); + + blocTest( + 'EIP-681 /transfer URI with malformed percent-encoding emits invalid (no throw)', + build: SendRecipientCubit.new, + act: (cubit) => cubit.submit( + 'ethereum:0x1111111111111111111111111111111111111111/transfer?address=%ZZ', + ), + expect: () => [isA()], + ); + + blocTest( + 'an invalid address emits SendRecipientInvalid', + build: SendRecipientCubit.new, + act: (cubit) => cubit.submit('not-an-address'), + expect: () => [isA()], + ); + + blocTest( + 'onCodeDetected decodes a scanned address like submit', + build: SendRecipientCubit.new, + act: (cubit) => cubit.onCodeDetected(checksummed), + expect: () => [const SendRecipientValid(checksummed)], + ); + + blocTest( + 'onCodeDetected ignores further detections once valid (no re-emit)', + build: SendRecipientCubit.new, + act: (cubit) { + cubit.onCodeDetected(checksummed); + cubit.onCodeDetected(checksummed); + }, + expect: () => [const SendRecipientValid(checksummed)], + ); + + blocTest( + 'reset returns to empty', + build: SendRecipientCubit.new, + act: (cubit) { + cubit.submit(checksummed); + cubit.reset(); + }, + expect: () => [const SendRecipientValid(checksummed), const SendRecipientEmpty()], + ); + }); +} diff --git a/test/screens/send/send_amount_page_test.dart b/test/screens/send/send_amount_page_test.dart new file mode 100644 index 000000000..360eeb3ac --- /dev/null +++ b/test/screens/send/send_amount_page_test.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/models/balance.dart'; +import 'package:realunit_wallet/packages/config/api_config.dart'; +import 'package:realunit_wallet/packages/repository/balance_repository.dart'; +import 'package:realunit_wallet/packages/service/app_store.dart'; +import 'package:realunit_wallet/packages/utils/default_assets.dart'; +import 'package:realunit_wallet/screens/send/send_amount_page.dart'; +import 'package:realunit_wallet/screens/send/send_confirm_page.dart'; + +import '../../helper/helper.dart'; + +class _MockBalanceRepository extends Mock implements BalanceRepository {} + +class _MockAppStore extends Mock implements AppStore {} + +class _MockApiConfig extends Mock implements ApiConfig {} + +void main() { + Balance balanceOf(BigInt value) => Balance( + chainId: realUnitAsset.chainId, + contractAddress: realUnitAsset.address, + walletAddress: '0xwallet', + balance: value, + asset: realUnitAsset, + ); + + late _MockBalanceRepository balanceRepo; + + setUpAll(() { + registerFallbackValue( + Balance( + chainId: 1, + contractAddress: '0x', + walletAddress: '0x', + balance: BigInt.zero, + asset: realUnitAsset, + ), + ); + }); + + void registerGraph(BigInt available) { + final getIt = GetIt.instance; + balanceRepo = _MockBalanceRepository(); + when(() => balanceRepo.watchBalance(any())).thenAnswer( + (_) => Stream.value(balanceOf(available)), + ); + getIt.registerFactory(() => balanceRepo); + final appStore = _MockAppStore(); + final apiConfig = _MockApiConfig(); + when(() => apiConfig.asset).thenReturn(realUnitAsset); + when(() => appStore.apiConfig).thenReturn(apiConfig); + when(() => appStore.primaryAddress).thenReturn('0xwallet'); + getIt.registerSingleton(appStore); + } + + tearDown(() async => GetIt.instance.reset()); + + group('$SendAmountPage', () { + testWidgets('shows the available balance and gates the continue button', (tester) async { + registerGraph(BigInt.from(42)); + await tester.pumpApp(const SendAmountPage(recipient: '0xRecipient')); + await tester.pumpAndSettle(); + + expect(find.text(S.current.sendAmountAvailable('42')), findsOne); + + // Continue is disabled until a valid amount is entered. + final disabled = tester.widget(find.byType(FilledButton)); + expect(disabled.onPressed, isNull); + }); + + testWidgets('an over-balance amount surfaces the insufficient error', (tester) async { + registerGraph(BigInt.from(5)); + await tester.pumpApp(const SendAmountPage(recipient: '0xRecipient')); + await tester.pumpAndSettle(); + + await tester.enterText(find.byType(TextField), '6'); + await tester.pump(); + + expect(find.text(S.current.sendAmountInsufficient), findsOne); + final stillDisabled = tester.widget(find.byType(FilledButton)); + expect(stillDisabled.onPressed, isNull); + }); + + testWidgets('a valid amount enables continue and navigates to confirm', (tester) async { + registerGraph(BigInt.from(42)); + await tester.pumpApp(const SendAmountPage(recipient: '0xRecipient')); + await tester.pumpAndSettle(); + + await tester.enterText(find.byType(TextField), '5'); + await tester.pumpAndSettle(); + + // The continue button is now enabled. + final enabled = tester.widget(find.byType(FilledButton)); + expect(enabled.onPressed, isNotNull); + + await tester.tap(find.widgetWithText(FilledButton, S.current.next)); + await tester.pumpAndSettle(); + + expect(find.byType(SendConfirmPage), findsOne); + }); + + testWidgets('MAX fills the available balance', (tester) async { + registerGraph(BigInt.from(42)); + await tester.pumpApp(const SendAmountPage(recipient: '0xRecipient')); + await tester.pumpAndSettle(); + + await tester.tap(find.text(S.current.max.toUpperCase())); + await tester.pumpAndSettle(); + + // MAX fills the field with the available balance and the amount validates. + expect(find.text('42'), findsOneWidget); + final enabled = tester.widget(find.byType(FilledButton)); + expect(enabled.onPressed, isNotNull); + }); + }); +} diff --git a/test/screens/send/send_amount_responsive_matrix_test.dart b/test/screens/send/send_amount_responsive_matrix_test.dart new file mode 100644 index 000000000..79fc30835 --- /dev/null +++ b/test/screens/send/send_amount_responsive_matrix_test.dart @@ -0,0 +1,93 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/models/balance.dart'; +import 'package:realunit_wallet/packages/utils/default_assets.dart'; +import 'package:realunit_wallet/screens/sell/cubits/sell_balance/sell_balance_cubit.dart'; +import 'package:realunit_wallet/screens/send/cubits/send_amount/send_amount_cubit.dart'; +import 'package:realunit_wallet/screens/send/send_amount_page.dart'; +import 'package:realunit_wallet/styles/themes.dart'; + +import '../../helper/helper.dart'; + +class _MockSellBalanceCubit extends MockCubit implements SellBalanceCubit {} + +Future _pumpScreen( + WidgetTester tester, + MatrixCell cell, + Widget child, +) async { + await tester.binding.setSurfaceSize(cell.mediaQuery.size); + addTearDown(() async => tester.binding.setSurfaceSize(null)); + await tester.pumpWidget( + MediaQuery( + data: cell.mediaQuery, + child: MaterialApp( + theme: realUnitTheme, + locale: const Locale('de'), + localizationsDelegates: const [ + S.delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + supportedLocales: S.delegate.supportedLocales, + home: child, + ), + ), + ); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 50)); +} + +void main() { + group('SendAmountView responsive matrix (full device × textScale)', () { + for (final cell in kFullResponsiveMatrix) { + testWidgets(cell.id, (tester) async { + await withTargetPlatform(cell.device.platform, () async { + final balance = Balance( + chainId: realUnitAsset.chainId, + contractAddress: realUnitAsset.address, + walletAddress: '0xwallet', + balance: BigInt.from(42), + asset: realUnitAsset, + ); + final balanceCubit = _MockSellBalanceCubit(); + when(() => balanceCubit.state).thenReturn(balance); + whenListen( + balanceCubit, + const Stream.empty(), + initialState: balance, + ); + final amountCubit = SendAmountCubit(availableShares: balance.balance)..amountChanged('5'); + addTearDown(amountCubit.close); + + final subject = MultiBlocProvider( + providers: [ + BlocProvider.value(value: balanceCubit), + BlocProvider.value(value: amountCubit), + ], + child: const SendAmountView(recipient: '0xRecipient'), + ); + + await expectNoLayoutOverflow( + tester, + () => _pumpScreen(tester, cell, subject), + reason: 'SendAmountView overflow / ${cell.label}', + ); + + await expectFullyTappable( + tester, + find.widgetWithText(FilledButton, S.current.next), + within: find.byType(SendAmountView), + reason: 'SendAmountView / ${cell.label}: Next CTA not tappable', + ); + }); + }); + } + }); +} diff --git a/test/screens/send/send_confirm_page_test.dart b/test/screens/send/send_confirm_page_test.dart new file mode 100644 index 000000000..4f37bb555 --- /dev/null +++ b/test/screens/send/send_confirm_page_test.dart @@ -0,0 +1,85 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/packages/config/api_config.dart'; +import 'package:realunit_wallet/packages/service/app_store.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_transfer_service.dart'; +import 'package:realunit_wallet/packages/utils/default_assets.dart'; +import 'package:realunit_wallet/packages/wallet/wallet.dart'; +import 'package:realunit_wallet/screens/send/send_confirm_page.dart'; +import 'package:realunit_wallet/screens/send/send_process_page.dart'; + +import '../../helper/helper.dart'; + +class _MockTransferService extends Mock implements RealUnitTransferService {} + +class _MockAppStore extends Mock implements AppStore {} + +class _MockApiConfig extends Mock implements ApiConfig {} + +class _MockWallet extends Mock implements SoftwareWallet {} + +void main() { + setUpAll(() { + // The confirm button pushes SendProcessPage, which builds a cubit off getIt + // and calls start(). A debug wallet makes start() settle immediately without + // any network call. + final getIt = GetIt.instance; + getIt.registerSingleton(_MockTransferService()); + final appStore = _MockAppStore(); + final apiConfig = _MockApiConfig(); + when(() => apiConfig.asset).thenReturn(realUnitAsset); + final wallet = _MockWallet(); + when(() => wallet.walletType).thenReturn(WalletType.debug); + when(() => appStore.wallet).thenReturn(wallet); + when(() => appStore.apiConfig).thenReturn(apiConfig); + getIt.registerSingleton(appStore); + }); + + tearDownAll(() async => GetIt.instance.reset()); + + group('$SendConfirmPage', () { + testWidgets('renders the recipient + amount summary', (tester) async { + await tester.pumpApp( + const SendConfirmPage( + recipient: '0x9F5713dEAcb8e9CaB6c2D3FaE1aFc2715F8D2D71', + amount: 5, + ), + ); + + expect(find.text(S.current.sendConfirmTitle), findsOne); + expect(find.text(S.current.sendConfirmSummary('5')), findsOne); + expect(find.text('0x9F5713dEAcb8e9CaB6c2D3FaE1aFc2715F8D2D71'), findsOne); + expect(find.text(S.current.sendShares('5')), findsOne); + }); + + testWidgets('confirming starts the process step', (tester) async { + await tester.pumpApp( + const SendConfirmPage(recipient: '0xRecipient', amount: 5), + ); + + await tester.tap(find.text(S.current.sendConfirmButton)); + // Let the page-route transition advance (the process page never settles — + // it keeps a CupertinoActivityIndicator animating — so pump fixed frames). + await tester.pump(); + await tester.pump(const Duration(milliseconds: 400)); + + expect(find.byType(SendProcessView), findsOne); + }); + + testWidgets('double-tap before rebuild pushes only one process page', (tester) async { + await tester.pumpApp( + const SendConfirmPage(recipient: '0xRecipient', amount: 5), + ); + + // Two tap-up callbacks before any rebuild (WidgetTester.tap does not pump). + await tester.tap(find.text(S.current.sendConfirmButton)); + await tester.tap(find.text(S.current.sendConfirmButton)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 400)); + + expect(find.byType(SendProcessView), findsOneWidget); + }); + }); +} diff --git a/test/screens/send/send_confirm_responsive_matrix_test.dart b/test/screens/send/send_confirm_responsive_matrix_test.dart new file mode 100644 index 000000000..0bf4bae44 --- /dev/null +++ b/test/screens/send/send_confirm_responsive_matrix_test.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/packages/config/api_config.dart'; +import 'package:realunit_wallet/packages/service/app_store.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_transfer_service.dart'; +import 'package:realunit_wallet/packages/utils/default_assets.dart'; +import 'package:realunit_wallet/packages/wallet/wallet.dart'; +import 'package:realunit_wallet/screens/send/send_confirm_page.dart'; +import 'package:realunit_wallet/styles/themes.dart'; + +import '../../helper/helper.dart'; + +class _MockTransferService extends Mock implements RealUnitTransferService {} + +class _MockAppStore extends Mock implements AppStore {} + +class _MockApiConfig extends Mock implements ApiConfig {} + +class _MockWallet extends Mock implements SoftwareWallet {} + +Future _pumpScreen( + WidgetTester tester, + MatrixCell cell, + Widget child, +) async { + await tester.binding.setSurfaceSize(cell.mediaQuery.size); + addTearDown(() async => tester.binding.setSurfaceSize(null)); + await tester.pumpWidget( + MediaQuery( + data: cell.mediaQuery, + child: MaterialApp( + theme: realUnitTheme, + locale: const Locale('de'), + localizationsDelegates: const [ + S.delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + supportedLocales: S.delegate.supportedLocales, + home: child, + ), + ), + ); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 50)); +} + +void main() { + setUpAll(() { + final getIt = GetIt.instance; + getIt.registerSingleton(_MockTransferService()); + final appStore = _MockAppStore(); + final apiConfig = _MockApiConfig(); + final wallet = _MockWallet(); + when(() => apiConfig.asset).thenReturn(realUnitAsset); + when(() => wallet.walletType).thenReturn(WalletType.debug); + when(() => appStore.wallet).thenReturn(wallet); + when(() => appStore.apiConfig).thenReturn(apiConfig); + getIt.registerSingleton(appStore); + }); + + tearDownAll(() async => GetIt.instance.reset()); + + group('SendConfirmPage responsive matrix (full device × textScale)', () { + for (final cell in kFullResponsiveMatrix) { + testWidgets(cell.id, (tester) async { + await withTargetPlatform(cell.device.platform, () async { + const subject = SendConfirmPage( + recipient: '0x9F5713dEAcb8e9CaB6c2D3FaE1aFc2715F8D2D71', + amount: 123456, + ); + + await expectNoLayoutOverflow( + tester, + () => _pumpScreen(tester, cell, subject), + reason: 'SendConfirmPage overflow / ${cell.label}', + ); + + await expectFullyTappable( + tester, + find.widgetWithText(FilledButton, S.current.sendConfirmButton), + within: find.byType(SendConfirmPage), + reason: 'SendConfirmPage / ${cell.label}: Confirm CTA not tappable', + ); + }); + }); + } + }); +} diff --git a/test/screens/send/send_process_page_test.dart b/test/screens/send/send_process_page_test.dart new file mode 100644 index 000000000..dd45b5fc3 --- /dev/null +++ b/test/screens/send/send_process_page_test.dart @@ -0,0 +1,308 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/packages/config/api_config.dart'; +import 'package:realunit_wallet/packages/service/app_store.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_transfer_service.dart'; +import 'package:realunit_wallet/packages/utils/default_assets.dart'; +import 'package:realunit_wallet/packages/wallet/wallet.dart'; +import 'package:realunit_wallet/screens/send/cubits/send_process/send_process_cubit.dart'; +import 'package:realunit_wallet/screens/send/send_process_page.dart'; + +import '../../helper/helper.dart'; + +class _MockSendProcessCubit extends MockCubit implements SendProcessCubit {} + +class _MockTransferService extends Mock implements RealUnitTransferService {} + +class _MockAppStore extends Mock implements AppStore {} + +class _MockApiConfig extends Mock implements ApiConfig {} + +class _MockWallet extends Mock implements SoftwareWallet {} + +void main() { + late _MockSendProcessCubit processCubit; + + setUpAll(() { + final getIt = GetIt.instance; + // SendProcessPage resolves the service + AppStore from getIt and calls + // start(). A debug wallet makes start() settle immediately + // (signatureUnsupported) without touching the network. + getIt.registerSingleton(_MockTransferService()); + final appStore = _MockAppStore(); + final apiConfig = _MockApiConfig(); + when(() => apiConfig.asset).thenReturn(realUnitAsset); + final wallet = _MockWallet(); + when(() => wallet.walletType).thenReturn(WalletType.debug); + when(() => appStore.wallet).thenReturn(wallet); + when(() => appStore.apiConfig).thenReturn(apiConfig); + getIt.registerSingleton(appStore); + }); + + tearDownAll(() async => GetIt.instance.reset()); + + setUp(() { + processCubit = _MockSendProcessCubit(); + when(() => processCubit.state).thenReturn(const SendProcessInitial()); + }); + + Widget buildSubject() => BlocProvider.value( + value: processCubit, + child: const SendProcessView(), + ); + + group('$SendProcessPage', () { + testWidgets('builds its own cubit and renders $SendProcessView', (tester) async { + await tester.pumpApp(const SendProcessPage(recipient: '0xRecipient', amount: 5)); + await tester.pump(); + + expect(find.byType(SendProcessView), findsOne); + }); + }); + + group('$SendProcessView progress labels', () { + Future expectLabel(WidgetTester tester, SendProcessState state, String label) async { + when(() => processCubit.state).thenReturn(state); + await tester.pumpApp(buildSubject()); + + expect(find.byType(CupertinoActivityIndicator), findsOne); + expect(find.text(label), findsOne); + } + + testWidgets('initial shows preparing', (tester) async { + await expectLabel(tester, const SendProcessInitial(), S.current.sendPreparing); + }); + + testWidgets('preparing label', (tester) async { + await expectLabel(tester, const SendProcessPreparing(), S.current.sendPreparing); + }); + + testWidgets('signing label', (tester) async { + await expectLabel(tester, const SendProcessSigning(), S.current.sendSigning); + }); + + testWidgets('success label', (tester) async { + await expectLabel(tester, const SendProcessSuccess('0xtx'), S.current.sendSuccess); + }); + + testWidgets('failure label', (tester) async { + await expectLabel( + tester, + const SendProcessFailure(SendProcessFailureReason.generic), + S.current.sendFailureTitle, + ); + }); + }); + + group('$SendProcessView PopScope canPop', () { + testWidgets('blocks back navigation while signing (non-terminal)', (tester) async { + when(() => processCubit.state).thenReturn(const SendProcessSigning()); + await tester.pumpApp(buildSubject()); + + final popScope = tester.widget(find.byType(PopScope)); + expect(popScope.canPop, isFalse); + }); + + testWidgets('allows back navigation on success (terminal)', (tester) async { + when(() => processCubit.state).thenReturn(const SendProcessSuccess('0xtx')); + await tester.pumpApp(buildSubject()); + + final popScope = tester.widget(find.byType(PopScope)); + expect(popScope.canPop, isTrue); + }); + + testWidgets('blocks back navigation on failure (closes the retry stale-window)', ( + tester, + ) async { + when(() => processCubit.state).thenReturn( + const SendProcessFailure(SendProcessFailureReason.generic), + ); + await tester.pumpApp(buildSubject()); + + final popScope = tester.widget(find.byType(PopScope)); + expect(popScope.canPop, isFalse); + }); + + testWidgets('blocks back navigation while preparing (non-terminal)', (tester) async { + when(() => processCubit.state).thenReturn(const SendProcessPreparing()); + await tester.pumpApp(buildSubject()); + + final popScope = tester.widget(find.byType(PopScope)); + expect(popScope.canPop, isFalse); + }); + }); + + // The result sheet is a modal bottom sheet shown from the listener. The view + // keeps a CupertinoActivityIndicator animating behind it, so pumpAndSettle + // never settles; pump fixed frames to open the sheet. + Future pumpWithState(WidgetTester tester, SendProcessState terminal) async { + tester.view.physicalSize = const Size(1200, 2400); + tester.view.devicePixelRatio = 1.0; + addTearDown(tester.view.resetPhysicalSize); + addTearDown(tester.view.resetDevicePixelRatio); + + whenListen( + processCubit, + Stream.fromIterable([terminal]), + initialState: const SendProcessSigning(), + ); + await tester.pumpApp(buildSubject()); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 400)); + } + + group('$SendProcessView result sheet', () { + testWidgets('success emits a success sheet with title + description', (tester) async { + await pumpWithState(tester, const SendProcessSuccess('0xtx')); + + expect(find.text(S.current.sendSuccessDescription), findsOne); + expect(find.byIcon(Icons.check_circle_rounded), findsOne); + expect(find.text(S.current.close), findsOne); + // Non-retryable terminal states never offer Retry. + expect(find.text(S.current.retry), findsNothing); + + await tester.tap(find.text(S.current.close)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 400)); + + expect(find.byIcon(Icons.check_circle_rounded), findsNothing); + }); + + testWidgets('signature-unsupported failure message', (tester) async { + await pumpWithState( + tester, + const SendProcessFailure(SendProcessFailureReason.signatureUnsupported), + ); + + expect(find.text(S.current.sendFailureSignatureUnsupported), findsOne); + expect(find.byIcon(Icons.error_rounded), findsOne); + expect(find.text(S.current.retry), findsNothing); + }); + + testWidgets('signature-cancelled failure message', (tester) async { + await pumpWithState( + tester, + const SendProcessFailure(SendProcessFailureReason.signatureCancelled), + ); + + expect(find.text(S.current.sendFailureSignatureCancelled), findsOne); + }); + + testWidgets('gas-unavailable failure message', (tester) async { + await pumpWithState( + tester, + const SendProcessFailure(SendProcessFailureReason.gasFundingUnavailable), + ); + + expect(find.text(S.current.sendFailureGasUnavailable), findsOne); + }); + + testWidgets('invalid-request failure message', (tester) async { + await pumpWithState( + tester, + const SendProcessFailure(SendProcessFailureReason.invalidRequest), + ); + + expect(find.text(S.current.sendFailureInvalidRequest), findsOne); + }); + + testWidgets( + 'registration/KYC failure always shows localized copy, never raw API message', + (tester) async { + await pumpWithState( + tester, + const SendProcessFailure( + SendProcessFailureReason.registrationOrKycRequired, + message: 'Please complete KYC', + ), + ); + + expect(find.text(S.current.sendFailureRegistrationOrKycRequired), findsOne); + expect(find.text('Please complete KYC'), findsNothing); + }, + ); + + testWidgets('registration/KYC failure falls back to localized copy without message', ( + tester, + ) async { + await pumpWithState( + tester, + const SendProcessFailure(SendProcessFailureReason.registrationOrKycRequired), + ); + + expect(find.text(S.current.sendFailureRegistrationOrKycRequired), findsOne); + }); + + testWidgets('generic failure message', (tester) async { + await pumpWithState( + tester, + const SendProcessFailure(SendProcessFailureReason.generic), + ); + + expect(find.text(S.current.sendFailureGeneric), findsOne); + expect(find.text(S.current.retry), findsNothing); + }); + + testWidgets('confirm-mismatch failure message (non-retryable)', (tester) async { + await pumpWithState( + tester, + const SendProcessFailure(SendProcessFailureReason.confirmMismatch), + ); + + expect(find.text(S.current.sendFailureConfirmMismatch), findsOne); + expect(find.text(S.current.retry), findsNothing); + expect(find.text(S.current.close), findsOne); + }); + + testWidgets( + 'retryable failure shows Retry; tapping it calls retryConfirm and keeps the page', + (tester) async { + when(() => processCubit.retryConfirm()).thenAnswer((_) async {}); + + await pumpWithState( + tester, + const SendProcessFailure( + SendProcessFailureReason.generic, + message: 'socket hung up', + canRetry: true, + ), + ); + + expect(find.text(S.current.retry), findsOne); + expect(find.text(S.current.close), findsOne); + expect(find.byType(SendProcessView), findsOne); + + await tester.tap(find.text(S.current.retry)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 400)); + + verify(() => processCubit.retryConfirm()).called(1); + // Retry must dismiss only the sheet — the SendProcessPage/View stays. + expect(find.byType(SendProcessView), findsOne); + expect(find.text(S.current.retry), findsNothing); + }, + ); + + testWidgets('non-retryable failure: Close only, no Retry button', (tester) async { + await pumpWithState( + tester, + const SendProcessFailure(SendProcessFailureReason.signatureUnsupported), + ); + + expect(find.text(S.current.retry), findsNothing); + expect(find.text(S.current.close), findsOne); + + await tester.tap(find.text(S.current.close)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 400)); + + expect(find.byIcon(Icons.error_rounded), findsNothing); + }); + }); +} diff --git a/test/screens/send/send_process_result_sheet_responsive_matrix_test.dart b/test/screens/send/send_process_result_sheet_responsive_matrix_test.dart new file mode 100644 index 000000000..357f6dbce --- /dev/null +++ b/test/screens/send/send_process_result_sheet_responsive_matrix_test.dart @@ -0,0 +1,82 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/screens/send/cubits/send_process/send_process_cubit.dart'; +import 'package:realunit_wallet/screens/send/send_process_page.dart'; +import 'package:realunit_wallet/styles/themes.dart'; + +import '../../helper/helper.dart'; + +class _MockSendProcessCubit extends MockCubit implements SendProcessCubit {} + +Future _pumpResultSheet( + WidgetTester tester, + MatrixCell cell, + SendProcessCubit cubit, +) async { + await tester.binding.setSurfaceSize(cell.mediaQuery.size); + addTearDown(() async => tester.binding.setSurfaceSize(null)); + await tester.pumpWidget( + MediaQuery( + data: cell.mediaQuery, + child: MaterialApp( + theme: realUnitTheme, + locale: const Locale('de'), + localizationsDelegates: const [ + S.delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + supportedLocales: S.delegate.supportedLocales, + home: BlocProvider.value( + value: cubit, + child: const SendProcessView(), + ), + ), + ), + ); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 400)); +} + +void main() { + group('SendProcess result sheet responsive matrix (full device × textScale)', () { + for (final cell in kFullResponsiveMatrix) { + testWidgets(cell.id, (tester) async { + await withTargetPlatform(cell.device.platform, () async { + final processCubit = _MockSendProcessCubit(); + when(() => processCubit.retryConfirm()).thenAnswer((_) async {}); + whenListen( + processCubit, + Stream.value( + const SendProcessFailure( + SendProcessFailureReason.generic, + message: 'socket hung up', + canRetry: true, + ), + ), + initialState: const SendProcessSigning(), + ); + + await expectNoLayoutOverflow( + tester, + () => _pumpResultSheet(tester, cell, processCubit), + reason: 'SendProcess result sheet overflow / ${cell.label}', + ); + + await expectFullyTappable( + tester, + find.widgetWithText(FilledButton, S.current.retry), + within: find.byType(SendProcessView), + reason: 'SendProcess result sheet / ${cell.label}: Retry CTA not tappable', + ); + }); + }); + } + }); +} diff --git a/test/screens/send/send_recipient_page_test.dart b/test/screens/send/send_recipient_page_test.dart new file mode 100644 index 000000000..4d793d44e --- /dev/null +++ b/test/screens/send/send_recipient_page_test.dart @@ -0,0 +1,181 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mobile_scanner/mobile_scanner.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/models/balance.dart'; +import 'package:realunit_wallet/packages/config/api_config.dart'; +import 'package:realunit_wallet/packages/repository/balance_repository.dart'; +import 'package:realunit_wallet/packages/service/app_store.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/payment/transfer_exceptions.dart'; +import 'package:realunit_wallet/packages/utils/default_assets.dart'; +import 'package:realunit_wallet/screens/send/cubits/send_recipient/send_recipient_cubit.dart'; +import 'package:realunit_wallet/screens/send/send_amount_page.dart'; +import 'package:realunit_wallet/screens/send/send_recipient_page.dart'; + +import '../../helper/helper.dart'; + +class _MockSendRecipientCubit extends MockCubit implements SendRecipientCubit {} + +class _MockBalanceRepository extends Mock implements BalanceRepository {} + +class _MockAppStore extends Mock implements AppStore {} + +class _MockApiConfig extends Mock implements ApiConfig {} + +void main() { + late _MockSendRecipientCubit recipientCubit; + + Balance balanceOf(BigInt value) => Balance( + chainId: realUnitAsset.chainId, + contractAddress: realUnitAsset.address, + walletAddress: '0xwallet', + balance: value, + asset: realUnitAsset, + ); + + setUpAll(() { + registerFallbackValue( + Balance( + chainId: 1, + contractAddress: '0x', + walletAddress: '0x', + balance: BigInt.zero, + asset: realUnitAsset, + ), + ); + stubMobileScannerChannel(); + + // A valid recipient pushes SendAmountPage, which builds a SellBalanceCubit + // off getIt; register a balance repository + app store so the pushed route + // resolves and renders. + final getIt = GetIt.instance; + final balanceRepo = _MockBalanceRepository(); + when(() => balanceRepo.watchBalance(any())).thenAnswer( + (_) => Stream.value(balanceOf(BigInt.from(100))), + ); + getIt.registerFactory(() => balanceRepo); + final appStore = _MockAppStore(); + final apiConfig = _MockApiConfig(); + when(() => apiConfig.asset).thenReturn(realUnitAsset); + when(() => appStore.apiConfig).thenReturn(apiConfig); + when(() => appStore.primaryAddress).thenReturn('0xwallet'); + getIt.registerSingleton(appStore); + }); + + tearDownAll(() async => GetIt.instance.reset()); + + setUp(() { + recipientCubit = _MockSendRecipientCubit(); + when(() => recipientCubit.state).thenReturn(const SendRecipientEmpty()); + when(() => recipientCubit.reset()).thenReturn(null); + when(() => recipientCubit.submit(any())).thenReturn(null); + when(() => recipientCubit.onCodeDetected(any())).thenReturn(null); + }); + + Widget buildSubject() => BlocProvider.value( + value: recipientCubit, + child: const SendRecipientView(), + ); + + group('$SendRecipientPage', () { + testWidgets('builds its own cubit and renders $SendRecipientView', (tester) async { + await tester.pumpApp(const SendRecipientPage()); + + expect(find.byType(SendRecipientView), findsOne); + }); + }); + + group('$SendRecipientView', () { + testWidgets('renders the title, scanner preview and the manual field', (tester) async { + await tester.pumpApp(buildSubject()); + + expect(find.text(S.current.sendRecipientTitle), findsOne); + expect(find.byType(MobileScanner), findsOne); + expect(find.text(S.current.sendRecipientManualHint), findsOne); + }); + + testWidgets('onDetect forwards a scanned raw value to the cubit', (tester) async { + await tester.pumpApp(buildSubject()); + + final scanner = tester.widget(find.byType(MobileScanner)); + scanner.onDetect!(const BarcodeCapture()); + scanner.onDetect!(const BarcodeCapture(barcodes: [Barcode(rawValue: '0xabc')])); + + verify(() => recipientCubit.onCodeDetected('0xabc')).called(1); + }); + + testWidgets('the continue button submits the typed address', (tester) async { + await tester.pumpApp(buildSubject()); + + await tester.enterText(find.byType(TextField), '0xRecipient'); + await tester.tap(find.text(S.current.next)); + await tester.pump(); + + verify(() => recipientCubit.submit('0xRecipient')).called(1); + }); + + testWidgets('the paste button fills the field from the clipboard', (tester) async { + // Stub the clipboard platform channel so getData returns a known address. + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.platform, + (call) async { + if (call.method == 'Clipboard.getData') { + return {'text': ' 0xPasted '}; + } + return null; + }, + ); + addTearDown( + () => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.platform, + null, + ), + ); + + await tester.pumpApp(buildSubject()); + + await tester.tap(find.byIcon(Icons.paste_rounded)); + await tester.pump(); + + final field = tester.widget(find.byType(TextField)); + expect(field.controller!.text, '0xPasted'); + }); + + testWidgets('an invalid recipient shows a snackbar', (tester) async { + whenListen( + recipientCubit, + Stream.fromIterable([ + const SendRecipientInvalid(InvalidRecipientAddressException('bad')), + ]), + initialState: const SendRecipientEmpty(), + ); + + await tester.pumpApp(buildSubject()); + await tester.pump(); + + expect(find.byType(SnackBar), findsOne); + expect(find.text(S.current.sendRecipientInvalid), findsOne); + }); + + testWidgets('a valid recipient navigates to the amount step and resets', (tester) async { + whenListen( + recipientCubit, + Stream.fromIterable([ + const SendRecipientValid('0x9F5713dEAcb8e9CaB6c2D3FaE1aFc2715F8D2D71'), + ]), + initialState: const SendRecipientEmpty(), + ); + + await tester.pumpApp(buildSubject()); + await tester.pumpAndSettle(); + + expect(find.byType(SendAmountView), findsOne); + verify(() => recipientCubit.reset()).called(1); + }); + }); +} diff --git a/test/screens/send/send_recipient_responsive_matrix_test.dart b/test/screens/send/send_recipient_responsive_matrix_test.dart new file mode 100644 index 000000000..cf5563e60 --- /dev/null +++ b/test/screens/send/send_recipient_responsive_matrix_test.dart @@ -0,0 +1,82 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/screens/send/cubits/send_recipient/send_recipient_cubit.dart'; +import 'package:realunit_wallet/screens/send/send_recipient_page.dart'; +import 'package:realunit_wallet/styles/themes.dart'; + +import '../../helper/helper.dart'; + +class _MockSendRecipientCubit extends MockCubit implements SendRecipientCubit {} + +Future _pumpScreen( + WidgetTester tester, + MatrixCell cell, + Widget child, +) async { + await tester.binding.setSurfaceSize(cell.mediaQuery.size); + addTearDown(() async => tester.binding.setSurfaceSize(null)); + await tester.pumpWidget( + MediaQuery( + data: cell.mediaQuery, + child: MaterialApp( + theme: realUnitTheme, + locale: const Locale('de'), + localizationsDelegates: const [ + S.delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + supportedLocales: S.delegate.supportedLocales, + home: child, + ), + ), + ); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 50)); +} + +void main() { + setUpAll(stubMobileScannerChannel); + + group('SendRecipientView responsive matrix (full device × textScale)', () { + for (final cell in kFullResponsiveMatrix) { + testWidgets(cell.id, (tester) async { + await withTargetPlatform(cell.device.platform, () async { + final recipientCubit = _MockSendRecipientCubit(); + when(() => recipientCubit.state).thenReturn(const SendRecipientEmpty()); + whenListen( + recipientCubit, + const Stream.empty(), + initialState: const SendRecipientEmpty(), + ); + when(() => recipientCubit.submit(any())).thenReturn(null); + when(() => recipientCubit.onCodeDetected(any())).thenReturn(null); + + final subject = BlocProvider.value( + value: recipientCubit, + child: const SendRecipientView(), + ); + + await expectNoLayoutOverflow( + tester, + () => _pumpScreen(tester, cell, subject), + reason: 'SendRecipientView overflow / ${cell.label}', + ); + + await expectFullyTappable( + tester, + find.widgetWithText(FilledButton, S.current.next), + within: find.byType(SendRecipientView), + reason: 'SendRecipientView / ${cell.label}: Next CTA not tappable', + ); + }); + }); + } + }); +} diff --git a/test/setup/di_pay_transfer_service_test.dart b/test/setup/di_pay_transfer_service_test.dart new file mode 100644 index 000000000..239f1b514 --- /dev/null +++ b/test/setup/di_pay_transfer_service_test.dart @@ -0,0 +1,62 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/packages/repository/balance_repository.dart'; +import 'package:realunit_wallet/packages/repository/settings_repository.dart'; +import 'package:realunit_wallet/packages/repository/wallet_repository.dart'; +import 'package:realunit_wallet/packages/service/app_store.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_pay_service.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_transfer_service.dart'; +import 'package:realunit_wallet/setup/di.dart'; + +class _MockAppStore extends Mock implements AppStore {} + +class _MockBalanceRepository extends Mock implements BalanceRepository {} + +class _MockSettingsRepository extends Mock implements SettingsRepository {} + +class _MockWalletRepository extends Mock implements WalletRepository {} + +void main() { + // The pay and wallet-to-wallet flows wire their backend clients through + // `setupServices()` as factories: + // () => RealUnitPayService(getIt(), getIt()) + // () => RealUnitTransferService(getIt(), getIt()) + // This test exercises both registrations and then resolves each factory. + // + // `setupServices()` constructs `BalanceService` (eager singleton) up front, + // so `AppStore` + `BalanceRepository` must already be registered. Resolving + // either service then pulls the lazy `WalletService`, whose dependencies + // bottom out at `WalletRepository` + `SettingsRepository` + `AppStore` + // (`BitboxService` is registered by `setupServices()` itself). Registering + // those leaf mocks keeps the whole chain construct-only — none of the mocked + // collaborators perform I/O on construction. + setUp(() { + getIt.reset(); + getIt.registerSingleton(_MockAppStore()); + getIt.registerSingleton(_MockBalanceRepository()); + getIt.registerSingleton(_MockSettingsRepository()); + getIt.registerSingleton(_MockWalletRepository()); + }); + + tearDown(() => getIt.reset()); + + test('setupServices registers a resolvable RealUnitPayService factory', () { + setupServices(); + + expect(getIt.isRegistered(), isTrue); + + final service = getIt(); + expect(service, isA()); + expect(identical(service, getIt()), isFalse); + }); + + test('setupServices registers a resolvable RealUnitTransferService factory', () { + setupServices(); + + expect(getIt.isRegistered(), isTrue); + + final service = getIt(); + expect(service, isA()); + expect(identical(service, getIt()), isFalse); + }); +} diff --git a/test/setup/routing/pay_send_routes_test.dart b/test/setup/routing/pay_send_routes_test.dart new file mode 100644 index 000000000..27667f561 --- /dev/null +++ b/test/setup/routing/pay_send_routes_test.dart @@ -0,0 +1,75 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/screens/home/bloc/home_bloc.dart'; +import 'package:realunit_wallet/screens/pay/pay_scan_page.dart'; +import 'package:realunit_wallet/screens/send/send_recipient_page.dart'; +import 'package:realunit_wallet/setup/routing/router_config.dart'; +import 'package:realunit_wallet/setup/routing/routes/app_routes.dart'; + +import '../../helper/helper.dart'; + +class _MockHomeBloc extends MockBloc implements HomeBloc {} + +void main() { + late _MockHomeBloc homeBloc; + + setUpAll(() { + // Both routed pages embed a QrScannerView (MobileScanner); the stub keeps + // the headless camera preview deterministic and free of + // MissingPluginException. + stubMobileScannerChannel(); + }); + + setUp(() { + homeBloc = _MockHomeBloc(); + when(() => homeBloc.state).thenReturn(const HomeState()); + }); + + // Mirrors the production wiring in main.dart: the routed pages read their + // blocs from above MaterialApp.router, so HomeBloc (used by the initial + // /home route) is provided here. Navigation then drives the real + // `routerConfig` to the /pay and /send GoRoutes under test. + Future pumpRouter(WidgetTester tester) async { + await tester.pumpWidget( + BlocProvider.value( + value: homeBloc, + child: MaterialApp.router( + routerConfig: routerConfig, + localizationsDelegates: const [ + S.delegate, + GlobalMaterialLocalizations.delegate, + ], + supportedLocales: S.delegate.supportedLocales, + ), + ), + ); + await tester.pumpAndSettle(); + } + + // Restore the global router singleton to its initial location after each + // test so the /pay or /send location does not leak into any later test. + tearDown(() => routerConfig.goNamed(AppRoutes.home)); + + testWidgets('the pay route builds PayScanPage', (tester) async { + await pumpRouter(tester); + + routerConfig.goNamed(AppRoutes.pay); + await tester.pumpAndSettle(); + + expect(find.byType(PayScanPage), findsOneWidget); + }); + + testWidgets('the send route builds SendRecipientPage', (tester) async { + await pumpRouter(tester); + + routerConfig.goNamed(AppRoutes.send); + await tester.pumpAndSettle(); + + expect(find.byType(SendRecipientPage), findsOneWidget); + }); +} From f6bfcd12d5390e6f256b32609f76d92dab6362b7 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Thu, 23 Jul 2026 11:39:33 +0200 Subject: [PATCH 6/9] fix: upgrade mobile_scanner to 7.4.0 for arm64 iOS-26 simulator support (#875) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem `mobile_scanner 5.2.3` depends on GoogleMLKit, whose frameworks ship **no arm64-iphonesimulator slice**. On GitHub's arm64 macOS runners the iOS 26 simulator is arm-only (Apple removed Rosetta for the iOS 26 simulator), so the app could neither build nor run there — breaking the `tier3-handbook` CI with `Framework 'Pods_Runner' not found`. This is what red-flags the staging→develop promote. > Note: `mobile_scanner` was reintroduced in #674 at `^5.2.3` in pubspec but without a matching `pod install` / `ios/Podfile.lock` commit, leaving the lockfile out of sync. This PR both bumps the version (5.2.3 → 7.4.0) and restores lockfile consistency. ## Fix Upgrade `mobile_scanner` to **7.4.0**, which drops GoogleMLKit for Apple's native **Vision** API → the app builds and installs/launches **natively on arm64** iOS 26.5 simulators. - Migrate the two `errorBuilder` call sites to 7.x's 2-arg signature (dropped the unused `Widget? child`). - `QrScannerView` now supplies a compact, textScale-safe **default** error placeholder (icon-only) so 7.x's taller default no longer overflows `send_recipient_page`'s bounded `Expanded` at large accessibility text sizes. - Add a no-op stub for mobile_scanner 7.x's new `deviceOrientation` event channel in the golden-test helper, plus a focused widget test asserting the default placeholder stays overflow-safe at `TextScaler.linear(3.0)`. ## Verification - `flutter build ios --simulator --debug` → succeeds; `simctl install` + `launch` on iPhone 17 / iOS 26.5 → succeeds (no arch error, app renders). - `flutter analyze` clean; `flutter test --exclude-tags golden` → all pass. - Golden baselines regenerated on the self-hosted runner (one changed: the send scanner error icon). - Tier 3 handbook flows (iOS build + 26 Maestro flows) and RealUnit Build (analyze/test, visual regression, coverage, BitBox) both green on the branch head. ## Note on the scanner backend The iOS scan backend changes GoogleMLKit → Apple Vision. The app only consumes `Barcode.rawValue`, so Dart-side handling is unchanged. QR-decode parity between the two backends is architecturally expected (both fully support the QR symbology) but has **not** been verified against a live camera — the scanner is `@no-integration-test` and cannot run in CI, so a device smoke-test (bare address, checksummed address, `ethereum:` URI, LNURL) is worth doing before release. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- ios/Podfile.lock | 7 +++ lib/screens/pay/pay_scan_page.dart | 2 +- lib/widgets/scanner/qr_scanner_view.dart | 28 ++++++++++-- pubspec.lock | 6 +-- pubspec.yaml | 2 +- .../macos/send_recipient_page_empty.png | Bin 18266 -> 19208 bytes test/helper/golden_plugin_stubs.dart | 6 +++ test/screens/pay/pay_scan_page_test.dart | 1 - .../widgets/scanner/qr_scanner_view_test.dart | 40 ++++++++++++++++++ 9 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 test/widgets/scanner/qr_scanner_view_test.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock index bd793d782..78b4b8d8b 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -26,6 +26,9 @@ PODS: - local_auth_darwin (0.0.1): - Flutter - FlutterMacOS + - mobile_scanner (7.0.0): + - Flutter + - FlutterMacOS - no_screenshot (0.10.0): - Flutter - open_file_ios (1.0.3): @@ -49,6 +52,7 @@ DEPENDENCIES: - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`) + - mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`) - no_screenshot (from `.symlinks/plugins/no_screenshot/ios`) - open_file_ios (from `.symlinks/plugins/open_file_ios/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) @@ -78,6 +82,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/image_picker_ios/ios" local_auth_darwin: :path: ".symlinks/plugins/local_auth_darwin/darwin" + mobile_scanner: + :path: ".symlinks/plugins/mobile_scanner/darwin" no_screenshot: :path: ".symlinks/plugins/no_screenshot/ios" open_file_ios: @@ -99,6 +105,7 @@ SPEC CHECKSUMS: IdensicMobileSDK: a8ec2cf5c216ae138b00e2ff32e0e31c5e366cec image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326 local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb + mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 no_screenshot: 03c8ac6586f9652cd45e3d12d74e5992256403ac open_file_ios: 46184d802ee7959203f6392abcfa0dd49fdb5be0 OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 diff --git a/lib/screens/pay/pay_scan_page.dart b/lib/screens/pay/pay_scan_page.dart index 8ca3f74d4..01affc73b 100644 --- a/lib/screens/pay/pay_scan_page.dart +++ b/lib/screens/pay/pay_scan_page.dart @@ -55,7 +55,7 @@ class PayScanView extends StatelessWidget { appBar: AppBar(title: Text(S.of(context).payScanTitle)), body: QrScannerView( onDetect: (raw) => context.read().onCodeDetected(raw), - errorBuilder: (context, error, child) { + errorBuilder: (context, error) { final message = error.errorCode == MobileScannerErrorCode.permissionDenied ? S.of(context).payScanCameraPermissionDenied : S.of(context).payScanCameraUnavailable; diff --git a/lib/widgets/scanner/qr_scanner_view.dart b/lib/widgets/scanner/qr_scanner_view.dart index 6ef8f793f..6d5e9c384 100644 --- a/lib/widgets/scanner/qr_scanner_view.dart +++ b/lib/widgets/scanner/qr_scanner_view.dart @@ -4,8 +4,26 @@ // pay flow (LNURL decode) and the wallet-to-wallet send flow (EVM address // decode); the per-flow decode logic it feeds is unit-tested in the respective // cubit tests. -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; +import 'package:realunit_wallet/styles/colors.dart'; + +/// Compact icon-only error placeholder used when [QrScannerView.errorBuilder] +/// is null. Replaces mobile_scanner 7.x's own (taller, textScale-scaling) +/// default to avoid overflow in bounded layouts like send_recipient_page.dart's +/// Expanded. +Widget _defaultErrorBuilder(BuildContext context, MobileScannerException error) { + return ColoredBox( + color: RealUnitColors.basic.black, + child: Center( + child: Icon( + Icons.error_outline, + size: 48, + color: RealUnitColors.status.red600, + ), + ), + ); +} /// Thin wrapper around [MobileScanner] that surfaces the first raw barcode /// value of each capture via [onDetect]. Keeping the camera/MethodChannel @@ -17,8 +35,10 @@ class QrScannerView extends StatelessWidget { final ValueChanged onDetect; /// Optional error UI builder forwarded to [MobileScanner.errorBuilder]. - /// When null, MobileScanner's own default error handling is used. - final Widget Function(BuildContext, MobileScannerException, Widget?)? errorBuilder; + /// When null, this compact icon-only placeholder is used instead of + /// mobile_scanner 7.x's own (taller, textScale-scaling) default, to avoid + /// overflow in bounded layouts like send_recipient_page.dart's Expanded. + final Widget Function(BuildContext, MobileScannerException)? errorBuilder; const QrScannerView({ super.key, @@ -33,7 +53,7 @@ class QrScannerView extends StatelessWidget { final raw = capture.barcodes.firstOrNull?.rawValue; if (raw != null) onDetect(raw); }, - errorBuilder: errorBuilder, + errorBuilder: errorBuilder ?? _defaultErrorBuilder, ); } } diff --git a/pubspec.lock b/pubspec.lock index 5c204b0f5..759158cf0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -78,7 +78,7 @@ packages: description: path: "." ref: "v0.0.10" - resolved-ref: "cd99ce656410e8df6c6585076d6ee0205a67b34c" + resolved-ref: cd99ce656410e8df6c6585076d6ee0205a67b34c url: "https://github.com/DFXswiss/bitbox_flutter.git" source: git version: "0.0.1" @@ -955,10 +955,10 @@ packages: dependency: "direct main" description: name: mobile_scanner - sha256: d234581c090526676fd8fab4ada92f35c6746e3fb4f05a399665d75a399fb760 + sha256: ce3f059ebd6dbfab7292bba0e893e354b46730636820d3c9ef69005ce2d55bce url: "https://pub.dev" source: hosted - version: "5.2.3" + version: "7.4.0" mocktail: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index 67fd1d5ce..c6634fa58 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -65,7 +65,7 @@ dependencies: http: ^1.1.0 intl: any local_auth: ^3.0.0 - mobile_scanner: ^5.2.3 + mobile_scanner: ^7.4.0 no_screenshot: ^1.1.0 open_file: ^3.5.11 path: ^1.9.0 diff --git a/test/goldens/screens/send/goldens/macos/send_recipient_page_empty.png b/test/goldens/screens/send/goldens/macos/send_recipient_page_empty.png index 7b64f40a5e4d71f611099f72b372c6dc9acd3692..850d2b9747adf5a192984551f455bf0e1fa5f84c 100644 GIT binary patch literal 19208 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%0D5e99A1htx!TjbAQ?mD9bjQqGTymHACia8| z%;)Y8u~^xi#CrF)A63c_V+Xke3Dig+!UPe^@nif|rcE9v#(U*H*c&qe8wtooy|Azj z33tJh%4Y`NQSuk({IzMzfMkQVHkoZYSOLtd_DG<*jU2udF|<$g3V;& zuq-BRE-y&b2hs+BZTlHP)xi0UYHX8Uz`R!m3L5MK0BSwsMj-i-+(Gf)-kunC(A3z- zsNh!thqAc41+J4RF(HBW-aW9vnY2e6UI*A7hQ&iK7f<#~d&9nP6Pp-%HUVG#X05KF zqpq*0_zr*qBizhT&p=XK$N=3SpCP~kN#jQ1F$Y_}Y>az{di7mg(ixSX8~uS80uTD| zi=)}<@a+{(A%(f#S?=%HH^*w_9?kj7iJQA;{O$FQ^I;cza|uF8VPaVA;Q*LUBeG+^ zSx_=d$H&ag-N>)0q_L*9mX<9$M^h!XctKPP+MNz1!OERcCuF~)64nt!C}H%vUU#OcXd&m`gSH^n3K^MyNQ)Qt}{9?Bo@uutF^W2vafEo!FRc=#TDCtQnMI68Y=U3WeF z{=%PKY9}|B-$Jam!N?c)^L}9lnhsyC{=(Syq&&;FlgNk{|6TaPK7h;a^5qI9=Hqa{ zl7pDQxnDom1{(5DWE|_W!88lWgw39Qrs(=I)7fuQv7c083ko=7Yqy|*p=TL2MRKEW z9150tC4;Y|eLJH6^hu5779#H5r%y>;FYZ5l$Ut(9imF$bcs%0A)924c$VnbPOt2IF zhbo@@J#f`)dh+MbIXeIIOZCq|J9wtBs)Mi3pC7O^cuW_s1#Wad^(t%VjLyH~F&;pf zGr^uS$WHthi9@y26~CU=>hVgA-xOv?+5K+v<=+z0=;DBC1*H01lDf0Rq?=(lR?q*=di z{lZk*NuX2B?b9A`FLepfpY&-+E>lLFSYBmknIErL?tp!#rz7o)FfZ`+VJ-tw;5LhN z*UXMaZ)`}e=YFi71I!==i!3Q z`CM_VJaDyE`C00`-CZT$lHy+GIQznWXl&SoL=Y?FsU-} z*1)8dN*c}ILG%RQcG-=-5;CyBnGD>3HW<-T>UT?Dvyn00&bX~4*X8amqv6XJ!e9S} zS2+aJFnRVm9!CN19v)e~q*d~q3@cey=dOK-9a`V@V`(4&p z(?i*UBiHwGOkA(c?6a!|)F~isHG9);FGmYrWN0ZS=`q<|yFZ-s$;(pb-rF+mU+WigII`9Tnm2v$`Ts0TyZC9}> z8i~ArO1EtF)!J(MagptOR`lhAkvl4^JzivG|@ zK*i2)l$mZ!?;Lv@TyrEZXmV(*QI)uv_kln3DrjM`Hx2Z5h4vK))Xjvx`2oz8p``gV zx%o%hFT%*6d6|0LFl*KGw0;tnx3G5)fL50yo>1bp`$<7S>&(7@&9r~merC8)BFt1N zz#DXNw=L{eIFGWpjlGA6jk(cPpaGz_6d!e=n`?A5EO9r1cze&-&(zHnd<9h74Zi}q z_rc-Dr9A3m%ADdF=#gOm2cVZlgV#aF7%TC8rlu&LUPCd!9jEqg?X!&3^ zBMfop47Uq5<_F~z2K=)!Q>xcdK~~Q)SNvOa-EZfF^HESM$t&A)av>IXgM*egyr0Lv z7jkIaY#s7{iCxx8`Y?GG{p^Tot^)i0HI#*)zjh~oZ(yfc>mvM9%18{x`IX_@kvppj zLu{lVwd(7Tg9@(Dz#^&=r|w`6TRC}82PQ)eW}qjt^sVZ%QQZ~qbU!eDs zkfm(kDc7I}02_Y>Dl$gCqs=YMc)7Cf8VhZEQ|7%`%0!Vv-)o6s>Zx zPrZEVS9acvD)Yc~!ef5y@T%444A{T#d`W*8sG%vrq_~Ma7Y^w`%To<*{rJ?kQpT{S@}eqn->XZM~K)45{tRP;<7@cxW5~36a){SUV)uTh=16{>9yWS2-HM zM73)AH=c^6llEWCj_wsal!|Knb65F2aAKBExeqmn-RCw5Dj z4=eiVA4?4Jl4Y}M7ThDbR^nlAZxYxoa%VoOwf1$^`ls=qcRXmQ!+774UWQ4B6j3QV zf??OTyS5$Z-n_r>91bvZsmtNQ$@@FS9?@v8iQV#tG7Lfk0zd2Y zxTPNo0Oa9w1;m!Q`D>Lu#>tn`#X6QNO0@isa2e>frs#L6tIje1zlFkI6YEGiRmRz- zak;nstXJGwuT-#J$(s9fXxw3#CPD4Z?C@G<0MH|k<k*`xpf literal 18266 zcmeIac|4Tw-#4tUQmOPUq^uPZLS^48A!O|PR%9FdG8k*+8>Nsv+t>|bnQYljC1f4j z*v1&L3J2NlvFtZ zQ&;Y|en!#$)Tzg(i>NapLZfWYNa@hyXAd`ZvmZVA8;|r`%6hetttcy*4nvO7?|Y{A!isEoUi?4V7Pydk>UHziwq2r_Zb*2{wprvS8d{*McM-iqx!@}YaR z)=Uh{SE3}Rae+7+Tp7dc@?Ojqkh5_#X5AXYbP2|75u=mW~MUUb2STl))JO`ZWV&byRC;XsD;BXZTOYN65nOgjHLy+^I%+ zpZ$XDs7qW+Gfj{(?4obu$DZC^Kd-6UZsix>UxV%RKv{?t<8{&RA4Xgi&zwHp0Dqot zc#o-QvMsDxQA7kWHJ-@|AAxQQ@12q(QkI9*YMjxg{Tb4t{*8To&nYV-nq_9l%4!JX zfWSYeBF=%}Mm|11%$F~#mi9V3I+i|;WKsxNGcQ7Nc^A)>2prxDrC`j2c+Rk|6~g5G zoQf)0y^H_wFb)KBSbM)&>d!P=QaU=IrU~??OTznT`@8F9?=EnduI`_5=A3yjTTcCX zLBe>d8WwR()3&%A$0}^Vw~jOqd#)ns*O%v}+|;!S20e zZ*+Aa*ucPG3KbENPHs8aXih8)A+^?zLVbOG+1S~O8X6k3?KEef8?ECPx;_}0M89uwpJ+K4918+NHeX& zEnTnrje&G?cpSSDjKN!gh8)5ivM z7*B5Z&igh(HlMw%uJ-Gr374+K=sde86Jh-%-*9-i)p9pthNjVL>XjfMc&BKUBoS`> z=b`9G-o$B-$tt}`d3iyQDtDmq*?58F*@V-A+&U_yM7bb=$H35E=t)V^KKp-#Ngt0J z*@Dyy3yX-r{e|sSnxbKont$R}$fRN-9TsNh75>mxBnV{D>5Dh)xw5-HRS(Rhk2WRJ%B5DMb!||~(o@4r=;~tnm)&8MIo2l-(&C(rWj+8?l zFviY8BtI~%8Sh5x;C+mqwEYCbrM9olgu_6U%#!So%d7o_4Kw{0m$VE)o|Ukm6^GdC zV|I}eYX%$D4lEra!h#sT$-M%to`C^MFiyq`U&cgPy<2YD?@Qp3bxmCovw&w!TceLA z1Vjv}_<)q3YHCX4Bl6;dCEqvFfBE~Mc$-28p^oDCMcQtt71Lf%Ez)&Uc-;{ zl)RJ1Ba95HwmxWUgA`YKb3V0Lr~Qe)BP^-dJe+pa|0_L4HMKCo>~bo~?s#N0Vb3A> zV1$n{%o^z0In6%RNPT_0HQ1jLj$?n|)vXXFA08!-^zdg@-cGxcSQy`RT%N~>#%$gS zD|5Z12TJ*8F3HD;otHBoPA6T%oWE=Wwv5{4mGd3bc$`>#_|5z*i_r3ivtEWZ8kdqY z)5`j>RD{L+eq!MpLZA_Kzyl$`oVb&giTvDa6GrL@4a)|PHMEU+O|G0*3joChY$)H= zvaIoe@9!+%)S9X~cr8%sfL($+;${x)t2f%9=8Z4+_Pg*>z(jH3?fMYXv}GFzZa>dT z8JZ9dK8}^3tnnAH8A|?^q>GKS3_+Erh=U2ID=bkqNyQSz#z#%7Q&o;1dIJG75|hqP zwK(K>HtyPIkEmEmM>4WUNUhYWmgaO@i>JwuCg4~kAHp-+5<1}Zw}mEB)h`Fkbau7r zq=`txexXzWyLUZ5x}d#ls|BQ%x(e#cNsI z5EqjN9JY8Y=b(S@f10tuPW5vuo~PjiOiu2;cm!cj-3q znPDRp3jMb6X()bV$t|$&rMkvHD?K04X7PSgca`~dB-)PZP9k&w&dNSZf{R9HA&1EDMOp>$$czSm;3fr*k9Aib%||^9-h?i0D75-l{wj%AJTDDeb((k7;;W z7!Ip5ZrWkWM;wxDl(o`OUU&!dwuK~=pL$1a(1I-RP^)=7!a3Y{c&H9 z!B^Q=(uI~@WU1VcHHnT}K#u2LXo7~>mCX834vBWFd5Xv(v-t?j%#r5K_jNPCh}U4f zr0Ed-oG|J~zΤ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 E0*SBAV*mgE diff --git a/test/helper/golden_plugin_stubs.dart b/test/helper/golden_plugin_stubs.dart index d20b47b1c..d9141c945 100644 --- a/test/helper/golden_plugin_stubs.dart +++ b/test/helper/golden_plugin_stubs.dart @@ -68,4 +68,10 @@ void stubMobileScannerChannel() { const EventChannel('dev.steenbakker.mobile_scanner/scanner/event'), MockStreamHandler.inline(onListen: (arguments, sink) {}), ); + // mobile_scanner 7.x additionally subscribes to a device-orientation event + // stream; stub it as a no-op so `listen`/`cancel` don't throw MissingPluginException. + messenger.setMockStreamHandler( + const EventChannel('dev.steenbakker.mobile_scanner/scanner/deviceOrientation'), + MockStreamHandler.inline(onListen: (arguments, sink) {}), + ); } diff --git a/test/screens/pay/pay_scan_page_test.dart b/test/screens/pay/pay_scan_page_test.dart index 4689fd5b6..b1718d948 100644 --- a/test/screens/pay/pay_scan_page_test.dart +++ b/test/screens/pay/pay_scan_page_test.dart @@ -132,7 +132,6 @@ void main() { final errorWidget = scanner.errorBuilder!( context, MobileScannerException(errorCode: nonPermissionCode), - null, ); await tester.pumpWidget(MaterialApp(home: errorWidget)); diff --git a/test/widgets/scanner/qr_scanner_view_test.dart b/test/widgets/scanner/qr_scanner_view_test.dart new file mode 100644 index 000000000..ac9c0b5bc --- /dev/null +++ b/test/widgets/scanner/qr_scanner_view_test.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:realunit_wallet/widgets/scanner/qr_scanner_view.dart'; + +import '../../helper/helper.dart'; + +void main() { + setUpAll(stubMobileScannerChannel); + + group('$QrScannerView default error placeholder', () { + testWidgets( + 'renders the compact icon placeholder without overflow at high textScale in a tight box', + (tester) async { + await tester.pumpApp( + MediaQuery( + data: const MediaQueryData(size: Size(400, 800)) + .copyWith(textScaler: const TextScaler.linear(3.0)), + child: Center( + child: SizedBox( + width: 60, + height: 60, + child: QrScannerView(onDetect: (_) {}), + ), + ), + ), + ); + + // Let the stubbed permission handshake resolve into the + // permission-denied error state so the default error builder paints. + await tester.pump(); + await tester.pump(const Duration(milliseconds: 50)); + + expect(tester.takeException(), isNull); + expect(find.byIcon(Icons.error_outline), findsOneWidget); + final icon = tester.widget(find.byIcon(Icons.error_outline)); + expect(icon.size, 48); + }, + ); + }); +} From 98ce01fb34b7223f8c5ecf40955ab1544f401af2 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Fri, 24 Jul 2026 10:25:17 +0200 Subject: [PATCH 7/9] Add a payment-deeplink entry point for OpenCryptoPay (#876) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What Adds a payment-deeplink entry point so an incoming `realunit-wallet:lightning:` URL is routed straight into the existing OpenCryptoPay settlement flow (`PayScanCubit.onCodeDetected` → `LnurlDecoder` → REALU→ZCHF swap → ZCHF transfer). Until now that flow was reachable **only** by manually scanning a QR code from the dashboard "Pay" button; the `realunit-wallet://` scheme only foregrounded the app with no payload. This wires the missing external entry point. ## Why So DFX payment-link pages (e.g. `/pl`) can list RealUnit as a wallet and open it directly with a pre-filled payment. Companion to the backend listing in **DFXswiss/api#4339** (adds the `wallet_app` row with `deepLink='realunit-wallet:'`, from which the frontend builds `realunit-wallet:lightning:`). ## How - `extractPaymentDeeplinkPayload` (string-level, so it handles both the opaque `realunit-wallet:lightning:` and a `//`-normalized variant) strips the scheme and feeds `lightning:` (or a bare `LNURL…`/https lnurlp URL) verbatim into `onCodeDetected`. No pre-validation — a malformed payload is left for `LnurlDecoder` to reject (no silent fallback). - **Warm resume:** the redirect itself still returns `null` (a true no-op — match list / back-stack / `extra` untouched); the actual navigation is a deferred imperative `pushNamed('/pay')`, so it lands on top without clobbering a pushed route. - **Cold start:** the payload is stashed and replayed as a `/pay` push **only** once the boot/unlock ladder lands on the dashboard — i.e. strictly after the same PIN/biometric gate a normal `/pay` navigation passes. It can never bypass the lock. - `/pay` accepts an optional `initialPayload` via a **guarded** `state.extra` cast (never an unchecked cast); when present it skips the live camera and feeds the cubit once. - Preserves the documented `app_link_entry.dart` hardening for all non-payment scheme URLs. **No** iOS/Android manifest or entitlement changes — the `realunit-wallet` scheme is already registered; no new deeplink package. ## Tests 16 new widget/unit tests: payload-extraction forms (single-colon, `//`, bare LNURL, https lnurlp, canonical open→null, path-carrying→null, non-scheme→null); warm-resume push without back-stack clobber; canonical open never pushes `/pay`; cold-start stash + replay-only-after-dashboard; guarded non-String extra → null; `initialPayload` feeds the cubit once and skips the camera. `flutter analyze` clean; full suite green. ## Notes - **Staging-only feature.** The OpenCryptoPay pay flow (`LnurlDecoder`/`PayScanCubit`/`/pay`) currently lives only on `staging`; this builds on it. - **Device smoke test recommended before release:** OS-level custom-scheme delivery + a real LNURL round-trip are `@no-integration-test` (cannot run in CI). - The two-line diff in `assets/languages/strings_{de,en}.arb` is an incidental normalization produced by the mandatory `generate_localization.dart` codegen step (it split two entries that were on one line); no localization key or value was changed. --- assets/languages/strings_de.arb | 3 +- assets/languages/strings_en.arb | 3 +- lib/screens/home/bloc/home_bloc.dart | 5 + lib/screens/pay/pay_scan_page.dart | 84 ++++-- lib/screens/pin/bloc/auth/pin_auth_cubit.dart | 4 + lib/setup/routing/boot_navigation.dart | 81 +++++- lib/setup/routing/router_config.dart | 5 +- lib/setup/routing/routes/app_link_entry.dart | 140 +++++++++- test/screens/home/home_bloc_test.dart | 16 ++ test/screens/pay/pay_scan_page_test.dart | 99 +++++++ test/screens/pin/pin_auth_cubit_test.dart | 17 ++ test/setup/routing/app_link_entry_test.dart | 259 ++++++++++++++++++ .../routing/boot_navigation_apply_test.dart | 194 +++++++++++++ test/setup/routing/pay_route_test.dart | 46 ++++ 14 files changed, 927 insertions(+), 29 deletions(-) diff --git a/assets/languages/strings_de.arb b/assets/languages/strings_de.arb index 99cd63395..d27b1caea 100644 --- a/assets/languages/strings_de.arb +++ b/assets/languages/strings_de.arb @@ -57,7 +57,8 @@ "connectBitboxContentIos": "Bitte verbinden Sie Ihre BitBox mit Ihrem Smartphone und aktivieren Sie zusätzlich Bluetooth.", "connectBitboxFailed": "Es ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.", "connectBitboxNotInitialized": "Auf dieser BitBox ist noch keine Wallet eingerichtet. Bitte richten Sie über die BitBox-App eine Wallet auf dem Gerät ein oder stellen Sie eine wieder her und versuchen Sie es erneut.", - "connectBitboxNotInitializedTitle": "BitBox noch nicht eingerichtet", "connectBitboxSignatureCapturing": "Bitte bestätigen Sie die Anmeldeanfrage auf Ihrem BitBox-Gerät. Diese Signatur wird einmalig erfasst, damit künftige Käufe Ihre BitBox nicht erneut benötigen.", + "connectBitboxNotInitializedTitle": "BitBox noch nicht eingerichtet", + "connectBitboxSignatureCapturing": "Bitte bestätigen Sie die Anmeldeanfrage auf Ihrem BitBox-Gerät. Diese Signatur wird einmalig erfasst, damit künftige Käufe Ihre BitBox nicht erneut benötigen.", "connectBitboxSignatureCapturingTitle": "Anmeldung bestätigen", "connectBitboxSignatureFailed": "Ihre Anmeldesignatur konnte nicht erfasst werden. Sie können es erneut versuchen oder trotzdem fortfahren – Ihre BitBox wird dann möglicherweise für Ihren ersten Kauf erneut benötigt.", "connectBitboxSignatureFailedTitle": "Anmeldung nicht abgeschlossen", diff --git a/assets/languages/strings_en.arb b/assets/languages/strings_en.arb index 36cca22f9..913f4061a 100644 --- a/assets/languages/strings_en.arb +++ b/assets/languages/strings_en.arb @@ -57,7 +57,8 @@ "connectBitboxContentIos": "Please connect your BitBox with your Smartphone and activate Bluetooth.", "connectBitboxFailed": "Something went wrong. Please try to connect again.", "connectBitboxNotInitialized": "This BitBox has no wallet set up yet. Set up or restore a wallet on the device using the BitBox app, then try again.", - "connectBitboxNotInitializedTitle": "BitBox not set up yet", "connectBitboxSignatureCapturing": "Please confirm the sign-in request on your BitBox device. This signature is captured once so future purchases won't need your BitBox again.", + "connectBitboxNotInitializedTitle": "BitBox not set up yet", + "connectBitboxSignatureCapturing": "Please confirm the sign-in request on your BitBox device. This signature is captured once so future purchases won't need your BitBox again.", "connectBitboxSignatureCapturingTitle": "Confirm sign-in", "connectBitboxSignatureFailed": "We couldn't capture your sign-in signature. You can retry, or continue anyway – your BitBox may then be needed again for your first purchase.", "connectBitboxSignatureFailedTitle": "Sign-in not completed", diff --git a/lib/screens/home/bloc/home_bloc.dart b/lib/screens/home/bloc/home_bloc.dart index 6fd9d36ba..f57e4bdf5 100644 --- a/lib/screens/home/bloc/home_bloc.dart +++ b/lib/screens/home/bloc/home_bloc.dart @@ -9,6 +9,7 @@ import 'package:realunit_wallet/packages/service/settings_service.dart'; import 'package:realunit_wallet/packages/service/transaction_history_service.dart'; import 'package:realunit_wallet/packages/service/wallet_service.dart'; import 'package:realunit_wallet/packages/wallet/wallet.dart'; +import 'package:realunit_wallet/setup/routing/boot_navigation.dart'; part 'home_event.dart'; part 'home_state.dart'; @@ -102,6 +103,10 @@ class HomeBloc extends Bloc { await _walletService.deleteCurrentWallet(); _settingsService.setTermsAccepted(false); } + // Drop any stashed payment deeplink so it cannot replay into a re-onboarded + // wallet. Covers every DeleteCurrentWalletEvent path (settings delete and + // BitBox recovery cancel), including those that never call PinAuthCubit.reset(). + clearPendingPaymentDeeplink(); emit( HomeState( hasWallet: false, diff --git a/lib/screens/pay/pay_scan_page.dart b/lib/screens/pay/pay_scan_page.dart index 01affc73b..7d982ac5a 100644 --- a/lib/screens/pay/pay_scan_page.dart +++ b/lib/screens/pay/pay_scan_page.dart @@ -3,6 +3,7 @@ // camera. The decode logic it feeds is unit-tested in lnurl_decoder_test.dart // and the cubit behaviour in pay_scan_cubit_test.dart; the camera preview // itself is out of scope for widget tests. +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; @@ -13,25 +14,63 @@ import 'package:realunit_wallet/styles/colors.dart'; import 'package:realunit_wallet/widgets/scanner/qr_scanner_view.dart'; class PayScanPage extends StatelessWidget { - const PayScanPage({super.key}); + /// A DFX OpenCryptoPay payload already extracted from a payment deeplink + /// (e.g. `lightning:LNURL1...`), fed into the cubit as if it had been + /// scanned. Null for the normal live-camera entry (dashboard "Pay" button). + final String? initialPayload; + + const PayScanPage({super.key, this.initialPayload}); @override Widget build(BuildContext context) { return BlocProvider( create: (_) => PayScanCubit(), - child: const PayScanView(), + child: PayScanView(initialPayload: initialPayload), ); } } -class PayScanView extends StatelessWidget { - const PayScanView({super.key}); +class PayScanView extends StatefulWidget { + final String? initialPayload; + + const PayScanView({super.key, this.initialPayload}); + + @override + State createState() => _PayScanViewState(); +} + +class _PayScanViewState extends State { + /// True while waiting for the one-shot [initialPayload] decode; drives the + /// spinner so it can be dismissed after invalid/decoded (unlike the + /// immutable [PayScanView.initialPayload] widget field). + bool _awaitingInitialDecode = false; + + @override + void initState() { + super.initState(); + final payload = widget.initialPayload; + if (payload == null) return; + _awaitingInitialDecode = true; + // Deferred to after the first frame: BlocConsumer below only subscribes + // during that first build. Calling onCodeDetected synchronously here + // (before it subscribes) would emit PayScanDecoded/Invalid before + // anything is listening — BlocConsumer would just pick it up as its + // INITIAL state on first build, and its `listener` callback never fires + // for the initial state, only for later transitions. Deferring one frame + // guarantees the listener is already subscribed when the emission + // happens, so the PayQuotePage push / invalid-snackbar actually fires. + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + context.read().onCodeDetected(payload); + }); + } @override Widget build(BuildContext context) { return BlocConsumer( listener: (context, state) { if (state is PayScanDecoded) { + setState(() => _awaitingInitialDecode = false); Navigator.of(context).push( MaterialPageRoute( builder: (_) => PayQuotePage(paymentLinkId: state.link.id), @@ -41,6 +80,7 @@ class PayScanView extends StatelessWidget { context.read().reset(); } if (state is PayScanInvalid) { + setState(() => _awaitingInitialDecode = false); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(S.of(context).payScanInvalid), @@ -53,24 +93,26 @@ class PayScanView extends StatelessWidget { builder: (context, state) { return Scaffold( appBar: AppBar(title: Text(S.of(context).payScanTitle)), - body: QrScannerView( - onDetect: (raw) => context.read().onCodeDetected(raw), - errorBuilder: (context, error) { - final message = error.errorCode == MobileScannerErrorCode.permissionDenied - ? S.of(context).payScanCameraPermissionDenied - : S.of(context).payScanCameraUnavailable; - return Center( - child: Padding( - padding: const EdgeInsets.all(24), - child: Text( - message, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: RealUnitColors.neutral500), - ), + body: _awaitingInitialDecode + ? const Center(child: CupertinoActivityIndicator()) + : QrScannerView( + onDetect: (raw) => context.read().onCodeDetected(raw), + errorBuilder: (context, error) { + final message = error.errorCode == MobileScannerErrorCode.permissionDenied + ? S.of(context).payScanCameraPermissionDenied + : S.of(context).payScanCameraUnavailable; + return Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Text( + message, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: RealUnitColors.neutral500), + ), + ), + ); + }, ), - ); - }, - ), ); }, ); diff --git a/lib/screens/pin/bloc/auth/pin_auth_cubit.dart b/lib/screens/pin/bloc/auth/pin_auth_cubit.dart index c4e321efc..1a6ed44ac 100644 --- a/lib/screens/pin/bloc/auth/pin_auth_cubit.dart +++ b/lib/screens/pin/bloc/auth/pin_auth_cubit.dart @@ -3,6 +3,7 @@ import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:realunit_wallet/packages/storage/secure_storage.dart'; import 'package:realunit_wallet/screens/pin/constants/pin_constants.dart'; +import 'package:realunit_wallet/setup/routing/boot_navigation.dart'; part 'pin_auth_state.dart'; @@ -79,6 +80,9 @@ class PinAuthCubit extends Cubit { _secureStorage.resetPinLockout(), ]); _resumeLocation = null; + // A stashed payment deeplink must not survive into a freshly reset / + // re-onboarded wallet (Forgot PIN / Delete Wallet). + clearPendingPaymentDeeplink(); emit(const PinAuthState()); } } diff --git a/lib/setup/routing/boot_navigation.dart b/lib/setup/routing/boot_navigation.dart index bece67e06..b841f9e46 100644 --- a/lib/setup/routing/boot_navigation.dart +++ b/lib/setup/routing/boot_navigation.dart @@ -151,6 +151,47 @@ BootNavAction resolveBootNavigation({ return const BootNavGoNamed(AppRoutes.dashboard); } +/// Payment-deeplink payload captured by `appLinkSchemeRedirect` +/// (app_link_entry.dart) on a COLD start, or on a warm resume while the app is +/// still on a PIN/unlock gate, before the boot/unlock gate ladder has cleared. +/// Held here until [applyBootNavAction] replays it as an imperative `/pay` +/// push at the first post-unlock terminal landing — dashboard, restore, or +/// stay — i.e. strictly after the same PIN/unlock gate ladder a normal `/pay` +/// navigation passes, so the replay can never bypass it. A warm-resume +/// payment deeplink while already unlocked normally pushes straight away, +/// unless a re-lock is detected at the deferred execution-time check, in +/// which case it stashes here for post-unlock replay (same path as the +/// warm-locked branch). Pre-unlock gate waypoints (e.g. +/// [BootNavGoNamed] for PIN verify) never consume or replay the stash; every +/// post-unlock terminal landing (dashboard / restore / stay) replays and +/// consumes it, so the stash can never survive past the first of those. +/// +/// Last-write-wins on the stash — a newer deeplink deliberately supersedes an +/// older un-replayed one; no queueing. +String? _pendingPaymentDeeplink; + +/// Stashes [payload] for post-unlock replay via [applyBootNavAction]. +/// Last-write-wins: a newer deeplink deliberately supersedes an older +/// un-replayed one; no queueing. +void stashPendingPaymentDeeplink(String payload) { + _pendingPaymentDeeplink = payload; +} + +/// Returns the current stashed payload and clears it in the same call. +String? takePendingPaymentDeeplink() { + final payload = _pendingPaymentDeeplink; + _pendingPaymentDeeplink = null; + return payload; +} + +/// Clears any stashed payment deeplink without returning it. +void clearPendingPaymentDeeplink() { + _pendingPaymentDeeplink = null; +} + +/// Returns the current stashed payload without clearing it (read-only). +String? peekPendingPaymentDeeplink() => _pendingPaymentDeeplink; + /// Executes a [resolveBootNavigation] decision against [router]. Split out from /// `main.dart`'s `_navigate` so the routing side effects can be driven against a /// real [GoRouter] in a widget test (the decision itself is covered by the pure @@ -171,8 +212,24 @@ void applyBootNavAction( return; case BootNavGoNamed(:final routeName): // Reaching the dashboard is the final landing — drop any stale resume - // capture so a later benign emission can't bounce the user around. - if (routeName == AppRoutes.dashboard) onClearResume(); + // capture so a later benign emission can't bounce the user around, and + // replay a cold-start / locked-warm-resume payment deeplink now that the + // same gate ladder a normal /pay navigation passes has just been cleared + // (see takePendingPaymentDeeplink / stashPendingPaymentDeeplink). + if (routeName == AppRoutes.dashboard) { + onClearResume(); + final payload = takePendingPaymentDeeplink(); + if (payload != null) { + router.goNamed(routeName); + unawaited(router.pushNamed(AppRoutes.pay, extra: payload)); + return; + } + } + // Non-dashboard named navigation (PIN/setup/onboarding gates, …) is an + // intermediate step of the same boot ladder: the stash must survive + // through to the eventual dashboard landing so cold-start and + // locked-warm-resume payment deeplinks can still replay. Do NOT clear + // the pending-payment stash here. router.goNamed(routeName); return; case BootNavRestore(:final location): @@ -181,7 +238,11 @@ void applyBootNavAction( // real entry shape — dashboard as base, flow pushed on top — so the // flow's pop-based exits (Close buttons, AppBar auto-back) keep working; // a bare `go` would strand the restored route as the only match with - // nothing underneath to pop back to. + // nothing underneath to pop back to. onClearResume drops the spent + // capture so a later emission can't re-restore. Then, if a payment + // deeplink was stashed while locked, replay it as an imperative /pay + // push on top of the restored location (same post-unlock terminal + // consumption as the dashboard branch). onClearResume(); if (Uri.parse(location).path == '/dashboard') { router.go(location); @@ -189,10 +250,22 @@ void applyBootNavAction( router.goNamed(AppRoutes.dashboard); unawaited(router.push(location)); } + final payload = takePendingPaymentDeeplink(); + if (payload != null) { + unawaited(router.pushNamed(AppRoutes.pay, extra: payload)); + } return; case BootNavStay(): - // Already on a valid non-gate route — discard any stale capture. + // Already on a valid non-gate route — discard any stale resume capture. + // This is a post-unlock terminal landing: if a payment deeplink was + // stashed while locked, replay it as an imperative /pay push on top of + // the current location (the only navigation this branch performs when + // a stash exists). onClearResume(); + final payload = takePendingPaymentDeeplink(); + if (payload != null) { + unawaited(router.pushNamed(AppRoutes.pay, extra: payload)); + } return; } } diff --git a/lib/setup/routing/router_config.dart b/lib/setup/routing/router_config.dart index 83d3533a3..72c9d050f 100644 --- a/lib/setup/routing/router_config.dart +++ b/lib/setup/routing/router_config.dart @@ -67,6 +67,7 @@ final GoRouter routerConfig = GoRouter( redirect: (context, state) => appLinkSchemeRedirect( state, effectiveLocation(routerConfig.routerDelegate.currentConfiguration), + routerConfig, ), onException: appLinkOnException, routes: [ @@ -179,7 +180,9 @@ final GoRouter routerConfig = GoRouter( GoRoute( name: AppRoutes.pay, path: '/pay', - builder: (_, _) => const PayScanPage(), + builder: (_, state) => PayScanPage( + initialPayload: state.extra is String ? state.extra as String : null, + ), ), GoRoute( diff --git a/lib/setup/routing/routes/app_link_entry.dart b/lib/setup/routing/routes/app_link_entry.dart index 4a846fb80..277f19859 100644 --- a/lib/setup/routing/routes/app_link_entry.dart +++ b/lib/setup/routing/routes/app_link_entry.dart @@ -1,5 +1,11 @@ +import 'dart:async'; + import 'package:flutter/widgets.dart'; import 'package:go_router/go_router.dart'; +import 'package:realunit_wallet/screens/pin/bloc/auth/pin_auth_cubit.dart'; +import 'package:realunit_wallet/setup/di.dart'; +import 'package:realunit_wallet/setup/routing/boot_navigation.dart'; +import 'package:realunit_wallet/setup/routing/routes/app_routes.dart'; /// Custom URL scheme the app is opened with. Registered in /// `ios/Runner/Info.plist` (`CFBundleURLSchemes`) and @@ -14,6 +20,44 @@ const String appLinkUrl = 'realunit-wallet://open'; /// normal entry, identical to `GoRouter.initialLocation`. const String appLinkColdStartLocation = '/home'; +/// Detects and extracts a DFX OpenCryptoPay payment payload out of a +/// `realunit-wallet:…` deeplink. +/// +/// The DFX `/pl` frontend builds the deeplink generically as +/// `{deepLink}lightning:{LNURL}` with the backend `deepLink='realunit-wallet:'` +/// (no `//`), so the app receives the single-colon opaque form +/// `realunit-wallet:lightning:`. A `//`-prefixed variant +/// (`realunit-wallet://lightning:`) never reaches this function in +/// production: go_router derives `state.uri` via `Uri.parse` of the route-info, +/// which throws on that form before [appLinkSchemeRedirect] / +/// [extractPaymentDeeplinkPayload] is ever called — there is nothing to strip. +/// +/// Works on the raw URI STRING, not `Uri` component accessors: this is an +/// opaque URI, not a hierarchical one, so `.host`/`.path` are not a reliable +/// general parsing strategy. +/// +/// Returns the payload to feed into `PayScanCubit.onCodeDetected` VERBATIM +/// (e.g. `lightning:LNURL1...`, a bare `LNURL1...`, or a bare `https://...` +/// lnurlp URL), or null when [rawUri] is not a recognized payment deeplink — +/// including the canonical path-less open and any other path-carrying scheme +/// URL, which stay on the existing no-op / rewrite path in +/// [appLinkSchemeRedirect]. No validation beyond this structural sniff: a +/// malformed match is still returned and left for `LnurlDecoder` (reached via +/// `onCodeDetected`) to reject — this function must not swallow or +/// pre-validate (no silent fallback). +String? extractPaymentDeeplinkPayload(String rawUri) { + const prefix = '$appLinkScheme:'; + if (!rawUri.startsWith(prefix)) return null; + final remainder = rawUri.substring(prefix.length); + if (remainder.isEmpty) return null; + + final isLightning = remainder.startsWith('lightning:'); + final isBareLnurl = remainder.toUpperCase().startsWith('LNURL'); + final isHttpLnurlp = remainder.startsWith('http://') || remainder.startsWith('https://'); + if (!isLightning && !isBareLnurl && !isHttpLnurlp) return null; + return remainder; +} + /// Top-level go_router redirect for custom-scheme opens. /// /// A `realunit-wallet://…` open (canonical `realunit-wallet://open`) only brings @@ -45,16 +89,110 @@ const String appLinkColdStartLocation = '/home'; /// and [appLinkOnException] keeps the configuration — the same true no-op as /// the canonical open, with no rebuild at all. /// +/// Payment-deeplink carve-out: when the scheme URL carries a DFX OpenCryptoPay +/// payload (see [extractPaymentDeeplinkPayload]), the same security properties +/// still hold. Locked vs unlocked is classified by the authoritative pair +/// `isPinVerified && isPinSetup` (read only when already in-app / booted, so +/// the DI lookup is safe), not by location membership in [isGateLocation] and +/// not by `isPinVerified` alone. `PinAuthCubit.initialize()` sets +/// `isPinVerified: !isPinSetup`, so before any PIN is configured +/// `isPinVerified` is vacuously true — on `/setupPin` that auto-pass must not +/// be treated as "session unlocked" or a warm-resume payment deeplink would +/// deferred-push `/pay` and bypass the mandatory PIN-setup gate. +/// +/// Warm resume, unlocked (`isInAppRoute` and `isPinVerified && isPinSetup`, +/// including a step-up gate like `/pinGate` reached from an already-unlocked +/// seed-view / change-PIN settings flow): the redirect itself still returns +/// `null` — a true no-op for the match list / back-stack / `extra` — and the +/// actual navigation is an imperative `pushNamed` deferred to +/// `addPostFrameCallback`, so it lands on top of whatever is currently shown +/// (including on top of a step-up gate) without replacing it. Step-up gates +/// complete independently via `pushReplacementNamed` and are not part of the +/// boot/unlock ladder, so a stash while sitting on one would never be consumed +/// — deferred push is the only path that actually resolves a deeplink arriving +/// there. The deferred callback re-checks the same pair +/// `isPinVerified && isPinSetup` at execution time: an independent app-resume +/// re-lock (`AppLifecycleState.resumed` → `PinAuthCubit.onAppResumed()`) can +/// land between decision and push. If no longer unlocked by that pair, the +/// payload is stashed via [stashPendingPaymentDeeplink] instead — same +/// post-unlock replay path as the warm-locked branch. +/// +/// Warm resume, genuinely locked or not-yet-set-up (`isInAppRoute` and +/// `!(isPinVerified && isPinSetup)`, e.g. sitting on `/verifyPin` with +/// `isPinVerified == false`, or on `/setupPin` with `isPinSetup == false` even +/// when `isPinVerified` is the vacuous auto-pass): the payload is stashed via +/// [stashPendingPaymentDeeplink] and the redirect returns `null` so the gate +/// stays put — never push `/pay` over the lock screen or the PIN-setup gate. +/// The stash is replayed only once the PIN/unlock gate ladder lands on the +/// dashboard ([applyBootNavAction]'s dashboard / restore / stay branches), +/// never before — same post-unlock replay path as a cold start. +/// +/// Cold start (`!isInAppRoute`): the PIN gate and boot ladder have not run yet, +/// and getIt may not be ready — [PinAuthCubit] is not read here. The payload +/// is stashed via [stashPendingPaymentDeeplink] and the redirect hands off to +/// [appLinkColdStartLocation] so the normal PIN/unlock gate ladder runs first; +/// [applyBootNavAction] replays the payload as an imperative `/pay` push only +/// once that ladder lands on the dashboard. +/// /// Non-scheme (in-app) navigation is left untouched (`null`). // // @no-integration-test: OS-level custom-scheme delivery and foregrounding can // only be exercised on a real device, and no integration_test/ harness exists // in this repo yet. The redirect is covered by widget tests in // app_link_entry_test.dart. -String? appLinkSchemeRedirect(GoRouterState state, String currentLocation) { +String? appLinkSchemeRedirect(GoRouterState state, String currentLocation, GoRouter router) { if (state.uri.scheme != appLinkScheme) return null; final current = Uri.parse(currentLocation); final isInAppRoute = current.scheme.isEmpty && current.path.startsWith('/'); + + final paymentPayload = extractPaymentDeeplinkPayload(state.uri.toString()); + if (paymentPayload != null) { + if (isInAppRoute) { + if (getIt().state.isPinVerified && getIt().state.isPinSetup) { + // Warm resume, unlocked (including a step-up gate like /pinGate reached + // from an already-unlocked seed-view / change-PIN flow): defer the push + // to addPostFrameCallback (calling into the router synchronously from + // inside its own redirect is reentrant/unsafe) and re-check the pair + // isPinVerified && isPinSetup at execution time — a location snapshot + // is not proof against an independent AppLifecycleState.resumed re-lock + // landing between this decision and the deferred frame (TOCTOU). If + // still unlocked by that pair, push /pay on top of whatever is currently + // shown, including on top of a step-up gate; that gate's own flow + // (seed-view / change-PIN) completes independently via + // pushReplacementNamed and is not part of the boot/unlock ladder, so a + // stash here would never be consumed — deferred-push is the only path + // that actually resolves a deeplink arriving while on a step-up gate. + WidgetsBinding.instance.addPostFrameCallback((_) { + final pinState = getIt().state; + if (!(pinState.isPinVerified && pinState.isPinSetup)) { + stashPendingPaymentDeeplink(paymentPayload); + return; + } + unawaited(router.pushNamed(AppRoutes.pay, extra: paymentPayload)); + }); + return null; + } + // Warm resume, locked or not-yet-set-up (!(isPinVerified && isPinSetup) — + // on /verifyPin with isPinVerified false, or on /setupPin with + // isPinSetup false even when isPinVerified is the vacuous auto-pass from + // PinAuthCubit.initialize()). Stash the payload and stay on the gate — + // never push /pay over the lock screen or the PIN-setup gate. + // applyBootNavAction replays the stash only after the boot/unlock gate + // ladder lands on the dashboard (same post-unlock replay path as a + // cold-start stash). + stashPendingPaymentDeeplink(paymentPayload); + return null; + } + // Cold start: the PIN gate and boot ladder have not run yet, and getIt may + // not be ready — do not read PinAuthCubit here. Stash the payload; + // applyBootNavAction (boot_navigation.dart) replays it as an imperative + // /pay push strictly after resolveBootNavigation lands on the dashboard — + // i.e. after the same PIN/unlock gate ladder a normal /pay navigation + // passes, never before. + stashPendingPaymentDeeplink(paymentPayload); + return appLinkColdStartLocation; + } + if (!isInAppRoute) return appLinkColdStartLocation; final hasPath = state.uri.path.isNotEmpty && state.uri.path != '/'; return hasPath ? appLinkUrl : null; diff --git a/test/screens/home/home_bloc_test.dart b/test/screens/home/home_bloc_test.dart index 7f2ef30a6..b4e734976 100644 --- a/test/screens/home/home_bloc_test.dart +++ b/test/screens/home/home_bloc_test.dart @@ -9,6 +9,7 @@ import 'package:realunit_wallet/packages/service/transaction_history_service.dar import 'package:realunit_wallet/packages/service/wallet_service.dart'; import 'package:realunit_wallet/packages/wallet/wallet.dart'; import 'package:realunit_wallet/screens/home/bloc/home_bloc.dart'; +import 'package:realunit_wallet/setup/routing/boot_navigation.dart'; class _MockWalletService extends Mock implements WalletService {} @@ -401,6 +402,21 @@ void main() { // again. expect(bloc.state.softwareTermsAccepted, isTrue); }); + + test('clears a stashed payment deeplink so it cannot replay into a re-onboarded wallet', () async { + addTearDown(clearPendingPaymentDeeplink); + stashPendingPaymentDeeplink('lightning:LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD'); + + final bloc = build(); + await bloc.stream.firstWhere((s) => true); + + bloc.add(const DeleteCurrentWalletEvent()); + await bloc.stream.firstWhere( + (s) => s.isLoadingWallet == false && s.hasWallet == false, + ); + + expect(peekPendingPaymentDeeplink(), isNull); + }); }); group('CompleteOnboardingEvent', () { diff --git a/test/screens/pay/pay_scan_page_test.dart b/test/screens/pay/pay_scan_page_test.dart index b1718d948..404af7bff 100644 --- a/test/screens/pay/pay_scan_page_test.dart +++ b/test/screens/pay/pay_scan_page_test.dart @@ -1,4 +1,5 @@ import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -140,5 +141,103 @@ void main() { expect(find.text(S.current.payScanCameraPermissionDenied), findsNothing); }, ); + + testWidgets( + 'an initialPayload feeds the cubit exactly once and skips the live camera', + (tester) async { + when(() => scanCubit.onCodeDetected(any())).thenReturn(null); + + await tester.pumpApp( + BlocProvider.value( + value: scanCubit, + child: const PayScanView( + initialPayload: 'lightning:LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD', + ), + ), + ); + // CupertinoActivityIndicator animates indefinitely, so pumpAndSettle + // would hang. One extra pump is enough for the deferred post-frame + // onCodeDetected callback to fire. + await tester.pump(); + + verify( + () => scanCubit.onCodeDetected( + 'lightning:LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD', + ), + ).called(1); + expect(find.byType(MobileScanner), findsNothing); + }, + ); + + testWidgets( + 'an initialPayload with a successful decode dismisses the spinner and navigates to the quote step', + (tester) async { + when(() => scanCubit.onCodeDetected(any())).thenReturn(null); + final link = DecodedPaymentLink( + id: 'pl_abc123', + lnurlpUrl: Uri.parse('https://api.dfx.swiss/v1/lnurlp/pl_abc123'), + ); + whenListen( + scanCubit, + Stream.fromIterable([PayScanDecoded(link)]), + initialState: const PayScanScanning(), + ); + + await tester.pumpApp( + BlocProvider.value( + value: scanCubit, + child: const PayScanView( + initialPayload: 'lightning:LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD', + ), + ), + ); + await tester.pumpAndSettle(); + + // Spinner dismissed, quote step pushed and rendered; the cubit is reset. + expect(find.byType(CupertinoActivityIndicator), findsNothing); + expect(find.byType(PayQuoteView), findsOne); + verify(() => scanCubit.reset()).called(1); + }, + ); + + testWidgets( + 'an invalid initialPayload dismisses the spinner and shows the live camera', + (tester) async { + whenListen( + scanCubit, + Stream.fromIterable([ + const PayScanInvalid('bad code'), + ]), + initialState: const PayScanScanning(), + ); + + await tester.pumpApp( + BlocProvider.value( + value: scanCubit, + child: const PayScanView( + initialPayload: 'lightning:LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD', + ), + ), + ); + // CupertinoActivityIndicator animates indefinitely, so pumpAndSettle + // would hang. One extra pump is enough for the listener (invalid → + // setState dismissing the spinner) and snackbar to run. + await tester.pump(); + + expect(find.byType(SnackBar), findsOne); + expect(find.byType(MobileScanner), findsOneWidget); + }, + ); + + testWidgets( + 'no initialPayload never calls onCodeDetected and shows the live camera', + (tester) async { + await tester.pumpApp(buildSubject()); + await tester.pumpAndSettle(); + + verifyNever(() => scanCubit.onCodeDetected(any())); + expect(find.byType(MobileScanner), findsOneWidget); + }, + ); }); } diff --git a/test/screens/pin/pin_auth_cubit_test.dart b/test/screens/pin/pin_auth_cubit_test.dart index d6f52e388..43b8a8583 100644 --- a/test/screens/pin/pin_auth_cubit_test.dart +++ b/test/screens/pin/pin_auth_cubit_test.dart @@ -5,6 +5,7 @@ import 'package:mocktail/mocktail.dart'; import 'package:realunit_wallet/packages/storage/secure_storage.dart'; import 'package:realunit_wallet/screens/pin/bloc/auth/pin_auth_cubit.dart'; import 'package:realunit_wallet/screens/pin/constants/pin_constants.dart'; +import 'package:realunit_wallet/setup/routing/boot_navigation.dart'; class _MockSecureStorage extends Mock implements SecureStorage {} @@ -226,6 +227,22 @@ void main() { await cubit.reset(); expect(cubit.peekResumeLocation(), isNull); }); + + test( + 'reset clears a pending payment deeplink stash (no replay into a reset/re-onboarded wallet)', + () async { + when(() => storage.deletePinHash()).thenAnswer((_) async {}); + when(() => storage.deleteBiometricEnabled()).thenAnswer((_) async {}); + when(() => storage.resetPinLockout()).thenAnswer((_) async {}); + addTearDown(clearPendingPaymentDeeplink); + + stashPendingPaymentDeeplink('lightning:LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD'); + final cubit = build(); + await cubit.reset(); + + expect(peekPendingPaymentDeeplink(), isNull); + }, + ); }); group('reset', () { diff --git a/test/setup/routing/app_link_entry_test.dart b/test/setup/routing/app_link_entry_test.dart index f61c5bb89..608cfafe0 100644 --- a/test/setup/routing/app_link_entry_test.dart +++ b/test/setup/routing/app_link_entry_test.dart @@ -2,11 +2,29 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; import 'package:go_router/go_router.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/screens/pin/bloc/auth/pin_auth_cubit.dart'; import 'package:realunit_wallet/setup/routing/boot_navigation.dart'; import 'package:realunit_wallet/setup/routing/routes/app_link_entry.dart'; +import 'package:realunit_wallet/setup/routing/routes/app_routes.dart'; + +class _MockPinAuthCubit extends Mock implements PinAuthCubit {} void main() { + late _MockPinAuthCubit pinAuthCubit; + + setUp(() { + pinAuthCubit = _MockPinAuthCubit(); + GetIt.instance.registerSingleton(pinAuthCubit); + when(() => pinAuthCubit.state).thenReturn( + const PinAuthState(isPinVerified: true, isPinSetup: true), + ); + }); + + tearDown(() => GetIt.instance.reset()); + // A minimal router wired to the *production* scheme handling — the // [appLinkSchemeRedirect] + [appLinkOnException] pair with the push-aware // [effectiveLocation], exactly as router_config.dart wires it. `/home` is the @@ -19,6 +37,7 @@ void main() { redirect: (context, state) => appLinkSchemeRedirect( state, effectiveLocation(router.routerDelegate.currentConfiguration), + router, ), onException: appLinkOnException, routes: [ @@ -41,6 +60,28 @@ void main() { builder: (_, state) => Scaffold(body: Text(state.extra! as String, key: const Key('details'))), ), + // Gate routes from `gateLocations` — needed so warm-resume tests can + // land on a real lock path and prove /pay is NOT pushed over it. + GoRoute( + path: '/verifyPin', + builder: (_, _) => + const Scaffold(body: Text('VERIFY', key: Key('verifyPin'))), + ), + GoRoute( + path: '/pinGate', + builder: (_, _) => + const Scaffold(body: Text('PINGATE', key: Key('pinGate'))), + ), + GoRoute( + path: '/setupPin', + builder: (_, _) => + const Scaffold(body: Text('SETUP', key: Key('setupPin'))), + ), + GoRoute( + name: AppRoutes.pay, + path: '/pay', + builder: (_, state) => Scaffold(body: Text('PAY:${state.extra}', key: const Key('pay'))), + ), ], ); return router; @@ -62,6 +103,48 @@ void main() { expect(appLinkColdStartLocation, '/home'); }); + group('extractPaymentDeeplinkPayload', () { + const sampleLnurl = + 'LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD9E3K7MF0V9CXJTMKXP6XCEF'; + + test('single-colon lightning form extracts the lightning payload', () { + expect( + extractPaymentDeeplinkPayload('realunit-wallet:lightning:$sampleLnurl'), + 'lightning:$sampleLnurl', + ); + }); + + test('bare LNURL form extracts the LNURL verbatim', () { + expect( + extractPaymentDeeplinkPayload('realunit-wallet:$sampleLnurl'), + sampleLnurl, + ); + }); + + test('https lnurlp form extracts the URL verbatim', () { + const url = 'https://api.dfx.swiss/v1/lnurlp/pl_abc123'; + expect( + extractPaymentDeeplinkPayload('realunit-wallet:$url'), + url, + ); + }); + + test('canonical path-less open returns null', () { + expect(extractPaymentDeeplinkPayload(appLinkUrl), isNull); + }); + + test('path-carrying scheme URL returns null', () { + expect( + extractPaymentDeeplinkPayload('realunit-wallet://open/settings'), + isNull, + ); + }); + + test('non-scheme string returns null', () { + expect(extractPaymentDeeplinkPayload('https://example.com'), isNull); + }); + }); + testWidgets('warm resume: a scheme open keeps the user on /dashboard', ( tester, ) async { @@ -130,6 +213,164 @@ void main() { }, ); + testWidgets( + 'warm resume: a payment deeplink pushes /pay with the payload as extra ' + 'on top of the current stack (pushed route not clobbered)', + (tester) async { + const sampleLnurl = + 'LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD9E3K7MF0V9CXJTMKXP6XCEF'; + final router = await pump(tester); + router.go('/dashboard'); + await tester.pumpAndSettle(); + + // Mirror the KYC-pattern test: push a route so the stack is non-trivial. + unawaited(router.push('/settings')); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('settings')), findsOneWidget); + + // Single-colon form — the double-slash form throws in Uri.parse / go. + router.go('realunit-wallet:lightning:$sampleLnurl'); + await tester.pumpAndSettle(); + + expect(find.byKey(const Key('pay')), findsOneWidget); + expect(find.text('PAY:lightning:$sampleLnurl'), findsOneWidget); + + // The push sits ON TOP of the existing stack — nothing was replaced. + expect(router.canPop(), isTrue); + router.pop(); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('settings')), findsOneWidget); + expect(router.canPop(), isTrue); + router.pop(); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('dashboard')), findsOneWidget); + }, + ); + + testWidgets( + 'warm resume: a re-lock landing before the deferred push runs is honored — ' + 'no /pay push, payload stashed for post-unlock replay', + (tester) async { + addTearDown(clearPendingPaymentDeeplink); + const sampleLnurl = + 'LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD9E3K7MF0V9CXJTMKXP6XCEF'; + final router = await pump(tester); + router.go('/dashboard'); + await tester.pumpAndSettle(); + + // Keep setUp's unlocked stub (isPinVerified && isPinSetup) so the + // decision-time check schedules the deferred push callback. + router.go('realunit-wallet:lightning:$sampleLnurl'); + // Flip the mock AFTER router.go and BEFORE pumpAndSettle so the + // decision-time check reads unlocked (schedules the deferred callback) + // while the execution-time re-check reads locked — this is what makes + // the test non-vacuous (it would fail if the postFrame re-check were + // deleted, since the deferred callback would then unconditionally push). + // Simulates a re-lock (AppLifecycleState.resumed -> onAppResumed()) + // landing between decision and deferred frame. + when(() => pinAuthCubit.state).thenReturn(const PinAuthState(isPinVerified: false)); + await tester.pumpAndSettle(); + + expect(find.byKey(const Key('pay')), findsNothing); + expect(currentPath(router), '/dashboard'); + expect(peekPendingPaymentDeeplink(), 'lightning:$sampleLnurl'); + }, + ); + + testWidgets( + 'warm resume: the canonical no-payload open never pushes /pay', + (tester) async { + final router = await pump(tester); + router.go('/dashboard'); + await tester.pumpAndSettle(); + + router.go(appLinkUrl); + await tester.pumpAndSettle(); + + expect(find.byKey(const Key('pay')), findsNothing); + expect(find.byKey(const Key('dashboard')), findsOneWidget); + }, + ); + + // Genuinely-locked warm-resume: a payment deeplink while isPinVerified is + // false (real PIN/unlock gates) must stash and stay — never push /pay over + // the lock screen. Step-up gates (e.g. /pinGate while already unlocked) take + // the deferred-push path instead; see the /pinGate test below. + testWidgets( + 'warm resume on /verifyPin while locked: payment deeplink stashes and does not push /pay', + (tester) async { + addTearDown(clearPendingPaymentDeeplink); + when(() => pinAuthCubit.state).thenReturn(const PinAuthState(isPinVerified: false)); + const sampleLnurl = + 'LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD9E3K7MF0V9CXJTMKXP6XCEF'; + final router = await pump(tester); + router.go('/verifyPin'); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('verifyPin')), findsOneWidget); + + router.go('realunit-wallet:lightning:$sampleLnurl'); + await tester.pumpAndSettle(); + + expect(find.byKey(const Key('pay')), findsNothing); + expect(find.byKey(const Key('verifyPin')), findsOneWidget); + expect(currentPath(router), '/verifyPin'); + expect(peekPendingPaymentDeeplink(), 'lightning:$sampleLnurl'); + }, + ); + + // /setupPin runtime state from PinAuthCubit.initialize() is + // isPinSetup=false, isPinVerified=true (vacuous auto-pass before any PIN is + // configured). Classifying unlock by isPinVerified alone would deferred-push + // /pay and bypass the mandatory PIN-setup gate — this regression guard uses + // the real pair state. + testWidgets( + 'warm resume on /setupPin (vacuous isPinVerified before any PIN is configured): ' + 'payment deeplink stashes and does not push /pay', + (tester) async { + addTearDown(clearPendingPaymentDeeplink); + when(() => pinAuthCubit.state).thenReturn( + const PinAuthState(isPinSetup: false, isPinVerified: true), + ); + const sampleLnurl = + 'LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD9E3K7MF0V9CXJTMKXP6XCEF'; + final router = await pump(tester); + router.go('/setupPin'); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('setupPin')), findsOneWidget); + + router.go('realunit-wallet:lightning:$sampleLnurl'); + await tester.pumpAndSettle(); + + expect(find.byKey(const Key('pay')), findsNothing); + expect(find.byKey(const Key('setupPin')), findsOneWidget); + expect(currentPath(router), '/setupPin'); + expect(peekPendingPaymentDeeplink(), 'lightning:$sampleLnurl'); + }, + ); + + testWidgets( + 'warm resume on /pinGate while unlocked (step-up): payment deeplink ' + 'deferred-pushes /pay and does not stash', + (tester) async { + addTearDown(clearPendingPaymentDeeplink); + // Already unlocked — e.g. /pinGate reached from settings seed-view / + // change-PIN. setUp already stubs isPinVerified && isPinSetup. + const sampleLnurl = + 'LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD9E3K7MF0V9CXJTMKXP6XCEF'; + final router = await pump(tester); + router.go('/pinGate'); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('pinGate')), findsOneWidget); + + router.go('realunit-wallet:lightning:$sampleLnurl'); + await tester.pumpAndSettle(); + + expect(find.byKey(const Key('pay')), findsOneWidget); + expect(find.text('PAY:lightning:$sampleLnurl'), findsOneWidget); + expect(peekPendingPaymentDeeplink(), isNull); + }, + ); + testWidgets('cold start on the scheme URL boots to the normal entry', ( tester, ) async { @@ -141,6 +382,24 @@ void main() { expect(find.byKey(const Key('home')), findsOneWidget); }); + testWidgets( + 'cold start on a payment deeplink boots to /home and stashes the payload', + (tester) async { + addTearDown(clearPendingPaymentDeeplink); + const sampleLnurl = + 'LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD9E3K7MF0V9CXJTMKXP6XCEF'; + + final router = await pump( + tester, + initial: 'realunit-wallet:lightning:$sampleLnurl', + ); + + expect(currentPath(router), appLinkColdStartLocation); + expect(find.byKey(const Key('home')), findsOneWidget); + expect(peekPendingPaymentDeeplink(), 'lightning:$sampleLnurl'); + }, + ); + testWidgets( 'cold start on any scheme host/path boots without an error screen', (tester) async { diff --git a/test/setup/routing/boot_navigation_apply_test.dart b/test/setup/routing/boot_navigation_apply_test.dart index 53be5a027..64d5a9067 100644 --- a/test/setup/routing/boot_navigation_apply_test.dart +++ b/test/setup/routing/boot_navigation_apply_test.dart @@ -42,6 +42,11 @@ void main() { path: '/buyPaymentDetails', builder: (_, state) => Text('buy ${state.extra as Object}'), ), + GoRoute( + name: AppRoutes.pay, + path: '/pay', + builder: (_, state) => Text('pay ${state.extra}'), + ), ], ); @@ -201,4 +206,193 @@ void main() { expect(effectiveLocation(router.routerDelegate.currentConfiguration), '/kyc'); }, ); + + group('pendingPaymentDeeplink replay', () { + setUp(clearPendingPaymentDeeplink); + tearDown(clearPendingPaymentDeeplink); + + testWidgets( + 'dashboard landing with a stashed payload pushes /pay on top and consumes the stash', + (tester) async { + final router = buildRouter(); + await tester.pumpWidget(MaterialApp.router(routerConfig: router)); + await tester.pumpAndSettle(); + + stashPendingPaymentDeeplink('lightning:LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD'); + applyBootNavAction( + const BootNavGoNamed(AppRoutes.dashboard), + router, + onLoadWallet: () {}, + onClearResume: () {}, + ); + await tester.pumpAndSettle(); + + // /pay is pushed ON TOP of /dashboard: once an opaque push settles, + // Flutter only keeps the topmost route built, so /dashboard's own + // Text is correctly absent from the tree right now (not a bug) — + // prove "on top of, not instead of" via the match list + canPop, and + // via the AFTER-pop check below where /dashboard is onstage again. + expect(find.text('pay lightning:LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD'), findsOneWidget); + expect( + router.routerDelegate.currentConfiguration.matches + .map((m) => m.matchedLocation) + .toList(), + ['/dashboard', '/pay'], + ); + expect(peekPendingPaymentDeeplink(), isNull); + expect(router.canPop(), isTrue); + router.pop(); + await tester.pumpAndSettle(); + expect(find.text('dashboard'), findsOneWidget); + }, + ); + + testWidgets( + 'dashboard landing with no stash pushes nothing extra', + (tester) async { + final router = buildRouter(); + await tester.pumpWidget(MaterialApp.router(routerConfig: router)); + await tester.pumpAndSettle(); + + applyBootNavAction( + const BootNavGoNamed(AppRoutes.dashboard), + router, + onLoadWallet: () {}, + onClearResume: () {}, + ); + await tester.pumpAndSettle(); + + expect(find.text('dashboard'), findsOneWidget); + expect(router.canPop(), isFalse); + }, + ); + + testWidgets( + 'a non-dashboard gate landing never consumes or replays the stash', + (tester) async { + final router = buildRouter(); + await tester.pumpWidget(MaterialApp.router(routerConfig: router)); + await tester.pumpAndSettle(); + + stashPendingPaymentDeeplink('lightning:LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD'); + applyBootNavAction( + const BootNavGoNamed(PinRoutes.verify), + router, + onLoadWallet: () {}, + onClearResume: () {}, + ); + await tester.pumpAndSettle(); + + expect(find.text('verify'), findsOneWidget); + expect(peekPendingPaymentDeeplink(), 'lightning:LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD'); + }, + ); + + testWidgets( + 'warm unlock with stash: resolveAfterRelock(null) lands on dashboard, ' + 'pushes /pay, and consumes the stash', + (tester) async { + final router = buildRouter(); + await tester.pumpWidget(MaterialApp.router(routerConfig: router)); + await tester.pumpAndSettle(); + + // Full gate-ladder path (not a bare BootNavGoNamed): after a plain + // PIN unlock with no captured resume location, resolveBootNavigation + // returns BootNavGoNamed(dashboard), which replays the stash. + stashPendingPaymentDeeplink('lightning:LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD'); + applyBootNavAction( + resolveAfterRelock(null), + router, + onLoadWallet: () {}, + onClearResume: () {}, + ); + await tester.pumpAndSettle(); + + expect(find.text('pay lightning:LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD'), findsOneWidget); + expect( + router.routerDelegate.currentConfiguration.matches + .map((m) => m.matchedLocation) + .toList(), + ['/dashboard', '/pay'], + ); + expect(peekPendingPaymentDeeplink(), isNull); + expect(router.canPop(), isTrue); + router.pop(); + await tester.pumpAndSettle(); + expect(find.text('dashboard'), findsOneWidget); + }, + ); + + testWidgets( + 'BootNavRestore replays a stashed pendingPaymentDeeplink, pushing /pay ' + 'on top of the restored location', + (tester) async { + final router = buildRouter(); + await tester.pumpWidget(MaterialApp.router(routerConfig: router)); + await tester.pumpAndSettle(); + + stashPendingPaymentDeeplink('lightning:LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD'); + applyBootNavAction( + resolveAfterRelock('/kyc'), + router, + onLoadWallet: () {}, + onClearResume: () {}, + ); + await tester.pumpAndSettle(); + + // /pay is pushed ON TOP of the restored stack (dashboard base + /kyc): + // prove via the match list + canPop, and via the AFTER-pop check + // where /kyc is onstage again. + expect(find.text('pay lightning:LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD'), findsOneWidget); + expect( + router.routerDelegate.currentConfiguration.matches + .map((m) => m.matchedLocation) + .toList(), + ['/dashboard', '/kyc', '/pay'], + ); + expect(peekPendingPaymentDeeplink(), isNull); + expect(router.canPop(), isTrue); + router.pop(); + await tester.pumpAndSettle(); + expect(find.text('kyc'), findsOneWidget); + }, + ); + + testWidgets( + 'BootNavStay replays a stashed pendingPaymentDeeplink, pushing /pay on ' + 'top of the current location', + (tester) async { + final router = buildRouter(); + await tester.pumpWidget(MaterialApp.router(routerConfig: router)); + await tester.pumpAndSettle(); + + // BootNavStay itself never navigates — put the router on a real + // non-gate location first so /pay has something to land on top of. + router.go('/dashboard'); + await tester.pumpAndSettle(); + + stashPendingPaymentDeeplink('lightning:LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD'); + applyBootNavAction( + const BootNavStay(), + router, + onLoadWallet: () {}, + onClearResume: () {}, + ); + await tester.pumpAndSettle(); + + expect(find.text('pay lightning:LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD'), findsOneWidget); + expect( + router.routerDelegate.currentConfiguration.matches + .map((m) => m.matchedLocation) + .toList(), + ['/dashboard', '/pay'], + ); + expect(peekPendingPaymentDeeplink(), isNull); + expect(router.canPop(), isTrue); + router.pop(); + await tester.pumpAndSettle(); + expect(find.text('dashboard'), findsOneWidget); + }, + ); + }); } diff --git a/test/setup/routing/pay_route_test.dart b/test/setup/routing/pay_route_test.dart index 4cdb9a707..c0a1dda27 100644 --- a/test/setup/routing/pay_route_test.dart +++ b/test/setup/routing/pay_route_test.dart @@ -62,4 +62,50 @@ void main() { // does not leak the /pay location into any later test. addTearDown(() => routerConfig.goNamed(AppRoutes.home)); }); + + testWidgets( + 'the pay route passes a String extra through as initialPayload', + (tester) async { + await pumpRouter(tester); + + routerConfig.pushNamed( + AppRoutes.pay, + extra: 'lightning:LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD', + ); + // A single pump is not enough for an imperative pushNamed to land in + // the tree yet, and pumpAndSettle is unsafe here (PayScanView shows an + // indefinitely-animating CupertinoActivityIndicator once initialPayload + // is set). Two plain pumps reliably lands the pushed route. + await tester.pump(); + await tester.pump(); + + expect(tester.takeException(), isNull); + expect( + tester.widget(find.byType(PayScanPage)).initialPayload, + 'lightning:LNURL1DP68GURN8GHJ7VF3XGENJVE5UMD', + ); + + addTearDown(() => routerConfig.goNamed(AppRoutes.home)); + }, + ); + + testWidgets( + 'the pay route guards a non-String extra to null (never an unchecked cast)', + (tester) async { + await pumpRouter(tester); + + routerConfig.pushNamed(AppRoutes.pay, extra: 42); + // Same reasoning as the test above: two plain pumps, no pumpAndSettle. + await tester.pump(); + await tester.pump(); + + expect(tester.takeException(), isNull); + expect( + tester.widget(find.byType(PayScanPage)).initialPayload, + isNull, + ); + + addTearDown(() => routerConfig.goNamed(AppRoutes.home)); + }, + ); } From af2f1b20527b8e734ea3824ed2c29d9da490d2c2 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Fri, 24 Jul 2026 21:41:26 +0200 Subject: [PATCH 8/9] Wire Sentry into native release builds (#880) ## Summary - inject the repository `SENTRY_DSN` into Android and iOS store builds with an explicit `production`/`internal` environment - keep compile-time values out of Fastlane process arguments via a mode-0600 temporary define file and fail release builds when the DSN is missing - upload Android split debug info, Dart obfuscation maps, and iOS archive symbols to the self-hosted Sentry project - identify uploads with the native release name `swiss.realunit.app@+` and matching distribution The Sentry project `realunitchapp` and the repository secrets `SENTRY_DSN` and `SENTRY_AUTH_TOKEN` have been provisioned. Runtime reporting activates together with #878. Related to DFXServer/server#958. ## Validation - Ruby syntax checks for both Fastfiles - workflow YAML parse and `git diff --check` - `flutter analyze --no-fatal-infos` - non-golden Flutter suite: 4,687 tests passed; the 16 subprocess cases initially failed only because local `dart` was absent from PATH and all 16 passed when rerun with the Flutter SDK path - DFX PR precheck: 3 rounds, final conformity and logic reviews both at 0 findings ## Follow-up verification The first tagged native release should confirm the uploaded debug files in Sentry and symbolicate one controlled test event. --------- Co-authored-by: Daniel Padrino --- .github/workflows/release.yaml | 28 +++++++-- android/fastlane/Fastfile | 104 ++++++++++++++++++++++++++------- ios/fastlane/Fastfile | 67 ++++++++++++++++++--- pubspec.lock | 48 +++++++++++++++ pubspec.yaml | 12 ++++ 5 files changed, 225 insertions(+), 34 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 8732c0366..7fdbe341a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -39,6 +39,7 @@ jobs: outputs: proceed: ${{ steps.check.outputs.proceed }} is_prerelease: ${{ steps.check.outputs.is_prerelease }} + sentry_environment: ${{ steps.check.outputs.sentry_environment }} steps: - name: Inspect tag id: check @@ -48,19 +49,28 @@ jobs: # Strict shape: vX.Y.Z with all parts numeric, no suffix. if ! echo "$TAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then echo "::notice::Tag $TAG is not a plain vX.Y.Z tag; skipping release." - echo "proceed=false" >> $GITHUB_OUTPUT - echo "is_prerelease=false" >> $GITHUB_OUTPUT + { + echo "proceed=false" + echo "is_prerelease=false" + echo "sentry_environment=internal" + } >> "$GITHUB_OUTPUT" exit 0 fi PATCH=$(echo "$TAG" | cut -d. -f3) if [ "$PATCH" -eq 0 ]; then echo "::notice::Tag $TAG has PATCH=0; running production-candidate release." - echo "proceed=true" >> $GITHUB_OUTPUT - echo "is_prerelease=false" >> $GITHUB_OUTPUT + { + echo "proceed=true" + echo "is_prerelease=false" + echo "sentry_environment=production" + } >> "$GITHUB_OUTPUT" else echo "::notice::Tag $TAG has PATCH=$PATCH; running internal release." - echo "proceed=true" >> $GITHUB_OUTPUT - echo "is_prerelease=true" >> $GITHUB_OUTPUT + { + echo "proceed=true" + echo "is_prerelease=true" + echo "sentry_environment=internal" + } >> "$GITHUB_OUTPUT" fi store-metadata-preflight: @@ -172,6 +182,9 @@ jobs: working-directory: android env: NEW_VERSION: ${{ github.ref_name }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + SENTRY_ENVIRONMENT: ${{ needs.guard.outputs.sentry_environment }} - name: Upload APK artifact uses: actions/upload-artifact@v4 @@ -269,6 +282,9 @@ jobs: working-directory: ios env: NEW_VERSION: ${{ github.ref_name }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + SENTRY_ENVIRONMENT: ${{ needs.guard.outputs.sentry_environment }} - name: Upload IPA artifact uses: actions/upload-artifact@v4 diff --git a/android/fastlane/Fastfile b/android/fastlane/Fastfile index 93593b8eb..eb2a22c02 100644 --- a/android/fastlane/Fastfile +++ b/android/fastlane/Fastfile @@ -13,8 +13,51 @@ # Uncomment the line if you want fastlane to automatically update itself # update_fastlane +require "json" +require "tempfile" + default_platform(:android) +# Keeps the DSN out of the process arguments Fastlane prints. Flutter reads +# both compile-time values from this short-lived, mode-0600 JSON file. +def with_crash_reporting_defines + dsn = ENV.fetch("SENTRY_DSN", "") + UI.user_error!("SENTRY_DSN is required for store release builds") if dsn.empty? + + environment = ENV.fetch("SENTRY_ENVIRONMENT", "production") + file = Tempfile.new(["sentry-defines", ".json"]) + File.chmod(0o600, file.path) + file.write(JSON.generate({ + "SENTRY_DSN" => dsn, + "SENTRY_ENVIRONMENT" => environment, + })) + file.flush + yield file.path +ensure + file&.close! +end + +def upload_sentry_symbols(marketing_version:, version_code:, symbols_path:, dart_symbol_map_path:) + ENV["SENTRY_RELEASE"] = "swiss.realunit.app@#{marketing_version}+#{version_code}" + ENV["SENTRY_DIST"] = version_code.to_s + Dir.chdir("../..") do + # The plugin only warns and continues on missing input, which would ship + # a release with degraded (or no) crash symbolication silently. Fail the + # build instead. + if Dir.glob("#{symbols_path}/**/*").select { |f| File.file?(f) }.empty? + UI.user_error!("Sentry upload: no native debug symbols found under #{symbols_path}") + end + unless File.exist?(dart_symbol_map_path) + UI.user_error!("Sentry upload: Dart obfuscation map not found at #{dart_symbol_map_path}") + end + + sh("dart", "run", "sentry_dart_plugin", + "--sentry-define=symbols_path=#{symbols_path}", + "--sentry-define=dart_symbol_map_path=#{dart_symbol_map_path}" + ) + end +end + # Runs tool/generate_release_info.dart (single source of truth for tag → # marketing version + version code) and reads the values back from the # generated Dart file. The generated file is the canonical artefact — @@ -61,29 +104,48 @@ platform :android do marketing_version = info["marketing_version"] version_code = info["version_code"] - Dir.chdir("..") do - sh("flutter", "build", "appbundle", - "--release", - "--build-name=#{marketing_version}", - "--build-number=#{version_code}", - "--no-tree-shake-icons", - "--obfuscate", - "--split-debug-info=build/debug_info" - ) - sh("flutter", "build", "apk", - "--release", - "--build-name=#{marketing_version}", - "--build-number=#{version_code}", - "--no-tree-shake-icons", - "--obfuscate", - "--split-debug-info=build/debug_info" - ) - File.rename( - "../build/app/outputs/flutter-apk/app-release.apk", - "../build/app/outputs/flutter-apk/realunit-#{tag_name}.apk" - ) + with_crash_reporting_defines do |defines_file| + Dir.chdir("..") do + sh("flutter", "build", "appbundle", + "--release", + "--build-name=#{marketing_version}", + "--build-number=#{version_code}", + "--dart-define-from-file=#{defines_file}", + "--no-tree-shake-icons", + "--obfuscate", + "--split-debug-info=build/debug_info/appbundle", + "--extra-gen-snapshot-options=--save-obfuscation-map=build/appbundle-obfuscation-map.json" + ) + sh("flutter", "build", "apk", + "--release", + "--build-name=#{marketing_version}", + "--build-number=#{version_code}", + "--dart-define-from-file=#{defines_file}", + "--no-tree-shake-icons", + "--obfuscate", + "--split-debug-info=build/debug_info/apk", + "--extra-gen-snapshot-options=--save-obfuscation-map=build/apk-obfuscation-map.json" + ) + File.rename( + "../build/app/outputs/flutter-apk/app-release.apk", + "../build/app/outputs/flutter-apk/realunit-#{tag_name}.apk" + ) + end end + upload_sentry_symbols( + marketing_version: marketing_version, + version_code: version_code, + symbols_path: "build/debug_info/appbundle", + dart_symbol_map_path: "build/appbundle-obfuscation-map.json" + ) + upload_sentry_symbols( + marketing_version: marketing_version, + version_code: version_code, + symbols_path: "build/debug_info/apk", + dart_symbol_map_path: "build/apk-obfuscation-map.json" + ) + # Upload the binary + changelog only. The changelog is tied to this # build's version code (taken from the AAB); the listing metadata and # images are pushed by the dedicated call below, keeping the two diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile index 80d96da79..63eae30cc 100644 --- a/ios/fastlane/Fastfile +++ b/ios/fastlane/Fastfile @@ -13,8 +13,45 @@ # Uncomment the line if you want fastlane to automatically update itself # update_fastlane +require "json" +require "tempfile" + default_platform(:ios) +# Keeps the DSN out of the process arguments Fastlane prints. Flutter writes +# the encoded defines into Generated.xcconfig before gym invokes xcodebuild. +def with_crash_reporting_defines + dsn = ENV.fetch("SENTRY_DSN", "") + UI.user_error!("SENTRY_DSN is required for store release builds") if dsn.empty? + + environment = ENV.fetch("SENTRY_ENVIRONMENT", "production") + file = Tempfile.new(["sentry-defines", ".json"]) + File.chmod(0o600, file.path) + file.write(JSON.generate({ + "SENTRY_DSN" => dsn, + "SENTRY_ENVIRONMENT" => environment, + })) + file.flush + yield file.path +ensure + file&.close! +end + +def upload_sentry_symbols(marketing_version:, version_code:) + ENV["SENTRY_RELEASE"] = "swiss.realunit.app@#{marketing_version}+#{version_code}" + ENV["SENTRY_DIST"] = version_code.to_s + Dir.chdir("../..") do + # The plugin only warns and continues on missing input, which would ship + # a release with degraded (or no) crash symbolication silently. Fail the + # build instead. + if Dir.glob("build/ios/archive/**/*.dSYM").empty? + UI.user_error!("Sentry upload: no dSYMs found under build/ios/archive") + end + + sh("dart", "run", "sentry_dart_plugin") + end +end + def get_api_key app_store_connect_api_key( key_id: ENV['FASTLANE_APPLE_API_KEY_ID'], @@ -110,13 +147,29 @@ platform :ios do xcodeproj: "Runner.xcodeproj" ) - gym( - workspace: "Runner.xcworkspace", - scheme: "Runner", - output_name: "realunit-#{tag_name}.ipa", - xcargs: "-verbose", - verbose: true - ) + with_crash_reporting_defines do |defines_file| + Dir.chdir("../..") do + sh("flutter", "build", "ios", + "--config-only", + "--release", + "--build-name=#{marketing_version}", + "--build-number=#{version_code}", + "--dart-define-from-file=#{defines_file}" + ) + end + gym( + workspace: "Runner.xcworkspace", + scheme: "Runner", + archive_path: File.expand_path("../../build/ios/archive/Runner.xcarchive", __dir__), + output_name: "realunit-#{tag_name}.ipa", + xcargs: "-verbose", + verbose: true + ) + upload_sentry_symbols( + marketing_version: marketing_version, + version_code: version_code + ) + end upload_to_testflight( api_key: get_api_key, diff --git a/pubspec.lock b/pubspec.lock index 759158cf0..a6d39b085 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -655,6 +655,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" + globbing: + dependency: transitive + description: + name: globbing + sha256: "4f89cfaf6fa74c9c1740a96259da06bd45411ede56744e28017cc534a12b6e2d" + url: "https://pub.dev" + source: hosted + version: "1.0.0" go_router: dependency: "direct main" description: @@ -791,6 +799,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.2" + injector: + dependency: transitive + description: + name: injector + sha256: ed389bed5b48a699d5b9561c985023d0d5cc88dd5ff2237aadcce5a5ab433e4e + url: "https://pub.dev" + source: hosted + version: "3.0.0" intl: dependency: "direct main" description: @@ -1183,6 +1199,22 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.3" + process: + dependency: transitive + description: + name: process + sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744 + url: "https://pub.dev" + source: hosted + version: "5.0.5" + properties: + dependency: transitive + description: + name: properties + sha256: "333f427dd4ed07bdbe8c75b9ff864a1e70b5d7a8426a2e8bdd457b65ae5ac598" + url: "https://pub.dev" + source: hosted + version: "2.1.1" provider: dependency: transitive description: @@ -1239,6 +1271,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + sentry_dart_plugin: + dependency: "direct dev" + description: + name: sentry_dart_plugin + sha256: da9c1d0b3c87a251bfc36301f16af090a88c2d59128fe9a6f908f5ac20340c97 + url: "https://pub.dev" + source: hosted + version: "3.4.0" shared_preferences: dependency: "direct main" description: @@ -1412,6 +1452,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + system_info2: + dependency: transitive + description: + name: system_info2 + sha256: b937736ecfa63c45b10dde1ceb6bb30e5c0c340e14c441df024150679d65ac43 + url: "https://pub.dev" + source: hosted + version: "4.1.0" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c6634fa58..0a22ecbc0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -100,6 +100,7 @@ dev_dependencies: # Used by test/packages/config/legal_documents_config_test.dart to swap in a # recording fake for url_launcher without touching the platform channel. plugin_platform_interface: ^2.1.8 + sentry_dart_plugin: 3.4.0 url_launcher_platform_interface: ^2.3.2 dependency_overrides: @@ -111,6 +112,17 @@ dependency_overrides: url: https://github.com/cake-tech/web3dart.git ref: aa3f932dbf54eda651b7bd01ad00204acf998bd0 +sentry: + upload_debug_symbols: true + upload_source_maps: false + upload_sources: false + project: realunitchapp + org: sentry + url: https://sentry.dfxserve.com/ + wait_for_processing: true + log_level: error + commits: false + hooks: user_defines: sqlite3: From 35447f60dd877fc38ac9fbb00dfd76612164f3e2 Mon Sep 17 00:00:00 2001 From: Blume1977 Date: Fri, 31 Jul 2026 20:43:54 +0200 Subject: [PATCH 9/9] fix(bitbox): sign the registration with a chainId-extended EIP-712 domain (#877) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem BitBox users cannot complete the RealUnit registration: on submit, the BitBox02 (Nova) rejects the signing request and shows **"typed data has no chain ID"** on the device (first affected customer: userData 412822, 23.07.). The firmware requires a `chainId` member in the EIP712Domain; our registration payload signs the chainId-less domain `{ name: 'RealUnitUser', version: '1' }`. Software wallets sign it regardless, which is why this never surfaced. ## Change - `Eip712Signer.signRegistration` includes `chainId` (value: `apiConfig.asset.chainId`, already plumbed through) in the EIP712Domain **for BitboxCredentials only**. - Software wallets keep the legacy domain — pinned by the existing golden-signature test, so this path provably does not change. ## Pair PR **DFXswiss/api#4542 — merged and live on production since 2026-07-31 16:47Z.** Verification there accepts both domain variants, trying the legacy one first, so software wallets are untouched. The API side is already deployed, so this PR no longer has a merge-order prerequisite. (The original pair PR #4354 was closed unmerged and superseded by #4542.) ## Validated end-to-end on production Run on 2026-07-31 with a real BitBox02 Nova (`bb02p-multi`, main firmware v9.26.4 — the build measured to refuse the chainId-less envelope) on an iPhone 17e, against `api.dfx.swiss`: | hop | evidence | |---|---| | device signs over BLE | no "typed data has no chain ID" screen, no NACK | | DFX accepts the extended domain | `[RealUnitService] RealUnit registration signature matched chainId 1 domain / …` at 16:54:28Z | | forward to Aktionariat | `POST /v1/realunit/register/complete → 201`, no `Failed to forward RealUnit registration` | | persisted | `aktionariat_registration` row → `status = Completed`, `active = true` | This is the scenario the earlier review asked for real-firmware data on: the same device and firmware that produced the NACK now completes a registration end to end. Note on scope of that evidence: `Completed` is written after Aktionariat's `/registerUser` returns 2xx. It proves their registration endpoint accepted the forwarded payload; it does not by itself prove which step re-verifies the EIP-712 signature on their side, so a later step exercising the signature is still worth watching. ## Tests - new: BitBox path signs with `domain.chainId` + `chainId` member in EIP712Domain types - existing golden signature (software wallet) unchanged → legacy path frozen - wallet package + registration service suites: 83/83 green (62 + 21); `flutter analyze`: no issues --------- Co-authored-by: Daniel Padrino --- lib/packages/wallet/eip712_signer.dart | 15 ++++++- .../wallet/eip712_signer_bitbox_test.dart | 39 ++++++++++++++++++- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/lib/packages/wallet/eip712_signer.dart b/lib/packages/wallet/eip712_signer.dart index f19f0e789..e4a1d093e 100644 --- a/lib/packages/wallet/eip712_signer.dart +++ b/lib/packages/wallet/eip712_signer.dart @@ -24,11 +24,20 @@ class Eip712Signer { required bool swissTaxResidence, required String registrationDate, }) { + // The BitBox02 firmware refuses to sign typed data whose EIP712Domain has + // no chainId ("typed data has no chain ID" shown on the device), so + // hardware wallets sign with the chainId-extended domain. Software wallets + // keep the legacy chainId-less domain until Aktionariat has confirmed its + // signature re-verification accepts the extended variant — the API + // accepts both (pair PR DFXswiss/api#4542). + final includeChainIdInDomain = credentials is BitboxCredentials; + final Map typedDataMap = { 'types': { 'EIP712Domain': [ {'name': 'name', 'type': 'string'}, {'name': 'version', 'type': 'string'}, + if (includeChainIdInDomain) {'name': 'chainId', 'type': 'uint256'}, ], 'RealUnitUser': [ {'name': 'email', 'type': 'string'}, @@ -47,7 +56,11 @@ class Eip712Signer { ], }, 'primaryType': 'RealUnitUser', - 'domain': {'name': 'RealUnitUser', 'version': '1'}, + 'domain': { + 'name': 'RealUnitUser', + 'version': '1', + if (includeChainIdInDomain) 'chainId': chainId, + }, 'message': { 'email': email, 'name': name, diff --git a/test/packages/wallet/eip712_signer_bitbox_test.dart b/test/packages/wallet/eip712_signer_bitbox_test.dart index 438ccc787..006e85a60 100644 --- a/test/packages/wallet/eip712_signer_bitbox_test.dart +++ b/test/packages/wallet/eip712_signer_bitbox_test.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:typed_data'; import 'package:bitbox_flutter/bitbox_manager.dart'; @@ -23,9 +24,9 @@ void main() { BitboxCredentials connected() => BitboxCredentials('0x000000000000000000000000000000000000dead')..setBitbox(manager); - Future signRegistration() => Eip712Signer.signRegistration( + Future signRegistration({int chainId = 1}) => Eip712Signer.signRegistration( credentials: connected(), - chainId: 1, + chainId: chainId, email: 'jk@dfx.swiss', name: 'Joshua', type: 'human', @@ -48,6 +49,40 @@ void main() { expect(await signRegistration(), '0xcafebabe'); }); + // The BitBox02 firmware rejects typed data whose EIP712Domain has no + // chainId ("typed data has no chain ID" on the device) — hardware-wallet + // registrations must sign the chainId-extended domain. The software-wallet + // path keeps the legacy domain (pinned by the golden signature in + // eip712_signer_test.dart). + // Signs with a non-default chainId so a hardcoded value cannot pass, and asserts the + // EIP712Domain member list exactly: the domain typehash covers the names, their types + // and their order, so any of those drifting changes the digest and the API recovers a + // foreign address. Mirrors the chainId-wiring pin in + // test/integration/eip7702_delegation_bitbox_test.dart. + for (final chainId in const [1, 11155111]) { + test('signs with the chainId-extended EIP-712 domain (chainId $chainId)', () async { + when( + () => manager.signETHTypedMessage(any(), any(), any()), + ).thenAnswer((_) async => Uint8List.fromList([0x01])); + + await signRegistration(chainId: chainId); + + // called(1): a second sign would mean a second on-device confirmation prompt. + final captured = (verify( + () => manager.signETHTypedMessage(captureAny(), any(), captureAny()), + )..called(1)).captured; + final typedData = jsonDecode(utf8.decode(captured[1] as Uint8List)) as Map; + + expect(captured[0], chainId); + expect(typedData['domain']['chainId'], chainId); + expect(typedData['types']['EIP712Domain'], [ + {'name': 'name', 'type': 'string'}, + {'name': 'version', 'type': 'string'}, + {'name': 'chainId', 'type': 'uint256'}, + ]); + }); + } + test('throws SigningCancelledException on empty signature', () async { when( () => manager.signETHTypedMessage(any(), any(), any()),

m0<$^y8ysFQ2DqHntlpR7diw-#?oj=# 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 literal 25894 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 E52ZLYDF6Tf diff --git a/test/goldens/screens/pay/goldens/macos/pay_process_page_awaiting_settlement.png b/test/goldens/screens/pay/goldens/macos/pay_process_page_awaiting_settlement.png new file mode 100644 index 0000000000000000000000000000000000000000..f180456ff2ac97868195cda0718d56bf42b2f70e GIT binary patch literal 8160 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 PRjxR>J2qUp{>%RWL_omA literal 0 HcmV?d00001 diff --git a/test/goldens/screens/pay/goldens/macos/pay_process_page_pay_retry.png b/test/goldens/screens/pay/goldens/macos/pay_process_page_pay_retry.png new file mode 100644 index 0000000000000000000000000000000000000000..9c782538de3ce630bbedf2327a852fa9faa28cd7 GIT binary patch literal 8621 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 O#ns8vk?_OiU;iKezh%n+ literal 0 HcmV?d00001 diff --git a/test/goldens/screens/pay/goldens/macos/pay_quote_page_loading.png b/test/goldens/screens/pay/goldens/macos/pay_quote_page_loading.png new file mode 100644 index 0000000000000000000000000000000000000000..66fa82dc26b731e6e44bcbcfcaf697e9c0906dca GIT binary patch literal 3647 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 implements PayProcessCubit {} + +void main() { + late _MockPayProcessCubit processCubit; + + setUp(() { + processCubit = _MockPayProcessCubit(); + when(() => processCubit.state).thenReturn(const PayProcessInitial()); + }); + + // PayProcessPage resolves its cubit from getIt and calls start(); the golden + // renders PayProcessView directly with a mocked cubit. Terminal states + // (success/failure/retry) are surfaced via modal sheets from the listener, + // not the build tree — exercised in the widget test. The build tree shows the + // in-progress indicator with a per-state label, captured here. + group('$PayProcessView', () { + goldenTest( + 'in-progress swapping state', + fileName: 'pay_process_page_swapping', + constraints: phoneConstraints, + // The CupertinoActivityIndicator animates forever, so pumpAndSettle would + // time out; pumpOnce captures the first frame. + pumpBeforeTest: pumpOnce, + builder: () { + when(() => processCubit.state).thenReturn(const PayProcessSwapping()); + return wrapForGolden( + BlocProvider.value( + value: processCubit, + child: const PayProcessView(), + ), + ); + }, + ); + + goldenTest( + 'awaiting settlement state', + fileName: 'pay_process_page_awaiting_settlement', + constraints: phoneConstraints, + pumpBeforeTest: pumpOnce, + builder: () { + when(() => processCubit.state).thenReturn(const PayProcessAwaitingSettlement('0xtx')); + return wrapForGolden( + BlocProvider.value( + value: processCubit, + child: const PayProcessView(), + ), + ); + }, + ); + + goldenTest( + 'pay-retry state label', + fileName: 'pay_process_page_pay_retry', + constraints: phoneConstraints, + pumpBeforeTest: pumpOnce, + builder: () { + when( + () => processCubit.state, + ).thenReturn(const PayProcessPayRetry(PayRetryReason.quoteExpired)); + return wrapForGolden( + BlocProvider.value( + value: processCubit, + child: const PayProcessView(), + ), + ); + }, + ); + }); +} diff --git a/test/goldens/screens/pay/pay_quote_golden_test.dart b/test/goldens/screens/pay/pay_quote_golden_test.dart new file mode 100644 index 000000000..34f2d8099 --- /dev/null +++ b/test/goldens/screens/pay/pay_quote_golden_test.dart @@ -0,0 +1,108 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/screens/pay/cubits/pay_quote/pay_quote_cubit.dart'; +import 'package:realunit_wallet/screens/pay/pay_quote_page.dart'; + +import '../../../helper/helper.dart'; + +class _MockPayQuoteCubit extends MockCubit implements PayQuoteCubit {} + +void main() { + late _MockPayQuoteCubit quoteCubit; + + setUp(() { + quoteCubit = _MockPayQuoteCubit(); + when(() => quoteCubit.state).thenReturn(const PayQuoteLoading()); + }); + + // PayQuotePage resolves its cubit from getIt and calls load(); the golden + // renders PayQuoteView directly with a mocked cubit so every state is + // deterministic without the service/DI graph. + group('$PayQuoteView', () { + goldenTest( + 'loading state', + fileName: 'pay_quote_page_loading', + constraints: phoneConstraints, + // The CupertinoActivityIndicator animates forever, so pumpAndSettle + // would time out; pumpOnce captures the first frame. + pumpBeforeTest: pumpOnce, + builder: () => wrapForGolden( + BlocProvider.value( + value: quoteCubit, + child: const PayQuoteView(), + ), + ), + ); + + // Real Sepolia OCP capture (DFXswiss/api #3819): a CHF 2.00 payment link + // whose Ethereum method settles 2.0 ZCHF. The screen renders the amounts + // straight from the quote — the app never computes them. + goldenTest( + 'ready quote with CHF amount and ZCHF needed', + fileName: 'pay_quote_page_ready', + constraints: phoneConstraints, + builder: () { + when(() => quoteCubit.state).thenReturn( + const PayQuoteReady( + paymentLinkId: 'pl_realunit_ocp_sepolia', + quoteId: 'plq_realunit_ocp_sepolia', + fiatAsset: 'CHF', + fiatAmount: 2, + zchfAmount: 2.0, + ), + ); + return wrapForGolden( + BlocProvider.value( + value: quoteCubit, + child: const PayQuoteView(), + ), + ); + }, + ); + + goldenTest( + 'ready quote with merchant and REALU swap details', + fileName: 'pay_quote_page_ready_with_merchant', + constraints: phoneConstraints, + builder: () { + when(() => quoteCubit.state).thenReturn( + const PayQuoteReady( + paymentLinkId: 'pl_realunit_ocp_sepolia', + quoteId: 'plq_realunit_ocp_sepolia', + fiatAsset: 'CHF', + fiatAmount: 2, + zchfAmount: 2.0, + merchantName: 'Café Zürich', + merchantCity: 'Zürich', + realuAmount: 5, + realuEstimatedZchf: 1.98, + realuFeesTotal: 0.02, + ), + ); + return wrapForGolden( + BlocProvider.value( + value: quoteCubit, + child: const PayQuoteView(), + ), + ); + }, + ); + + goldenTest( + 'expired quote message', + fileName: 'pay_quote_page_expired', + constraints: phoneConstraints, + builder: () { + when(() => quoteCubit.state).thenReturn(const PayQuoteExpired()); + return wrapForGolden( + BlocProvider.value( + value: quoteCubit, + child: const PayQuoteView(), + ), + ); + }, + ); + }); +} diff --git a/test/goldens/screens/pay/pay_scan_golden_test.dart b/test/goldens/screens/pay/pay_scan_golden_test.dart new file mode 100644 index 000000000..dacdb4306 --- /dev/null +++ b/test/goldens/screens/pay/pay_scan_golden_test.dart @@ -0,0 +1,45 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/screens/pay/cubits/pay_scan/pay_scan_cubit.dart'; +import 'package:realunit_wallet/screens/pay/pay_scan_page.dart'; + +import '../../../helper/helper.dart'; + +class _MockPayScanCubit extends MockCubit implements PayScanCubit {} + +void main() { + late _MockPayScanCubit scanCubit; + + setUpAll(() { + // The QR scanner is camera-coupled (mobile_scanner). The stub answers the + // permission handshake so the preview settles into its deterministic + // placeholder state instead of throwing MissingPluginException — the live + // camera carries the `@no-integration-test` note on pay_scan_page.dart. + stubMobileScannerChannel(); + }); + + setUp(() { + scanCubit = _MockPayScanCubit(); + when(() => scanCubit.state).thenReturn(const PayScanScanning()); + }); + + group('$PayScanView', () { + goldenTest( + 'scanning state with camera preview placeholder', + fileName: 'pay_scan_page_scanning', + constraints: phoneConstraints, + // The camera preview never reaches an `isInitialized` frame headlessly, + // so pumpAndSettle (default in precacheImages) would await a settle that + // never comes. pumpOnce captures the deterministic placeholder frame. + pumpBeforeTest: pumpOnce, + builder: () => wrapForGolden( + BlocProvider.value( + value: scanCubit, + child: const PayScanView(), + ), + ), + ); + }); +} diff --git a/test/helper/golden_plugin_stubs.dart b/test/helper/golden_plugin_stubs.dart index be77be50e..d20b47b1c 100644 --- a/test/helper/golden_plugin_stubs.dart +++ b/test/helper/golden_plugin_stubs.dart @@ -26,3 +26,46 @@ void unstubNoScreenshotChannel() { null, ); } + +/// Stub the `mobile_scanner` plugin's method + event channels so the camera +/// preview renders a deterministic state in headless widget/golden tests. +/// +/// The QR scanner is camera/MethodChannel-coupled — the live preview has no +/// headless representation and `MobileScanner.initState` fires +/// `controller.start()` against the platform channel. This stub answers the +/// permission handshake (`state` → undetermined, `request` → not granted) so +/// `MobileScannerController.start()` settles into its permission-denied error +/// state. The widget then paints its default error placeholder (a black +/// `ColoredBox` with a centered error icon) instead of throwing +/// `MissingPluginException` — a stable, deterministic preview-placeholder +/// state that mirrors the `@no-integration-test` note on `pay_scan_page.dart` +/// (the live camera is exercised only on a real device). +/// +/// Call from `setUpAll`. +void stubMobileScannerChannel() { + final messenger = TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger; + messenger.setMockMethodCallHandler( + const MethodChannel('dev.steenbakker.mobile_scanner/scanner/method'), + (call) async { + switch (call.method) { + // Camera authorization is undetermined … + case 'state': + return 0; + // … and the follow-up request is not granted, so start() settles into + // the permission-denied placeholder without touching a real camera. + case 'request': + return false; + default: + return null; + } + }, + ); + // PayScanView wires an onDetect callback, so MobileScanner subscribes to the + // controller's barcode stream (the event channel) in initState. Install a + // no-op stream handler that never emits, so the `listen` does not throw + // MissingPluginException and no synthetic barcode ever fires. + messenger.setMockStreamHandler( + const EventChannel('dev.steenbakker.mobile_scanner/scanner/event'), + MockStreamHandler.inline(onListen: (arguments, sink) {}), + ); +} diff --git a/test/helper/responsive_surface_catalog.dart b/test/helper/responsive_surface_catalog.dart index a81eb8439..c529c93d3 100644 --- a/test/helper/responsive_surface_catalog.dart +++ b/test/helper/responsive_surface_catalog.dart @@ -180,6 +180,12 @@ const kResponsiveSurfaceCatalog = [ matrixTestPath: 'test/screens/buy/buy_responsive_matrix_test.dart', productionPath: 'lib/screens/buy/buy_page.dart', ), + ResponsiveSurface( + id: 'pay_quote_page', + description: 'Pay quote page (confirm CTA)', + matrixTestPath: 'test/screens/pay/pay_quote_responsive_matrix_test.dart', + productionPath: 'lib/screens/pay/pay_quote_page.dart', + ), ResponsiveSurface( id: 'sell_page', description: 'Sell page (primary CTA)', @@ -228,7 +234,7 @@ const kResponsiveSurfaceCatalog = [ matrixTestPath: 'test/screens/pin/pin_sheets_responsive_matrix_test.dart', productionPath: 'lib/screens/pin/widgets/enable_biometric_bottom_sheet.dart', ), - // Migration covers 29 surfaces total (bitbox_connect_sheet + 28 above). No + // Migration covers 30 surfaces total (bitbox_connect_sheet + 29 above). No // further known candidates remain from the prior sweep. welcome_page was // reviewed and found safe (scrolls end-to-end, no separate sticky CTA) — not // a migration candidate. Not exhaustive — review responsibility for every diff --git a/test/packages/service/dfx/eip1559_unsigned_tx_decoder_test.dart b/test/packages/service/dfx/eip1559_unsigned_tx_decoder_test.dart new file mode 100644 index 000000000..904bba20a --- /dev/null +++ b/test/packages/service/dfx/eip1559_unsigned_tx_decoder_test.dart @@ -0,0 +1,291 @@ +import 'dart:typed_data'; + +import 'package:convert/convert.dart' as convert; +import 'package:flutter_test/flutter_test.dart'; +import 'package:realunit_wallet/packages/service/dfx/eip1559_unsigned_tx_decoder.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/payment/pay_exceptions.dart'; + +/// Independently verified (Python reference RLP round-trip) EIP-1559 type-2 unsigned +/// tx: chainId=11155111, to=0x1111…ac01, value=0, ERC20 transfer to 0x2222…bc02 for +/// amount=5000000000000000000. +const _validUnsignedTx = + '0x02f87183aa36a7018459682f008504a817c800830186a094111111111111111111111111111111111111ac0180b844a9059cbb000000000000000000000000222222222222222222222222222222222222bc020000000000000000000000000000000000000000000000004563918244f40000c0'; + +const _validCalldataHex = + 'a9059cbb000000000000000000000000222222222222222222222222222222222222bc020000000000000000000000000000000000000000000000004563918244f40000'; + +void main() { + group('Eip1559UnsignedTxDecoder.decode', () { + test('decodes a valid EIP-1559 ERC20-transfer unsigned tx', () { + final tx = Eip1559UnsignedTxDecoder.decode(_validUnsignedTx); + + expect(tx.chainId, BigInt.from(11155111)); + expect(tx.maxPriorityFeePerGas, BigInt.from(1500000000)); + expect(tx.maxFeePerGas, BigInt.from(20000000000)); + expect(tx.gasLimit, BigInt.from(100000)); + expect(tx.to, '0x111111111111111111111111111111111111ac01'); + expect(tx.value, BigInt.zero); + expect(tx.data.length, 68); + expect(convert.hex.encode(tx.data), _validCalldataHex); + }); + + test('rejects long-form RLP length-of-length > 4 with typed exception (not RangeError)', () { + // type=0x02, then long-STRING prefix 0xbf (lengthOfLength = 0xbf - 0xb7 = 8) + // followed by 8 length-bytes '7ffffffffffffffe' (0x7ffffffffffffffe = 9223372036854775806) + // so start2+length overflows a signed 64-bit int and wraps negative; without the + // length-of-length cap, _requireBytes lets it through and data.sublist throws a raw + // RangeError; must stay typed as PayUnsignedTxMismatchException. + expect( + () => Eip1559UnsignedTxDecoder.decode('0x02bf7ffffffffffffffe'), + throwsA(isA()), + ); + }); + + test('rejects a non-type-2 transaction', () { + expect( + () => Eip1559UnsignedTxDecoder.decode( + '0x01f87183aa36a7018459682f008504a817c800830186a094111111111111111111111111111111111111ac0180b844a9059cbb000000000000000000000000222222222222222222222222222222222222bc020000000000000000000000000000000000000000000000004563918244f40000c0', + ), + throwsA(isA()), + ); + }); + + test('rejects trailing bytes after a complete RLP encoding', () { + expect( + () => Eip1559UnsignedTxDecoder.decode( + '0x02f87183aa36a7018459682f008504a817c800830186a094111111111111111111111111111111111111ac0180b844a9059cbb000000000000000000000000222222222222222222222222222222222222bc020000000000000000000000000000000000000000000000004563918244f40000c0ff', + ), + throwsA(isA()), + ); + }); + + test('rejects a truncated encoding', () { + expect( + () => Eip1559UnsignedTxDecoder.decode( + '0x02f87183aa36a7018459682f008504a817c800830186a094111111111111111111111111111111111111ac0180b844a9059cbb000000000000000000000000222222222222222222222222222222222222bc0200000000000000000000000000000000000000000000000045639182', + ), + throwsA(isA()), + ); + }); + + test('rejects a tx whose RLP root has the wrong field count (accessList stripped)', () { + // Same tx as _validUnsignedTx but with the trailing accessList field (empty list `c0`) + // removed and the outer list-length prefix corrected from 0x71 (113 bytes) to 0x70 + // (112 bytes) so the RLP still decodes cleanly with zero trailing bytes — root then has + // 8 fields instead of 9. Verified byte-for-byte against a reference RLP decoder. + expect( + () => Eip1559UnsignedTxDecoder.decode( + '0x02f87083aa36a7018459682f008504a817c800830186a094111111111111111111111111111111111111ac0180b844a9059cbb000000000000000000000000222222222222222222222222222222222222bc020000000000000000000000000000000000000000000000004563918244f40000', + ), + throwsA( + isA().having( + (e) => e.reason, + 'reason', + 'unsigned tx has 8 RLP fields, expected 9', + ), + ), + ); + }); + + test('rejects invalid hex', () { + expect( + () => Eip1559UnsignedTxDecoder.decode('0x02zzzz'), + throwsA(isA()), + ); + }); + + test('rejects an empty string', () { + expect( + () => Eip1559UnsignedTxDecoder.decode(''), + throwsA(isA()), + ); + }); + + test('rejects a non-empty access list', () { + // Same as _validUnsignedTx but trailing empty accessList `c0` replaced with + // `c180` (RLP list of one empty string) and the outer list-length prefix + // bumped from 0x71 (113) to 0x72 (114) for the extra payload byte. Verified + // by counting payload bytes after `f872` = 114. + expect( + () => Eip1559UnsignedTxDecoder.decode( + '0x02f87283aa36a7018459682f008504a817c800830186a094111111111111111111111111111111111111ac0180b844a9059cbb000000000000000000000000222222222222222222222222222222222222bc020000000000000000000000000000000000000000000000004563918244f40000c180', + ), + throwsA( + isA().having( + (e) => e.reason, + 'reason', + 'unsigned tx "accessList" must be an empty RLP list', + ), + ), + ); + }); + + test('rejects a quantity field with a non-canonical leading zero byte', () { + // Same as _validUnsignedTx but gasLimit `830186a0` (canonical 100000) replaced + // with `84000186a0` (same magnitude, leading 0x00 — non-canonical RLP integer) + // and outer list-length 0x71→0x72 for the extra byte. Verified: only the + // gasLimit encoding and length prefix differ from _validUnsignedTx. + expect( + () => Eip1559UnsignedTxDecoder.decode( + '0x02f87283aa36a7018459682f008504a817c80084000186a094111111111111111111111111111111111111ac0180b844a9059cbb000000000000000000000000222222222222222222222222222222222222bc020000000000000000000000000000000000000000000000004563918244f40000c0', + ), + throwsA( + isA().having( + (e) => e.reason, + 'reason', + 'unsigned tx "gasLimit" has a non-canonical leading zero byte', + ), + ), + ); + }); + + // Canonical bare-byte nonce `01` is already accepted by + // 'decodes a valid EIP-1559 ERC20-transfer unsigned tx' above. + test('rejects a non-canonical single-byte short string (nonce encoded as 8101 instead of bare 01)', + () { + // Same as _validUnsignedTx but the nonce field (RLP field index 1, right after chainId + // `83aa36a7`) is re-encoded from the canonical bare byte `01` to the non-canonical length-1 + // short string `8101` (same decoded value, 1), and the outer list-length prefix is bumped + // from 0x71 (113 bytes) to 0x72 (114 bytes) for the extra encoding byte. Independently + // verified byte-for-byte against a reference RLP decoder (only the nonce encoding and the + // outer list-length prefix differ from _validUnsignedTx; every other field decodes to the + // identical value). + expect( + () => Eip1559UnsignedTxDecoder.decode( + '0x02f87283aa36a781018459682f008504a817c800830186a094111111111111111111111111111111111111ac0180b844a9059cbb000000000000000000000000222222222222222222222222222222222222bc020000000000000000000000000000000000000000000000004563918244f40000c0', + ), + throwsA( + isA().having( + (e) => e.reason, + 'reason', + 'RLP: non-canonical single-byte short string (must be the bare byte, not a length-1 string)', + ), + ), + ); + }); + + test('rejects a quantity field that is an RLP list instead of a byte string', () { + // RLP: type=0x02, long-form list (f8 65 = 101-byte body), fields: + // chainId= (invalid: must be a byte string), nonce=1, + // maxPriorityFeePerGas=1, maxFeePerGas=1, gasLimit=100000 (830186a0), + // to=20 zero-ish bytes (94 + 20x11), value=0 (80), data=68-byte ERC20 + // transfer calldata (b844...), accessList=. Independently + // constructed (not derived from _validUnsignedTx by truncation) and + // verified byte-for-byte against a reference RLP encoder/decoder so that + // every check before `_requireCanonicalInteger(root[chainId], 'chainId')` + // passes, and that call is the first one reached — hitting exactly the + // "field is not a Uint8List" branch for the chainId field. + expect( + () => Eip1559UnsignedTxDecoder.decode( + '0x02f865c0010101830186a094111111111111111111111111111111111111111180b844a9059cbb000000000000000000000000222222222222222222222222222222222222bc020000000000000000000000000000000000000000000000004563918244f40000c0', + ), + throwsA( + isA().having( + (e) => e.reason, + 'reason', + 'unsigned tx "chainId" is not a byte string', + ), + ), + ); + }); + + test('rejects a non-minimal long-form string length encoding', () { + // type=0x02, then long-form string prefix 0xb8 (lengthOfLength=1) with + // length byte 0x01 (≤55) and one payload byte — must use short-form 0x81 + // instead. Hits the long-form string branch before root-type checks. + expect( + () => Eip1559UnsignedTxDecoder.decode('0x02b80101'), + throwsA( + isA().having( + (e) => e.reason, + 'reason', + 'RLP: non-minimal long-form string length encoding', + ), + ), + ); + }); + + test('rejects a non-minimal long-form list length encoding', () { + // type=0x02, then long-form list prefix 0xf8 (lengthOfLength=1) with + // length byte 0x01 (≤55) and one payload byte `80` — must use short-form + // 0xc1 instead. Hits the long-form list branch before field-count checks. + expect( + () => Eip1559UnsignedTxDecoder.decode('0x02f80180'), + throwsA( + isA().having( + (e) => e.reason, + 'reason', + 'RLP: non-minimal long-form list length encoding', + ), + ), + ); + }); + + test('rejects a length-of-length encoding with a leading zero byte', () { + // type=0x02, long-form string prefix 0xb9 (lengthOfLength=2) with length + // bytes `00 40` (=64) — a minimal length-of-length never needs a leading + // zero; must use a single length byte instead. Verified: lengthOfLength=2 + // and bytes[0]==0 hit the new _bytesToLength guard before the length≤55 + // check (which would also reject 64… wait, 64>55, so only the leading-zero + // guard rejects this fixture). + expect( + () => Eip1559UnsignedTxDecoder.decode( + '0x02b90040${'11' * 64}', + ), + throwsA( + isA().having( + (e) => e.reason, + 'reason', + 'RLP: non-minimal length-of-length encoding (leading zero byte)', + ), + ), + ); + }); + }); + + group('Erc20TransferCalldataDecoder.decode', () { + test('decodes a valid transfer(address,uint256) call', () { + final data = Uint8List.fromList(convert.hex.decode(_validCalldataHex)); + final transfer = Erc20TransferCalldataDecoder.decode(data); + + expect(transfer.recipient, '0x222222222222222222222222222222222222bc02'); + expect(transfer.amountWei, BigInt.parse('5000000000000000000')); + }); + + test('rejects a wrong function selector', () { + final data = Uint8List.fromList( + convert.hex.decode( + 'aaaaaaaa000000000000000000000000222222222222222222222222222222222222bc020000000000000000000000000000000000000000000000004563918244f40000', + ), + ); + expect( + () => Erc20TransferCalldataDecoder.decode(data), + throwsA(isA()), + ); + }); + + test('rejects wrong calldata length', () { + final data = Uint8List.fromList( + convert.hex.decode( + 'a9059cbb000000000000000000000000222222222222222222222222222222222222bc020000000000000000000000000000000000000000000000004563918244f400', + ), + ); + expect( + () => Erc20TransferCalldataDecoder.decode(data), + throwsA(isA()), + ); + }); + + test('rejects a non-zero-padded recipient address slot', () { + final data = Uint8List.fromList( + convert.hex.decode( + 'a9059cbb010000000000000000000000222222222222222222222222222222222222bc020000000000000000000000000000000000000000000000004563918244f40000', + ), + ); + expect( + () => Erc20TransferCalldataDecoder.decode(data), + throwsA(isA()), + ); + }); + }); +} diff --git a/test/packages/service/dfx/exceptions/exception_surface_test.dart b/test/packages/service/dfx/exceptions/exception_surface_test.dart index 80c34544f..fea010bba 100644 --- a/test/packages/service/dfx/exceptions/exception_surface_test.dart +++ b/test/packages/service/dfx/exceptions/exception_surface_test.dart @@ -3,6 +3,7 @@ import 'package:realunit_wallet/packages/service/dfx/exceptions/api_exception.da import 'package:realunit_wallet/packages/service/dfx/exceptions/bitbox_address_unavailable_exception.dart'; import 'package:realunit_wallet/packages/service/dfx/exceptions/bitbox_exception.dart'; import 'package:realunit_wallet/packages/service/dfx/exceptions/payment/buy_exceptions.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/payment/pay_exceptions.dart'; import 'package:realunit_wallet/packages/service/dfx/exceptions/payment/sell_exceptions.dart'; import 'package:realunit_wallet/packages/service/dfx/exceptions/registration_rejected_exception.dart'; import 'package:realunit_wallet/packages/storage/secure_storage.dart'; @@ -32,6 +33,9 @@ void main() { ), const SeedDecryptionException('test'), const AlreadyConfirmedException(code: 'TEST', message: 'test'), + const InvalidPaymentLinkException('test'), + const PaySignatureUnsupportedException(), + const PayUnsignedTxMismatchException('test'), ]; for (final ex in exceptions) { diff --git a/test/packages/service/dfx/lnurl_decoder_test.dart b/test/packages/service/dfx/lnurl_decoder_test.dart new file mode 100644 index 000000000..bd660bf64 --- /dev/null +++ b/test/packages/service/dfx/lnurl_decoder_test.dart @@ -0,0 +1,110 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/payment/pay_exceptions.dart'; +import 'package:realunit_wallet/packages/service/dfx/lnurl_decoder.dart'; + +void main() { + group('LnurlDecoder.decode', () { + // LUD-01 bech32 of `https://api.dfx.swiss/v1/lnurlp/pl_abc123`. + const lnurl = 'LNURL1DP68GURN8GHJ7CTSDYHXGENC9EEHW6TNWVHHVVF0D3H82UNVWQHHQMZLV93XXVFJXV5T0E5A'; + + test('decodes a bech32 LNURL to the api lnurlp url + id', () { + final result = LnurlDecoder.decode(lnurl); + + expect(result.lnurlpUrl.toString(), 'https://api.dfx.swiss/v1/lnurlp/pl_abc123'); + expect(result.id, 'pl_abc123'); + }); + + test('decodes the lightning= query param of an app.dfx.swiss wrapper URL', () { + final result = LnurlDecoder.decode('https://app.dfx.swiss/pl/?lightning=$lnurl'); + + expect(result.lnurlpUrl.host, 'api.dfx.swiss'); + expect(result.id, 'pl_abc123'); + }); + + test('accepts a lowercase lnurl and a lightning: scheme prefix', () { + final result = LnurlDecoder.decode('lightning:${lnurl.toLowerCase()}'); + + expect(result.id, 'pl_abc123'); + }); + + test('rewrites a plain app.dfx.swiss lnurlp url to the api host', () { + final result = LnurlDecoder.decode('https://app.dfx.swiss/v1/lnurlp/pl_xyz'); + + expect(result.lnurlpUrl.toString(), 'https://api.dfx.swiss/v1/lnurlp/pl_xyz'); + expect(result.id, 'pl_xyz'); + }); + + test('keeps an already-api lnurlp url and forces https', () { + final result = LnurlDecoder.decode('http://api.dfx.swiss/v1/lnurlp/plp_123'); + + expect(result.lnurlpUrl.scheme, 'https'); + expect(result.id, 'plp_123'); + }); + + test('rewrites the dev testnet host twin', () { + final result = LnurlDecoder.decode('https://dev.app.dfx.swiss/v1/lnurlp/pl_dev'); + + expect(result.lnurlpUrl.host, 'dev.api.dfx.swiss'); + expect(result.id, 'pl_dev'); + }); + + test('rejects an empty code', () { + expect( + () => LnurlDecoder.decode(' '), + throwsA(isA()), + ); + }); + + test('rejects a non-DFX host', () { + expect( + () => LnurlDecoder.decode('https://evil.example.com/v1/lnurlp/pl_x'), + throwsA(isA()), + ); + }); + + test('rejects a non-http payload', () { + expect( + () => LnurlDecoder.decode('not-a-url'), + throwsA(isA()), + ); + }); + + test('rejects a bech32 with an invalid checksum', () { + // Flip the last data character to break the checksum. + final broken = '${lnurl.substring(0, lnurl.length - 1)}Q'; + expect( + () => LnurlDecoder.decode(broken), + throwsA(isA()), + ); + }); + + test('rejects a bech32 with an invalid character in the data part', () { + // Replace a data char with 'b' (not in the bech32 charset) while keeping + // the overall length valid, so the per-character guard fires. + final withBadChar = '${lnurl.substring(0, 20)}B${lnurl.substring(21)}'; + expect( + () => LnurlDecoder.decode(withBadChar), + throwsA(isA()), + ); + }); + + test('rejects a too-short bech32', () { + expect( + () => LnurlDecoder.decode('LNURL1bbb'), + throwsA(isA()), + ); + }); + + test('falls back to the last path segment when there is no lnurlp segment', () { + final result = LnurlDecoder.decode('https://api.dfx.swiss/pl_direct'); + expect(result.id, 'pl_direct'); + }); + + test('rejects a DFX url with an empty path', () { + expect( + () => LnurlDecoder.decode('https://api.dfx.swiss/'), + throwsA(isA()), + ); + }); + }); +} diff --git a/test/packages/service/dfx/models/payment/pay/pay_dtos_test.dart b/test/packages/service/dfx/models/payment/pay/pay_dtos_test.dart new file mode 100644 index 000000000..1a58083a1 --- /dev/null +++ b/test/packages/service/dfx/models/payment/pay/pay_dtos_test.dart @@ -0,0 +1,493 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/lnurlp_payment_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_result_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_status_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_submit_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_unsigned_transaction_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_swap_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_swap_payment_info_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_swap_unsigned_transaction_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/swap_payment_info.dart'; + +void main() { + group('RealUnitSwapDto', () { + test('fromTargetAmount serialises only targetAmount (no amount key)', () { + expect( + const RealUnitSwapDto.fromTargetAmount(95.5).toJson(), + {'targetAmount': 95.5}, + ); + }); + }); + + group('RealUnitSwapPaymentInfoDto.fromJson', () { + test('maps every field with no dynamic access', () { + final dto = RealUnitSwapPaymentInfoDto.fromJson({ + 'id': 99, + 'uid': 'MOCK-UID', + 'routeId': 7, + 'timestamp': '2026-06-03T00:00:00.000Z', + 'amount': 10, + 'estimatedAmount': 960, + 'targetAsset': 'ZCHF', + 'fees': {'dfx': 1, 'network': 0.5, 'total': 1.5}, + 'minVolume': 1, + 'maxVolume': 1000, + 'minVolumeTarget': 95, + 'maxVolumeTarget': 95000, + 'ethBalance': 1.0, + 'requiredGasEth': 0.001, + 'isValid': true, + }); + + expect(dto.id, 99); + expect(dto.uid, 'MOCK-UID'); + expect(dto.routeId, 7); + expect(dto.targetAsset, 'ZCHF'); + expect(dto.estimatedAmount, 960); + expect(dto.minVolumeTarget, 95); + expect(dto.isValid, isTrue); + expect(dto.error, isNull); + expect(dto.fees?.total, 1.5); + }); + + test('maps the error code when isValid is false', () { + final dto = RealUnitSwapPaymentInfoDto.fromJson({ + 'id': 1, + 'uid': 'u', + 'routeId': 1, + 'timestamp': '2026-06-03T00:00:00.000Z', + 'amount': 1, + 'estimatedAmount': 1, + 'targetAsset': 'ZCHF', + 'minVolume': 1, + 'maxVolume': 2, + 'minVolumeTarget': 1, + 'maxVolumeTarget': 2, + 'ethBalance': 0, + 'requiredGasEth': 0.001, + 'isValid': false, + 'error': 'LIMIT_EXCEEDED', + }); + + expect(dto.isValid, isFalse); + expect(dto.error, 'LIMIT_EXCEEDED'); + expect(dto.fees, isNull); + }); + }); + + test('SwapPaymentInfo.fromDto carries the swap-relevant fields', () { + final dto = RealUnitSwapPaymentInfoDto.fromJson({ + 'id': 5, + 'uid': 'u', + 'routeId': 2, + 'timestamp': '2026-06-03T00:00:00.000Z', + 'amount': 10, + 'estimatedAmount': 960, + 'targetAsset': 'ZCHF', + 'minVolume': 1, + 'maxVolume': 1000, + 'minVolumeTarget': 95, + 'maxVolumeTarget': 95000, + 'ethBalance': 0.4, + 'requiredGasEth': 0.002, + 'isValid': true, + }); + + final info = SwapPaymentInfo.fromDto(dto); + + expect(info.id, 5); + expect(info.estimatedAmount, 960); + expect(info.ethBalance, 0.4); + expect(info.requiredGasEth, 0.002); + expect(info.isValid, isTrue); + expect(info.feesTotal, isNull); + }); + + test('SwapPaymentInfo.fromDto carries fees.total as feesTotal', () { + final dto = RealUnitSwapPaymentInfoDto.fromJson({ + 'id': 99, + 'uid': 'MOCK-UID', + 'routeId': 7, + 'timestamp': '2026-06-03T00:00:00.000Z', + 'amount': 10, + 'estimatedAmount': 960, + 'targetAsset': 'ZCHF', + 'fees': {'total': 3.25}, + 'minVolume': 1, + 'maxVolume': 1000, + 'minVolumeTarget': 95, + 'maxVolumeTarget': 95000, + 'ethBalance': 1.0, + 'requiredGasEth': 0.001, + 'isValid': true, + }); + + final info = SwapPaymentInfo.fromDto(dto); + + expect(info.feesTotal, 3.25); + }); + + test('SwapPaymentInfo equality is value-based (Equatable props)', () { + const a = SwapPaymentInfo( + id: 1, + amount: 10, + estimatedAmount: 960, + targetAsset: 'ZCHF', + ethBalance: 1, + requiredGasEth: 0.001, + isValid: true, + ); + const same = SwapPaymentInfo( + id: 1, + amount: 10, + estimatedAmount: 960, + targetAsset: 'ZCHF', + ethBalance: 1, + requiredGasEth: 0.001, + isValid: true, + ); + const different = SwapPaymentInfo( + id: 2, + amount: 10, + estimatedAmount: 960, + targetAsset: 'ZCHF', + ethBalance: 1, + requiredGasEth: 0.001, + isValid: true, + error: 'LIMIT_EXCEEDED', + ); + + expect(a, equals(same)); + expect(a, isNot(equals(different))); + }); + + test('RealUnitSwapUnsignedTransactionDto.fromJson', () { + final dto = RealUnitSwapUnsignedTransactionDto.fromJson({'swap': '0xswap'}); + expect(dto.swap, '0xswap'); + }); + + test('RealUnitOcpPayDto.toJson', () { + const dto = RealUnitOcpPayDto(paymentLinkId: 'pl_abc', quoteId: 'q1'); + expect(dto.toJson(), {'paymentLinkId': 'pl_abc', 'quoteId': 'q1'}); + }); + + test('RealUnitOcpPayUnsignedTransactionDto.fromJson', () { + final dto = RealUnitOcpPayUnsignedTransactionDto.fromJson({ + 'unsignedTx': '0xtx', + 'tokenAddress': '0xzchf', + 'recipient': '0xrecipient', + 'amountWei': '5000000000000000000', + 'chainId': 1, + }); + + expect(dto.unsignedTx, '0xtx'); + expect(dto.tokenAddress, '0xzchf'); + expect(dto.recipient, '0xrecipient'); + expect(dto.amountWei, '5000000000000000000'); + expect(dto.chainId, 1); + }); + + test('RealUnitOcpPaySubmitDto.toJson carries the signed envelope + refs', () { + const dto = RealUnitOcpPaySubmitDto( + unsignedTx: '0xtx', + r: '0xr', + s: '0xs', + v: 27, + paymentLinkId: 'pl_abc', + quoteId: 'q1', + ); + + expect(dto.toJson(), { + 'unsignedTx': '0xtx', + 'r': '0xr', + 's': '0xs', + 'v': 27, + 'paymentLinkId': 'pl_abc', + 'quoteId': 'q1', + }); + }); + + test('RealUnitOcpPayResultDto.fromJson', () { + expect(RealUnitOcpPayResultDto.fromJson({'txId': '0xTxId'}).txId, '0xTxId'); + }); + + group('RealUnitOcpPayStatusDto.fromJson', () { + test('maps each known status', () { + expect( + RealUnitOcpPayStatusDto.fromJson({'status': 'Completed'}).status, + OcpPaymentStatus.completed, + ); + expect( + RealUnitOcpPayStatusDto.fromJson({'status': 'Pending'}).status, + OcpPaymentStatus.pending, + ); + expect( + RealUnitOcpPayStatusDto.fromJson({'status': 'Cancelled'}).status, + OcpPaymentStatus.cancelled, + ); + expect( + RealUnitOcpPayStatusDto.fromJson({'status': 'Expired'}).status, + OcpPaymentStatus.expired, + ); + }); + + test('falls back to unknown for an unmapped status', () { + expect( + RealUnitOcpPayStatusDto.fromJson({'status': 'Whatever'}).status, + OcpPaymentStatus.unknown, + ); + }); + + test('falls back to unknown for an empty status without matching the unknown sentinel', () { + // Regression test: `unknown` is internally represented as `unknown('')`. Before the fix, + // an empty backend value matched that sentinel directly in the known-value loop and bypassed + // the developer.log(...) drift-visibility call. The observable return value is `unknown` + // either way; this pins the mapping so a future regression that makes '' match something + // else (or crash) is caught. The developer.log call itself has no mockable surface in Dart + // (same limitation documented in test/screens/sell/widgets/sell_bank_account_field_test.dart). + expect( + RealUnitOcpPayStatusDto.fromJson({'status': ''}).status, + OcpPaymentStatus.unknown, + ); + }); + + test('isTerminal / isCompleted predicates', () { + expect(OcpPaymentStatus.completed.isTerminal, isTrue); + expect(OcpPaymentStatus.completed.isCompleted, isTrue); + expect(OcpPaymentStatus.cancelled.isTerminal, isTrue); + expect(OcpPaymentStatus.cancelled.isCompleted, isFalse); + expect(OcpPaymentStatus.expired.isTerminal, isTrue); + expect(OcpPaymentStatus.pending.isTerminal, isFalse); + expect(OcpPaymentStatus.unknown.isTerminal, isTrue); + }); + }); + + group('LnurlpPaymentDto.fromJson', () { + test('maps requestedAmount, quote and ZCHF transfer amounts', () { + final dto = LnurlpPaymentDto.fromJson({ + 'requestedAmount': {'asset': 'CHF', 'amount': 42.5}, + 'quote': {'id': 'quote_xyz', 'expiration': '2026-06-03T12:00:00.000Z'}, + 'transferAmounts': [ + { + 'method': 'Ethereum', + 'assets': [ + {'asset': 'ZCHF', 'amount': 42.7}, + ], + }, + { + 'method': 'Bitcoin', + 'assets': [ + {'asset': 'BTC', 'amount': 0.0005}, + ], + }, + ], + }); + + expect(dto.requestedAmount.asset, 'CHF'); + expect(dto.requestedAmount.amount, 42.5); + expect(dto.quote.id, 'quote_xyz'); + expect(dto.transferAmounts, hasLength(2)); + expect(dto.transferAmounts.first.method, 'Ethereum'); + expect(dto.transferAmounts.first.assets.first.asset, 'ZCHF'); + expect(dto.transferAmounts.first.assets.first.amount, 42.7); + // Additive rawAmount captures the JSON value's toString before double parse. + expect(dto.transferAmounts.first.assets.first.rawAmount, '42.7'); + }); + + test('rejects a missing transferAmounts list', () { + expect( + () => LnurlpPaymentDto.fromJson({ + 'requestedAmount': {'asset': 'CHF', 'amount': 1.0}, + 'quote': {'id': 'q', 'expiration': '2026-06-03T12:00:00.000Z'}, + }), + throwsA(isA()), + ); + }); + + test('rejects a missing assets list within a transferAmounts entry', () { + expect( + () => LnurlpPaymentDto.fromJson({ + 'requestedAmount': {'asset': 'CHF', 'amount': 1.0}, + 'quote': {'id': 'q', 'expiration': '2026-06-03T12:00:00.000Z'}, + 'transferAmounts': [ + {'method': 'Ethereum'}, + ], + }), + throwsA(isA()), + ); + }); + + test('parses amount-less asset entries (non-priced display path) as null', () { + final dto = LnurlpPaymentDto.fromJson({ + 'requestedAmount': {'asset': 'CHF', 'amount': 1.0}, + 'quote': {'id': 'q', 'expiration': '2026-06-03T12:00:00.000Z'}, + 'transferAmounts': [ + { + 'method': 'Ethereum', + 'assets': [ + // Optional `amount?` omitted by the backend. + {'asset': 'ZCHF'}, + ], + }, + ], + }); + + expect(dto.transferAmounts.first.assets.first.asset, 'ZCHF'); + expect(dto.transferAmounts.first.assets.first.amount, isNull); + expect(dto.transferAmounts.first.assets.first.rawAmount, isNull); + }); + + test('preserves a string amount as rawAmount without double drift', () { + final dto = LnurlpPaymentDto.fromJson({ + 'requestedAmount': {'asset': 'CHF', 'amount': '10.10'}, + 'quote': {'id': 'q', 'expiration': '2026-06-03T12:00:00.000Z'}, + 'transferAmounts': [ + { + 'method': 'Ethereum', + 'assets': [ + {'asset': 'ZCHF', 'amount': '42.70000000000001'}, + ], + }, + ], + }); + + expect(dto.requestedAmount.amount, 10.10); + expect(dto.transferAmounts.first.assets.first.rawAmount, '42.70000000000001'); + expect(dto.transferAmounts.first.assets.first.amount, isNotNull); + }); + + test('rejects NaN in requestedAmount.amount', () { + expect( + () => LnurlpPaymentDto.fromJson({ + 'requestedAmount': {'asset': 'CHF', 'amount': 'NaN'}, + 'quote': {'id': 'q', 'expiration': '2026-06-03T12:00:00.000Z'}, + 'transferAmounts': [ + { + 'method': 'Ethereum', + 'assets': [ + {'asset': 'ZCHF', 'amount': 1.0}, + ], + }, + ], + }), + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('not a finite number'), + ), + ), + ); + }); + + test('rejects Infinity in transfer asset amount', () { + expect( + () => LnurlpPaymentDto.fromJson({ + 'requestedAmount': {'asset': 'CHF', 'amount': 1.0}, + 'quote': {'id': 'q', 'expiration': '2026-06-03T12:00:00.000Z'}, + 'transferAmounts': [ + { + 'method': 'Ethereum', + 'assets': [ + {'asset': 'ZCHF', 'amount': 'Infinity'}, + ], + }, + ], + }), + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('not a finite number'), + ), + ), + ); + }); + + test('rejects -Infinity in requestedAmount.amount', () { + expect( + () => LnurlpPaymentDto.fromJson({ + 'requestedAmount': {'asset': 'CHF', 'amount': '-Infinity'}, + 'quote': {'id': 'q', 'expiration': '2026-06-03T12:00:00.000Z'}, + }), + throwsA(isA()), + ); + }); + + test('rejects a negative transfer asset amount', () { + expect( + () => LnurlpPaymentDto.fromJson({ + 'requestedAmount': {'asset': 'CHF', 'amount': 1.0}, + 'quote': {'id': 'q', 'expiration': '2026-06-03T12:00:00.000Z'}, + 'transferAmounts': [ + { + 'method': 'Ethereum', + 'assets': [ + {'asset': 'ZCHF', 'amount': -1}, + ], + }, + ], + }), + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('must not be negative'), + ), + ), + ); + }); + + test('rejects a negative requestedAmount.amount', () { + expect( + () => LnurlpPaymentDto.fromJson({ + 'requestedAmount': {'asset': 'CHF', 'amount': -0.01}, + 'quote': {'id': 'q', 'expiration': '2026-06-03T12:00:00.000Z'}, + 'transferAmounts': [ + { + 'method': 'Ethereum', + 'assets': [ + {'asset': 'ZCHF', 'amount': 1.0}, + ], + }, + ], + }), + throwsA( + isA().having( + (e) => e.message, + 'message', + contains('must not be negative'), + ), + ), + ); + }); + + test('ignores the structured recipient object instead of throwing on it', () { + // The backend `recipient` is a PaymentLinkRecipientDto object, not a + // String; reading the quote must not throw on it. Only name+city are + // mapped; other nested address fields are intentionally left unmapped. + final dto = LnurlpPaymentDto.fromJson({ + 'recipient': { + 'name': 'Acme GmbH', + 'address': {'street': 'Bahnhofstrasse', 'houseNumber': '1', 'city': 'Zürich'}, + }, + 'requestedAmount': {'asset': 'CHF', 'amount': 42.5}, + 'quote': {'id': 'quote_xyz', 'expiration': '2026-06-03T12:00:00.000Z'}, + 'transferAmounts': [ + { + 'method': 'Ethereum', + 'assets': [ + {'asset': 'ZCHF', 'amount': 42.7}, + ], + }, + ], + }); + + expect(dto.quote.id, 'quote_xyz'); + expect(dto.transferAmounts.first.assets.first.amount, 42.7); + expect(dto.recipient?.name, 'Acme GmbH'); + expect(dto.recipient?.city, 'Zürich'); + }); + }); +} diff --git a/test/packages/service/dfx/real_unit_pay_service_test.dart b/test/packages/service/dfx/real_unit_pay_service_test.dart new file mode 100644 index 000000000..969965031 --- /dev/null +++ b/test/packages/service/dfx/real_unit_pay_service_test.dart @@ -0,0 +1,341 @@ +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:web3dart/web3dart.dart'; +import 'package:realunit_wallet/packages/config/api_config.dart'; +import 'package:realunit_wallet/packages/config/network_mode.dart'; +import 'package:realunit_wallet/packages/repository/cache_repository.dart'; +import 'package:realunit_wallet/packages/service/app_store.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/api_exception.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_status_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_submit_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_swap_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/sell/dto/broadcast_transaction_request_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_pay_service.dart'; +import 'package:realunit_wallet/packages/service/session_cache.dart'; +import 'package:realunit_wallet/packages/service/wallet_service.dart'; +import 'package:realunit_wallet/packages/wallet/wallet.dart'; +import 'package:realunit_wallet/packages/wallet/wallet_account.dart'; + +class _MockAppStore extends Mock implements AppStore {} + +class _MockWallet extends Mock implements AWallet {} + +class _MockAccount extends Mock implements AWalletAccount {} + +class _MockCacheRepository extends Mock implements CacheRepository {} + +class _MockWalletService extends Mock implements WalletService {} + +class _StubCreds extends Fake implements CredentialsWithKnownAddress { + @override + EthereumAddress get address => + EthereumAddress.fromHex('0x0000000000000000000000000000000000000001'); +} + +Map _swapInfoJson() => { + 'id': 99, + 'uid': 'MOCK-UID', + 'routeId': 7, + 'timestamp': '2026-06-03T00:00:00.000Z', + 'amount': 10, + 'estimatedAmount': 960, + 'targetAsset': 'ZCHF', + 'fees': {'dfx': 1, 'network': 0.5, 'total': 1.5}, + 'minVolume': 1, + 'maxVolume': 1000, + 'minVolumeTarget': 95, + 'maxVolumeTarget': 95000, + 'ethBalance': 1.0, + 'requiredGasEth': 0.001, + 'isValid': true, +}; + +void main() { + late _MockAppStore appStore; + late _MockWallet wallet; + late _MockAccount account; + late _MockWalletService walletService; + late SessionCache session; + + setUp(() { + appStore = _MockAppStore(); + wallet = _MockWallet(); + account = _MockAccount(); + walletService = _MockWalletService(); + session = SessionCache(_MockCacheRepository()); + session.setAuthToken('jwt-1'); + + when(() => appStore.apiConfig).thenReturn(const ApiConfig(networkMode: NetworkMode.mainnet)); + when(() => appStore.sessionCache).thenReturn(session); + when(() => appStore.wallet).thenReturn(wallet); + when(() => wallet.currentAccount).thenReturn(account); + when(() => account.primaryAddress).thenReturn(_StubCreds()); + }); + + RealUnitPayService build(http.Client client) { + when(() => appStore.httpClient).thenReturn(client); + return RealUnitPayService(appStore, walletService); + } + + group('getPaymentDetails', () { + test('GETs the public lnurlp endpoint (no auth header) and parses it', () async { + Uri? sentUri; + Map? headers; + final client = MockClient((request) async { + sentUri = request.url; + headers = request.headers; + return http.Response( + jsonEncode({ + 'requestedAmount': {'asset': 'CHF', 'amount': 42.5}, + 'quote': {'id': 'quote_xyz', 'expiration': '2026-06-03T12:00:00.000Z'}, + 'transferAmounts': [ + { + 'method': 'Ethereum', + 'assets': [ + {'asset': 'ZCHF', 'amount': 42.7}, + ], + }, + ], + }), + 200, + ); + }); + + final dto = await build(client).getPaymentDetails('pl_abc'); + + expect(sentUri!.path, '/v1/lnurlp/pl_abc'); + expect(headers!.containsKey('Authorization'), isFalse); + expect(dto.quote.id, 'quote_xyz'); + expect(dto.transferAmounts.first.assets.first.amount, 42.7); + }); + + test('non-200 → ApiException', () async { + final client = MockClient( + (_) async => http.Response(jsonEncode({'statusCode': 404, 'message': 'gone'}), 404), + ); + expect( + () => build(client).getPaymentDetails('pl_abc'), + throwsA(isA()), + ); + }); + }); + + group('getSwapPaymentInfo', () { + test('PUTs /swap with targetAmount and parses the quote', () async { + Uri? sentUri; + Map? body; + final client = MockClient((request) async { + sentUri = request.url; + body = jsonDecode(request.body) as Map; + return http.Response(jsonEncode(_swapInfoJson()), 200); + }); + + final info = await build( + client, + ).getSwapPaymentInfo(const RealUnitSwapDto.fromTargetAmount(95.5)); + + expect(sentUri!.path, '/v1/realunit/swap'); + expect(body!['targetAmount'], 95.5); + expect(body!.containsKey('amount'), isFalse); + expect(info.id, 99); + expect(info.estimatedAmount, 960); + expect(info.isValid, isTrue); + }); + + test('non-200 → ApiException', () async { + final client = MockClient( + (_) async => http.Response(jsonEncode({'statusCode': 400, 'message': 'bad'}), 400), + ); + expect( + () => build(client).getSwapPaymentInfo(const RealUnitSwapDto.fromTargetAmount(95.5)), + throwsA(isA()), + ); + }); + }); + + group('createSwapUnsignedTransaction', () { + test('200 → parses swap hex', () async { + Uri? sentUri; + final client = MockClient((request) async { + sentUri = request.url; + return http.Response(jsonEncode({'swap': '0xswap'}), 200); + }); + + final dto = await build(client).createSwapUnsignedTransaction(42); + + expect(sentUri!.path, '/v1/realunit/swap/42/unsigned-transaction'); + expect(dto.swap, '0xswap'); + }); + + test('non-200 → ApiException', () async { + final client = MockClient( + (_) async => http.Response(jsonEncode({'statusCode': 400, 'message': 'no eth'}), 400), + ); + expect( + () => build(client).createSwapUnsignedTransaction(42), + throwsA(isA()), + ); + }); + }); + + group('broadcastSwapTransaction', () { + test('201 → returns txHash', () async { + Uri? sentUri; + Map? body; + final client = MockClient((request) async { + sentUri = request.url; + body = jsonDecode(request.body) as Map; + return http.Response(jsonEncode({'txHash': '0xabc'}), 201); + }); + + final txHash = await build(client).broadcastSwapTransaction( + 42, + const BroadcastTransactionRequestDto(unsignedTx: '0xtx', r: '0xr', s: '0xs', v: 27), + ); + + expect(sentUri!.path, '/v1/realunit/swap/42/broadcast'); + expect(body!['unsignedTx'], '0xtx'); + expect(body!['v'], 27); + expect(txHash, '0xabc'); + }); + + test('non-200 → ApiException', () async { + final client = MockClient( + (_) async => http.Response(jsonEncode({'statusCode': 400, 'message': 'bad sig'}), 400), + ); + expect( + () => build(client).broadcastSwapTransaction( + 42, + const BroadcastTransactionRequestDto(unsignedTx: '0xtx', r: '0xr', s: '0xs', v: 27), + ), + throwsA(isA()), + ); + }); + }); + + group('createPayUnsignedTransaction', () { + // Values from the real Sepolia OCP testnet run (DFXswiss/api #3819): + // a CHF 2.00 link settling 2.0 ZCHF (2000000000000000000 wei) to the DFX + // deposit recipient on chain 11155111. + test('PUTs /pay/unsigned-transaction with the payment refs', () async { + Uri? sentUri; + Map? body; + final client = MockClient((request) async { + sentUri = request.url; + body = jsonDecode(request.body) as Map; + return http.Response( + jsonEncode({ + 'unsignedTx': + '0x02f87483aa36a782019b8502284a84ae8502284a84ae830186a094d3117681ca462268048f57d106d312ba0b1215ea80b844a9059cbb000000000000000000000000fb2a9731cda8b3fca015723ef40f310c1e48366b0000000000000000000000000000000000000000000000001bc16d674ec80000c0', + 'tokenAddress': '0xD3117681cA462268048f57D106d312Ba0b1215eA', + 'recipient': '0xfB2a9731cdA8b3FCa015723EF40f310C1E48366b', + 'amountWei': '2000000000000000000', + 'chainId': 11155111, + }), + 200, + ); + }); + + final dto = await build(client).createPayUnsignedTransaction( + const RealUnitOcpPayDto(paymentLinkId: 'pl_realunit_ocp_sepolia', quoteId: 'q1'), + ); + + expect(sentUri!.path, '/v1/realunit/pay/unsigned-transaction'); + expect(body!['paymentLinkId'], 'pl_realunit_ocp_sepolia'); + expect(body!['quoteId'], 'q1'); + expect(dto.recipient, '0xfB2a9731cdA8b3FCa015723EF40f310C1E48366b'); + expect(dto.tokenAddress, '0xD3117681cA462268048f57D106d312Ba0b1215eA'); + expect(dto.amountWei, '2000000000000000000'); + expect(dto.chainId, 11155111); + }); + + test('non-200 → ApiException', () async { + final client = MockClient( + (_) async => + http.Response(jsonEncode({'statusCode': 400, 'message': 'unsupported method'}), 400), + ); + expect( + () => build(client).createPayUnsignedTransaction( + const RealUnitOcpPayDto(paymentLinkId: 'pl_abc', quoteId: 'q1'), + ), + throwsA(isA()), + ); + }); + }); + + group('submitPay', () { + test('PUTs /pay/submit and returns txId', () async { + Uri? sentUri; + Map? body; + final client = MockClient((request) async { + sentUri = request.url; + body = jsonDecode(request.body) as Map; + return http.Response(jsonEncode({'txId': '0xTxId'}), 200); + }); + + final txId = await build(client).submitPay( + const RealUnitOcpPaySubmitDto( + unsignedTx: '0xtx', + r: '0xr', + s: '0xs', + v: 27, + paymentLinkId: 'pl_abc', + quoteId: 'q1', + ), + ); + + expect(sentUri!.path, '/v1/realunit/pay/submit'); + expect(body!['paymentLinkId'], 'pl_abc'); + expect(txId, '0xTxId'); + }); + + test('non-200 → ApiException', () async { + final client = MockClient( + (_) async => http.Response(jsonEncode({'statusCode': 400, 'message': 'fail'}), 400), + ); + expect( + () => build(client).submitPay( + const RealUnitOcpPaySubmitDto( + unsignedTx: '0xtx', + r: '0xr', + s: '0xs', + v: 27, + paymentLinkId: 'pl_abc', + quoteId: 'q1', + ), + ), + throwsA(isA()), + ); + }); + }); + + group('getPayStatus', () { + test('GETs /pay/:id/status and parses the status', () async { + Uri? sentUri; + final client = MockClient((request) async { + sentUri = request.url; + return http.Response(jsonEncode({'status': 'Completed'}), 200); + }); + + final dto = await build(client).getPayStatus('pl_abc'); + + expect(sentUri!.path, '/v1/realunit/pay/pl_abc/status'); + expect(dto.status, OcpPaymentStatus.completed); + }); + + test('non-200 → ApiException', () async { + final client = MockClient( + (_) async => http.Response(jsonEncode({'statusCode': 404, 'message': 'none'}), 404), + ); + expect( + () => build(client).getPayStatus('pl_abc'), + throwsA(isA()), + ); + }); + }); +} diff --git a/test/packages/utils/plain_decimal_test.dart b/test/packages/utils/plain_decimal_test.dart new file mode 100644 index 000000000..08ca20001 --- /dev/null +++ b/test/packages/utils/plain_decimal_test.dart @@ -0,0 +1,68 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:realunit_wallet/packages/utils/plain_decimal.dart'; + +void main() { + group('comparePlainDecimalStrings', () { + test('orders plain decimals exactly (no double conversion)', () { + expect(comparePlainDecimalStrings('1', '2'), lessThan(0)); + expect(comparePlainDecimalStrings('2', '1'), greaterThan(0)); + expect(comparePlainDecimalStrings('1.0', '1.00'), 0); + expect(comparePlainDecimalStrings('1.50', '1.5'), 0); + expect(comparePlainDecimalStrings('10.10', '10.1'), 0); + // Boundary that binary doubles can get wrong at enough fractional digits. + expect(comparePlainDecimalStrings('0.1', '0.10'), 0); + expect(comparePlainDecimalStrings('1000.01', '1000.010'), 0); + expect(comparePlainDecimalStrings('42.70000000000001', '42.7'), greaterThan(0)); + expect(comparePlainDecimalStrings('960', '1000'), lessThan(0)); + expect(comparePlainDecimalStrings('1000', '960'), greaterThan(0)); + }); + + test('handles leading zeros and optional sign', () { + expect(comparePlainDecimalStrings('01.5', '1.5'), 0); + expect(comparePlainDecimalStrings('+1.5', '1.5'), 0); + expect(comparePlainDecimalStrings('-1', '1'), lessThan(0)); + expect(comparePlainDecimalStrings('-2', '-1'), lessThan(0)); + expect(comparePlainDecimalStrings('-0', '0'), 0); + expect(comparePlainDecimalStrings('.5', '0.5'), 0); + expect(comparePlainDecimalStrings('5.', '5'), 0); + }); + + test('rejects scientific notation', () { + expect( + () => comparePlainDecimalStrings('1e3', '1000'), + throwsA(isA()), + ); + expect( + () => comparePlainDecimalStrings('1.2E-3', '0.0012'), + throwsA(isA()), + ); + }); + + test('rejects malformed input fail-closed', () { + expect( + () => comparePlainDecimalStrings('', '1'), + throwsA(isA()), + ); + expect( + () => comparePlainDecimalStrings('1.2.3', '1'), + throwsA(isA()), + ); + expect( + () => comparePlainDecimalStrings('abc', '1'), + throwsA(isA()), + ); + expect( + () => comparePlainDecimalStrings('-', '1'), + throwsA(isA()), + ); + expect( + () => comparePlainDecimalStrings('.', '1'), + throwsA(isA()), + ); + expect( + () => comparePlainDecimalStrings('1.2a', '1'), + throwsA(isA()), + ); + }); + }); +} diff --git a/test/screens/dashboard/widgets/sections/dashboard_actions_test.dart b/test/screens/dashboard/widgets/sections/dashboard_actions_test.dart new file mode 100644 index 000000000..43eb39755 --- /dev/null +++ b/test/screens/dashboard/widgets/sections/dashboard_actions_test.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:go_router/go_router.dart'; +import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/screens/dashboard/widgets/sections/dashboard_actions.dart'; +import 'package:realunit_wallet/setup/routing/routes/app_routes.dart'; +import 'package:realunit_wallet/widgets/action_button.dart'; + +void main() { + late List pushedRoutes; + + setUp(() { + pushedRoutes = []; + }); + + // Routes the three action buttons can push. Each target records the + // pushed route name so the `onPressed` closures are both executed and + // asserted, instead of only painted. + GoRouter buildRouter() { + GoRoute target(String name, String path) => GoRoute( + name: name, + path: path, + builder: (_, _) { + pushedRoutes.add(name); + return Scaffold(body: Text('ROUTE:$name')); + }, + ); + + return GoRouter( + initialLocation: '/', + routes: [ + GoRoute( + path: '/', + builder: (_, _) => const Scaffold(body: DashboardActions()), + ), + target(AppRoutes.buy, '/buy'), + target(AppRoutes.sell, '/sell'), + target(AppRoutes.pay, '/pay'), + ], + ); + } + + Future pumpActions(WidgetTester tester) async { + final router = buildRouter(); + addTearDown(router.dispose); + await tester.pumpWidget( + MaterialApp.router( + routerConfig: router, + localizationsDelegates: const [ + S.delegate, + GlobalMaterialLocalizations.delegate, + ], + supportedLocales: S.delegate.supportedLocales, + ), + ); + await tester.pumpAndSettle(); + } + + Finder actionButtonByLabel(String label) => find.byWidgetPredicate( + (w) => w is ActionButton && w.label == label, + ); + + group('$DashboardActions', () { + testWidgets('renders the buy, sell and pay action buttons', (tester) async { + await pumpActions(tester); + + expect(actionButtonByLabel(S.current.buy), findsOneWidget); + expect(actionButtonByLabel(S.current.sell), findsOneWidget); + expect(actionButtonByLabel(S.current.pay), findsOneWidget); + // Each button is laid out inside an Expanded so the row divides + // the available width into three equal slots. + expect(find.byType(Expanded), findsNWidgets(3)); + }); + + testWidgets('renders the expected icons for each action', (tester) async { + await pumpActions(tester); + + expect(find.byIcon(Icons.add_circle_rounded), findsOneWidget); + expect(find.byIcon(Icons.do_not_disturb_on_rounded), findsOneWidget); + expect(find.byIcon(Icons.qr_code_scanner_rounded), findsOneWidget); + }); + + testWidgets('buy button pushes the buy route', (tester) async { + await pumpActions(tester); + + await tester.tap(actionButtonByLabel(S.current.buy)); + await tester.pumpAndSettle(); + + expect(pushedRoutes, [AppRoutes.buy]); + }); + + testWidgets('sell button pushes the sell route', (tester) async { + await pumpActions(tester); + + await tester.tap(actionButtonByLabel(S.current.sell)); + await tester.pumpAndSettle(); + + expect(pushedRoutes, [AppRoutes.sell]); + }); + + testWidgets('pay button pushes the pay route', (tester) async { + await pumpActions(tester); + + await tester.tap(actionButtonByLabel(S.current.pay)); + await tester.pumpAndSettle(); + + expect(pushedRoutes, [AppRoutes.pay]); + }); + }); +} diff --git a/test/screens/pay/pay_process_cubit_test.dart b/test/screens/pay/pay_process_cubit_test.dart new file mode 100644 index 000000000..d75bfb0c1 --- /dev/null +++ b/test/screens/pay/pay_process_cubit_test.dart @@ -0,0 +1,1288 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:fake_async/fake_async.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:web3dart/crypto.dart'; +import 'package:web3dart/web3dart.dart'; +import 'package:realunit_wallet/packages/config/api_config.dart'; +import 'package:realunit_wallet/packages/config/network_mode.dart'; +import 'package:realunit_wallet/packages/service/app_store.dart'; +import 'package:realunit_wallet/packages/service/dfx/dfx_blockchain_api_service.dart'; +import 'package:realunit_wallet/packages/service/dfx/dfx_faucet_service.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/bitbox_exception.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/faucet/faucet_response_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/lnurlp_payment_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_status_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_submit_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_ocp_pay_unsigned_transaction_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_swap_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_swap_payment_info_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_swap_unsigned_transaction_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/swap_payment_info.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/sell/dto/broadcast_transaction_request_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_pay_service.dart'; +import 'package:realunit_wallet/packages/service/wallet_service.dart'; +import 'package:realunit_wallet/packages/wallet/wallet.dart'; +import 'package:realunit_wallet/packages/wallet/wallet_account.dart'; +import 'package:realunit_wallet/screens/pay/cubits/pay_process/pay_process_cubit.dart'; + +import '../../helper/fake_bitbox_credentials.dart'; + +class _MockPayService extends Mock implements RealUnitPayService {} + +class _MockFaucet extends Mock implements DfxFaucetService {} + +class _MockBlockchain extends Mock implements DfxBlockchainApiService {} + +class _MockWalletService extends Mock implements WalletService {} + +class _MockAppStore extends Mock implements AppStore {} + +class _MockWallet extends Mock implements AWallet {} + +class _MockAccount extends Mock implements AWalletAccount {} + +/// Credentials whose `signToSignature` throws [UnsupportedError] — the debug +/// wallet's behaviour, used to exercise the in-sign defensive guard. +class _UnsupportedCreds extends Fake implements CredentialsWithKnownAddress { + @override + Future signToSignature(Uint8List payload, {int? chainId, bool isEIP1559 = false}) => + throw UnsupportedError('Debug wallet cannot sign'); +} + +SwapPaymentInfo _swap({ + double ethBalance = 1.0, + double requiredGasEth = 0.001, + bool isValid = true, +}) { + return SwapPaymentInfo.fromDto( + RealUnitSwapPaymentInfoDto( + id: 99, + uid: 'u', + routeId: 7, + timestamp: DateTime.parse('2026-06-03T00:00:00.000Z'), + amount: 10, + estimatedAmount: 960, + targetAsset: 'ZCHF', + minVolume: 1, + maxVolume: 1000, + minVolumeTarget: 95, + maxVolumeTarget: 95000, + ethBalance: ethBalance, + requiredGasEth: requiredGasEth, + isValid: isValid, + ), + ); +} + +LnurlpPaymentDto _details({ + required DateTime expiration, + String quoteId = 'quote_fresh', + double zchf = 42.7, +}) { + // rawAmount mirrors production fromJson (always set when amount is present) + // so the settlement guard can prove exact plain-decimal coverage fail-closed + // instead of treating a missing raw string as "cannot prove coverage". + return LnurlpPaymentDto( + requestedAmount: const LnurlpRequestedAmountDto(asset: 'CHF', amount: 42.5), + quote: LnurlpQuoteDto(id: quoteId, expiration: expiration), + transferAmounts: [ + LnurlpTransferAmountDto( + method: 'Ethereum', + assets: [ + LnurlpTransferAssetDto( + asset: 'ZCHF', + amount: zchf, + rawAmount: zchf.toString(), + ), + ], + ), + ], + ); +} + +// A real EIP-1559 (type 2) unsigned tx RLP-encoding an ERC20 transfer(address,uint256) call to +// `recipient` for `amountWei`, `to` = tokenAddress, on `chainId` — matches the DTO's own security +// metadata so PayProcessCubit._validatePayUnsignedTx accepts it. Independently verified +// byte-for-byte against a reference RLP encoder/decoder. +const _unsignedPay = RealUnitOcpPayUnsignedTransactionDto( + unsignedTx: + '0x02f87183aa36a7018459682f008504a817c800830186a094111111111111111111111111111111111111ac0180b844a9059cbb000000000000000000000000222222222222222222222222222222222222bc020000000000000000000000000000000000000000000000004563918244f40000c0', + tokenAddress: '0x111111111111111111111111111111111111ac01', + recipient: '0x222222222222222222222222222222222222bc02', + amountWei: '5000000000000000000', + chainId: 11155111, +); + +// tx.to (0x333...dead) does not match tokenAddress (0x111...ac01) — tokenAddress mismatch. +const _unsignedPayWrongToken = RealUnitOcpPayUnsignedTransactionDto( + unsignedTx: + '0x02f87183aa36a7018459682f008504a817c800830186a094333333333333333333333333333333333333dead80b844a9059cbb000000000000000000000000222222222222222222222222222222222222bc020000000000000000000000000000000000000000000000004563918244f40000c0', + tokenAddress: '0x111111111111111111111111111111111111ac01', + recipient: '0x222222222222222222222222222222222222bc02', + amountWei: '5000000000000000000', + chainId: 11155111, +); + +// calldata recipient (0x444...dead) does not match recipient (0x222...bc02) — recipient mismatch. +const _unsignedPayWrongRecipient = RealUnitOcpPayUnsignedTransactionDto( + unsignedTx: + '0x02f87183aa36a7018459682f008504a817c800830186a094111111111111111111111111111111111111ac0180b844a9059cbb000000000000000000000000444444444444444444444444444444444444dead0000000000000000000000000000000000000000000000004563918244f40000c0', + tokenAddress: '0x111111111111111111111111111111111111ac01', + recipient: '0x222222222222222222222222222222222222bc02', + amountWei: '5000000000000000000', + chainId: 11155111, +); + +// calldata amount is amountWei + 1 (5000000000000000001) — amount mismatch. +const _unsignedPayWrongAmount = RealUnitOcpPayUnsignedTransactionDto( + unsignedTx: + '0x02f87183aa36a7018459682f008504a817c800830186a094111111111111111111111111111111111111ac0180b844a9059cbb000000000000000000000000222222222222222222222222222222222222bc020000000000000000000000000000000000000000000000004563918244f40001c0', + tokenAddress: '0x111111111111111111111111111111111111ac01', + recipient: '0x222222222222222222222222222222222222bc02', + amountWei: '5000000000000000000', + chainId: 11155111, +); + +// tx.chainId is 999 — chainId mismatch (DTO still claims 11155111). +const _unsignedPayWrongChainId = RealUnitOcpPayUnsignedTransactionDto( + unsignedTx: + '0x02f8708203e7018459682f008504a817c800830186a094111111111111111111111111111111111111ac0180b844a9059cbb000000000000000000000000222222222222222222222222222222222222bc020000000000000000000000000000000000000000000000004563918244f40000c0', + tokenAddress: '0x111111111111111111111111111111111111ac01', + recipient: '0x222222222222222222222222222222222222bc02', + amountWei: '5000000000000000000', + chainId: 11155111, +); + +// Same to/recipient/amount/chainId as _unsignedPay (all still valid), but gasLimit is raised to +// 300000 — exceeds the 200_000 local gasLimit cap alone (maxFeePerGas unchanged @ 20 gwei, so +// total fee stays 0.006 ETH, well under the total-fee cap) — isolates the gasLimit check. +const _unsignedPayGasLimitExceedsCap = RealUnitOcpPayUnsignedTransactionDto( + unsignedTx: + '0x02f87183aa36a7018459682f008504a817c800830493e094111111111111111111111111111111111111ac0180b844a9059cbb000000000000000000000000222222222222222222222222222222222222bc020000000000000000000000000000000000000000000000004563918244f40000c0', + tokenAddress: '0x111111111111111111111111111111111111ac01', + recipient: '0x222222222222222222222222222222222222bc02', + amountWei: '5000000000000000000', + chainId: 11155111, +); + +// Same to/recipient/amount/chainId as _unsignedPay, gasLimit unchanged at 100000 (under the +// 200_000 cap), but maxFeePerGas is raised to 600 gwei — total fee becomes 0.06 ETH, over the +// 0.05 ETH total-fee cap alone — isolates the total-fee check. +const _unsignedPayMaxFeeExceedsCap = RealUnitOcpPayUnsignedTransactionDto( + unsignedTx: + '0x02f87183aa36a7018459682f00858bb2c97000830186a094111111111111111111111111111111111111ac0180b844a9059cbb000000000000000000000000222222222222222222222222222222222222bc020000000000000000000000000000000000000000000000004563918244f40000c0', + tokenAddress: '0x111111111111111111111111111111111111ac01', + recipient: '0x222222222222222222222222222222222222bc02', + amountWei: '5000000000000000000', + chainId: 11155111, +); + +// Same to/recipient/amount/chainId as _unsignedPay, but the native ETH `value` field is 1 wei +// instead of 0 — an ERC20 transfer must carry zero value. Verified byte-for-byte against a +// reference RLP encoder/decoder (only the single value byte 0x80→0x01 differs from _unsignedPay). +const _unsignedPayNonZeroValue = RealUnitOcpPayUnsignedTransactionDto( + unsignedTx: + '0x02f87183aa36a7018459682f008504a817c800830186a094111111111111111111111111111111111111ac0101b844a9059cbb000000000000000000000000222222222222222222222222222222222222bc020000000000000000000000000000000000000000000000004563918244f40000c0', + tokenAddress: '0x111111111111111111111111111111111111ac01', + recipient: '0x222222222222222222222222222222222222bc02', + amountWei: '5000000000000000000', + chainId: 11155111, +); + +// Same unsignedTx/tokenAddress/recipient/chainId as _unsignedPay (all still valid and +// self-consistent), but the DTO's amountWei is not a parseable integer. +const _unsignedPayInvalidAmountWei = RealUnitOcpPayUnsignedTransactionDto( + unsignedTx: + '0x02f87183aa36a7018459682f008504a817c800830186a094111111111111111111111111111111111111ac0180b844a9059cbb000000000000000000000000222222222222222222222222222222222222bc020000000000000000000000000000000000000000000000004563918244f40000c0', + tokenAddress: '0x111111111111111111111111111111111111ac01', + recipient: '0x222222222222222222222222222222222222bc02', + amountWei: 'not-a-number', + chainId: 11155111, +); + +// Same unsignedTx/recipient/amountWei/chainId as _unsignedPay, but the DTO's tokenAddress is +// not a valid 20-byte hex address. +const _unsignedPayInvalidTokenAddress = RealUnitOcpPayUnsignedTransactionDto( + unsignedTx: + '0x02f87183aa36a7018459682f008504a817c800830186a094111111111111111111111111111111111111ac0180b844a9059cbb000000000000000000000000222222222222222222222222222222222222bc020000000000000000000000000000000000000000000000004563918244f40000c0', + tokenAddress: 'not-a-valid-address', + recipient: '0x222222222222222222222222222222222222bc02', + amountWei: '5000000000000000000', + chainId: 11155111, +); + +void main() { + late _MockPayService payService; + late _MockFaucet faucet; + late _MockBlockchain blockchain; + late _MockWalletService walletService; + late _MockAppStore appStore; + late _MockWallet wallet; + late _MockAccount account; + + setUpAll(() { + registerFallbackValue(const RealUnitSwapDto.fromTargetAmount(1)); + registerFallbackValue(const RealUnitOcpPayDto(paymentLinkId: 'pl_abc', quoteId: 'q')); + registerFallbackValue( + const BroadcastTransactionRequestDto(unsignedTx: '', r: '', s: '', v: 0), + ); + registerFallbackValue( + const RealUnitOcpPaySubmitDto( + unsignedTx: '', + r: '', + s: '', + v: 0, + paymentLinkId: 'pl_abc', + quoteId: 'q', + ), + ); + }); + + setUp(() { + payService = _MockPayService(); + faucet = _MockFaucet(); + blockchain = _MockBlockchain(); + walletService = _MockWalletService(); + appStore = _MockAppStore(); + wallet = _MockWallet(); + account = _MockAccount(); + + // testnet → apiConfig.asset.chainId = 11155111, matching every unsigned-tx fixture in this file + when(() => appStore.apiConfig).thenReturn(const ApiConfig(networkMode: NetworkMode.testnet)); + when(() => appStore.primaryAddress).thenReturn('0xwallet'); + when(() => appStore.wallet).thenReturn(wallet); + when(() => wallet.walletType).thenReturn(WalletType.software); + when(() => wallet.currentAccount).thenReturn(account); + when(() => account.primaryAddress).thenReturn(FakeBitboxCredentials(signDelay: Duration.zero)); + when(() => walletService.ensureCurrentWalletUnlocked()).thenAnswer((_) async {}); + when(() => walletService.lockCurrentWallet()).thenAnswer((_) async {}); + }); + + PayProcessCubit build({double zchfNeeded = 42.7}) => PayProcessCubit( + payService: payService, + faucetService: faucet, + blockchainService: blockchain, + walletService: walletService, + appStore: appStore, + paymentLinkId: 'pl_abc', + zchfNeeded: zchfNeeded, + ); + + void wireHappyPath() { + when(() => payService.getSwapPaymentInfo(any())).thenAnswer((_) async => _swap()); + when(() => payService.createSwapUnsignedTransaction(any())).thenAnswer( + (_) async => const RealUnitSwapUnsignedTransactionDto(swap: '0x02f8aa'), + ); + when( + () => payService.broadcastSwapTransaction(any(), any()), + ).thenAnswer((_) async => '0xswaptx'); + when(() => payService.getPaymentDetails('pl_abc')).thenAnswer( + (_) async => _details(expiration: DateTime.now().add(const Duration(minutes: 5))), + ); + when( + () => payService.createPayUnsignedTransaction(any()), + ).thenAnswer((_) async => _unsignedPay); + when(() => payService.submitPay(any())).thenAnswer((_) async => '0xpaytx'); + } + + // The sign step uses `Future.delayed(Duration.zero)` (FakeBitboxCredentials), + // which is a zero-duration *timer* under fakeAsync — `flushMicrotasks` alone + // does not fire it. Elapsing zero repeatedly drains the whole await chain + // (each mock `thenAnswer` future + every zero-delay sign timer) until the + // cubit settles. + void drain(FakeAsync async) { + for (var i = 0; i < 40; i++) { + async.flushMicrotasks(); + async.elapse(Duration.zero); + } + } + + test('debug wallet → signatureUnsupported before any network call', () async { + when(() => wallet.walletType).thenReturn(WalletType.debug); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as PayProcessFailure; + expect(state.reason, PayProcessFailureReason.signatureUnsupported); + verifyNever(() => payService.getSwapPaymentInfo(any())); + await cubit.close(); + }); + + test('invalid swap quote → insufficientZchf', () async { + when(() => payService.getSwapPaymentInfo(any())).thenAnswer((_) async => _swap(isValid: false)); + + final cubit = build(); + await cubit.start(); + + final state = cubit.state as PayProcessFailure; + expect(state.reason, PayProcessFailureReason.insufficientZchf); + await cubit.close(); + }); + + test('swap sizes the target with a slippage buffer over the ZCHF needed', () async { + wireHappyPath(); + when(() => payService.getPayStatus('pl_abc')).thenAnswer( + (_) async => const RealUnitOcpPayStatusDto(status: OcpPaymentStatus.completed), + ); + RealUnitSwapDto? sentDto; + when(() => payService.getSwapPaymentInfo(any())).thenAnswer((invocation) async { + sentDto = invocation.positionalArguments.first as RealUnitSwapDto; + return _swap(); + }); + + final cubit = build(zchfNeeded: 100); + await cubit.start(); + + // After start() resolves the chain the pay tx has been submitted. + expect(cubit.state, isA()); + // 100 * 1.03 swap headroom buffer (covers ordinary CHF→ZCHF / swap-rate + // drift between scan and settle). + expect(sentDto!.targetAmount, closeTo(103, 0.0001)); + expect(sentDto!.amount, isNull); + await cubit.close(); + }); + + test('happy path: swap → refresh quote → pay → polled Completed → success', () async { + fakeAsync((async) { + wireHappyPath(); + when(() => payService.getPayStatus('pl_abc')).thenAnswer( + (_) async => const RealUnitOcpPayStatusDto(status: OcpPaymentStatus.completed), + ); + + final cubit = build(); + cubit.start(); + drain(async); + + // Pay submitted → polling status. + expect(cubit.state, isA()); + + // First status poll @ 3s returns Completed → success. + async.elapse(const Duration(seconds: 3)); + drain(async); + expect(cubit.state, isA()); + + cubit.close(); + async.flushTimers(); + }); + }); + + test('re-fetched quote sends a fresh quoteId into the pay step', () async { + wireHappyPath(); + when(() => payService.getPaymentDetails('pl_abc')).thenAnswer( + (_) async => + _details(expiration: DateTime.now().add(const Duration(minutes: 5)), quoteId: 'q_fresh2'), + ); + RealUnitOcpPayDto? payDto; + when(() => payService.createPayUnsignedTransaction(any())).thenAnswer((invocation) async { + payDto = invocation.positionalArguments.first as RealUnitOcpPayDto; + return _unsignedPay; + }); + + final cubit = build(); + final settled = cubit.stream.firstWhere((s) => s is PayProcessAwaitingSettlement); + await cubit.start(); + await settled; + + expect(payDto!.quoteId, 'q_fresh2'); + await cubit.close(); + }); + + test('quote expired between swap and pay → pay-only retry (no re-scan)', () async { + wireHappyPath(); + when(() => payService.getPaymentDetails('pl_abc')).thenAnswer( + (_) async => _details(expiration: DateTime.now().subtract(const Duration(minutes: 1))), + ); + + final cubit = build(); + final retry = cubit.stream.firstWhere((s) => s is PayProcessPayRetry); + await cubit.start(); + final state = await retry as PayProcessPayRetry; + + // Genuine expiry surfaces as a retryable state — NOT a terminal failure — + // because the swap already ran. The pay leg is never submitted here. + expect(state.reason, PayRetryReason.quoteExpired); + verifyNever(() => payService.createPayUnsignedTransaction(any())); + await cubit.close(); + }); + + test('pay submit failure after swap → retry (transient), not terminal', () async { + wireHappyPath(); + when(() => payService.submitPay(any())).thenThrow(Exception('settlement rejected')); + + final cubit = build(); + final retry = cubit.stream.firstWhere((s) => s is PayProcessPayRetry); + await cubit.start(); + final state = await retry as PayProcessPayRetry; + + // The swap is done; a failed pay must NOT force a re-swap. + expect(state.reason, PayRetryReason.transient); + expect(cubit.state, isNot(isA())); + await cubit.close(); + }); + + test('unsigned pay tx "to" does not match DTO tokenAddress → unsignedTxMismatch, never signed', + () async { + wireHappyPath(); + when(() => payService.createPayUnsignedTransaction(any())) + .thenAnswer((_) async => _unsignedPayWrongToken); + + final cubit = build(); + final retry = cubit.stream.firstWhere((s) => s is PayProcessPayRetry); + await cubit.start(); + final state = await retry as PayProcessPayRetry; + + expect(state.reason, PayRetryReason.unsignedTxMismatch); + verifyNever(() => payService.submitPay(any())); + await cubit.close(); + }); + + test('unsigned pay tx calldata recipient does not match DTO recipient → unsignedTxMismatch, never signed', + () async { + wireHappyPath(); + when(() => payService.createPayUnsignedTransaction(any())) + .thenAnswer((_) async => _unsignedPayWrongRecipient); + + final cubit = build(); + final retry = cubit.stream.firstWhere((s) => s is PayProcessPayRetry); + await cubit.start(); + final state = await retry as PayProcessPayRetry; + + expect(state.reason, PayRetryReason.unsignedTxMismatch); + verifyNever(() => payService.submitPay(any())); + await cubit.close(); + }); + + test('unsigned pay tx calldata amount does not match DTO amountWei → unsignedTxMismatch, never signed', + () async { + wireHappyPath(); + when(() => payService.createPayUnsignedTransaction(any())) + .thenAnswer((_) async => _unsignedPayWrongAmount); + + final cubit = build(); + final retry = cubit.stream.firstWhere((s) => s is PayProcessPayRetry); + await cubit.start(); + final state = await retry as PayProcessPayRetry; + + expect(state.reason, PayRetryReason.unsignedTxMismatch); + verifyNever(() => payService.submitPay(any())); + await cubit.close(); + }); + + test('unsigned pay tx chainId does not match DTO chainId → unsignedTxMismatch, never signed', + () async { + wireHappyPath(); + when(() => payService.createPayUnsignedTransaction(any())) + .thenAnswer((_) async => _unsignedPayWrongChainId); + + final cubit = build(); + final retry = cubit.stream.firstWhere((s) => s is PayProcessPayRetry); + await cubit.start(); + final state = await retry as PayProcessPayRetry; + + expect(state.reason, PayRetryReason.unsignedTxMismatch); + verifyNever(() => payService.submitPay(any())); + await cubit.close(); + }); + + test( + 'unsigned pay tx+DTO self-consistent but chainId mismatches local apiConfig → unsignedTxMismatch, never signed', + () async { + // _unsignedPay is self-consistent at chainId 11155111 (DTO + RLP both agree), so the + // DTO-vs-tx check would pass. Override apiConfig to mainnet (local chainId=1) to prove + // the independent local-chainId check rejects it. + wireHappyPath(); + when(() => appStore.apiConfig).thenReturn(const ApiConfig(networkMode: NetworkMode.mainnet)); + + final cubit = build(); + final retry = cubit.stream.firstWhere((s) => s is PayProcessPayRetry); + await cubit.start(); + final state = await retry as PayProcessPayRetry; + + expect(state.reason, PayRetryReason.unsignedTxMismatch); + verifyNever(() => payService.submitPay(any())); + await cubit.close(); + }, + ); + + test('unsigned pay tx gasLimit exceeds local cap → unsignedTxMismatch, never signed', () async { + wireHappyPath(); + when(() => payService.createPayUnsignedTransaction(any())) + .thenAnswer((_) async => _unsignedPayGasLimitExceedsCap); + + final cubit = build(); + final retry = cubit.stream.firstWhere((s) => s is PayProcessPayRetry); + await cubit.start(); + final state = await retry as PayProcessPayRetry; + + expect(state.reason, PayRetryReason.unsignedTxMismatch); + verifyNever(() => payService.submitPay(any())); + await cubit.close(); + }); + + test('unsigned pay tx max total fee exceeds local cap → unsignedTxMismatch, never signed', + () async { + wireHappyPath(); + when(() => payService.createPayUnsignedTransaction(any())) + .thenAnswer((_) async => _unsignedPayMaxFeeExceedsCap); + + final cubit = build(); + final retry = cubit.stream.firstWhere((s) => s is PayProcessPayRetry); + await cubit.start(); + final state = await retry as PayProcessPayRetry; + + expect(state.reason, PayRetryReason.unsignedTxMismatch); + verifyNever(() => payService.submitPay(any())); + await cubit.close(); + }); + + test('unsigned pay tx sends non-zero native value → unsignedTxMismatch, never signed', () async { + wireHappyPath(); + when(() => payService.createPayUnsignedTransaction(any())) + .thenAnswer((_) async => _unsignedPayNonZeroValue); + + final cubit = build(); + final retry = cubit.stream.firstWhere((s) => s is PayProcessPayRetry); + await cubit.start(); + final state = await retry as PayProcessPayRetry; + + expect(state.reason, PayRetryReason.unsignedTxMismatch); + verifyNever(() => payService.submitPay(any())); + await cubit.close(); + }); + + test('unsigned pay tx DTO amountWei is not a valid integer → unsignedTxMismatch, never signed', + () async { + wireHappyPath(); + when(() => payService.createPayUnsignedTransaction(any())) + .thenAnswer((_) async => _unsignedPayInvalidAmountWei); + + final cubit = build(); + final retry = cubit.stream.firstWhere((s) => s is PayProcessPayRetry); + await cubit.start(); + final state = await retry as PayProcessPayRetry; + + expect(state.reason, PayRetryReason.unsignedTxMismatch); + expect(state.message, contains('amountWei is not a valid integer')); + verifyNever(() => payService.submitPay(any())); + await cubit.close(); + }); + + test( + 'unsigned pay tx DTO tokenAddress is not a valid 20-byte address → unsignedTxMismatch, never signed', + () async { + wireHappyPath(); + when(() => payService.createPayUnsignedTransaction(any())) + .thenAnswer((_) async => _unsignedPayInvalidTokenAddress); + + final cubit = build(); + final retry = cubit.stream.firstWhere((s) => s is PayProcessPayRetry); + await cubit.start(); + final state = await retry as PayProcessPayRetry; + + expect(state.reason, PayRetryReason.unsignedTxMismatch); + expect(state.message, contains('tokenAddress is not a valid 20-byte address')); + verifyNever(() => payService.submitPay(any())); + await cubit.close(); + }, + ); + + test('terminal non-completed status (Cancelled) → pay-only retry', () async { + fakeAsync((async) { + wireHappyPath(); + when(() => payService.getPayStatus('pl_abc')).thenAnswer( + (_) async => const RealUnitOcpPayStatusDto(status: OcpPaymentStatus.cancelled), + ); + + final cubit = build(); + cubit.start(); + drain(async); + expect(cubit.state, isA()); + + async.elapse(const Duration(seconds: 3)); + drain(async); + // A cancelled settlement after the swap leaves the user holding ZCHF — it + // is recoverable by retrying the pay leg, not a terminal failure. + final state = cubit.state as PayProcessPayRetry; + expect(state.reason, PayRetryReason.transient); + + cubit.close(); + async.flushTimers(); + }); + }); + + test('status polling ignores a transient error then settles', () async { + fakeAsync((async) { + wireHappyPath(); + var call = 0; + when(() => payService.getPayStatus('pl_abc')).thenAnswer((_) async { + call++; + if (call == 1) throw Exception('rpc 503'); + return const RealUnitOcpPayStatusDto(status: OcpPaymentStatus.completed); + }); + + final cubit = build(); + cubit.start(); + drain(async); + + // 1st poll throws → still awaiting. + async.elapse(const Duration(seconds: 3)); + drain(async); + expect(cubit.state, isA()); + + // 2nd poll completes → success. + async.elapse(const Duration(seconds: 3)); + drain(async); + expect(cubit.state, isA()); + + cubit.close(); + async.flushTimers(); + }); + }); + + test('status polling exceeds max attempts on a persistently non-terminal status → transient retry', + () { + fakeAsync((async) { + wireHappyPath(); + var pollCalls = 0; + when(() => payService.getPayStatus('pl_abc')).thenAnswer((_) async { + pollCalls++; + return const RealUnitOcpPayStatusDto(status: OcpPaymentStatus.pending); + }); + + final cubit = build(); + cubit.start(); + drain(async); + expect(cubit.state, isA()); + + // 40 polls @ 3s, each still Pending (never terminal) — the 40th hits the max-attempts + // cap and gives up rather than polling forever. Iterations 1-39 exercise the + // "still polling" branch; iteration 40 exercises the "give up" branch. + for (var i = 0; i < 40; i++) { + async.elapse(const Duration(seconds: 3)); + drain(async); + } + + final state = cubit.state as PayProcessPayRetry; + expect(state.reason, PayRetryReason.transient); + expect(state.message, 'status polling exceeded max attempts'); + expect(pollCalls, 40); + + // Polling has genuinely stopped — elapsing further must not trigger another call. + async.elapse(const Duration(seconds: 3)); + drain(async); + expect(pollCalls, 40); + + cubit.close(); + async.flushTimers(); + }); + }); + + test('status polling exceeds max attempts on persistent errors → transient retry', () { + fakeAsync((async) { + wireHappyPath(); + var pollCalls = 0; + when(() => payService.getPayStatus('pl_abc')).thenAnswer((_) async { + pollCalls++; + throw Exception('rpc 503'); + }); + + final cubit = build(); + cubit.start(); + drain(async); + expect(cubit.state, isA()); + + // 40 polls @ 3s, each throwing — the 40th hits the max-attempts cap via the catch path. + for (var i = 0; i < 40; i++) { + async.elapse(const Duration(seconds: 3)); + drain(async); + } + + final state = cubit.state as PayProcessPayRetry; + expect(state.reason, PayRetryReason.transient); + expect(state.message, 'status polling exceeded max attempts'); + expect(pollCalls, 40); + + cubit.close(); + async.flushTimers(); + }); + }); + + test('low ETH balance → faucet → eth polling crosses threshold → swap proceeds', () async { + fakeAsync((async) { + wireHappyPath(); + when( + () => payService.getSwapPaymentInfo(any()), + ).thenAnswer((_) async => _swap(ethBalance: 0, requiredGasEth: 0.001)); + when( + () => faucet.requestFaucet(), + ).thenAnswer((_) async => const FaucetResponseDto(txId: '0xf', amount: 0.01)); + var balanceCall = 0; + when(() => blockchain.getEthBalance(any())).thenAnswer((_) async { + balanceCall++; + return balanceCall == 1 ? 0.0 : 0.01; + }); + when(() => payService.getPayStatus('pl_abc')).thenAnswer( + (_) async => const RealUnitOcpPayStatusDto(status: OcpPaymentStatus.completed), + ); + + final cubit = build(); + cubit.start(); + drain(async); + expect(cubit.state, isA()); + + // 1st eth poll @ 5s — still 0. + async.elapse(const Duration(seconds: 5)); + drain(async); + expect(cubit.state, isA()); + + // 2nd eth poll @ 10s — funded → swap runs through to settlement polling. + async.elapse(const Duration(seconds: 5)); + drain(async); + expect(cubit.state, isA()); + + // status poll completes the flow. + async.elapse(const Duration(seconds: 3)); + drain(async); + expect(cubit.state, isA()); + + cubit.close(); + async.flushTimers(); + }); + }); + + test('eth polling ignores a transient balance-check error then proceeds once funded', () { + fakeAsync((async) { + wireHappyPath(); + when( + () => payService.getSwapPaymentInfo(any()), + ).thenAnswer((_) async => _swap(ethBalance: 0, requiredGasEth: 0.001)); + when( + () => faucet.requestFaucet(), + ).thenAnswer((_) async => const FaucetResponseDto(txId: '0xf', amount: 0.01)); + var balanceCall = 0; + when(() => blockchain.getEthBalance(any())).thenAnswer((_) async { + balanceCall++; + if (balanceCall == 1) throw Exception('rpc 503'); + return 0.01; + }); + when(() => payService.getPayStatus('pl_abc')).thenAnswer( + (_) async => const RealUnitOcpPayStatusDto(status: OcpPaymentStatus.completed), + ); + + final cubit = build(); + cubit.start(); + drain(async); + expect(cubit.state, isA()); + + // 1st eth poll @ 5s — balance check throws; must not get stuck, keeps polling. + async.elapse(const Duration(seconds: 5)); + drain(async); + expect(cubit.state, isA()); + + // 2nd eth poll @ 10s — funded → swap proceeds to settlement polling. + async.elapse(const Duration(seconds: 5)); + drain(async); + expect(cubit.state, isA()); + + cubit.close(); + async.flushTimers(); + }); + }); + + test('eth polling exceeds max attempts while balance stays short → insufficientEth', () { + fakeAsync((async) { + wireHappyPath(); + when( + () => payService.getSwapPaymentInfo(any()), + ).thenAnswer((_) async => _swap(ethBalance: 0, requiredGasEth: 0.001)); + when( + () => faucet.requestFaucet(), + ).thenAnswer((_) async => const FaucetResponseDto(txId: '0xf', amount: 0.01)); + var balanceCalls = 0; + when(() => blockchain.getEthBalance(any())).thenAnswer((_) async { + balanceCalls++; + return 0.0; // never crosses requiredGasEth + }); + + final cubit = build(); + cubit.start(); + drain(async); + expect(cubit.state, isA()); + + // 24 polls @ 5s, each still short — the 24th hits the max-attempts cap. + for (var i = 0; i < 24; i++) { + async.elapse(const Duration(seconds: 5)); + drain(async); + } + + final state = cubit.state as PayProcessFailure; + expect(state.reason, PayProcessFailureReason.insufficientEth); + expect(cubit.debugSwapInFlight, isFalse); + expect(state.message, 'eth balance polling exceeded max attempts'); + expect(balanceCalls, 24); + + // Polling has genuinely stopped — elapsing further must not trigger another call. + async.elapse(const Duration(seconds: 5)); + drain(async); + expect(balanceCalls, 24); + // Swap must never have been attempted after the cap. + verifyNever(() => payService.createSwapUnsignedTransaction(any())); + + cubit.close(); + async.flushTimers(); + }); + }); + + test('closing the cubit mid eth-poll-tick still releases the guard via finally', () { + fakeAsync((async) { + wireHappyPath(); + when( + () => payService.getSwapPaymentInfo(any()), + ).thenAnswer((_) async => _swap(ethBalance: 0, requiredGasEth: 0.001)); + when( + () => faucet.requestFaucet(), + ).thenAnswer((_) async => const FaucetResponseDto(txId: '0xf', amount: 0.01)); + final balanceCompleter = Completer(); + when(() => blockchain.getEthBalance(any())).thenAnswer((_) => balanceCompleter.future); + + final cubit = build(); + cubit.start(); + drain(async); + expect(cubit.state, isA()); + + // Trigger the first eth-poll tick; it awaits getEthBalance, which never completes yet. + async.elapse(const Duration(seconds: 5)); + drain(async); + expect(cubit.debugSwapInFlight, isTrue); + + // Close the cubit while the tick is still in-flight, then let the pending balance future + // resolve — the tick resumes into `if (isClosed) return;`, which must still release the + // guard via `finally`. + cubit.close(); + balanceCompleter.complete(0.0); + drain(async); + + expect(cubit.debugSwapInFlight, isFalse); + async.flushTimers(); + }); + }); + + test('hung getEthBalance is unwound by per-request timeout and still hits max attempts', () { + fakeAsync((async) { + wireHappyPath(); + when( + () => payService.getSwapPaymentInfo(any()), + ).thenAnswer((_) async => _swap(ethBalance: 0, requiredGasEth: 0.001)); + when( + () => faucet.requestFaucet(), + ).thenAnswer((_) async => const FaucetResponseDto(txId: '0xf', amount: 0.01)); + var balanceCalls = 0; + when(() => blockchain.getEthBalance(any())).thenAnswer((_) { + balanceCalls++; + // Never completes — only `.timeout(_ethPollTimeout)` (10s) unwedges the tick. + return Completer().future; + }); + + final cubit = build(); + cubit.start(); + drain(async); + expect(cubit.state, isA()); + + // Cadence under fakeAsync: tick @5s starts attempt 1; its 10s timeout and + // the next periodic may interleave such that a free tick is only every + // ~15s (periodic can fire while still in-flight, then timeout frees). + // 24 attempts ⇒ up to ~360s virtual time. Step in 5s until failure. + for (var i = 0; i < 100 && cubit.state is! PayProcessFailure; i++) { + async.elapse(const Duration(seconds: 5)); + drain(async); + } + + final state = cubit.state as PayProcessFailure; + expect(state.reason, PayProcessFailureReason.insufficientEth); + expect(state.message, 'eth balance polling exceeded max attempts'); + expect(balanceCalls, 24); + + cubit.close(); + async.flushTimers(); + }); + }); + + test('faucet request failure → insufficientEth', () async { + when( + () => payService.getSwapPaymentInfo(any()), + ).thenAnswer((_) async => _swap(ethBalance: 0, requiredGasEth: 0.001)); + when(() => faucet.requestFaucet()).thenThrow(Exception('faucet down')); + + final cubit = build(); + final failed = cubit.stream.firstWhere((s) => s is PayProcessFailure); + await cubit.start(); + final state = await failed as PayProcessFailure; + + expect(state.reason, PayProcessFailureReason.insufficientEth); + await cubit.close(); + }); + + test('UnsupportedError while signing → signatureUnsupported', () async { + wireHappyPath(); + // Wallet reports software type (passes the start() gate) but the credentials + // throw UnsupportedError on sign — exercises the in-sign defensive guard. + when(() => account.primaryAddress).thenReturn(_UnsupportedCreds()); + + final cubit = build(); + final failed = cubit.stream.firstWhere((s) => s is PayProcessFailure); + await cubit.start(); + final state = await failed as PayProcessFailure; + + expect(state.reason, PayProcessFailureReason.signatureUnsupported); + await cubit.close(); + }); + + test('swap quote fetch failure → generic', () async { + when(() => payService.getSwapPaymentInfo(any())).thenThrow(Exception('api 500')); + + final cubit = build(); + final failed = cubit.stream.firstWhere((s) => s is PayProcessFailure); + await cubit.start(); + final state = await failed as PayProcessFailure; + + expect(state.reason, PayProcessFailureReason.generic); + await cubit.close(); + }); + + test('BitBox disconnect during the swap sign → bitboxRequired', () async { + wireHappyPath(); + when(() => account.primaryAddress).thenReturn( + FakeBitboxCredentials(behavior: FakeBitboxBehavior.disconnect, signDelay: Duration.zero), + ); + + final cubit = build(); + final failed = cubit.stream.firstWhere((s) => s is PayProcessFailure); + await cubit.start(); + final state = await failed as PayProcessFailure; + + expect(state.reason, PayProcessFailureReason.bitboxRequired); + await cubit.close(); + }); + + test('generic sign failure during the swap → generic', () async { + wireHappyPath(); + when(() => account.primaryAddress).thenReturn( + FakeBitboxCredentials(behavior: FakeBitboxBehavior.malformed, signDelay: Duration.zero), + ); + + final cubit = build(); + final failed = cubit.stream.firstWhere((s) => s is PayProcessFailure); + await cubit.start(); + final state = await failed as PayProcessFailure; + + expect(state.reason, PayProcessFailureReason.generic); + await cubit.close(); + }); + + test('transient quote re-fetch failure after swap → retry (not re-scan)', () async { + wireHappyPath(); + when(() => payService.getPaymentDetails('pl_abc')).thenThrow(Exception('lnurlp 500')); + + final cubit = build(); + final retry = cubit.stream.firstWhere((s) => s is PayProcessPayRetry); + await cubit.start(); + final state = await retry as PayProcessPayRetry; + + // A transient fetch error is NOT a genuine expiry — it routes to the + // pay-only retry, never to a re-scan → re-swap. + expect(state.reason, PayRetryReason.transient); + await cubit.close(); + }); + + test('BitBox disconnect during the pay sign (after swap) → pay-only retry', () async { + wireHappyPath(); + // First sign (swap) succeeds; the second sign (pay) reports a dropped BLE + // link. Because the swap already happened, this is a retryable pay-leg + // failure rather than a terminal one. + final creds = _CountingSignCreds( + throwOnCall: 2, + error: const BitboxNotConnectedException(), + ); + when(() => account.primaryAddress).thenReturn(creds); + + final cubit = build(); + final retry = cubit.stream.firstWhere((s) => s is PayProcessPayRetry); + await cubit.start(); + final state = await retry as PayProcessPayRetry; + + expect(creds.calls, 2); + expect(state.reason, PayRetryReason.transient); + await cubit.close(); + }); + + test('insufficient ZCHF after swap (fresh amount > acquired) → typed retry', () async { + wireHappyPath(); + // Swap acquires estimatedAmount=960 ZCHF, but the fresh quote now demands + // 1000 ZCHF — more than was swapped. Surface the typed, retryable state. + when(() => payService.getPaymentDetails('pl_abc')).thenAnswer( + (_) async => _details( + expiration: DateTime.now().add(const Duration(minutes: 5)), + zchf: 1000, + ), + ); + + final cubit = build(); + final retry = cubit.stream.firstWhere((s) => s is PayProcessPayRetry); + await cubit.start(); + final state = await retry as PayProcessPayRetry; + + expect(state.reason, PayRetryReason.insufficientZchf); + // The pay leg is never attempted — the swapped ZCHF stays in the wallet. + verifyNever(() => payService.createPayUnsignedTransaction(any())); + await cubit.close(); + }); + + test('insufficient ZCHF with rawAmount uses exact plain-decimal comparison', () async { + wireHappyPath(); + // Swap acquires estimatedAmount=960. Fresh quote carries rawAmount '1000' + // (exact string path) — must hit the plain-decimal branch, not only double `>`. + when(() => payService.getPaymentDetails('pl_abc')).thenAnswer( + (_) async => LnurlpPaymentDto( + requestedAmount: const LnurlpRequestedAmountDto(asset: 'CHF', amount: 42.5), + quote: LnurlpQuoteDto( + id: 'quote_fresh', + expiration: DateTime.now().add(const Duration(minutes: 5)), + ), + transferAmounts: [ + const LnurlpTransferAmountDto( + method: 'Ethereum', + assets: [ + LnurlpTransferAssetDto( + asset: 'ZCHF', + amount: 1000, + rawAmount: '1000', + ), + ], + ), + ], + ), + ); + + final cubit = build(); + final retry = cubit.stream.firstWhere((s) => s is PayProcessPayRetry); + await cubit.start(); + final state = await retry as PayProcessPayRetry; + + expect(state.reason, PayRetryReason.insufficientZchf); + verifyNever(() => payService.createPayUnsignedTransaction(any())); + await cubit.close(); + }); + + test('malformed rawAmount fail-closed retries (never treats as OK)', () async { + wireHappyPath(); + // rawAmount is scientific notation → FormatException on plain-decimal parse → + // fail closed (cannot prove exact coverage) rather than falling back to a + // rounding-prone double comparison. Outcome here still retries; amount 1000 + // > acquired 960 would also have been true under the old double path, so + // this is a regression pin that the path still retries after the fix. + when(() => payService.getPaymentDetails('pl_abc')).thenAnswer( + (_) async => LnurlpPaymentDto( + requestedAmount: const LnurlpRequestedAmountDto(asset: 'CHF', amount: 42.5), + quote: LnurlpQuoteDto( + id: 'quote_fresh', + expiration: DateTime.now().add(const Duration(minutes: 5)), + ), + transferAmounts: [ + const LnurlpTransferAmountDto( + method: 'Ethereum', + assets: [ + LnurlpTransferAssetDto( + asset: 'ZCHF', + amount: 1000, + rawAmount: '1e3', + ), + ], + ), + ], + ), + ); + + final cubit = build(); + final retry = cubit.stream.firstWhere((s) => s is PayProcessPayRetry); + await cubit.start(); + final state = await retry as PayProcessPayRetry; + + expect(state.reason, PayRetryReason.insufficientZchf); + verifyNever(() => payService.createPayUnsignedTransaction(any())); + await cubit.close(); + }); + + test('non-plain-decimal fresh amount that would look covered by double comparison still retries ' + '(fail-closed)', () async { + wireHappyPath(); + // Swap acquires estimatedAmount=960. Fresh settlement amount is 900 (a plain double LESS than + // 960 — the old double `>` fallback would say "not exceeding", i.e. wrongly "covered"), but + // rawAmount is scientific notation ('9e2') so no exact plain-decimal comparison is possible. + // The fix must NOT fall back to the double comparison here — it must fail closed and retry, + // proving the conservative `true` return, not merely coincide with what double comparison + // would already have said. + when(() => payService.getPaymentDetails('pl_abc')).thenAnswer( + (_) async => LnurlpPaymentDto( + requestedAmount: const LnurlpRequestedAmountDto(asset: 'CHF', amount: 42.5), + quote: LnurlpQuoteDto( + id: 'quote_fresh', + expiration: DateTime.now().add(const Duration(minutes: 5)), + ), + transferAmounts: [ + const LnurlpTransferAmountDto( + method: 'Ethereum', + assets: [ + LnurlpTransferAssetDto( + asset: 'ZCHF', + amount: 900, + rawAmount: '9e2', + ), + ], + ), + ], + ), + ); + + final cubit = build(); + final retry = cubit.stream.firstWhere((s) => s is PayProcessPayRetry); + await cubit.start(); + final state = await retry as PayProcessPayRetry; + + expect(state.reason, PayRetryReason.insufficientZchf); + verifyNever(() => payService.createPayUnsignedTransaction(any())); + await cubit.close(); + }); + + test('missing rawAmount fails closed even when the double amount would look covered', () async { + wireHappyPath(); + // amount 900 < acquired 960 — double comparison would say "covered". rawAmount is null so + // exact plain-decimal coverage cannot be proven; fail closed and retry. + when(() => payService.getPaymentDetails('pl_abc')).thenAnswer( + (_) async => LnurlpPaymentDto( + requestedAmount: const LnurlpRequestedAmountDto(asset: 'CHF', amount: 42.5), + quote: LnurlpQuoteDto( + id: 'quote_fresh', + expiration: DateTime.now().add(const Duration(minutes: 5)), + ), + transferAmounts: [ + const LnurlpTransferAmountDto( + method: 'Ethereum', + assets: [ + LnurlpTransferAssetDto( + asset: 'ZCHF', + amount: 900, + ), + ], + ), + ], + ), + ); + + final cubit = build(); + final retry = cubit.stream.firstWhere((s) => s is PayProcessPayRetry); + await cubit.start(); + final state = await retry as PayProcessPayRetry; + + expect(state.reason, PayRetryReason.insufficientZchf); + verifyNever(() => payService.createPayUnsignedTransaction(any())); + await cubit.close(); + }); + + test('retryPay re-runs the pay leg only — never re-swaps', () async { + wireHappyPath(); + // First pass: quote re-fetch throws → PayProcessPayRetry. + var detailsCall = 0; + when(() => payService.getPaymentDetails('pl_abc')).thenAnswer((_) async { + detailsCall++; + if (detailsCall == 1) throw Exception('lnurlp 500'); + return _details(expiration: DateTime.now().add(const Duration(minutes: 5))); + }); + when(() => payService.getPayStatus('pl_abc')).thenAnswer( + (_) async => const RealUnitOcpPayStatusDto(status: OcpPaymentStatus.completed), + ); + + final cubit = build(); + final retry = cubit.stream.firstWhere((s) => s is PayProcessPayRetry); + await cubit.start(); + await retry; + expect(cubit.state, isA()); + + // Retry the pay leg: it must re-fetch the quote + submit WITHOUT re-swapping. + final settled = cubit.stream.firstWhere((s) => s is PayProcessAwaitingSettlement); + await cubit.retryPay(); + await settled; + + expect(cubit.state, isA()); + // The swap legs ran EXACTLY ONCE over the whole flow — the retry reused the + // already-acquired ZCHF and never re-swapped (the key fund-safety guarantee). + verify(() => payService.createSwapUnsignedTransaction(any())).called(1); + verify(() => payService.broadcastSwapTransaction(any(), any())).called(1); + // The pay leg's submit ran once (only on the successful retry). + verify(() => payService.submitPay(any())).called(1); + await cubit.close(); + }); + + test('retryPay is a no-op before a swap has completed', () async { + wireHappyPath(); + + final cubit = build(); + // Never started → swap not completed → retry must not touch the network. + await cubit.retryPay(); + + verifyNever(() => payService.getPaymentDetails(any())); + await cubit.close(); + }); + + test('non-signing wallet detected only at the pay sign (after swap) → retry', () async { + wireHappyPath(); + // Swap sign succeeds; the pay sign hits a non-signing credential + // (UnsupportedError). Post-swap, this is a retryable pay-leg failure. + final creds = _CountingSignCreds( + throwOnCall: 2, + error: UnsupportedError('cannot sign'), + ); + when(() => account.primaryAddress).thenReturn(creds); + + final cubit = build(); + final retry = cubit.stream.firstWhere((s) => s is PayProcessPayRetry); + await cubit.start(); + final state = await retry as PayProcessPayRetry; + + expect(creds.calls, 2); + expect(state.reason, PayRetryReason.transient); + await cubit.close(); + }); +} + +/// Credentials that produce a real signature for every sign except the +/// [throwOnCall]-th, which throws [error]. Lets a test target the swap (call 1) +/// vs. the pay (call 2) sign deterministically. +class _CountingSignCreds extends Fake implements CredentialsWithKnownAddress { + _CountingSignCreds({required this.throwOnCall, required this.error}); + + final int throwOnCall; + final Object error; + int calls = 0; + + @override + EthereumAddress get address => + EthereumAddress.fromHex('0x9F5713dEAcb8e9CaB6c2D3FaE1aFc2715F8D2D71'); + + @override + Future signToSignature( + Uint8List payload, { + int? chainId, + bool isEIP1559 = false, + }) async { + calls++; + if (calls == throwOnCall) throw error; + return EthPrivateKey.fromHex( + 'fb1ace12f9801e85f3db1b3935dd47d9f064f98152466f47c701b5e12680e612', + ).signToSignature(payload, chainId: chainId, isEIP1559: isEIP1559); + } +} diff --git a/test/screens/pay/pay_process_page_test.dart b/test/screens/pay/pay_process_page_test.dart new file mode 100644 index 000000000..7c0fb6d66 --- /dev/null +++ b/test/screens/pay/pay_process_page_test.dart @@ -0,0 +1,274 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/packages/config/api_config.dart'; +import 'package:realunit_wallet/packages/service/app_store.dart'; +import 'package:realunit_wallet/packages/service/dfx/dfx_blockchain_api_service.dart'; +import 'package:realunit_wallet/packages/service/dfx/dfx_faucet_service.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_pay_service.dart'; +import 'package:realunit_wallet/packages/service/wallet_service.dart'; +import 'package:realunit_wallet/packages/utils/default_assets.dart'; +import 'package:realunit_wallet/packages/wallet/wallet.dart'; +import 'package:realunit_wallet/screens/pay/cubits/pay_process/pay_process_cubit.dart'; +import 'package:realunit_wallet/screens/pay/pay_process_page.dart'; + +import '../../helper/helper.dart'; + +class _MockPayProcessCubit extends MockCubit implements PayProcessCubit {} + +class _MockPayService extends Mock implements RealUnitPayService {} + +class _MockFaucetService extends Mock implements DfxFaucetService {} + +class _MockBlockchainService extends Mock implements DfxBlockchainApiService {} + +class _MockWalletService extends Mock implements WalletService {} + +class _MockAppStore extends Mock implements AppStore {} + +class _MockApiConfig extends Mock implements ApiConfig {} + +class _MockWallet extends Mock implements SoftwareWallet {} + +void main() { + late _MockPayProcessCubit processCubit; + + setUpAll(() { + final getIt = GetIt.instance; + // PayProcessPage resolves a full service graph from getIt and calls + // start(). A debug wallet makes start() settle immediately + // (signatureUnsupported) without touching the chain. + final payService = _MockPayService(); + getIt.registerSingleton(payService); + getIt.registerSingleton(_MockFaucetService()); + getIt.registerSingleton(_MockBlockchainService()); + getIt.registerSingleton(_MockWalletService()); + final appStore = _MockAppStore(); + final apiConfig = _MockApiConfig(); + when(() => apiConfig.asset).thenReturn(realUnitAsset); + final wallet = _MockWallet(); + when(() => wallet.walletType).thenReturn(WalletType.debug); + when(() => appStore.wallet).thenReturn(wallet); + when(() => appStore.apiConfig).thenReturn(apiConfig); + getIt.registerSingleton(appStore); + }); + + tearDownAll(() async => GetIt.instance.reset()); + + setUp(() { + processCubit = _MockPayProcessCubit(); + when(() => processCubit.state).thenReturn(const PayProcessInitial()); + when(() => processCubit.retryPay()).thenAnswer((_) async {}); + }); + + Widget buildSubject() => BlocProvider.value( + value: processCubit, + child: const PayProcessView(), + ); + + group('$PayProcessPage', () { + testWidgets('builds its own cubit and renders $PayProcessView', (tester) async { + await tester.pumpApp(const PayProcessPage(paymentLinkId: 'pl_abc', zchfNeeded: 42.7)); + // start() runs and emits a failure on the debug wallet; pump a frame to + // let the cubit settle (the sheet animation is not awaited here). + await tester.pump(); + + expect(find.byType(PayProcessView), findsOne); + }); + }); + + group('$PayProcessView progress labels', () { + Future expectLabel(WidgetTester tester, PayProcessState state, String label) async { + when(() => processCubit.state).thenReturn(state); + await tester.pumpApp(buildSubject()); + + expect(find.byType(CupertinoActivityIndicator), findsOne); + expect(find.text(label), findsOne); + } + + testWidgets('initial shows preparing-swap', (tester) async { + await expectLabel(tester, const PayProcessInitial(), S.current.payPreparingSwap); + }); + + testWidgets('preparing-swap label', (tester) async { + await expectLabel(tester, const PayProcessPreparingSwap(), S.current.payPreparingSwap); + }); + + testWidgets('waiting-for-eth label', (tester) async { + await expectLabel(tester, const PayProcessWaitingForEth(), S.current.payWaitingForEth); + }); + + testWidgets('swapping label', (tester) async { + await expectLabel(tester, const PayProcessSwapping(), S.current.paySwapping); + }); + + testWidgets('refreshing-quote label', (tester) async { + await expectLabel(tester, const PayProcessRefreshingQuote(), S.current.payRefreshingQuote); + }); + + testWidgets('paying label', (tester) async { + await expectLabel(tester, const PayProcessPaying(), S.current.payPaying); + }); + + testWidgets('awaiting-settlement label', (tester) async { + await expectLabel( + tester, + const PayProcessAwaitingSettlement('0xtx'), + S.current.payAwaitingSettlement, + ); + }); + + testWidgets('success label', (tester) async { + await expectLabel(tester, const PayProcessSuccess(), S.current.paySuccess); + }); + + testWidgets('pay-retry label', (tester) async { + await expectLabel( + tester, + const PayProcessPayRetry(PayRetryReason.quoteExpired), + S.current.payRetryTitle, + ); + }); + + testWidgets('failure label', (tester) async { + await expectLabel( + tester, + const PayProcessFailure(PayProcessFailureReason.generic), + S.current.payFailureTitle, + ); + }); + }); + + // The result/retry sheets are modal bottom sheets shown from the listener. + // The PayProcessView keeps a CupertinoActivityIndicator animating behind the + // sheet, so pumpAndSettle never settles; pump fixed frames to open the sheet. + // A phone-sized surface keeps the taller retry sheet from overflowing the + // default 800x600 test viewport (mirrors the logout-sheet test convention). + Future pumpWithState(WidgetTester tester, PayProcessState terminal) async { + tester.view.physicalSize = const Size(1200, 2400); + tester.view.devicePixelRatio = 1.0; + addTearDown(tester.view.resetPhysicalSize); + addTearDown(tester.view.resetDevicePixelRatio); + + whenListen( + processCubit, + Stream.fromIterable([terminal]), + initialState: const PayProcessSwapping(), + ); + await tester.pumpApp(buildSubject()); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 400)); + } + + group('$PayProcessView result sheet', () { + testWidgets('success emits a success sheet with title + description', (tester) async { + await pumpWithState(tester, const PayProcessSuccess()); + + expect(find.text(S.current.paySuccessDescription), findsOne); + expect(find.byIcon(Icons.check_circle_rounded), findsOne); + expect(find.text(S.current.close), findsOne); + + // Tapping close pops the sheet and then pops the page. + await tester.tap(find.text(S.current.close)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 400)); + + expect(find.byIcon(Icons.check_circle_rounded), findsNothing); + }); + + testWidgets('insufficient-zchf failure emits a failure sheet', (tester) async { + await pumpWithState( + tester, + const PayProcessFailure(PayProcessFailureReason.insufficientZchf), + ); + + // payFailureTitle also renders as the progress-label behind the sheet, + // so it appears twice; the reason message is the sheet-unique assertion. + expect(find.text(S.current.payFailureTitle), findsWidgets); + expect(find.text(S.current.payFailureInsufficientZchf), findsOne); + expect(find.byIcon(Icons.error_rounded), findsOne); + }); + + testWidgets('insufficient-eth failure message', (tester) async { + await pumpWithState( + tester, + const PayProcessFailure(PayProcessFailureReason.insufficientEth), + ); + + expect(find.text(S.current.payFailureInsufficientEth), findsOne); + }); + + testWidgets('signature-unsupported failure message', (tester) async { + await pumpWithState( + tester, + const PayProcessFailure(PayProcessFailureReason.signatureUnsupported), + ); + + expect(find.text(S.current.payFailureSignatureUnsupported), findsOne); + }); + + testWidgets('bitbox-required failure message', (tester) async { + await pumpWithState( + tester, + const PayProcessFailure(PayProcessFailureReason.bitboxRequired), + ); + + expect(find.text(S.current.payFailureBitboxRequired), findsOne); + }); + + testWidgets('generic failure message', (tester) async { + await pumpWithState( + tester, + const PayProcessFailure(PayProcessFailureReason.generic), + ); + + expect(find.text(S.current.payFailureGeneric), findsOne); + }); + }); + + group('$PayProcessView retry sheet', () { + testWidgets('pay-retry emits a retry sheet whose primary action calls retryPay', ( + tester, + ) async { + await pumpWithState(tester, const PayProcessPayRetry(PayRetryReason.quoteExpired)); + + expect(find.text(S.current.payRetryQuoteExpired), findsOne); + expect(find.byIcon(Icons.replay_rounded), findsOne); + + await tester.tap(find.text(S.current.payRetryButton)); + await tester.pump(); + + verify(() => processCubit.retryPay()).called(1); + }); + + testWidgets('retry sheet close action dismisses without retrying', (tester) async { + await pumpWithState(tester, const PayProcessPayRetry(PayRetryReason.transient)); + + expect(find.text(S.current.payRetryTransient), findsOne); + + await tester.tap(find.text(S.current.close)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 400)); + + verifyNever(() => processCubit.retryPay()); + expect(find.text(S.current.payRetryTransient), findsNothing); + }); + + testWidgets('insufficient-zchf retry reason shows its message', (tester) async { + await pumpWithState(tester, const PayProcessPayRetry(PayRetryReason.insufficientZchf)); + + expect(find.text(S.current.payRetryInsufficientZchf), findsOne); + }); + + testWidgets('unsigned-tx-mismatch retry reason shows its message', (tester) async { + await pumpWithState(tester, const PayProcessPayRetry(PayRetryReason.unsignedTxMismatch)); + + expect(find.text(S.current.payRetryUnsignedTxMismatch), findsOne); + }); + }); +} diff --git a/test/screens/pay/pay_process_state_test.dart b/test/screens/pay/pay_process_state_test.dart new file mode 100644 index 000000000..354720b60 --- /dev/null +++ b/test/screens/pay/pay_process_state_test.dart @@ -0,0 +1,64 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:realunit_wallet/screens/pay/cubits/pay_process/pay_process_cubit.dart'; + +void main() { + group('PayProcessState equality (Equatable props)', () { + test('progress states with no fields expose empty props and compare by type', () { + // Reading `.props` directly evaluates the inherited base getter (const + // canonicalization would otherwise make `==` short-circuit via identical). + expect(const PayProcessPreparingSwap().props, isEmpty); + expect(const PayProcessWaitingForEth().props, isEmpty); + expect(const PayProcessInitial().props, isEmpty); + expect(const PayProcessSwapping().props, isEmpty); + expect(const PayProcessRefreshingQuote().props, isEmpty); + expect(const PayProcessPaying().props, isEmpty); + expect(const PayProcessSuccess().props, isEmpty); + expect( + const PayProcessPreparingSwap(), + isNot(equals(const PayProcessWaitingForEth())), + ); + }); + + test('PayProcessAwaitingSettlement is keyed on txId', () { + expect( + const PayProcessAwaitingSettlement('0xtx'), + const PayProcessAwaitingSettlement('0xtx'), + ); + expect( + const PayProcessAwaitingSettlement('0xtx'), + isNot(equals(const PayProcessAwaitingSettlement('0xother'))), + ); + expect(const PayProcessAwaitingSettlement('0xtx').props, ['0xtx']); + }); + + test('PayProcessFailure is keyed on reason + message', () { + expect( + const PayProcessFailure(PayProcessFailureReason.generic), + const PayProcessFailure(PayProcessFailureReason.generic), + ); + expect( + const PayProcessFailure(PayProcessFailureReason.generic), + isNot(equals(const PayProcessFailure(PayProcessFailureReason.insufficientEth))), + ); + expect( + const PayProcessFailure(PayProcessFailureReason.generic, message: 'boom').props, + [PayProcessFailureReason.generic, 'boom'], + ); + }); + + test('PayProcessPayRetry is keyed on reason + message', () { + expect( + const PayProcessPayRetry(PayRetryReason.transient), + const PayProcessPayRetry(PayRetryReason.transient), + ); + expect( + const PayProcessPayRetry(PayRetryReason.transient), + isNot(equals(const PayProcessPayRetry(PayRetryReason.quoteExpired))), + ); + expect( + const PayProcessPayRetry(PayRetryReason.insufficientZchf, message: 'short').props, + [PayRetryReason.insufficientZchf, 'short'], + ); + }); + }); +} diff --git a/test/screens/pay/pay_quote_cubit_test.dart b/test/screens/pay/pay_quote_cubit_test.dart new file mode 100644 index 000000000..bf4789f4c --- /dev/null +++ b/test/screens/pay/pay_quote_cubit_test.dart @@ -0,0 +1,155 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/api_exception.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/lnurlp_payment_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/dto/real_unit_swap_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/pay/swap_payment_info.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_pay_service.dart'; +import 'package:realunit_wallet/screens/pay/cubits/pay_quote/pay_quote_cubit.dart'; + +class _MockPayService extends Mock implements RealUnitPayService {} + +// Real Sepolia OCP capture (DFXswiss/api #3819): a CHF 2.00 payment link whose +// Ethereum method settles 2.0 ZCHF. The cubit reads these amounts verbatim from +// the public lnurlp quote — it never computes them. +LnurlpPaymentDto _details({ + required DateTime expiration, + bool withEthZchf = true, + double zchf = 2.0, + LnurlpRecipientDto? recipient, +}) { + return LnurlpPaymentDto( + requestedAmount: const LnurlpRequestedAmountDto(asset: 'CHF', amount: 2), + quote: LnurlpQuoteDto(id: 'plq_realunit_ocp_sepolia', expiration: expiration), + transferAmounts: [ + if (withEthZchf) + LnurlpTransferAmountDto( + method: 'Ethereum', + assets: [LnurlpTransferAssetDto(asset: 'ZCHF', amount: zchf)], + ) + else + const LnurlpTransferAmountDto( + method: 'Bitcoin', + assets: [LnurlpTransferAssetDto(asset: 'BTC', amount: 0.0005)], + ), + ], + recipient: recipient, + ); +} + +SwapPaymentInfo _swap({ + double amount = 5, + double estimatedAmount = 1.98, + double? feesTotal = 0.02, +}) { + return SwapPaymentInfo( + id: 99, + amount: amount, + estimatedAmount: estimatedAmount, + targetAsset: 'ZCHF', + ethBalance: 1.0, + requiredGasEth: 0.001, + isValid: true, + feesTotal: feesTotal, + ); +} + +void main() { + late _MockPayService payService; + + setUpAll(() { + registerFallbackValue(const RealUnitSwapDto.fromTargetAmount(1)); + }); + + setUp(() { + payService = _MockPayService(); + }); + + PayQuoteCubit build() => PayQuoteCubit(payService, 'pl_realunit_ocp_sepolia'); + + blocTest( + 'a fresh quote with an Ethereum/ZCHF method emits PayQuoteReady', + build: build, + setUp: () { + when(() => payService.getPaymentDetails('pl_realunit_ocp_sepolia')).thenAnswer( + (_) async => _details(expiration: DateTime.now().add(const Duration(minutes: 5))), + ); + when(() => payService.getSwapPaymentInfo(any())).thenAnswer((_) async => _swap()); + }, + act: (cubit) => cubit.load(), + expect: () => [isA(), isA()], + verify: (cubit) { + final state = cubit.state as PayQuoteReady; + expect(state.quoteId, 'plq_realunit_ocp_sepolia'); + expect(state.fiatAsset, 'CHF'); + expect(state.fiatAmount, 2); + expect(state.zchfAmount, 2.0); + expect(state.realuAmount, 5); + expect(state.realuEstimatedZchf, 1.98); + expect(state.realuFeesTotal, 0.02); + expect(state.merchantName, isNull); + expect(state.merchantCity, isNull); + }, + ); + + blocTest( + 'a fresh quote with a recipient surfaces merchant name and city', + build: build, + setUp: () { + when(() => payService.getPaymentDetails('pl_realunit_ocp_sepolia')).thenAnswer( + (_) async => _details( + expiration: DateTime.now().add(const Duration(minutes: 5)), + recipient: const LnurlpRecipientDto(name: 'Café Zürich', city: 'Zürich'), + ), + ); + when(() => payService.getSwapPaymentInfo(any())).thenAnswer((_) async => _swap()); + }, + act: (cubit) => cubit.load(), + expect: () => [isA(), isA()], + verify: (cubit) { + final state = cubit.state as PayQuoteReady; + expect(state.merchantName, 'Café Zürich'); + expect(state.merchantCity, 'Zürich'); + }, + ); + + blocTest( + 'an expired quote emits PayQuoteExpired', + build: build, + setUp: () { + when(() => payService.getPaymentDetails('pl_realunit_ocp_sepolia')).thenAnswer( + (_) async => _details(expiration: DateTime.now().subtract(const Duration(minutes: 1))), + ); + }, + act: (cubit) => cubit.load(), + expect: () => [isA(), isA()], + ); + + blocTest( + 'a link without an Ethereum/ZCHF method emits PayQuoteUnavailable', + build: build, + setUp: () { + when(() => payService.getPaymentDetails('pl_realunit_ocp_sepolia')).thenAnswer( + (_) async => _details( + expiration: DateTime.now().add(const Duration(minutes: 5)), + withEthZchf: false, + ), + ); + }, + act: (cubit) => cubit.load(), + expect: () => [isA(), isA()], + ); + + blocTest( + 'a service error emits PayQuoteError', + build: build, + setUp: () { + when(() => payService.getPaymentDetails('pl_realunit_ocp_sepolia')).thenThrow( + const ApiException(code: 'X', message: 'boom'), + ); + }, + act: (cubit) => cubit.load(), + expect: () => [isA(), isA()], + ); +} diff --git a/test/screens/pay/pay_quote_page_test.dart b/test/screens/pay/pay_quote_page_test.dart new file mode 100644 index 000000000..e6496b5ea --- /dev/null +++ b/test/screens/pay/pay_quote_page_test.dart @@ -0,0 +1,187 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/packages/config/api_config.dart'; +import 'package:realunit_wallet/packages/service/app_store.dart'; +import 'package:realunit_wallet/packages/service/dfx/dfx_blockchain_api_service.dart'; +import 'package:realunit_wallet/packages/service/dfx/dfx_faucet_service.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/api_exception.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_pay_service.dart'; +import 'package:realunit_wallet/packages/service/wallet_service.dart'; +import 'package:realunit_wallet/packages/utils/default_assets.dart'; +import 'package:realunit_wallet/packages/wallet/wallet.dart'; +import 'package:realunit_wallet/screens/pay/cubits/pay_quote/pay_quote_cubit.dart'; +import 'package:realunit_wallet/screens/pay/pay_process_page.dart'; +import 'package:realunit_wallet/screens/pay/pay_quote_page.dart'; + +import '../../helper/helper.dart'; + +class _MockPayQuoteCubit extends MockCubit implements PayQuoteCubit {} + +class _MockPayService extends Mock implements RealUnitPayService {} + +class _MockFaucetService extends Mock implements DfxFaucetService {} + +class _MockBlockchainService extends Mock implements DfxBlockchainApiService {} + +class _MockWalletService extends Mock implements WalletService {} + +class _MockAppStore extends Mock implements AppStore {} + +class _MockApiConfig extends Mock implements ApiConfig {} + +class _MockWallet extends Mock implements SoftwareWallet {} + +void main() { + late _MockPayQuoteCubit quoteCubit; + + // Real Sepolia OCP capture (DFXswiss/api #3819): CHF 2.00 → 2.0 ZCHF on the + // Ethereum method. + const ready = PayQuoteReady( + paymentLinkId: 'pl_realunit_ocp_sepolia', + quoteId: 'plq_realunit_ocp_sepolia', + fiatAsset: 'CHF', + fiatAmount: 2, + zchfAmount: 2.0, + ); + + setUpAll(() { + final getIt = GetIt.instance; + + // PayQuotePage resolves the pay service from getIt and calls load(); the + // load throws a typed error here so the pushed route builds deterministically + // (rendering PayQuoteError) without a live backend. + final payService = _MockPayService(); + when(() => payService.getPaymentDetails(any())).thenThrow( + const ApiException(code: 'TEST', message: 'no backend in widget test'), + ); + getIt.registerSingleton(payService); + + // The confirm button pushes PayProcessPage, which resolves a full service + // graph from getIt and calls start(). A debug wallet makes start() settle + // immediately (signatureUnsupported) without touching the chain. + getIt.registerSingleton(_MockFaucetService()); + getIt.registerSingleton(_MockBlockchainService()); + getIt.registerSingleton(_MockWalletService()); + final appStore = _MockAppStore(); + final apiConfig = _MockApiConfig(); + when(() => apiConfig.asset).thenReturn(realUnitAsset); + final wallet = _MockWallet(); + when(() => wallet.walletType).thenReturn(WalletType.debug); + when(() => appStore.wallet).thenReturn(wallet); + when(() => appStore.apiConfig).thenReturn(apiConfig); + getIt.registerSingleton(appStore); + }); + + tearDownAll(() async => GetIt.instance.reset()); + + setUp(() { + quoteCubit = _MockPayQuoteCubit(); + when(() => quoteCubit.state).thenReturn(const PayQuoteLoading()); + }); + + Widget buildSubject() => BlocProvider.value( + value: quoteCubit, + child: const PayQuoteView(), + ); + + group('$PayQuotePage', () { + testWidgets('builds its own cubit and renders $PayQuoteView', (tester) async { + await tester.pumpApp(const PayQuotePage(paymentLinkId: 'pl_abc')); + + expect(find.byType(PayQuoteView), findsOne); + }); + }); + + group('$PayQuoteView', () { + testWidgets('loading state shows a $CupertinoActivityIndicator', (tester) async { + when(() => quoteCubit.state).thenReturn(const PayQuoteLoading()); + await tester.pumpApp(buildSubject()); + + expect(find.byType(CupertinoActivityIndicator), findsOne); + }); + + testWidgets('ready state shows the CHF amount, ZCHF needed and confirm button', (tester) async { + when(() => quoteCubit.state).thenReturn(ready); + await tester.pumpApp(buildSubject()); + + expect(find.text(S.current.payQuoteSummary('2.00', 'CHF')), findsOne); + expect(find.text('2.00 CHF'), findsOne); + expect(find.text('2.00 ZCHF'), findsOne); + expect(find.text(S.current.payConfirmButton), findsOne); + }); + + testWidgets('ready state shows merchant and REALU swap details when present', (tester) async { + when(() => quoteCubit.state).thenReturn( + const PayQuoteReady( + paymentLinkId: 'pl_realunit_ocp_sepolia', + quoteId: 'plq_realunit_ocp_sepolia', + fiatAsset: 'CHF', + fiatAmount: 2, + zchfAmount: 2.0, + merchantName: 'Café Zürich', + merchantCity: 'Zürich', + realuAmount: 5, + realuEstimatedZchf: 1.98, + realuFeesTotal: 0.02, + ), + ); + await tester.pumpApp(buildSubject()); + + expect(find.text('Café Zürich, Zürich'), findsOne); + expect(find.text('5 REALU'), findsOne); + expect(find.text('1.98 ZCHF'), findsOne); + expect(find.text('0.02 REALU'), findsOne); + }); + + testWidgets('confirm button navigates to the process step', (tester) async { + when(() => quoteCubit.state).thenReturn(ready); + await tester.pumpApp(buildSubject()); + + await tester.tap(find.text(S.current.payConfirmButton)); + // The process page renders a CupertinoActivityIndicator that animates + // forever, so pumpAndSettle would time out; pump fixed frames to drive + // the push transition instead. + await tester.pump(); + await tester.pump(const Duration(milliseconds: 400)); + + expect(find.byType(PayProcessView), findsOne); + }); + + testWidgets('expired state shows the re-scan message', (tester) async { + when(() => quoteCubit.state).thenReturn(const PayQuoteExpired()); + await tester.pumpApp(buildSubject()); + + expect(find.text(S.current.payFailureQuoteExpired), findsOne); + }); + + testWidgets('unavailable state shows the unavailable message', (tester) async { + when(() => quoteCubit.state).thenReturn(const PayQuoteUnavailable()); + await tester.pumpApp(buildSubject()); + + expect(find.text(S.current.payQuoteUnavailable), findsOne); + }); + + testWidgets('error state shows the generic failure message', (tester) async { + when(() => quoteCubit.state).thenReturn(const PayQuoteError('boom')); + await tester.pumpApp(buildSubject()); + + expect(find.text(S.current.payFailureGeneric), findsOne); + }); + + testWidgets('error state retry button re-invokes load()', (tester) async { + when(() => quoteCubit.state).thenReturn(const PayQuoteError('boom')); + when(() => quoteCubit.load()).thenAnswer((_) async {}); + await tester.pumpApp(buildSubject()); + + await tester.tap(find.byKey(const ValueKey('payQuoteRetryButton'))); + await tester.pump(); + + verify(() => quoteCubit.load()).called(1); + }); + }); +} diff --git a/test/screens/pay/pay_quote_responsive_matrix_test.dart b/test/screens/pay/pay_quote_responsive_matrix_test.dart new file mode 100644 index 000000000..ceda13aeb --- /dev/null +++ b/test/screens/pay/pay_quote_responsive_matrix_test.dart @@ -0,0 +1,124 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/packages/config/api_config.dart'; +import 'package:realunit_wallet/packages/service/app_store.dart'; +import 'package:realunit_wallet/packages/service/dfx/dfx_blockchain_api_service.dart'; +import 'package:realunit_wallet/packages/service/dfx/dfx_faucet_service.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_pay_service.dart'; +import 'package:realunit_wallet/packages/service/wallet_service.dart'; +import 'package:realunit_wallet/packages/utils/default_assets.dart'; +import 'package:realunit_wallet/packages/wallet/wallet.dart'; +import 'package:realunit_wallet/screens/pay/cubits/pay_quote/pay_quote_cubit.dart'; +import 'package:realunit_wallet/screens/pay/pay_quote_page.dart'; +import 'package:realunit_wallet/styles/themes.dart'; + +import '../../helper/helper.dart'; + +class _MockPayQuoteCubit extends MockCubit implements PayQuoteCubit {} + +class _MockPayService extends Mock implements RealUnitPayService {} + +class _MockFaucetService extends Mock implements DfxFaucetService {} + +class _MockBlockchainService extends Mock implements DfxBlockchainApiService {} + +class _MockWalletService extends Mock implements WalletService {} + +class _MockAppStore extends Mock implements AppStore {} + +class _MockApiConfig extends Mock implements ApiConfig {} + +class _MockWallet extends Mock implements SoftwareWallet {} + +Future _pumpScreen(WidgetTester tester, MatrixCell cell, Widget child) async { + await tester.binding.setSurfaceSize(cell.mediaQuery.size); + addTearDown(() async => await tester.binding.setSurfaceSize(null)); + + await tester.pumpWidget( + MediaQuery( + data: cell.mediaQuery, + child: MaterialApp( + theme: realUnitTheme, + locale: const Locale('de'), + localizationsDelegates: const [ + S.delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + supportedLocales: S.delegate.supportedLocales, + home: child, + ), + ), + ); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 50)); +} + +void main() { + const ready = PayQuoteReady( + paymentLinkId: 'pl_realunit_ocp_sepolia', + quoteId: 'plq_realunit_ocp_sepolia', + fiatAsset: 'CHF', + fiatAmount: 2, + zchfAmount: 2.0, + ); + + late _MockPayQuoteCubit quoteCubit; + + setUpAll(() { + final getIt = GetIt.instance; + getIt.registerSingleton(_MockPayService()); + getIt.registerSingleton(_MockFaucetService()); + getIt.registerSingleton(_MockBlockchainService()); + getIt.registerSingleton(_MockWalletService()); + + final appStore = _MockAppStore(); + final apiConfig = _MockApiConfig(); + final wallet = _MockWallet(); + when(() => apiConfig.asset).thenReturn(realUnitAsset); + when(() => wallet.walletType).thenReturn(WalletType.debug); + when(() => appStore.wallet).thenReturn(wallet); + when(() => appStore.apiConfig).thenReturn(apiConfig); + getIt.registerSingleton(appStore); + }); + + tearDownAll(() async => GetIt.instance.reset()); + + setUp(() { + quoteCubit = _MockPayQuoteCubit(); + when(() => quoteCubit.state).thenReturn(ready); + }); + + group('PayQuoteView responsive matrix (full device x textScale)', () { + for (final cell in kFullResponsiveMatrix) { + testWidgets(cell.id, (tester) async { + await withTargetPlatform(cell.device.platform, () async { + final subject = BlocProvider.value( + value: quoteCubit, + child: const PayQuoteView(), + ); + + await expectNoLayoutOverflow( + tester, + () => _pumpScreen(tester, cell, subject), + reason: 'PayQuoteView overflow / ${cell.label}', + ); + + await expectFullyTappable( + tester, + find.widgetWithText(FilledButton, S.current.payConfirmButton), + within: find.byType(PayQuoteView), + reason: 'PayQuoteView / ${cell.label}: Confirm CTA not tappable', + ); + }); + }); + } + }); +} diff --git a/test/screens/pay/pay_scan_cubit_test.dart b/test/screens/pay/pay_scan_cubit_test.dart new file mode 100644 index 000000000..5d17648f2 --- /dev/null +++ b/test/screens/pay/pay_scan_cubit_test.dart @@ -0,0 +1,51 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:realunit_wallet/screens/pay/cubits/pay_scan/pay_scan_cubit.dart'; + +void main() { + // LUD-01 bech32 of `https://api.dfx.swiss/v1/lnurlp/pl_abc123`. + const lnurl = 'LNURL1DP68GURN8GHJ7CTSDYHXGENC9EEHW6TNWVHHVVF0D3H82UNVWQHHQMZLV93XXVFJXV5T0E5A'; + + group('PayScanCubit', () { + test('starts in PayScanScanning', () { + expect(PayScanCubit().state, isA()); + }); + + blocTest( + 'a valid LNURL emits PayScanDecoded with the id', + build: PayScanCubit.new, + act: (cubit) => cubit.onCodeDetected(lnurl), + verify: (cubit) { + final state = cubit.state as PayScanDecoded; + expect(state.link.id, 'pl_abc123'); + }, + ); + + blocTest( + 'an invalid code emits PayScanInvalid', + build: PayScanCubit.new, + act: (cubit) => cubit.onCodeDetected('not-a-payment-code'), + expect: () => [isA()], + ); + + blocTest( + 'ignores further detections once decoded (no re-emit)', + build: PayScanCubit.new, + act: (cubit) { + cubit.onCodeDetected(lnurl); + cubit.onCodeDetected(lnurl); + }, + expect: () => [isA()], + ); + + blocTest( + 'reset returns to PayScanScanning', + build: PayScanCubit.new, + act: (cubit) { + cubit.onCodeDetected('bad'); + cubit.reset(); + }, + expect: () => [isA(), isA()], + ); + }); +} diff --git a/test/screens/pay/pay_scan_page_test.dart b/test/screens/pay/pay_scan_page_test.dart new file mode 100644 index 000000000..4689fd5b6 --- /dev/null +++ b/test/screens/pay/pay_scan_page_test.dart @@ -0,0 +1,145 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mobile_scanner/mobile_scanner.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/api_exception.dart'; +import 'package:realunit_wallet/packages/service/dfx/lnurl_decoder.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_pay_service.dart'; +import 'package:realunit_wallet/screens/pay/cubits/pay_scan/pay_scan_cubit.dart'; +import 'package:realunit_wallet/screens/pay/pay_quote_page.dart'; +import 'package:realunit_wallet/screens/pay/pay_scan_page.dart'; + +import '../../helper/helper.dart'; + +class _MockPayScanCubit extends MockCubit implements PayScanCubit {} + +class _MockPayService extends Mock implements RealUnitPayService {} + +void main() { + late _MockPayScanCubit scanCubit; + + setUpAll(() { + // pay_scan_page.dart carries the `@no-integration-test` note: the live + // camera is exercised only on a real device. The stub keeps the headless + // preview deterministic and free of MissingPluginException. + stubMobileScannerChannel(); + + // The decoded-link navigation pushes PayQuotePage, which resolves the pay + // service from getIt and triggers a load(); register a mock whose quote + // fetch throws so the pushed route builds deterministically (rendering the + // PayQuoteError message) without touching a live backend. + final payService = _MockPayService(); + when(() => payService.getPaymentDetails(any())).thenThrow( + const ApiException(code: 'TEST', message: 'no backend in widget test'), + ); + GetIt.instance.registerSingleton(payService); + }); + + tearDownAll(() async => GetIt.instance.reset()); + + setUp(() { + scanCubit = _MockPayScanCubit(); + when(() => scanCubit.state).thenReturn(const PayScanScanning()); + when(() => scanCubit.reset()).thenReturn(null); + }); + + Widget buildSubject() => BlocProvider.value( + value: scanCubit, + child: const PayScanView(), + ); + + group('$PayScanPage', () { + testWidgets('builds its own cubit and renders $PayScanView', (tester) async { + await tester.pumpApp(const PayScanPage()); + + expect(find.byType(PayScanView), findsOne); + }); + }); + + group('$PayScanView', () { + testWidgets('renders the scan title and the scanner preview', (tester) async { + await tester.pumpApp(buildSubject()); + + expect(find.text(S.current.payScanTitle), findsOne); + expect(find.byType(MobileScanner), findsOne); + }); + + testWidgets('onDetect forwards a scanned raw value to the cubit', (tester) async { + when(() => scanCubit.onCodeDetected(any())).thenReturn(null); + + await tester.pumpApp(buildSubject()); + + final scanner = tester.widget(find.byType(MobileScanner)); + // A capture with no barcodes is ignored (rawValue is null) … + scanner.onDetect!(const BarcodeCapture()); + // … while a barcode with a raw value is forwarded to the cubit. + scanner.onDetect!( + const BarcodeCapture(barcodes: [Barcode(rawValue: 'lnurl_raw')]), + ); + + verify(() => scanCubit.onCodeDetected('lnurl_raw')).called(1); + }); + + testWidgets('an invalid scan shows a snackbar and resets the cubit', (tester) async { + whenListen( + scanCubit, + Stream.fromIterable([const PayScanInvalid('bad code')]), + initialState: const PayScanScanning(), + ); + + await tester.pumpApp(buildSubject()); + await tester.pump(); + + expect(find.byType(SnackBar), findsOne); + expect(find.text(S.current.payScanInvalid), findsOne); + verify(() => scanCubit.reset()).called(1); + }); + + testWidgets('a decoded link navigates to the quote step and resets the cubit', (tester) async { + final link = DecodedPaymentLink( + id: 'pl_abc123', + lnurlpUrl: Uri.parse('https://api.dfx.swiss/v1/lnurlp/pl_abc123'), + ); + whenListen( + scanCubit, + Stream.fromIterable([PayScanDecoded(link)]), + initialState: const PayScanScanning(), + ); + + await tester.pumpApp(buildSubject()); + await tester.pumpAndSettle(); + + // The quote step is pushed and rendered; the cubit is reset so returning + // to the scanner re-arms detection. + expect(find.byType(PayQuoteView), findsOne); + verify(() => scanCubit.reset()).called(1); + }); + + testWidgets( + 'errorBuilder shows the camera-unavailable message for non-permission-denied errors', + (tester) async { + await tester.pumpApp(buildSubject()); + + final scanner = tester.widget(find.byType(MobileScanner)); + final context = tester.element(find.byType(MobileScanner)); + final nonPermissionCode = MobileScannerErrorCode.values.firstWhere( + (code) => code != MobileScannerErrorCode.permissionDenied, + ); + final errorWidget = scanner.errorBuilder!( + context, + MobileScannerException(errorCode: nonPermissionCode), + null, + ); + + await tester.pumpWidget(MaterialApp(home: errorWidget)); + + expect(find.text(S.current.payScanCameraUnavailable), findsOne); + expect(find.text(S.current.payScanCameraPermissionDenied), findsNothing); + }, + ); + }); +} diff --git a/test/setup/di_pay_service_test.dart b/test/setup/di_pay_service_test.dart new file mode 100644 index 000000000..f8d88e2a8 --- /dev/null +++ b/test/setup/di_pay_service_test.dart @@ -0,0 +1,51 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/packages/repository/balance_repository.dart'; +import 'package:realunit_wallet/packages/repository/settings_repository.dart'; +import 'package:realunit_wallet/packages/repository/wallet_repository.dart'; +import 'package:realunit_wallet/packages/service/app_store.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_pay_service.dart'; +import 'package:realunit_wallet/setup/di.dart'; + +class _MockAppStore extends Mock implements AppStore {} + +class _MockBalanceRepository extends Mock implements BalanceRepository {} + +class _MockSettingsRepository extends Mock implements SettingsRepository {} + +class _MockWalletRepository extends Mock implements WalletRepository {} + +void main() { + // The pay flow's backend client is wired through `setupServices()` as a + // factory: `() => RealUnitPayService(getIt(), getIt())`. + // This test exercises that exact registration and then resolves the factory. + // + // `setupServices()` constructs `BalanceService` (eager singleton) up front, + // so `AppStore` + `BalanceRepository` must already be registered. Resolving + // `RealUnitPayService` then pulls the lazy `WalletService`, whose own + // dependencies bottom out at `WalletRepository` + `SettingsRepository` + + // `AppStore` (`BitboxService` is registered by `setupServices()` itself). + // Registering those leaf mocks keeps the whole chain construct-only — none + // of the mocked collaborators perform I/O on construction. + setUp(() { + getIt.reset(); + getIt.registerSingleton(_MockAppStore()); + getIt.registerSingleton(_MockBalanceRepository()); + getIt.registerSingleton(_MockSettingsRepository()); + getIt.registerSingleton(_MockWalletRepository()); + }); + + tearDown(() => getIt.reset()); + + test('setupServices registers a resolvable RealUnitPayService factory', () { + setupServices(); + + expect(getIt.isRegistered(), isTrue); + + final service = getIt(); + expect(service, isA()); + + // registerFactory hands back a fresh instance on every resolution. + expect(identical(service, getIt()), isFalse); + }); +} diff --git a/test/setup/routing/pay_route_test.dart b/test/setup/routing/pay_route_test.dart new file mode 100644 index 000000000..4cdb9a707 --- /dev/null +++ b/test/setup/routing/pay_route_test.dart @@ -0,0 +1,65 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/screens/home/bloc/home_bloc.dart'; +import 'package:realunit_wallet/screens/pay/pay_scan_page.dart'; +import 'package:realunit_wallet/setup/routing/router_config.dart'; +import 'package:realunit_wallet/setup/routing/routes/app_routes.dart'; + +import '../../helper/helper.dart'; + +class _MockHomeBloc extends MockBloc implements HomeBloc {} + +void main() { + late _MockHomeBloc homeBloc; + + setUpAll(() { + // PayScanPage embeds a MobileScanner; the stub keeps the headless camera + // preview deterministic and free of MissingPluginException. + stubMobileScannerChannel(); + }); + + setUp(() { + homeBloc = _MockHomeBloc(); + when(() => homeBloc.state).thenReturn(const HomeState()); + }); + + // Mirrors the production wiring in main.dart: the routed pages read their + // blocs from above MaterialApp.router, so HomeBloc (used by the initial + // /home route) is provided here. Navigation then drives the real + // `routerConfig` to the /pay GoRoute under test. + Future pumpRouter(WidgetTester tester) async { + await tester.pumpWidget( + BlocProvider.value( + value: homeBloc, + child: MaterialApp.router( + routerConfig: routerConfig, + localizationsDelegates: const [ + S.delegate, + GlobalMaterialLocalizations.delegate, + ], + supportedLocales: S.delegate.supportedLocales, + ), + ), + ); + await tester.pumpAndSettle(); + } + + testWidgets('the pay route builds PayScanPage', (tester) async { + await pumpRouter(tester); + + routerConfig.goNamed(AppRoutes.pay); + await tester.pumpAndSettle(); + + expect(find.byType(PayScanPage), findsOneWidget); + expect(find.byType(PayScanView), findsOneWidget); + + // Restore the router to its initial location so the global singleton + // does not leak the /pay location into any later test. + addTearDown(() => routerConfig.goNamed(AppRoutes.home)); + }); +} From a59b989612a957d2011376757a713be876dada33 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Wed, 22 Jul 2026 23:09:47 +0200 Subject: [PATCH 5/9] feat: RealUnit wallet-to-wallet (W2W) gasless transfer flow (#687) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Phase 2 **Baustein 3 — RealUnit wallet-to-wallet (W2W) transfer**: send REALU to another wallet, recipient picked via QR scan or manual entry. Implements RealUnitCH/app#684 (umbrella RealUnitCH/app#666). The transfer is **gasless via EIP-7702** — DFX pays gas from a dedicated W2W gas wallet — so the app signs an **EIP-712 delegation + an EIP-7702 authorization**, exactly like the existing SOFTWARE gasless sell confirm (`real_unit_sell_payment_info_service.dart`). It reuses `eip712_signer.dart` / `eip7702_signer.dart` and the wallet unlock/lock boundary; it is **not** the bitbox raw-tx path. Flow (Page + Cubit per step, separate state files): 1. **Recipient** — scan a wallet QR or paste/type an EVM address; client-side checksum validation for UX only (the API is the final authority). An `ethereum:` EIP-681 URI is normalized to the bare address. 2. **Amount** — whole REALU shares (REALU `decimals = 0`); the available balance is read via the shared balance watcher and the over-balance guard is local UX only. 3. **Confirm** — review recipient + amount. 4. **Process** — capability gate (software-only signing) → `PUT /transfer` → sign EIP-712 delegation + EIP-7702 authorization → `PUT /transfer/:id/confirm` → success (`txHash`) / typed failure. Typed failures rendered as states (no error-string parsing): unsupported wallet (debug/BitBox), signature cancelled, invalid request (API 400/404 — invalid recipient / self-transfer / token-contract recipient / insufficient REALU), and gas-funding-unavailable (API `ServiceUnavailable` 503 → friendly "temporarily unavailable", REALU untouched). ## Scanner reuse (no duplication) The scanner from #674 was an inline `MobileScanner` in `PayScanPage`. Extracted a shared `lib/widgets/scanner/qr_scanner_view.dart` (the camera/MethodChannel wrapper) and refactored both the pay scan page and the new send recipient page onto it — each flow keeps its own decode logic (LNURL vs EVM address). No scanner code is duplicated. ## API / decision authority Consumes **DFXswiss/api#3820** (pair-PR, backend lands first): `PUT /v1/realunit/transfer` + `PUT /v1/realunit/transfer/:id/confirm`. The app renders API-signaled outcomes and does not duplicate backend KYC/registration/limit/eligibility logic. ## Branch / stacking Branched **from `feature/ocp-pay-flow` (#674)** to reuse the scanner without duplication; PR base is **`staging`**. **Stacked on #674 (scanner) — review/merge #674 first; this diff will shrink once #674 merges to staging.** ## Tests / gates - `flutter analyze`: 0 issues. - `flutter test --coverage --exclude-tags golden`: all pass; **100% scoped coverage** on every new file (no `coverage:ignore`). - `flutter test --tags golden`: golden tests + baselines for the new screens. - `dart format` (repo config: page_width 100, trailing_commas preserve): clean. **Goldens regenerate pending on the runner:** baselines here were rendered locally on macOS and will mismatch the CI runner; the Golden Regenerate workflow is being dispatched so the runner pushes authoritative baselines. Stays **Draft** (no ready-for-review, no merge). --- assets/languages/strings_de.arb | 30 + assets/languages/strings_en.arb | 30 + .../payment/transfer_exceptions.dart | 88 ++ .../transfer/dto/real_unit_transfer_dto.dart | 19 + .../real_unit_transfer_eip7702_data_dto.dart | 67 ++ .../real_unit_transfer_payment_info_dto.dart | 38 + .../dfx/real_unit_transfer_service.dart | 253 +++++ .../widgets/sections/dashboard_actions.dart | 11 + lib/screens/pay/pay_scan_page.dart | 8 +- .../cubits/send_amount/send_amount_cubit.dart | 60 ++ .../cubits/send_amount/send_amount_state.dart | 37 + .../send_process/send_process_cubit.dart | 259 +++++ .../send_process/send_process_state.dart | 82 ++ .../send_recipient/send_recipient_cubit.dart | 96 ++ .../send_recipient/send_recipient_state.dart | 33 + lib/screens/send/send_amount_page.dart | 152 +++ lib/screens/send/send_confirm_page.dart | 114 +++ lib/screens/send/send_process_page.dart | 224 +++++ lib/screens/send/send_recipient_page.dart | 124 +++ lib/setup/di.dart | 4 + lib/setup/routing/router_config.dart | 9 +- lib/setup/routing/routes/app_routes.dart | 1 + lib/widgets/scanner/qr_scanner_view.dart | 39 + .../macos/dashboard_hidden_amounts.png | Bin 53493 -> 54576 bytes .../macos/dashboard_recent_transactions.png | Bin 57659 -> 58654 bytes .../goldens/macos/dashboard_with_balance.png | Bin 27711 -> 28703 bytes .../goldens/macos/send_amount_page_empty.png | Bin 0 -> 13405 bytes .../macos/send_amount_page_insufficient.png | Bin 0 -> 16666 bytes .../send/goldens/macos/send_confirm_page.png | Bin 0 -> 24087 bytes .../macos/send_process_page_signing.png | Bin 0 -> 10320 bytes .../macos/send_recipient_page_empty.png | Bin 0 -> 18266 bytes .../screens/send/send_golden_test.dart | 161 ++++ test/helper/responsive_surface_catalog.dart | 27 +- .../exceptions/exception_surface_test.dart | 6 + .../payment/transfer/transfer_dtos_test.dart | 118 +++ .../dfx/real_unit_transfer_service_test.dart | 462 +++++++++ .../sections/dashboard_actions_test.dart | 26 +- .../send/cubits/send_amount_cubit_test.dart | 87 ++ .../send_process/send_process_state_test.dart | 161 ++++ .../send/cubits/send_process_cubit_test.dart | 886 ++++++++++++++++++ .../cubits/send_recipient_cubit_test.dart | 144 +++ test/screens/send/send_amount_page_test.dart | 120 +++ .../send_amount_responsive_matrix_test.dart | 93 ++ test/screens/send/send_confirm_page_test.dart | 85 ++ .../send_confirm_responsive_matrix_test.dart | 94 ++ test/screens/send/send_process_page_test.dart | 308 ++++++ ...s_result_sheet_responsive_matrix_test.dart | 82 ++ .../send/send_recipient_page_test.dart | 181 ++++ ...send_recipient_responsive_matrix_test.dart | 82 ++ test/setup/di_pay_transfer_service_test.dart | 62 ++ test/setup/routing/pay_send_routes_test.dart | 75 ++ 51 files changed, 5024 insertions(+), 14 deletions(-) create mode 100644 lib/packages/service/dfx/exceptions/payment/transfer_exceptions.dart create mode 100644 lib/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_dto.dart create mode 100644 lib/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_eip7702_data_dto.dart create mode 100644 lib/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_payment_info_dto.dart create mode 100644 lib/packages/service/dfx/real_unit_transfer_service.dart create mode 100644 lib/screens/send/cubits/send_amount/send_amount_cubit.dart create mode 100644 lib/screens/send/cubits/send_amount/send_amount_state.dart create mode 100644 lib/screens/send/cubits/send_process/send_process_cubit.dart create mode 100644 lib/screens/send/cubits/send_process/send_process_state.dart create mode 100644 lib/screens/send/cubits/send_recipient/send_recipient_cubit.dart create mode 100644 lib/screens/send/cubits/send_recipient/send_recipient_state.dart create mode 100644 lib/screens/send/send_amount_page.dart create mode 100644 lib/screens/send/send_confirm_page.dart create mode 100644 lib/screens/send/send_process_page.dart create mode 100644 lib/screens/send/send_recipient_page.dart create mode 100644 lib/widgets/scanner/qr_scanner_view.dart create mode 100644 test/goldens/screens/send/goldens/macos/send_amount_page_empty.png create mode 100644 test/goldens/screens/send/goldens/macos/send_amount_page_insufficient.png create mode 100644 test/goldens/screens/send/goldens/macos/send_confirm_page.png create mode 100644 test/goldens/screens/send/goldens/macos/send_process_page_signing.png create mode 100644 test/goldens/screens/send/goldens/macos/send_recipient_page_empty.png create mode 100644 test/goldens/screens/send/send_golden_test.dart create mode 100644 test/packages/service/dfx/models/payment/transfer/transfer_dtos_test.dart create mode 100644 test/packages/service/dfx/real_unit_transfer_service_test.dart create mode 100644 test/screens/send/cubits/send_amount_cubit_test.dart create mode 100644 test/screens/send/cubits/send_process/send_process_state_test.dart create mode 100644 test/screens/send/cubits/send_process_cubit_test.dart create mode 100644 test/screens/send/cubits/send_recipient_cubit_test.dart create mode 100644 test/screens/send/send_amount_page_test.dart create mode 100644 test/screens/send/send_amount_responsive_matrix_test.dart create mode 100644 test/screens/send/send_confirm_page_test.dart create mode 100644 test/screens/send/send_confirm_responsive_matrix_test.dart create mode 100644 test/screens/send/send_process_page_test.dart create mode 100644 test/screens/send/send_process_result_sheet_responsive_matrix_test.dart create mode 100644 test/screens/send/send_recipient_page_test.dart create mode 100644 test/screens/send/send_recipient_responsive_matrix_test.dart create mode 100644 test/setup/di_pay_transfer_service_test.dart create mode 100644 test/setup/routing/pay_send_routes_test.dart diff --git a/assets/languages/strings_de.arb b/assets/languages/strings_de.arb index acaa47814..99cd63395 100644 --- a/assets/languages/strings_de.arb +++ b/assets/languages/strings_de.arb @@ -329,7 +329,37 @@ "sellReviewAndConfirm": "Verkauf prüfen & bestätigen", "sellSuccess": "Verkauf erfolgreich", "sellSuccessDescription": "Der Betrag wird Ihnen auf das angegebene Bankkonto ausgezahlt.", + "send": "Senden", + "sendAmountAvailable": "Verfügbar: ${shares} REALU", + "sendAmountInsufficient": "Nicht genügend REALU in Ihrer Wallet.", + "sendAmountInvalid": "Geben Sie eine ganze Anzahl REALU-Anteile ein.", + "sendAmountLabel": "Betrag in REALU-Anteilen", + "sendAmountTitle": "Zu sendender Betrag", + "sendConfirmAmount": "Betrag", + "sendConfirmButton": "REALU senden", + "sendConfirmRecipient": "Empfänger", + "sendConfirmSummary": "Sie senden ${shares} REALU", + "sendConfirmTitle": "Überweisung prüfen", + "sendFailureConfirmMismatch": "Die Überweisungsdetails stimmen nicht mehr mit Ihrer Bestätigung überein. Bitte gehen Sie zurück und versuchen Sie es erneut.", + "sendFailureGasUnavailable": "Überweisungen sind vorübergehend nicht verfügbar. Ihre REALU bleiben unangetastet – bitte versuchen Sie es später erneut.", + "sendFailureGeneric": "Bei der Überweisung ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.", + "sendFailureInvalidRequest": "Die Überweisung konnte nicht vorbereitet werden. Bitte prüfen Sie Empfänger und Betrag und versuchen Sie es erneut.", + "sendFailureRegistrationOrKycRequired": "Bitte schliessen Sie zuerst Ihre Registrierung oder Identitätsprüfung ab, um REALU zu senden.", + "sendFailureSignatureCancelled": "Die Signatur wurde abgebrochen. Ihre REALU bleiben unangetastet.", + "sendFailureSignatureUnsupported": "Diese Wallet kann keine gaslosen Überweisungen signieren. Wechseln Sie zu einer Software-Wallet, um REALU zu senden.", + "sendFailureTitle": "Überweisung fehlgeschlagen", "sending": "Wird gesendet", + "sendPaste": "Einfügen", + "sendPreparing": "Überweisung wird vorbereitet", + "sendProcessTitle": "REALU wird gesendet", + "sendRecipientInvalid": "Dies ist keine gültige Wallet-Adresse.", + "sendRecipientLabel": "Empfängeradresse", + "sendRecipientManualHint": "Scannen Sie einen Wallet-QR-Code oder fügen Sie die Empfängeradresse unten ein.", + "sendRecipientTitle": "Empfänger scannen", + "sendShares": "${shares} REALU", + "sendSigning": "Bestätigen Sie die Überweisung in Ihrer Wallet", + "sendSuccess": "Überweisung gesendet", + "sendSuccessDescription": "Ihre REALU sind auf dem Weg zum Empfänger.", "setNationalityFailed": "Ihre Staatsangehörigkeit konnte nicht gesetzt werden:\n${message}", "settings": "Einstellungen", "settingsAppVersion": "Version ${tag}", diff --git a/assets/languages/strings_en.arb b/assets/languages/strings_en.arb index 9a546b20a..36cca22f9 100644 --- a/assets/languages/strings_en.arb +++ b/assets/languages/strings_en.arb @@ -329,7 +329,37 @@ "sellReviewAndConfirm": "Review & confirm sale", "sellSuccess": "Sell successful", "sellSuccessDescription": "The amount will be paid into the bank account you have specified.", + "send": "Send", + "sendAmountAvailable": "Available: ${shares} REALU", + "sendAmountInsufficient": "Not enough REALU in your wallet.", + "sendAmountInvalid": "Enter a whole number of REALU shares.", + "sendAmountLabel": "Amount in REALU shares", + "sendAmountTitle": "Amount to send", + "sendConfirmAmount": "Amount", + "sendConfirmButton": "Send REALU", + "sendConfirmRecipient": "Recipient", + "sendConfirmSummary": "You send ${shares} REALU", + "sendConfirmTitle": "Review transfer", + "sendFailureConfirmMismatch": "The transfer details no longer match what you confirmed. Please go back and try again.", + "sendFailureGasUnavailable": "Transfers are temporarily unavailable. Your REALU is untouched — please try again later.", + "sendFailureGeneric": "Something went wrong with the transfer. Please try again.", + "sendFailureInvalidRequest": "The transfer could not be prepared. Please check the recipient and amount and try again.", + "sendFailureRegistrationOrKycRequired": "Please complete your registration or identity verification before sending REALU.", + "sendFailureSignatureCancelled": "The signature was cancelled. Your REALU is untouched.", + "sendFailureSignatureUnsupported": "This wallet cannot sign gasless transfers. Switch to a software wallet to send REALU.", + "sendFailureTitle": "Transfer failed", "sending": "Sending", + "sendPaste": "Paste", + "sendPreparing": "Preparing transfer", + "sendProcessTitle": "Sending REALU", + "sendRecipientInvalid": "This is not a valid wallet address.", + "sendRecipientLabel": "Recipient address", + "sendRecipientManualHint": "Scan a wallet QR code, or paste the recipient address below.", + "sendRecipientTitle": "Scan recipient", + "sendShares": "${shares} REALU", + "sendSigning": "Confirm the transfer in your wallet", + "sendSuccess": "Transfer sent", + "sendSuccessDescription": "Your REALU is on its way to the recipient.", "setNationalityFailed": "Could not set your nationality:\n${message}", "settings": "Settings", "settingsAppVersion": "Version ${tag}", diff --git a/lib/packages/service/dfx/exceptions/payment/transfer_exceptions.dart b/lib/packages/service/dfx/exceptions/payment/transfer_exceptions.dart new file mode 100644 index 000000000..d97d403e3 --- /dev/null +++ b/lib/packages/service/dfx/exceptions/payment/transfer_exceptions.dart @@ -0,0 +1,88 @@ +// Typed failures for the wallet-to-wallet (W2W) RealUnit transfer flow +// (enter/scan recipient → amount → confirm → sign → transfer → confirm). Each +// one renders a human-readable string (enumerated in `exception_surface_test.dart`) +// so it surfaces cleanly in logs and user-facing error states instead of the +// Dart default `Instance of '...'`. Typed failures drive control flow — no +// error-string parsing. + +import 'package:realunit_wallet/packages/service/dfx/exceptions/api_exception.dart'; + +/// The recipient string the user scanned or pasted is not a syntactically valid +/// EVM address. This is a client-side UX guard only; the API remains the final +/// authority on the address. +class InvalidRecipientAddressException implements Exception { + /// The rejected raw input, for diagnostics. + final String input; + + const InvalidRecipientAddressException(this.input); + + @override + String toString() => 'InvalidRecipientAddressException: $input is not a valid wallet address'; +} + +/// The active wallet mode cannot produce the EIP-712 delegation + EIP-7702 +/// authorization the gasless transfer requires (today: the debug wallet, and +/// hardware wallets whose firmware exposes no raw EIP-7702 signing API). The +/// flow is not branched on wallet type beyond this capability gate. +class TransferSignatureUnsupportedException implements Exception { + /// Diagnostic detail (e.g. the underlying signer message). + final String detail; + + const TransferSignatureUnsupportedException([ + this.detail = 'this wallet mode cannot sign gasless transfers', + ]); + + @override + String toString() => 'TransferSignatureUnsupportedException: $detail'; +} + +/// DFX cannot currently fund gas for the gasless transfer (the backend's +/// dedicated W2W gas wallet is unconfigured or below its low-balance +/// threshold). Surfaced from the API's `ServiceUnavailable` (503) as a friendly +/// "temporarily unavailable" state — the user's REALU is untouched. +class TransferGasFundingUnavailableException implements Exception { + /// Diagnostic detail (e.g. the API message), for logs. + final String detail; + + const TransferGasFundingUnavailableException([ + this.detail = 'gas funding for transfers is temporarily unavailable', + ]); + + @override + String toString() => 'TransferGasFundingUnavailableException: $detail'; +} + +/// The prepare response's recipient/amount does not match what the user +/// confirmed on-screen. Fail-closed before any signature is produced so a +/// mismatched backend echo cannot be blind-signed. +class TransferConfirmMismatchException implements Exception { + /// Diagnostic detail (which field diverged), for logs. + final String detail; + + const TransferConfirmMismatchException([ + this.detail = 'prepare response does not match user-confirmed recipient/amount', + ]); + + @override + String toString() => 'TransferConfirmMismatchException: $detail'; +} + +/// The transfer was already confirmed server-side (HTTP 409 with an +/// "already confirmed" message). An earlier confirm for the same transfer `id` +/// landed but its response was lost (e.g. transport failure after the server +/// processed the request). Callers must treat this as success, not failure — +/// mirrors the sell flow's [AlreadyConfirmedException] for the W2W path. +class TransferAlreadyConfirmedException extends ApiException { + /// Server-reported tx hash when present on the 409 body; null otherwise. + final String? txHash; + + const TransferAlreadyConfirmedException({ + super.statusCode, + required super.code, + required super.message, + this.txHash, + }); + + @override + String toString() => 'TransferAlreadyConfirmedException: $message'; +} diff --git a/lib/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_dto.dart b/lib/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_dto.dart new file mode 100644 index 000000000..29cb4af4a --- /dev/null +++ b/lib/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_dto.dart @@ -0,0 +1,19 @@ +/// Request body for `PUT /v1/realunit/transfer` — the wallet-to-wallet +/// transfer intent. REALU has `decimals = 0`, so [amount] is a whole number of +/// shares. +class RealUnitTransferDto { + final String toAddress; + final int amount; + + const RealUnitTransferDto({ + required this.toAddress, + required this.amount, + }); + + Map toJson() { + return { + 'toAddress': toAddress, + 'amount': amount, + }; + } +} diff --git a/lib/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_eip7702_data_dto.dart b/lib/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_eip7702_data_dto.dart new file mode 100644 index 000000000..2f631793b --- /dev/null +++ b/lib/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_eip7702_data_dto.dart @@ -0,0 +1,67 @@ +import 'package:realunit_wallet/packages/service/dfx/models/payment/sell/dto/eip7702/eip7702_data_dto.dart'; + +/// EIP-7702 delegation data for a gasless wallet-to-wallet REALU transfer. +/// +/// Mirrors the sell flow's [Eip7702Data] (`domain`/`types`/`message` are the +/// exact same shape the EIP-712 delegation + EIP-7702 authorization signers +/// consume), but the transfer endpoint returns the on-chain destination as +/// `recipient` rather than the sell flow's `depositAddress`. +class RealUnitTransferEip7702DataDto { + final String relayerAddress; + final String delegationManagerAddress; + final String delegatorAddress; + final int userNonce; + final Eip7702Domain domain; + final Eip7702Types types; + final Eip7702Message message; + final String tokenAddress; + final String amountWei; + final String recipient; + + const RealUnitTransferEip7702DataDto({ + required this.relayerAddress, + required this.delegationManagerAddress, + required this.delegatorAddress, + required this.userNonce, + required this.domain, + required this.types, + required this.message, + required this.tokenAddress, + required this.amountWei, + required this.recipient, + }); + + factory RealUnitTransferEip7702DataDto.fromJson(Map json) { + return RealUnitTransferEip7702DataDto( + relayerAddress: json['relayerAddress'] as String, + delegationManagerAddress: json['delegationManagerAddress'] as String, + delegatorAddress: json['delegatorAddress'] as String, + userNonce: json['userNonce'] as int, + domain: Eip7702Domain.fromJson(json['domain'] as Map), + types: Eip7702Types.fromJson(json['types'] as Map), + message: Eip7702Message.fromJson(json['message'] as Map), + tokenAddress: json['tokenAddress'] as String, + amountWei: json['amountWei'] as String, + recipient: json['recipient'] as String, + ); + } + + /// Adapts to the sell flow's [Eip7702Data] so the shared + /// `Eip712Signer.signDelegation` / `Eip7702Signer.signAuthorization` can sign + /// it without a transfer-specific signer. The signers never read + /// `depositAddress`, so the `recipient` is mapped through it verbatim. + Eip7702Data toEip7702Data() { + return Eip7702Data( + relayerAddress: relayerAddress, + delegationManagerAddress: delegationManagerAddress, + delegatorAddress: delegatorAddress, + userNonce: userNonce, + domain: domain, + types: types, + message: message, + tokenAddress: tokenAddress, + amountWei: amountWei, + depositAddress: recipient, + ); + } +} diff --git a/lib/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_payment_info_dto.dart b/lib/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_payment_info_dto.dart new file mode 100644 index 000000000..a510a5887 --- /dev/null +++ b/lib/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_payment_info_dto.dart @@ -0,0 +1,38 @@ +import 'package:realunit_wallet/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_eip7702_data_dto.dart'; + +/// Response of `PUT /v1/realunit/transfer` — the persisted transfer intent plus +/// the EIP-7702 delegation data the app must sign for the gasless REALU +/// transfer. +class RealUnitTransferPaymentInfoDto { + final int id; + final String uid; + final String toAddress; + final int amount; + final String tokenAddress; + final int chainId; + final RealUnitTransferEip7702DataDto eip7702; + + const RealUnitTransferPaymentInfoDto({ + required this.id, + required this.uid, + required this.toAddress, + required this.amount, + required this.tokenAddress, + required this.chainId, + required this.eip7702, + }); + + factory RealUnitTransferPaymentInfoDto.fromJson(Map json) { + return RealUnitTransferPaymentInfoDto( + id: json['id'] as int, + uid: json['uid'] as String, + toAddress: json['toAddress'] as String, + amount: (json['amount'] as num).toInt(), + tokenAddress: json['tokenAddress'] as String, + chainId: json['chainId'] as int, + eip7702: RealUnitTransferEip7702DataDto.fromJson( + json['eip7702'] as Map, + ), + ); + } +} diff --git a/lib/packages/service/dfx/real_unit_transfer_service.dart b/lib/packages/service/dfx/real_unit_transfer_service.dart new file mode 100644 index 000000000..e6c1a5b87 --- /dev/null +++ b/lib/packages/service/dfx/real_unit_transfer_service.dart @@ -0,0 +1,253 @@ +import 'dart:convert'; + +import 'package:eip7702/eip7702.dart' as eip7702; +import 'package:realunit_wallet/packages/config/api_config.dart'; +import 'package:realunit_wallet/packages/service/dfx/dfx_auth_service.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/api_exception.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/payment/transfer_exceptions.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/sell/dto/eip7702/eip7702_confirm_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_eip7702_data_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_payment_info_dto.dart'; +import 'package:realunit_wallet/packages/wallet/eip712_signer.dart'; +import 'package:realunit_wallet/packages/wallet/eip7702_signer.dart'; + +/// Consumes the gasless wallet-to-wallet RealUnit transfer endpoints +/// (`PUT /v1/realunit/transfer`, `PUT /v1/realunit/transfer/:id/confirm` — +/// DFXswiss/api #3820). DFX pays gas via EIP-7702 from a dedicated W2W gas +/// wallet, so the app signs an EIP-712 delegation + an EIP-7702 authorization — +/// the exact pattern the SOFTWARE gasless sell confirm uses +/// ([RealUnitSellPaymentInfoService.confirmPayment]). +class RealUnitTransferService extends DFXAuthService { + static const _transferPath = '/v1/realunit/transfer'; + static String _confirmPath(int id) => '/v1/realunit/transfer/$id/confirm'; + + // MetaMask Delegation Framework v1.3.0, CREATE2 — identical on all EVM chains. + // Pinned exactly as the sell software-confirm path pins them, so a tampered + // delegation payload is rejected before it is signed. + static const _metaMaskDelegatorAddress = '0x63c0c19a282a1b52b07dd5a65b58948a07dae32b'; + static const _delegationManagerAddress = '0xdb9b1e94b5b69df7e401ddbede43491141047db3'; + + RealUnitTransferService(super.appStore, super.walletService); + + /// `PUT /transfer` — persists the transfer intent and returns the EIP-7702 + /// delegation data to sign. A 503 means DFX cannot currently fund gas; it is + /// surfaced as a typed [TransferGasFundingUnavailableException] so the flow can + /// render a friendly "temporarily unavailable" state. Every other non-2xx is + /// an [ApiException] (KYC30 / registration / invalid recipient are signaled by + /// the API and rendered from its message). + Future prepareTransfer(RealUnitTransferDto dto) async { + final uri = buildUri(host, _transferPath); + final response = await authenticatedPut( + uri, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(dto.toJson()), + ); + + if (response.statusCode == 200 || response.statusCode == 201) { + return RealUnitTransferPaymentInfoDto.fromJson( + jsonDecode(response.body) as Map, + ); + } + + final errorJson = jsonDecode(response.body) as Map; + if (response.statusCode == 503) { + throw TransferGasFundingUnavailableException( + (errorJson['message'] ?? 'gas funding for transfers is temporarily unavailable').toString(), + ); + } + throw ApiException.fromJson(errorJson, httpStatusCode: response.statusCode); + } + + /// `PUT /transfer/:id/confirm` — signs the EIP-712 delegation + EIP-7702 + /// authorization and relays them; DFX broadcasts the gasless transfer and + /// returns the tx hash. + /// + /// [confirmedRecipient] and [confirmedAmount] are the values the user reviewed + /// and approved on the confirm screen. They are required so every call site + /// (including retries) must explicitly supply them; the service fail-closes if + /// the prepare response echoes different values before any signature is + /// produced. + /// + /// Reuses the wallet unlock/lock boundary and the shared + /// `Eip712Signer.signDelegation` / `Eip7702Signer.signAuthorization` exactly + /// like the sell software-confirm. A wallet that cannot produce these + /// signatures (debug wallet, or hardware firmware without raw EIP-7702 + /// support) raises [TransferSignatureUnsupportedException] — the capability + /// gate, not a wallet-type branch. + Future confirmTransfer( + RealUnitTransferPaymentInfoDto info, { + required String confirmedRecipient, + required int confirmedAmount, + }) async { + // EIP-712 + EIP-7702 typed-data signing needs the private key; promote the + // view-wallet to a fully unlocked SoftwareWallet before reading credentials. + await walletService.ensureCurrentWalletUnlocked(); + try { + final credentials = appStore.wallet.currentAccount.primaryAddress; + final transferData = info.eip7702; + _validateAgainstUserConfirmation(info, confirmedRecipient, confirmedAmount); + _validateEip7702Data(transferData, credentials.address.hexEip55, info.amount); + + final eip7702Data = transferData.toEip7702Data(); + final String delegationSignature; + final eip7702.EIP7702MsgSignature authorizationSignature; + try { + delegationSignature = await Eip712Signer.signDelegation( + credentials: credentials, + eip7702Data: eip7702Data, + ); + authorizationSignature = Eip7702Signer.signAuthorization( + credentials: credentials, + eip7702Data: eip7702Data, + ); + } on UnsupportedError catch (e) { + // Debug-wallet credentials reject typed-data signing with UnsupportedError. + throw TransferSignatureUnsupportedException(e.message ?? e.toString()); + } + + return _sendConfirm( + info.id, + Eip7702ConfirmDto( + delegation: Eip7702DelegationDto( + delegate: transferData.relayerAddress, + delegator: transferData.message.delegator, + authority: transferData.message.authority, + salt: '${transferData.message.salt}', + signature: delegationSignature, + ), + authorization: Eip7702AuthorizationDto( + chainId: transferData.domain.chainId, + address: transferData.delegatorAddress, + nonce: transferData.userNonce, + r: '0x${authorizationSignature.r.toRadixString(16).padLeft(64, '0')}', + s: '0x${authorizationSignature.s.toRadixString(16).padLeft(64, '0')}', + yParity: authorizationSignature.yParity, + ), + ), + ); + } finally { + // Drop the mnemonic from memory as soon as signing is done — runs on the + // throw path too so a validation/sign failure mid-sequence does not leave + // the key resident. Mirrors [RealUnitSellPaymentInfoService.confirmPayment]. + await walletService.lockCurrentWallet(); + } + } + + /// Fail-closed blind-sign guard: the prepare response must echo the recipient + /// and amount the user just confirmed on-screen before any signature is + /// produced. + /// + /// Honesty note — the actually-signed EIP-712 struct (`Eip7702Message`: + /// delegate/delegator/authority/caveats/salt) carries no recipient/amount + /// field of its own. `recipient` / `amountWei` / `tokenAddress` are unsigned + /// metadata sitting alongside the signed message. This check only closes the + /// gap of "did the backend's prepare response echo something different than + /// what the user just confirmed on-screen" before signing. It is NOT a + /// decode/validation of the delegation framework's `caveats` payload (the + /// actual on-chain enforcement mechanism), which remains unparsed exactly as + /// before. This change alone does NOT establish a full on-chain + /// recipient/amount cryptographic binding. + void _validateAgainstUserConfirmation( + RealUnitTransferPaymentInfoDto info, + String confirmedRecipient, + int confirmedAmount, + ) { + if (info.toAddress.toLowerCase() != confirmedRecipient.toLowerCase()) { + throw TransferConfirmMismatchException( + 'toAddress ${info.toAddress} does not match confirmed recipient $confirmedRecipient', + ); + } + if (info.eip7702.recipient.toLowerCase() != confirmedRecipient.toLowerCase()) { + throw TransferConfirmMismatchException( + 'eip7702.recipient ${info.eip7702.recipient} does not match confirmed recipient $confirmedRecipient', + ); + } + if (info.amount != confirmedAmount) { + throw TransferConfirmMismatchException( + 'amount ${info.amount} does not match confirmed amount $confirmedAmount', + ); + } + } + + Future _sendConfirm(int id, Eip7702ConfirmDto dto) async { + final uri = buildUri(host, _confirmPath(id)); + final response = await authenticatedPut( + uri, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(dto.toJson()), + ); + + if (response.statusCode != 200 && response.statusCode != 201) { + final errorJson = jsonDecode(response.body) as Map; + if (response.statusCode == 503) { + throw TransferGasFundingUnavailableException( + (errorJson['message'] ?? 'gas funding for transfers is temporarily unavailable') + .toString(), + ); + } + final error = ApiException.fromJson(errorJson, httpStatusCode: response.statusCode); + // 409 "already confirmed": an earlier confirm for this id landed but its + // response was lost. Mirrors RealUnitSellPaymentInfoService._sendConfirm. + if (error.statusCode == 409 && error.message.toLowerCase().contains('already confirmed')) { + final rawTxHash = errorJson['txHash']; + throw TransferAlreadyConfirmedException( + statusCode: error.statusCode, + code: error.code, + message: error.message, + txHash: rawTxHash is String ? rawTxHash : null, + ); + } + throw error; + } + + return (jsonDecode(response.body) as Map)['txHash'] as String; + } + + /// Pins the signed contract addresses + cross-checks the signed/unsigned + /// fields against known values before signing, mirroring the sell flow. The + /// recipient is server-bound (the backend supplies the ERC20 transfer call at + /// execute time), so it is validated for amount/token/chain consistency here. + void _validateEip7702Data( + RealUnitTransferEip7702DataDto data, + String walletAddress, + int userAmount, + ) { + final expectedChainId = appStore.apiConfig.asset.chainId; + + if (data.delegatorAddress.toLowerCase() != _metaMaskDelegatorAddress) { + throw Exception( + 'EIP-7702 delegator address does not match expected MetaMask Delegator contract', + ); + } + if (data.delegationManagerAddress.toLowerCase() != _delegationManagerAddress) { + throw Exception('EIP-7702 delegation manager address does not match expected contract'); + } + if (data.domain.verifyingContract.toLowerCase() != _delegationManagerAddress) { + throw Exception('EIP-7702 verifying contract does not match expected DelegationManager'); + } + if (data.message.delegator.toLowerCase() != walletAddress.toLowerCase()) { + throw Exception('EIP-7702 message delegator does not match wallet address'); + } + if (data.domain.chainId != expectedChainId) { + throw Exception( + 'EIP-7702 chain ID mismatch: expected $expectedChainId, got ${data.domain.chainId}', + ); + } + if (data.message.delegate.toLowerCase() != data.relayerAddress.toLowerCase()) { + throw Exception('EIP-7702 message delegate does not match relayer address'); + } + if (data.tokenAddress.toLowerCase() != appStore.apiConfig.asset.address.toLowerCase()) { + throw Exception('EIP-7702 token address does not match RealUnit token'); + } + // REALU has decimals = 0, so the wei amount equals the share count; compute + // generically against the asset decimals so a non-zero-decimals asset would + // still be validated correctly. + final expectedWei = + BigInt.from(userAmount) * BigInt.from(10).pow(appStore.apiConfig.asset.decimals); + final actualWei = BigInt.tryParse(data.amountWei); + if (actualWei == null || actualWei != expectedWei) { + throw Exception('EIP-7702 amount mismatch: expected $expectedWei, got ${data.amountWei}'); + } + } +} diff --git a/lib/screens/dashboard/widgets/sections/dashboard_actions.dart b/lib/screens/dashboard/widgets/sections/dashboard_actions.dart index 2fb38a1f0..0c64a6a60 100644 --- a/lib/screens/dashboard/widgets/sections/dashboard_actions.dart +++ b/lib/screens/dashboard/widgets/sections/dashboard_actions.dart @@ -46,6 +46,17 @@ class DashboardActions extends StatelessWidget { onPressed: () => context.pushNamed(AppRoutes.pay), ), ), + Expanded( + child: ActionButton( + icon: Icon( + Icons.send_rounded, + color: RealUnitColors.basic.white, + size: 20, + ), + label: S.of(context).send, + onPressed: () => context.pushNamed(AppRoutes.send), + ), + ), ], ); } diff --git a/lib/screens/pay/pay_scan_page.dart b/lib/screens/pay/pay_scan_page.dart index 71c6be737..8ca3f74d4 100644 --- a/lib/screens/pay/pay_scan_page.dart +++ b/lib/screens/pay/pay_scan_page.dart @@ -10,6 +10,7 @@ import 'package:realunit_wallet/generated/i18n.dart'; import 'package:realunit_wallet/screens/pay/cubits/pay_scan/pay_scan_cubit.dart'; import 'package:realunit_wallet/screens/pay/pay_quote_page.dart'; import 'package:realunit_wallet/styles/colors.dart'; +import 'package:realunit_wallet/widgets/scanner/qr_scanner_view.dart'; class PayScanPage extends StatelessWidget { const PayScanPage({super.key}); @@ -52,11 +53,8 @@ class PayScanView extends StatelessWidget { builder: (context, state) { return Scaffold( appBar: AppBar(title: Text(S.of(context).payScanTitle)), - body: MobileScanner( - onDetect: (capture) { - final raw = capture.barcodes.firstOrNull?.rawValue; - if (raw != null) context.read().onCodeDetected(raw); - }, + body: QrScannerView( + onDetect: (raw) => context.read().onCodeDetected(raw), errorBuilder: (context, error, child) { final message = error.errorCode == MobileScannerErrorCode.permissionDenied ? S.of(context).payScanCameraPermissionDenied diff --git a/lib/screens/send/cubits/send_amount/send_amount_cubit.dart b/lib/screens/send/cubits/send_amount/send_amount_cubit.dart new file mode 100644 index 000000000..7a373374e --- /dev/null +++ b/lib/screens/send/cubits/send_amount/send_amount_cubit.dart @@ -0,0 +1,60 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +part 'send_amount_state.dart'; + +/// Validates the REALU amount to send. REALU has `decimals = 0`, so the amount +/// is a whole number of shares. The available balance (also whole shares) is +/// tracked here so the field can be validated against it locally for UX; the +/// API remains the authority and re-checks the balance on `PUT /transfer`. +class SendAmountCubit extends Cubit { + /// Available REALU shares in the wallet, or null when the balance is unknown + /// (the available-balance hint and the over-balance guard are then skipped — + /// the API still validates). Mutable because the balance arrives over a + /// stream and may update after the cubit is built. + BigInt? availableShares; + + SendAmountCubit({this.availableShares}) : super(const SendAmountState()); + + /// Updates the tracked balance when a fresh value arrives from the balance + /// stream, then re-evaluates the current input against it so an amount that + /// was provisionally valid (unknown balance) is re-checked. + void availableSharesChanged(BigInt shares) { + if (availableShares == shares) return; + availableShares = shares; + if (state.text.isNotEmpty) amountChanged(state.text); + } + + /// Re-evaluates [raw] on every keystroke and emits the parsed amount + a + /// validity flag the confirm button binds to. + void amountChanged(String raw) { + final text = raw.trim(); + if (text.isEmpty) { + emit(SendAmountState(text: text, amount: null, status: SendAmountStatus.empty)); + return; + } + + final parsed = int.tryParse(text); + if (parsed == null || parsed < 1) { + emit(SendAmountState(text: text, amount: parsed, status: SendAmountStatus.invalid)); + return; + } + + final balance = availableShares; + if (balance != null && BigInt.from(parsed) > balance) { + emit( + SendAmountState(text: text, amount: parsed, status: SendAmountStatus.insufficientBalance), + ); + return; + } + + emit(SendAmountState(text: text, amount: parsed, status: SendAmountStatus.valid)); + } + + /// Fills the field with the full available balance. + void useMax() { + final balance = availableShares; + if (balance == null || balance < BigInt.one) return; + amountChanged(balance.toString()); + } +} diff --git a/lib/screens/send/cubits/send_amount/send_amount_state.dart b/lib/screens/send/cubits/send_amount/send_amount_state.dart new file mode 100644 index 000000000..f666804f6 --- /dev/null +++ b/lib/screens/send/cubits/send_amount/send_amount_state.dart @@ -0,0 +1,37 @@ +part of 'send_amount_cubit.dart'; + +/// Validity of the entered amount. Each value maps to a localized hint/error in +/// the view — the cubit carries the status, not the copy. +enum SendAmountStatus { + /// Nothing entered yet. + empty, + + /// Not a whole number >= 1. + invalid, + + /// A valid whole number, but more than the available REALU balance. + insufficientBalance, + + /// A valid, sendable amount. + valid, +} + +class SendAmountState extends Equatable { + final String text; + + /// The parsed amount, or null when [text] is empty / not an integer. + final int? amount; + final SendAmountStatus status; + + const SendAmountState({ + this.text = '', + this.amount, + this.status = SendAmountStatus.empty, + }); + + /// The confirm button binds to this — only a fully valid amount may advance. + bool get isValid => status == SendAmountStatus.valid && amount != null; + + @override + List get props => [text, amount, status]; +} diff --git a/lib/screens/send/cubits/send_process/send_process_cubit.dart b/lib/screens/send/cubits/send_process/send_process_cubit.dart new file mode 100644 index 000000000..6923478fa --- /dev/null +++ b/lib/screens/send/cubits/send_process/send_process_cubit.dart @@ -0,0 +1,259 @@ +import 'dart:developer' as developer; + +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:realunit_wallet/packages/service/app_store.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/api_exception.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/bitbox_exception.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/payment/buy_exceptions.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/payment/transfer_exceptions.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/transfer/dto/real_unit_transfer_payment_info_dto.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_transfer_service.dart'; +import 'package:realunit_wallet/packages/wallet/exceptions/signing_cancelled_exception.dart'; +import 'package:realunit_wallet/packages/wallet/wallet.dart'; + +part 'send_process_state.dart'; + +/// Orchestrates the gasless wallet-to-wallet transfer once the user confirms: +/// gate the wallet's signing capability → `PUT /transfer` (prepare + EIP-7702 +/// delegation data) → sign the EIP-712 delegation + EIP-7702 authorization and +/// `PUT /transfer/:id/confirm` → success (txHash) / a typed failure. +/// +/// Prepare and confirm are split so a failed confirm can be retried against the +/// same server-assigned transfer `id` (DFXswiss/api#3820 keys idempotency off +/// that `id`). A new prepare would mint a second on-chain transfer intent. +/// +/// The gasless EIP-7702 path can only be signed by a software wallet today; +/// debug and BitBox wallets are gated up-front (the contract's capability gate — +/// the flow is NOT otherwise branched on wallet type). Insufficient REALU and +/// invalid-address are decided by the API and rendered from its signal; a 503 +/// (gas-funding-unavailable) surfaces as a dedicated "temporarily unavailable" +/// state. +class SendProcessCubit extends Cubit { + final RealUnitTransferService _transferService; + final AppStore _appStore; + final String _recipient; + final int _amount; + + /// Successfully prepared transfer intent (carries the server-assigned `id`). + /// Stored for the lifetime of the cubit so [retryConfirm] can re-confirm the + /// same intent without a second prepare. + RealUnitTransferPaymentInfoDto? _preparedInfo; + + /// Guards against concurrent confirm attempts (start + retry, or double-tap). + bool _confirmInFlight = false; + + SendProcessCubit({ + required RealUnitTransferService transferService, + required AppStore appStore, + required String recipient, + required int amount, + }) : _transferService = transferService, + _appStore = appStore, + _recipient = recipient, + _amount = amount, + super(const SendProcessInitial()); + + /// Entry point — called by the view once the user confirms the summary. + Future start() async { + // Capability gate BEFORE any network/sign action: only a software wallet can + // produce the EIP-712 delegation + EIP-7702 authorization the gasless + // transfer requires. Surface the dedicated unsupported state otherwise. + if (_appStore.wallet.walletType != WalletType.software) { + emit(const SendProcessFailure(SendProcessFailureReason.signatureUnsupported)); + return; + } + + // Phase 1 — prepare. Failures here are terminal and non-retryable: no + // transfer `id` was ever obtained, so there is nothing safe to retry. + // Classification into a local failure state happens in the try/catch; + // emission is gated once after so a closed cubit never emits. + SendProcessState? prepareFailure; + try { + emit(const SendProcessPreparing()); + final info = await _transferService.prepareTransfer( + RealUnitTransferDto(toAddress: _recipient, amount: _amount), + ); + if (isClosed) { + return; + } + _preparedInfo = info; + } on TransferSignatureUnsupportedException { + prepareFailure = const SendProcessFailure(SendProcessFailureReason.signatureUnsupported); + } on TransferGasFundingUnavailableException { + prepareFailure = const SendProcessFailure(SendProcessFailureReason.gasFundingUnavailable); + } on SigningCancelledException { + prepareFailure = const SendProcessFailure(SendProcessFailureReason.signatureCancelled); + } on BitboxNotConnectedException { + prepareFailure = const SendProcessFailure(SendProcessFailureReason.signatureUnsupported); + } on RegistrationRequiredException catch (e) { + prepareFailure = SendProcessFailure( + SendProcessFailureReason.registrationOrKycRequired, + message: e.message, + ); + } on KycLevelRequiredException catch (e) { + prepareFailure = SendProcessFailure( + SendProcessFailureReason.registrationOrKycRequired, + message: e.message, + ); + } on ApiException catch (e) { + // The API is the authority on recipient/amount/eligibility. Render its + // signaled reason rather than re-deriving limits locally. + prepareFailure = SendProcessFailure(_reasonForApi(e), message: e.message); + } catch (e) { + prepareFailure = SendProcessFailure(SendProcessFailureReason.generic, message: e.toString()); + } + + if (prepareFailure != null) { + if (isClosed) { + return; + } + emit(prepareFailure); + return; + } + + // Phase 2 — confirm against the stored prepare result. + await _confirmPrepared(); + } + + /// Re-signs and re-PUTs `/confirm` for the same prepared transfer `id`. + /// + /// Safe because the backend is idempotent per id: re-signing the same message + /// is not a new transfer. Fails loud when there is nothing prepared, when a + /// confirm is already in flight, or when the cubit is not in a retryable + /// failure state. + Future retryConfirm() async { + if (_preparedInfo == null) { + throw StateError('Cannot retry confirm: no prepared transfer info is stored'); + } + if (_confirmInFlight) { + throw StateError('Cannot retry confirm: a confirm is already in flight'); + } + final current = state; + if (current is! SendProcessFailure || !current.canRetry) { + throw StateError('Cannot retry confirm: cubit is not in a retryable failure state'); + } + await _confirmPrepared(); + } + + /// Shared confirm path used by [start] and [retryConfirm]. Never calls + /// prepare — only signs + PUTs against [_preparedInfo]. + /// + /// Callers ([start] after a successful prepare, [retryConfirm] after its own + /// null-check) guarantee [_preparedInfo] is non-null. Classification of the + /// confirm outcome into a single [SendProcessState] happens in the try/catch; + /// emission is gated once after [finally] so a closed cubit never emits. + Future _confirmPrepared() async { + final info = _preparedInfo!; + if (_confirmInFlight) { + throw StateError('Cannot confirm: a confirm is already in flight'); + } + + _confirmInFlight = true; + late SendProcessState nextState; + // Set only on the direct success path (confirmTransfer resolves); used for + // the diagnostic log when the cubit closed before that success could emit. + String? directSuccessTxHash; + try { + emit(const SendProcessSigning()); + final txHash = await _transferService.confirmTransfer( + info, + confirmedRecipient: _recipient, + confirmedAmount: _amount, + ); + directSuccessTxHash = txHash; + nextState = SendProcessSuccess(txHash); + } on TransferAlreadyConfirmedException catch (e) { + // An earlier confirm for this id landed; treat as success. Prefer the + // server-supplied txHash when present; empty string when the 409 body + // carried none (the success sheet does not display the hash — empty is + // the intentional "no hash available" representation, not a fake hash). + final alreadyConfirmedTxHash = e.txHash; + if (alreadyConfirmedTxHash != null) { + nextState = SendProcessSuccess(alreadyConfirmedTxHash); + } else { + nextState = const SendProcessSuccess(''); + } + } on TransferConfirmMismatchException catch (e) { + nextState = SendProcessFailure( + SendProcessFailureReason.confirmMismatch, + message: e.toString(), + ); + } on TransferSignatureUnsupportedException { + nextState = const SendProcessFailure(SendProcessFailureReason.signatureUnsupported); + } on TransferGasFundingUnavailableException { + nextState = const SendProcessFailure(SendProcessFailureReason.gasFundingUnavailable); + } on SigningCancelledException { + nextState = const SendProcessFailure(SendProcessFailureReason.signatureCancelled); + } on BitboxNotConnectedException { + nextState = const SendProcessFailure(SendProcessFailureReason.signatureUnsupported); + } on RegistrationRequiredException catch (e) { + nextState = SendProcessFailure( + SendProcessFailureReason.registrationOrKycRequired, + message: e.message, + ); + } on KycLevelRequiredException catch (e) { + nextState = SendProcessFailure( + SendProcessFailureReason.registrationOrKycRequired, + message: e.message, + ); + } on ApiException catch (e) { + // Definitive client/server business failures are terminal. Other API + // status codes (e.g. 500) may be transient after a confirm that already + // has a known-good id — offer retry against the same intent. + nextState = SendProcessFailure( + _reasonForApi(e), + message: e.message, + canRetry: !_isDefinitiveApiFailure(e), + ); + } catch (e) { + // Transport/timeout/unclassified: id is known-good; allow retryConfirm. + nextState = SendProcessFailure( + SendProcessFailureReason.generic, + message: e.toString(), + canRetry: true, + ); + } finally { + _confirmInFlight = false; + } + + if (isClosed) { + if (directSuccessTxHash != null) { + developer.log( + 'SendProcessCubit: transfer succeeded (txHash=$directSuccessTxHash) after the cubit was closed — ' + 'result could not be emitted', + ); + } + return; + } + emit(nextState); + } + + /// Maps an API error to a typed failure reason. A 400 from `PUT /transfer` + /// covers both an invalid recipient and insufficient REALU; both render a + /// generic "could not prepare the transfer" message keyed off the API text, + /// so they share [SendProcessFailureReason.invalidRequest]. A 403 (including + /// unmapped compliance codes) maps to [registrationOrKycRequired]. + static SendProcessFailureReason _reasonForApi(ApiException e) { + if (e.statusCode == 503) { + return SendProcessFailureReason.gasFundingUnavailable; + } + if (e.statusCode == 403) { + return SendProcessFailureReason.registrationOrKycRequired; + } + if (e.statusCode == 400 || e.statusCode == 404) { + return SendProcessFailureReason.invalidRequest; + } + return SendProcessFailureReason.generic; + } + + /// Definitive API failures that must not be retried via [retryConfirm]. + static bool _isDefinitiveApiFailure(ApiException e) { + final statusCode = e.statusCode; + return statusCode == 400 || + statusCode == 403 || + statusCode == 404 || + statusCode == 503; + } +} diff --git a/lib/screens/send/cubits/send_process/send_process_state.dart b/lib/screens/send/cubits/send_process/send_process_state.dart new file mode 100644 index 000000000..7bcafda19 --- /dev/null +++ b/lib/screens/send/cubits/send_process/send_process_state.dart @@ -0,0 +1,82 @@ +part of 'send_process_cubit.dart'; + +/// Why the transfer failed. Each reason maps to a localized, user-facing message +/// in the view — the cubit carries the reason, not the copy. +enum SendProcessFailureReason { + /// The active wallet mode cannot sign the gasless EIP-7702 transfer (debug or + /// BitBox wallet, or a debug credential detected at sign time). + signatureUnsupported, + + /// The user cancelled the signature (or the signing device dropped the link). + signatureCancelled, + + /// DFX cannot currently fund gas for the transfer (the API's 503). The user's + /// REALU is untouched — this is a transient "temporarily unavailable" state. + gasFundingUnavailable, + + /// The API rejected the prepare request (invalid recipient, self-transfer, + /// token-contract recipient, non-integer amount, or insufficient REALU). The + /// API message carries the specific detail. + invalidRequest, + + /// The API requires the user to complete registration or a higher KYC level + /// before this transfer can proceed (403 REGISTRATION_REQUIRED / + /// KYC_LEVEL_REQUIRED). + registrationOrKycRequired, + + /// The prepare response's recipient/amount did not match what the user + /// confirmed on-screen. Fail-closed before signing; not retryable with the + /// same prepared intent (the server-echoed data itself is wrong). + confirmMismatch, + + /// Any other unexpected error. + generic, +} + +sealed class SendProcessState extends Equatable { + const SendProcessState(); + + @override + List get props => []; +} + +class SendProcessInitial extends SendProcessState { + const SendProcessInitial(); +} + +/// `PUT /transfer` in flight — preparing the intent + EIP-7702 delegation data. +class SendProcessPreparing extends SendProcessState { + const SendProcessPreparing(); +} + +/// Signing the EIP-712 delegation + EIP-7702 authorization and confirming. +class SendProcessSigning extends SendProcessState { + const SendProcessSigning(); +} + +class SendProcessSuccess extends SendProcessState { + final String txHash; + + const SendProcessSuccess(this.txHash); + + @override + List get props => [txHash]; +} + +class SendProcessFailure extends SendProcessState { + final SendProcessFailureReason reason; + + /// Diagnostic detail for logs — not the user-facing copy. + final String? message; + + /// When true, the prepared transfer `id` is retained and the user may call + /// [SendProcessCubit.retryConfirm] to re-sign and re-PUT `/confirm` for the + /// same intent (no new prepare). Defaults to false — a UI affordance flag, + /// not a data-correctness fallback. + final bool canRetry; + + const SendProcessFailure(this.reason, {this.message, this.canRetry = false}); + + @override + List get props => [reason, message, canRetry]; +} diff --git a/lib/screens/send/cubits/send_recipient/send_recipient_cubit.dart b/lib/screens/send/cubits/send_recipient/send_recipient_cubit.dart new file mode 100644 index 000000000..d98e3556b --- /dev/null +++ b/lib/screens/send/cubits/send_recipient/send_recipient_cubit.dart @@ -0,0 +1,96 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:realunit_wallet/packages/service/dfx/exceptions/payment/transfer_exceptions.dart'; +import 'package:web3dart/web3dart.dart' show EthereumAddress; + +part 'send_recipient_state.dart'; + +/// Captures the transfer recipient — either scanned from a QR code or pasted / +/// typed manually. Validation here is a client-side UX guard only (a malformed +/// address is rejected before the user can advance); the API remains the final +/// authority on the address. A scanned `ethereum:0x…` URI is normalized to the +/// bare address so a wallet QR that encodes an EIP-681 URI is accepted. EIP-681 +/// function-call URIs of the form `…/transfer?address=0x…` extract the recipient +/// from the single non-empty `address` query parameter (never from the token +/// contract before `/`); zero, multiple, or empty `address=` values — and any +/// other function-call form — are rejected as unrecognized/ambiguous. +class SendRecipientCubit extends Cubit { + SendRecipientCubit() : super(const SendRecipientEmpty()); + + /// Called once per detected barcode while the scanner is open. Guards against + /// re-entry after a successful decode so a continuously-detecting scanner does + /// not re-emit. + void onCodeDetected(String raw) { + if (state is SendRecipientValid) { + return; + } + submit(raw); + } + + /// Validates a manually entered / pasted / scanned [input]. Emits + /// [SendRecipientValid] with the checksummed address on success, or + /// [SendRecipientInvalid] otherwise. Parsing and decoding exceptions are + /// caught and fail closed into [SendRecipientInvalid]. + void submit(String input) { + try { + final address = _extractAddress(input); + if (address == null) { + emit(SendRecipientInvalid(InvalidRecipientAddressException(input.trim()))); + return; + } + final checksummed = EthereumAddress.fromHex(address).hexEip55; + emit(SendRecipientValid(checksummed)); + } catch (_) { + emit(SendRecipientInvalid(InvalidRecipientAddressException(input.trim()))); + } + } + + /// Clears the current selection so the field/scanner is ready for new input. + void reset() => emit(const SendRecipientEmpty()); + + /// Strips an optional `ethereum:` EIP-681 scheme and optional `pay-` payment + /// marker, then extracts the recipient address. Simple form (`0x…[@chainId][?…]`, + /// no `/`): the substring before the first `@` or `?`. Function-call form + /// (`…/transfer?address=0x…`): exactly one non-empty `address` query value + /// when the function name is `transfer`; zero, multiple, or empty values — + /// and any other function-call form — return `null` (fail closed). + static String? _extractAddress(String input) { + var value = input.trim(); + if (value.toLowerCase().startsWith('ethereum:')) { + value = value.substring('ethereum:'.length); + } + if (value.toLowerCase().startsWith('pay-')) { + value = value.substring('pay-'.length); + } + + final slash = value.indexOf('/'); + if (slash != -1) { + // Function-call form: text after `/` up to `?` (or end) is the function name. + final afterSlash = value.substring(slash + 1); + final queryStart = afterSlash.indexOf('?'); + final functionName = queryStart == -1 ? afterSlash : afterSlash.substring(0, queryStart); + + if (functionName == 'transfer' && queryStart != -1) { + final query = afterSlash.substring(queryStart + 1); + final addresses = Uri(query: query).queryParametersAll['address']; + if (addresses != null && addresses.length == 1 && addresses.first.trim().isNotEmpty) { + return addresses.first.trim(); + } + } + // Unrecognized/ambiguous function-call form — do not guess an address. + return null; + } + + // Simple form: recipient is the substring before the first `@` or `?`. + final at = value.indexOf('@'); + final query = value.indexOf('?'); + var end = value.length; + if (at != -1) { + end = at; + } + if (query != -1 && query < end) { + end = query; + } + return value.substring(0, end).trim(); + } +} diff --git a/lib/screens/send/cubits/send_recipient/send_recipient_state.dart b/lib/screens/send/cubits/send_recipient/send_recipient_state.dart new file mode 100644 index 000000000..549d754e1 --- /dev/null +++ b/lib/screens/send/cubits/send_recipient/send_recipient_state.dart @@ -0,0 +1,33 @@ +part of 'send_recipient_cubit.dart'; + +sealed class SendRecipientState extends Equatable { + const SendRecipientState(); + + @override + List get props => []; +} + +/// No recipient entered yet. +class SendRecipientEmpty extends SendRecipientState { + const SendRecipientEmpty(); +} + +/// A syntactically valid EVM address, normalized to its EIP-55 checksum form. +class SendRecipientValid extends SendRecipientState { + final String address; + + const SendRecipientValid(this.address); + + @override + List get props => [address]; +} + +/// The entered/scanned value is not a valid EVM address (client-side UX guard). +class SendRecipientInvalid extends SendRecipientState { + final InvalidRecipientAddressException error; + + const SendRecipientInvalid(this.error); + + @override + List get props => [error.input]; +} diff --git a/lib/screens/send/send_amount_page.dart b/lib/screens/send/send_amount_page.dart new file mode 100644 index 000000000..bfcb1acf6 --- /dev/null +++ b/lib/screens/send/send_amount_page.dart @@ -0,0 +1,152 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/models/balance.dart'; +import 'package:realunit_wallet/packages/repository/balance_repository.dart'; +import 'package:realunit_wallet/packages/service/app_store.dart'; +import 'package:realunit_wallet/screens/sell/cubits/sell_balance/sell_balance_cubit.dart'; +import 'package:realunit_wallet/screens/send/cubits/send_amount/send_amount_cubit.dart'; +import 'package:realunit_wallet/screens/send/send_confirm_page.dart'; +import 'package:realunit_wallet/setup/di.dart'; +import 'package:realunit_wallet/styles/colors.dart'; +import 'package:realunit_wallet/widgets/scrollable_actions_layout.dart'; + +/// Second step: choose the whole-share REALU amount. The available balance is +/// read via the shared [SellBalanceCubit] (a generic REALU balance watcher) and +/// fed into [SendAmountCubit] for the local over-balance UX guard. REALU has +/// `decimals = 0`, so the raw balance equals whole shares. +class SendAmountPage extends StatelessWidget { + final String recipient; + + const SendAmountPage({super.key, required this.recipient}); + + @override + Widget build(BuildContext context) { + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (_) => SellBalanceCubit(getIt(), getIt()), + ), + BlocProvider(create: (_) => SendAmountCubit()), + ], + child: SendAmountView(recipient: recipient), + ); + } +} + +class SendAmountView extends StatelessWidget { + final String recipient; + + const SendAmountView({super.key, required this.recipient}); + + @override + Widget build(BuildContext context) { + // The balance arrives over a stream; push every update into the amount + // cubit so the available hint + over-balance guard track the live balance + // (BlocProvider.create runs once, so the balance can't be captured there). + return BlocListener( + listener: (context, balance) => + context.read().availableSharesChanged(balance.balance), + child: _SendAmountBody(recipient: recipient), + ); + } +} + +class _SendAmountBody extends StatefulWidget { + final String recipient; + + const _SendAmountBody({required this.recipient}); + + @override + State<_SendAmountBody> createState() => _SendAmountBodyState(); +} + +class _SendAmountBodyState extends State<_SendAmountBody> { + final _controller = TextEditingController(); + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + void _syncController(String text) { + if (_controller.text == text) return; + _controller.value = TextEditingValue( + text: text, + selection: TextSelection.collapsed(offset: text.length), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(S.of(context).sendAmountTitle)), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + child: BlocConsumer( + listenWhen: (previous, current) => previous.text != current.text, + listener: (context, state) => _syncController(state.text), + builder: (context, state) { + // The available hint tracks the live balance directly so it stays + // in sync with the stream. + final available = context.watch().state.balance; + return ScrollableActionsLayout( + body: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + spacing: 16, + children: [ + Text( + S.of(context).sendAmountAvailable(available.toString()), + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: RealUnitColors.neutral500, + ), + ), + TextField( + controller: _controller, + keyboardType: TextInputType.number, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + decoration: InputDecoration( + labelText: S.of(context).sendAmountLabel, + errorText: _errorText(context, state.status), + suffixIcon: TextButton( + onPressed: () => context.read().useMax(), + child: Text(S.of(context).max.toUpperCase()), + ), + ), + onChanged: (value) => + context.read().amountChanged(value), + ), + ], + ), + actions: [ + FilledButton( + onPressed: state.isValid + ? () => Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => SendConfirmPage( + recipient: widget.recipient, + amount: state.amount!, + ), + ), + ) + : null, + child: Text(S.of(context).next), + ), + ], + ); + }, + ), + ), + ), + ); + } + + String? _errorText(BuildContext context, SendAmountStatus status) => switch (status) { + SendAmountStatus.empty || SendAmountStatus.valid => null, + SendAmountStatus.invalid => S.of(context).sendAmountInvalid, + SendAmountStatus.insufficientBalance => S.of(context).sendAmountInsufficient, + }; +} diff --git a/lib/screens/send/send_confirm_page.dart b/lib/screens/send/send_confirm_page.dart new file mode 100644 index 000000000..22c4ffe80 --- /dev/null +++ b/lib/screens/send/send_confirm_page.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/screens/send/send_process_page.dart'; +import 'package:realunit_wallet/styles/colors.dart'; +import 'package:realunit_wallet/widgets/scrollable_actions_layout.dart'; + +/// Third step: review the recipient + amount before signing. Confirming starts +/// the on-chain process step. +class SendConfirmPage extends StatefulWidget { + final String recipient; + final int amount; + + const SendConfirmPage({super.key, required this.recipient, required this.amount}); + + @override + State createState() => _SendConfirmPageState(); +} + +class _SendConfirmPageState extends State { + /// Once true, permanently disables the confirm button on this route instance + /// so a second transfer cannot be re-triggered after push+pop of the process + /// page (terminal navigation pattern). + bool _navigating = false; + + @override + Widget build(BuildContext context) { + final recipient = widget.recipient; + final amount = widget.amount; + + return Scaffold( + appBar: AppBar(title: Text(S.of(context).sendConfirmTitle)), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + child: ScrollableActionsLayout( + centerBody: true, + body: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + spacing: 24, + children: [ + Text( + S.of(context).sendConfirmSummary(amount.toString()), + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headlineMedium, + ), + _SummaryRow( + label: S.of(context).sendConfirmAmount, + value: S.of(context).sendShares(amount.toString()), + ), + _SummaryRow( + label: S.of(context).sendConfirmRecipient, + value: recipient, + ), + ], + ), + actions: [ + FilledButton( + onPressed: _navigating + ? null + : () { + if (_navigating) { + return; + } + setState(() => _navigating = true); + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => SendProcessPage(recipient: recipient, amount: amount), + ), + ); + }, + child: Text(S.of(context).sendConfirmButton), + ), + ], + ), + ), + ), + ); + } +} + +class _SummaryRow extends StatelessWidget { + final String label; + final String value; + + const _SummaryRow({required this.label, required this.value}); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 16, + children: [ + Flexible( + child: Text( + label, + softWrap: true, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: RealUnitColors.neutral500, + ), + ), + ), + Flexible( + child: Text( + value, + textAlign: TextAlign.end, + softWrap: true, + style: Theme.of(context).textTheme.bodyLarge, + ), + ), + ], + ); + } +} diff --git a/lib/screens/send/send_process_page.dart b/lib/screens/send/send_process_page.dart new file mode 100644 index 000000000..7820036c7 --- /dev/null +++ b/lib/screens/send/send_process_page.dart @@ -0,0 +1,224 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/packages/service/app_store.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_transfer_service.dart'; +import 'package:realunit_wallet/screens/send/cubits/send_process/send_process_cubit.dart'; +import 'package:realunit_wallet/setup/di.dart'; +import 'package:realunit_wallet/styles/colors.dart'; +import 'package:realunit_wallet/widgets/scrollable_actions_layout.dart'; + +/// Final step: prepare → sign (EIP-712 delegation + EIP-7702 authorization) → +/// confirm, then render the txHash success or a typed failure. The cubit drives +/// every outcome as a state — no error-string parsing in the view. +class SendProcessPage extends StatelessWidget { + final String recipient; + final int amount; + + const SendProcessPage({super.key, required this.recipient, required this.amount}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => SendProcessCubit( + transferService: getIt(), + appStore: getIt(), + recipient: recipient, + amount: amount, + )..start(), + child: const SendProcessView(), + ); + } +} + +class SendProcessView extends StatelessWidget { + const SendProcessView({super.key}); + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listenWhen: (previous, current) => + current is SendProcessSuccess || current is SendProcessFailure, + listener: (context, state) async { + if (state is SendProcessSuccess) { + await _showResultSheet( + context, + icon: Icons.check_circle_rounded, + title: S.of(context).sendSuccess, + description: S.of(context).sendSuccessDescription, + ); + } else if (state is SendProcessFailure) { + await _showResultSheet( + context, + icon: Icons.error_rounded, + title: S.of(context).sendFailureTitle, + description: _failureMessage(context, state), + canRetry: state.canRetry, + ); + } + }, + builder: (context, state) { + final canPop = state is SendProcessSuccess; + return PopScope( + canPop: canPop, + child: Scaffold( + appBar: AppBar(title: Text(S.of(context).sendProcessTitle)), + body: SafeArea( + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + spacing: 24, + children: [ + const CupertinoActivityIndicator(radius: 16), + Text( + _progressLabel(context, state), + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyLarge, + ), + ], + ), + ), + ), + ), + ); + }, + ); + } + + String _progressLabel(BuildContext context, SendProcessState state) => switch (state) { + SendProcessInitial() || SendProcessPreparing() => S.of(context).sendPreparing, + SendProcessSigning() => S.of(context).sendSigning, + SendProcessSuccess() => S.of(context).sendSuccess, + SendProcessFailure() => S.of(context).sendFailureTitle, + }; + + String _failureMessage(BuildContext context, SendProcessFailure state) => switch (state.reason) { + SendProcessFailureReason.signatureUnsupported => S.of(context).sendFailureSignatureUnsupported, + SendProcessFailureReason.signatureCancelled => S.of(context).sendFailureSignatureCancelled, + SendProcessFailureReason.gasFundingUnavailable => S.of(context).sendFailureGasUnavailable, + SendProcessFailureReason.invalidRequest => S.of(context).sendFailureInvalidRequest, + SendProcessFailureReason.registrationOrKycRequired => + S.of(context).sendFailureRegistrationOrKycRequired, + SendProcessFailureReason.confirmMismatch => S.of(context).sendFailureConfirmMismatch, + SendProcessFailureReason.generic => S.of(context).sendFailureGeneric, + }; + + /// Shows the terminal result sheet. Returns after the sheet is dismissed. + /// + /// For success and non-retryable failures, Close pops the sheet then this + /// method pops the [SendProcessPage] route (today's behaviour). + /// + /// For retryable failures, Retry dismisses only the sheet and re-invokes + /// [SendProcessCubit.retryConfirm] without popping the page — the stored + /// prepared transfer `id` must stay alive for the same-intent retry. + Future _showResultSheet( + BuildContext context, { + required IconData icon, + required String title, + required String description, + bool canRetry = false, + }) async { + final shouldPopPage = await showModalBottomSheet( + context: context, + isDismissible: false, + builder: (sheetContext) => _SendProcessResultSheet( + icon: icon, + title: title, + description: description, + canRetry: canRetry, + ), + ); + if (!context.mounted) { + return; + } + if (shouldPopPage == false) { + // Retry was chosen — stay on this page and re-confirm the same id. + await context.read().retryConfirm(); + return; + } + // Close (or null) — leave the send-process route. + Navigator.of(context).pop(); + } +} + +/// Terminal result sheet content. Isolates the double-tap lock for Retry so the +/// parent view stays a [StatelessWidget]. +class _SendProcessResultSheet extends StatefulWidget { + final IconData icon; + final String title; + final String description; + final bool canRetry; + + const _SendProcessResultSheet({ + required this.icon, + required this.title, + required this.description, + required this.canRetry, + }); + + @override + State<_SendProcessResultSheet> createState() => _SendProcessResultSheetState(); +} + +class _SendProcessResultSheetState extends State<_SendProcessResultSheet> { + /// Once the user taps Retry, remove the button so a second tap cannot fire + /// another concurrent confirm (mirrors the double-tap-lock discipline used + /// elsewhere in this flow). + bool _retryConsumed = false; + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24), + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.sizeOf(context).height * 0.9, + ), + child: ScrollableActionsLayout( + shrinkWrap: true, + body: Column( + mainAxisSize: MainAxisSize.min, + spacing: 24, + children: [ + Icon(widget.icon, color: RealUnitColors.realUnitBlue, size: 64), + Text(widget.title, style: Theme.of(context).textTheme.headlineMedium), + Text( + widget.description, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: RealUnitColors.neutral500, + ), + ), + ], + ), + actions: [ + if (widget.canRetry && !_retryConsumed) + FilledButton( + onPressed: () { + // Fail-closed double-tap lock: consume before the pop so a + // second tap cannot schedule another concurrent confirm. + if (_retryConsumed) { + return; + } + setState(() { + _retryConsumed = true; + }); + // false → parent must NOT pop the SendProcessPage route. + Navigator.of(context).pop(false); + }, + child: Text(S.of(context).retry), + ), + FilledButton( + // true → parent pops the SendProcessPage route after the sheet. + onPressed: () => Navigator.of(context).pop(true), + child: Text(S.of(context).close), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/send/send_recipient_page.dart b/lib/screens/send/send_recipient_page.dart new file mode 100644 index 000000000..57a00aab6 --- /dev/null +++ b/lib/screens/send/send_recipient_page.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/screens/send/cubits/send_recipient/send_recipient_cubit.dart'; +import 'package:realunit_wallet/screens/send/send_amount_page.dart'; +import 'package:realunit_wallet/styles/colors.dart'; +import 'package:realunit_wallet/widgets/scanner/qr_scanner_view.dart'; +import 'package:realunit_wallet/widgets/scrollable_actions_layout.dart'; + +/// First step of the wallet-to-wallet send flow: pick the recipient by scanning +/// a wallet QR or pasting/typing the address. Reuses the shared +/// [QrScannerView] (same camera wrapper the OCP pay flow uses) so the scanner is +/// not duplicated; the EVM-address decode lives in [SendRecipientCubit]. +class SendRecipientPage extends StatelessWidget { + const SendRecipientPage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => SendRecipientCubit(), + child: const SendRecipientView(), + ); + } +} + +class SendRecipientView extends StatefulWidget { + const SendRecipientView({super.key}); + + @override + State createState() => _SendRecipientViewState(); +} + +class _SendRecipientViewState extends State { + final _controller = TextEditingController(); + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (context, state) { + if (state is SendRecipientValid) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => SendAmountPage(recipient: state.address), + ), + ); + context.read().reset(); + } + if (state is SendRecipientInvalid) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(S.of(context).sendRecipientInvalid), + backgroundColor: RealUnitColors.status.red600, + ), + ); + } + }, + child: Scaffold( + appBar: AppBar(title: Text(S.of(context).sendRecipientTitle)), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) => Column( + children: [ + Expanded( + child: QrScannerView( + onDetect: (raw) => context.read().onCodeDetected(raw), + ), + ), + ConstrainedBox( + constraints: BoxConstraints(maxHeight: constraints.maxHeight), + child: ScrollableActionsLayout( + shrinkWrap: true, + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + body: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + spacing: 12, + children: [ + Text( + S.of(context).sendRecipientManualHint, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: RealUnitColors.neutral500, + ), + ), + TextField( + controller: _controller, + autocorrect: false, + decoration: InputDecoration( + labelText: S.of(context).sendRecipientLabel, + suffixIcon: IconButton( + icon: const Icon(Icons.paste_rounded), + tooltip: S.of(context).sendPaste, + onPressed: () async { + final data = await Clipboard.getData(Clipboard.kTextPlain); + final text = data?.text; + if (text != null) _controller.text = text.trim(); + }, + ), + ), + ), + ], + ), + actions: [ + FilledButton( + onPressed: () => + context.read().submit(_controller.text), + child: Text(S.of(context).next), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/setup/di.dart b/lib/setup/di.dart index a3c54ef27..77f3f36b2 100644 --- a/lib/setup/di.dart +++ b/lib/setup/di.dart @@ -35,6 +35,7 @@ import 'package:realunit_wallet/packages/service/dfx/real_unit_pay_service.dart' import 'package:realunit_wallet/packages/service/dfx/real_unit_pdf_service.dart'; import 'package:realunit_wallet/packages/service/dfx/real_unit_registration_service.dart'; import 'package:realunit_wallet/packages/service/dfx/real_unit_sell_payment_info_service.dart'; +import 'package:realunit_wallet/packages/service/dfx/real_unit_transfer_service.dart'; import 'package:realunit_wallet/packages/service/session_cache.dart'; import 'package:realunit_wallet/packages/service/settings_service.dart'; import 'package:realunit_wallet/packages/service/transaction_history_service.dart'; @@ -213,6 +214,9 @@ void setupServices() { getIt.registerFactory( () => RealUnitSellPaymentInfoService(getIt(), getIt()), ); + getIt.registerFactory( + () => RealUnitTransferService(getIt(), getIt()), + ); getIt.registerFactory(() => SettingsService(getIt())); getIt.registerFactory( () => DebugAuthService(getIt(), getIt()), diff --git a/lib/setup/routing/router_config.dart b/lib/setup/routing/router_config.dart index e7d3559b8..83d3533a3 100644 --- a/lib/setup/routing/router_config.dart +++ b/lib/setup/routing/router_config.dart @@ -1,6 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:go_router/go_router.dart'; import 'package:realunit_wallet/generated/i18n.dart'; +import 'package:realunit_wallet/packages/service/dfx/models/payment/sell/sell_payment_info.dart'; import 'package:realunit_wallet/packages/wallet/wallet.dart'; import 'package:realunit_wallet/screens/buy/buy_page.dart'; import 'package:realunit_wallet/screens/buy/buy_payment_details_page.dart'; @@ -18,9 +19,9 @@ import 'package:realunit_wallet/screens/pin/setup_pin_page.dart'; import 'package:realunit_wallet/screens/pin/verify_pin_page.dart'; import 'package:realunit_wallet/screens/receive/receive_page.dart'; import 'package:realunit_wallet/screens/restore_wallet/restore_wallet_page.dart'; -import 'package:realunit_wallet/packages/service/dfx/models/payment/sell/sell_payment_info.dart'; import 'package:realunit_wallet/screens/sell/sell_page.dart'; import 'package:realunit_wallet/screens/sell_bitbox/sell_bitbox_page.dart'; +import 'package:realunit_wallet/screens/send/send_recipient_page.dart'; import 'package:realunit_wallet/screens/settings/settings_page.dart'; import 'package:realunit_wallet/screens/settings_contact/settings_contact_page.dart'; import 'package:realunit_wallet/screens/settings_currencies/settings_currencies_page.dart'; @@ -181,6 +182,12 @@ final GoRouter routerConfig = GoRouter( builder: (_, _) => const PayScanPage(), ), + GoRoute( + name: AppRoutes.send, + path: '/send', + builder: (_, _) => const SendRecipientPage(), + ), + GoRoute( name: LegalRoutes.disclaimer, path: '/legalDisclaimer', diff --git a/lib/setup/routing/routes/app_routes.dart b/lib/setup/routing/routes/app_routes.dart index e7cd66a96..d0ebf58ff 100644 --- a/lib/setup/routing/routes/app_routes.dart +++ b/lib/setup/routing/routes/app_routes.dart @@ -7,6 +7,7 @@ abstract final class AppRoutes { static const sell = 'sell'; static const sellBitbox = 'sellBitbox'; static const pay = 'pay'; + static const send = 'send'; static const kyc = 'kyc'; static const receive = 'receive'; static const bitboxAddressRecovery = 'bitboxAddressRecovery'; diff --git a/lib/widgets/scanner/qr_scanner_view.dart b/lib/widgets/scanner/qr_scanner_view.dart new file mode 100644 index 000000000..6ef8f793f --- /dev/null +++ b/lib/widgets/scanner/qr_scanner_view.dart @@ -0,0 +1,39 @@ +// @no-integration-test: the QR scanner is camera/MethodChannel-coupled +// (mobile_scanner) and can only be exercised on a real device with a live +// camera. This widget is the shared camera-preview wrapper reused by the OCP +// pay flow (LNURL decode) and the wallet-to-wallet send flow (EVM address +// decode); the per-flow decode logic it feeds is unit-tested in the respective +// cubit tests. +import 'package:flutter/widgets.dart'; +import 'package:mobile_scanner/mobile_scanner.dart'; + +/// Thin wrapper around [MobileScanner] that surfaces the first raw barcode +/// value of each capture via [onDetect]. Keeping the camera/MethodChannel +/// wiring in one widget lets every flow reuse the scanner without duplicating +/// it — each flow decides what the scanned payload means in its own cubit. +class QrScannerView extends StatelessWidget { + /// Invoked with the raw string value of the first detected barcode in a + /// capture. Null raw values are filtered out before this is called. + final ValueChanged onDetect; + + /// Optional error UI builder forwarded to [MobileScanner.errorBuilder]. + /// When null, MobileScanner's own default error handling is used. + final Widget Function(BuildContext, MobileScannerException, Widget?)? errorBuilder; + + const QrScannerView({ + super.key, + required this.onDetect, + this.errorBuilder, + }); + + @override + Widget build(BuildContext context) { + return MobileScanner( + onDetect: (capture) { + final raw = capture.barcodes.firstOrNull?.rawValue; + if (raw != null) onDetect(raw); + }, + errorBuilder: errorBuilder, + ); + } +} diff --git a/test/goldens/screens/dashboard/goldens/macos/dashboard_hidden_amounts.png b/test/goldens/screens/dashboard/goldens/macos/dashboard_hidden_amounts.png index 2e8d0176b0c6c9cf690182f2735900eb992fe38a..6d13463d77ca63401bf9415449e4e409da7d1371 100644 GIT binary patch delta 36721 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 DGJelf delta 35632 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 diff --git a/test/goldens/screens/dashboard/goldens/macos/dashboard_recent_transactions.png b/test/goldens/screens/dashboard/goldens/macos/dashboard_recent_transactions.png index f4b55f0d8cb666e3fadcd345774af03707457be3..d90b7b99bc1c41d7e4fa6d5d887fa92e600f7cbd 100644 GIT binary patch delta 41414 zcmbrmby$?&yDpCU5mZnRBm@CLLK^8t0YQ)u>F(|v`c)}ukcJ@?$)OvDknWD5hY*nN zuHSO+^WE3)y7s=#-sc?t0cPf1YrQM(=YH6Cqv11osu+1#_$vG zUOjQ{mxp&59z39CQh$x-a(82(A?~dPp^gKCp<3P-naBUU@z~*}8ug2l^hi0rF#1Nt z8%ASH_Xa(1@cVP`OwCEJVD!P9XMfFV%*`A(7c|+5r{Wc7R-%3ncdtr#j_XI%)(un@ zMQp+yX71NzU4LkUhB$sSgcuQ<;YXkCcO2zUyC#rhEoz)!$cLxZV!W0$w)ZbO6Ks7O zv$~p>q4ZTy!=DabZfP6BDmt%kGD8?@iY=~rI@Az_+%hwN4_)fGY$6lHKgvJ8nf3Yn ze)4VT4!&$?Blay@0o^U)&qLM*`*!pj%U5Hq4^v2FkTnk4mY+lq@7oCN`X^=GnMm$K zp7)0$BJp&d{7C1SffO`=>AF{yfwHi?PK@z3m~; zA$yf4hanJ_r`tpQcR*!E@8DZSbNJ$yvA47G1>`Pcznt^cwX~kr>`wlolW-Fp>)E6I zUxP0*8hvl5P6YEujnjTGuPtG_$?K6TTBt~FAsHVYfWCLc{#)mNN4zxH8fnIUu#$G4 zIRv?wBhK@(Me}^mw6UGfYHPZS#vzVR@C&%NXN0T6&*DfHi=MDsv@zgwV3RvkV=}c5 z_(yDq(zFk>v0lXUX-UJ$#TPe8j>DZa9!cYvVu$Lveq*_>aV(&)PZvHd_3$(};SHQXI0Yl#GVmT`)RK_*YcAY!?D8vHc8#y6_END$ z{3l+pHuE_Dz;EuZV+_c+XfmOGc}?yX&9kxHI}>{a9oIfnf<%gJrl$!>{WtCegVl!tZ&Sc*lgA*z?sP#5${A%XxZmH%W_|)18XJ?#_X`*zDOINA7*Pg96 zbynWu_R&GVvuxp$y$xd9?2jtj$U6)_b)}G|;-Lo> z@8Aftr9>P}uhL}nQb8 z!jpN=i=z4QokA!_NSF@6#msdz>n{bt)E zP|t@rske7h>7sHpfMnGwiVr1kq+pi1NzpA_aM>daQ(%Hpp-l=IBZXBD2CX2nSN&s& zX3}?^aa_&1d<*z@m5EdT5G_Uyy@{=vq z2gy+BmXIgg3P$nH50{@!LFHZ;0)f?>HD(Eh7p9MDKmRNji%w(-nhf2~o2+~Jkh|YR z8eW9%^zY~nV;8XRBHfu76>3{TrFzm96+HKUNgReUQ0!nDU4XX0xPC?kbG~!Mlq)Y>RS#?2PtB%~gi_ z@FVYi`uwT^qx_ydOI5w_^j6+}M=p+J1p`FqR)87}v}&ti1~869Bo-BjF-F0S!WPn3 ze7ve@p(w?z+g;4;d&_h0kCeL+wxfIqmBG;g|H?PpZE@yROdGXaa)jztAK_C|OF4OEEtITc7hh^6ud8Qbbn94U_?e*{ zX0$&!_o-1FAF4m>$!h5%o&L;?d~YGOb@>&xW9`N8BHTUalMg)buIE%`k!ey@xhO|R z$hXbiae!S+=x9vlfn=F~MfUKTFieyea!A*WzFqOv$z{ylkS|3SI{2Br{FE;$NpJ0- z>g|QSmh&aXQ>%0{Y=GfkTQF}2##Zr-HoNR@Xo+heBHSkqXgD^afb z{E707FYSJK!QHD)+pCh`S5qxv_^a!1$l8pMG_hpqcAv;x+eDfn|Lmu6-yCT;0@0uR zjrB>@5-L7hwe{elWvg4q_0Azqkrz(skoCeFEOeBXs+=Jza!H#o$mvL~mcb8o{&shL ziW9^6!Lj~@UwCjPrGgH_y!l7g*=C{6m+XK&RTs%&FcMaxeCh`pc*Rv#B!iqFmBo?$ z#7c(GXnC#~hQ_VONCDBz;uG0$lp_s8{F>GP>eR)qP!PdKfWYMrN@=Y*wvFMdV%{#_ zd9o;P`uTCjq*!_~1#e1#mv3UZ&>gLQ6ArH?ML3rw5xKl+OCrt};lp6mcsv=ofUxFpi2;2CvaWK}xnLyT zm28-UB`Q?vr2oOxr0u6DK^(M3Y7UtB(wgqnc6L{DiBvNsiA3oxGeU*b(TP{_j&5$! z&l2w*-E=5CG@^GXtOt$Js;SQPYt{z14^mBBC2hPCa#zYR^pi1tbJfT1oYCsOo%+64 zG)C4|cwj(T>-cNRZ1!xSLQtZ=Q1KrUOG2&_Sf*71mcJyYl|q&iZKHwmN9ZoT*+oJn ze^iXN@7A?{N)U>|8{#Cn=~Z% z)Ehdz2xx7@JiwKPTkU{_`##j0ud__wi|CGXO0?!z6J=V7b;)QwlT-tNRV@W3f>nK; z$tJd$M^_Tfhf4i-N@fr-0k-ol%!cnE4sC9UFBk+lZT*X>d`+PVz?8U-eD;^O08&;> zg)bf`Y3ow|9vQUjx`KvDl>BZQ7aC&u*$|?XF)=0uB4b4_<K*^+r^E4y2yRKVf+^OZ0SsEz)=$gdPpE*rY|b>4;Pdtka%J| zK#;Nr8^J&O?h<+|D)7%Zfjy2f!RY zzT`-(fz2r%>*YbB4!sqKTKoQ>IG?p|LD7!Os( zug*xyaP5<8kg$Q_Pk4(Y%X6OWcPDYWeC(g9Wf&EIHP%!CX*2cr7>;~E=u70fBuZ7` zO5DBNOz-|q_w2}h)CJBI{mp8=g(WCIsPRj7SzPW_^5{meUieB5U;IiIYImjJakcrF z>~zd8`ssdx_@sfTpXVvbJ*k$YtLvpb72{h&bJo%?8};37?$&H*2$d27ghMv)OMD+n z`kuBy*#0va5_1^JW#C9WTXrJ_Oww;r@O2sp(7)_(ow)dkMb*eJQa5{>#k(%eoTxV_ zY;a%4v*Y&H52jS6ZD$P{8!U9jIX^s-$Sjpdc<$7_821;d_22!N*3K*(*L`J^AG_6m zpfS@pubxuZF`Z%6@IynzA+_d!TEQ9H!1>}i(K8t+VDUZ*V`a0lA0=AtPf7XkYr9U- z_d)ZOdb#^vx|cIw9<$tcMmdR;MNa?lK3!54_%|?;lnz~`lwDco&bIqHkWsj#o%nl( zdOC(GxdZlm3UwEko&`Wa(y2h1xiOQNPzrk@OTKVozmaIiE&JoM7v*_}g2dGIz~Ju-z=(>lS6JUDC2yslP<@L(qkA6CtBOF}{X zveTVoY>Z2@gE6w(h~NuwQx==cvV~0&$_ewuc{wO>4 zXgC6%)+voBjIYh$aVUI*LH)Oo=>$_QF7$FX11mb?Wf{!-hemlWSuGjlOSIKGoJ!M#AwZ!3SiN= zhMH~2vB6EIwQ5YFuA4IQHAHuUvt;dYi>-+NA@8qMg9tt~x`~z`lwFYi-kk#T#f|34 z!`ss-==YS?;>Cdk*toP{n|FL7(X)L}#)IWmal(P#YV56aO8hg*I2@t}*fb3|c)S;^ z2V-;ZCC>N`EF15&z0$(0cBB(d6J9L~+Jh}j>KYKn^wx?TR@Xhm;xe>TM8-2gY6TJ8&DZeU5o2~O4K})jL_2T!2_nb%e zK3sGYx8&YWku*U-+WWyVHtu>m$lfdL@RC)rM;i)SYLG|zRz*azin-xb);}1J^!gXJ zLDZ zqVA%C4~C6$&Bqu6$`8#XreBXTo|d>3{wN{${eYN{P+GHwMB=;HItr%ere)52Qgt5> z%3@=u7g`TjrPVC|6c#c*ImmS$7I>!ro*JBlE(VUFSL*z4 z=FalPwolI?Jn>cXCjCm~+QQ@N{&Aze37^j(7Yvf?deM#27@|>h)vJGzr+15=qu=CV zG87Yf`^8oAx&3Y~YuyY=lV6W%o0j5=KfD9tZ>qY8{&4F~l2)S~`IlJ$*lVxMMDH{T zT5HkdN_dZwSA;paOcYG}h{Zqm5lMBOg>h;IqX?kXVa`yy0N(7QY~(BA69sP{1sp)P z-y#~OEHaCX9bHhCp%nJxxr(>YkL(oJUAm8veP#u)SPF&8xmnNpEzfO_hXhETyrT_7 z@WG}Iv3srOOCv$d@2H%TCj;1b@Rp5)m!1wcM^Ld8hMTSpO+?dXe*0C_(Q_gd(>qaf zV+d6mmX-uux=(QS6Y?j)d&u=De>mk}IR&(`7rtk2?8u&$ERi_pW13TQxU`)gI$W^N-VojDNOe_DLrcVbuguAZbM~b;iELnt5*J;S9A>W(lril`0+O%$UTU zTZcYudU1bjR!t)uC=ys~tAp!}$ixX2$nOSOQ{r+fW8`V&&RZ1+tcj18-U2DM1*gI( z`6@rhTRfic206wv>28zLFBzfc1f7CaQd7ES%!LVGgyEl_*#^}fn`lA0QTJnI zRF<3Pp1KGAR8^Dn4(rplxzRdU}d2gxK)_t{@S6$apc*aKf zM%Y2cgbUtw3;D+O$JInadXQsr{E)*S_nqCdFEyQ}j`sIaoLioPl;WRNq^>ng@xrrf z1NRNntg50Ys^Pu}`M#=?igdjgUZ9g6$VLnbFhFlFwaY`d?62f(RodLUhMoXo0hmxo z1kE&s-s9weyKY%hFC${p`I@A_iVpFL^0SFepTn>NwY=9ki7Ry12(DxM;M}7 z=f9(Nccz;+j1yA33=OOure}Ctw2CjhcuE-DogX-u^$(KB>veTlmza77voYZ6^wTzX zB&wYUM8mcGnygEN^@iM={?mHj6Z`uMO4xma%r7aBE_}&iMFvc|+FF)FAVsUU%RKXJN13Wm-~wnh&dHLos*Wk(~MQ#Gpt- z07$cepbM}7KEPZ)>*vPOMB%|Yw(v0=hz7<_NQyiQLIa+d5vP#C>a&JpZ(9+Z;Db*9 z5slH>uY^V9tvg^~s9mV`&u4G*nXS~_5FCr!}!tBC(4L3_5>FbB#V z?k%K5aqT%2w#Pv>1-G@`caFbWT5Aa6n=$>HBuw5i=fK)y5Y!}Iw9xUmZ*8)AJG-Y^ zaRz^_?W84XA-_Uzg0>&uOezW}zAqNF<64rwK{5%OkJPiD>;56s zG=ur+h5Sp$c4ZEv55VtPD%{<#b#mdrwh~{-S2>v5dW>W3dZlDG3bgBg7rb3VJ>KJB ztd<3zT0;1uB<%8(Lj$~RiF&%5Btt=tIDAobjXP!ZwW1Jir*E|M%$-U(`M!RW^KSb(JW4tlLY)(DC|X;b%aVH-v0d+%EfD;#J8JNGDDVnY9PH}?oVKX_@P54cpA}2~o0%Bdf^j?q(?-J(7=j#@}0o_OiLq$sSqQ+LA-r@(z`mvBBZ+_4CMw zUbrFI7#Pnnq$S=ci``5^b_N~CTlwhXL{=T-ILraup=$Z@#9B?6St6&EssDg~KtQSM zm1J{s^ZI2q+YLJ8^y1=R=74Sdtic~|ai_UZPP%HwgREmwzSL&bcE#t6dwZKgyW0Nq zj*O1nXzbx3)1_T|_A^0`{QeUbKRk%uRSu`Hu+Y!ne<({%OA-=c5AJ9W!A3aE6re*#PWa4zh&(F_(ZJ0LTQA08u zZoN&xl~AZ-XE;6HXN_(kEKuz!$&l)L*Qn`$#SK~h^GPzJtW+f!>}ccx0Y%1<^8|2oqD& z!73P=qhsm)hYvH;i$8%G>U;eCiP$}4*#==tH(`&(Qvx~jjMoA%^Ny$&f$m<51URHZ ztx#p3rxNq#j12=5r!F}S(&-;)WX)D|__DjZYmAz^+7z@;7-&2Vwh@F8JSBV!+Q7o4 zU{>2sSm`5_)2H3ZY=xnHjF<)*?gaANLw{bPeMASsqrgZzN~{d+VJFXNxMKcf$vMDI zF!Y!AP2hZw&8AHcvcfQOlo^S7(>lJK9k7$&d=RR=pAIGhqqUu4b=-&d;9!_Gq0iLI z+Z&-1_vsVH@#b8gLJ0BRGO^R%(b1iG8BNU^o4`4zhytg&n-tjX$Zy}iv>B2^zg$iS zsW33-gzpinsf}84pUepM9#ep=Eoq9%`q^-GUZOt~fsr6fr|WgCvEm~Vn4Jc14Up4f zCk?Cg`2@Cz`t}j+$(_^l^V01BIiZ8Y=)X%-f*VH1#T8T3c=dT(*h9PQ8FXEf>JSl1 zOG|qMe=lQmDqm}O9C@NNA)h4=i?_HXve4&zNKQLwG?umVBXy`EA+=ba&6 zY+}~In`5n)%*^@w)pXH8L%5+dw6y;5dhTJr%W>pWN+Xe;{_LkPyF<%=PP??rJ zjg@IDsG9A!L!j{S@GxGBHS=S@Q&MidlNHM5vo$rP$6cB%!f~9+}!vAamlH{ zWW!*%q`Z%?(p5HiFg7O|QaU<>gR|p_qv?d-M*lHCKZdpd`jENp2i-3>ANtl7LZpql z@G$RH{|g`1)3ve3Hk!k^PQ-C+7e}?n41RbI{dMqtWioF+r>CDDU)kQ?euF&TN;v#I zg7efP!e;t-OR(6oPgB?P>}I0c;m*ih+Jo04rhQ+(e#Lms&W!% zq_cHwNr;=q*L<_=G3RnT8>X$R zs|QC@SqK<0-pR>jJ{L_JEN0hIQr7VI_lJJc&@Zj728JN^1Il7zp48RV4Q?E{sIL6} zZS3XcHMr*NQmH`Ym!t5I@o&h=fjG7*|F`jRu~0cfmZRpQmdcFK z6N0JxRIOrQIbcm(^xZp>BjtZkTd~+IGzp6DIWihB+dYOeM_H}9` zXY7v)DRK+V%3URsDOOZ~(xL(Fv&j{WO%+IYu?96sXGc-xirq7iVuWHV3M&Ey6mS>` z|CApiTOWg0Ge@RYY3lk3d=HtxlrK$W^cNfmTQ2+D1vG@jbF{{#L8`{=%{{7T9TYCM zDY)dNt6{`M*uXDPEW{J$jurh?p$F@EJ34k~%a>%PnV6C|MYf;b?{vCYDg^;JLe5Ac zuaI`ix+9Ed@-0N?N^80|86L9P7~?DXkisKui@)rtfPeLY^2rCF{m&Q1Ke7kwBkrne zkg{F$h8`39gE^NIBTL!lmOe+?q$A_O72hT@e8~k8sfW zVgZzyXjPq6HN1}S4i2bEYN60QL{$wSE*Q|OUjzB>h`9X{v3I< z8IkeYtgPzAeIGJUIN#Rd<7Ze(jF0aaJ-P9f&tC0A>E)J(Dxq(1-QC^mPA|*cJUkdv zFODxLLDcF>2$Uok$=3**L*iw!Xq;nT;69t*{(R@p&=p;q)5guruX9n}7*Dx))|47E zs8j!?uCM;=x#(n#6)*kb(%$j$%CVN+@`_0P?*4x5%AP_Re@{|{)nuIs+0c+2iPy>N zTGzGwiFU)1VzV8Q@#~WCjE?Y;5^+xo5(iNOgYRP=5gHmAo0G_hEkReuO*iIS*VpBC z=(x{!wy9sT<9$*PO_p|af7_evg#2t@GA;LY3+P4;IvUAZN;IO1Rfv z%lWJp{@yj}F>ZaFlpwF6MDpv)neso}F7eM*i!^#(L|rn^As(;Rmz6f?Tffwt%sA%@1qlcAYQD*D9H(GZ$GBw;bMRPA2EgIk z$2)WK8Is*>>wjKF6kiWK?7g`rvt2`-B<-kuEq#EE?Y`Alb!sqo8zZaXf+!-CJY!;| z0G7pZaZt0YD|q}J+PxlyVYux>YhBD&%P7a`Y9w-RbqbBhuTw}7klXR8iuJs_uxt2z zJZ)QXbv^Pe&D~?aEZFAq>PmfY<0Ex*3&JbSfnJ{7{13aMqhpUSFCd8Sd+r-P7jsi- z1bch=Iu&1dY&4lFJuidkH5wLq+t5xbE}cKlMD3l@bXDRI`L*&m4{f1)icwu+GKHT_ zN-RRp7~&EI*$mkeEhsnpL*K|H3UdX#qcnq8&>|W>m#NDAjNmK_Y-cz6pfttpnB8f- zGO|TXf~1K<%WG3{qdqk$R~?KUZ;wTjYw?!a&%r_|PhMhBdL6#XmQP`=+Tv6Y?K);5 zdm?IUTNrGEk54dJ=^Y5MXfR(pNTRHQlX38NG3)dP$)*_%(IH>b15 zoM(yI*3&}go$A|UK5SNM1)dsN!Ap5@+}>Z zW=)Q_LpDXy!tR=HF1JpEhk;X%D~jr-Usgi=0;LRvpTq^XXVoc^+p8|voOm)m#jB=e zNmbJmApHj!e-T2kdQ3U;!{10`&Ev-u@)B-*^`1w@ZQbZ<=QWNJ2Zv?WyLWEi{u=r< z#}N{}DaHA;nkCe-s^`_V&TS2Fp z66Wi`<1KRv0;L6>6hW7tPTAp==!}fy!6I}Y{A+|%(FmlX8E|;$%ws$E1&Jh6kac@j zpxUx)aKi3%{4AV2!sO<&uk-rEXL6)#baZr&&ecx3g58G3>A`^f3c{{o3AwYn*WWnz z5(U>hSWT?n z{0GRvkUea8S0DKrXd!T*O5d{|QL)NWt&n;9DLEYc|5p~FuqOVBj1FK~<#4}T<-0~+ z-IlJw3|*(5Nd zi>Z&CEY2HKgr7Lg`ZcSNZjV37XMT$SKLz(082D?3jR4BwegYZQ<#d4*`gtMAN2iBg z#r#p=Eb&>59R}?#rU{E#ko^vqS3=@SWT=8-fj_9yvzKR5rr9?BDm<9i6p1$UJa<25#n83=9vvO(Ksm?pR-Fv4DlCp?ZNNl#6~-@aT;*CR?PTY-RxsJ$yX zTfO2O)(_Fru&+dQOw@a0&$Y!?v>8${2`0}7S>5$urTVOcgpJgDRyk%|%KTngzLn0v z$(CH{rfQZkbT5<9#3d2w>(VxzC`(wBp|CQ5={awXYBLnh|V`$_Nf3Rr@ucf*yCO^=9vu9hFOwvowX_A97eAXz;uJ zVTZi!w(q=lTWYtSt)>W;niUq>c4#Ic+3PE3!vl`;7Zk&2X44X=seE%tS6m z7ytT&vEf?d9=yfD!GUq_!GnDJn}!Avpo>I-hh;lxJpOc9^RynFr2f4fG zq-5R58$*Y{9O{TnGHbOi0jhhw=|_xCh>`1+VUwZ<)PO$cgo%2Kh`vU%J!0QQwlE9D zXd6&|(amU|*6^J)1jau5*=1XN!mgaC7YWDLje932Mtc$OC#o%+!cGo4YDIxE&^9|8 zR%tUm;UmLpK+JmmIHP2dGr8OEpFoTlWFWPuXc~)F)h#?y(vM4}4-~a@b#*Htx}5Uz z@&{EQM_vf(=(tsUn%yn696KWlHjezBTi2w!G^g^14|q>-ZCDK0N%&lah(678h}XQ| z7;F&6KtOs*1t)4`VP9^q%_6e5vegDCTm@C-xW2Z2_)%Lke4C_?2zWmwL++N{l_lE9 z$tqhrtE<^&MpM3Pwq{6srrZVz=FI169vtq@r&Q&eohhY4B{P#<6)~5$>dyDy=bzcs zY?-yxqVGSlm9VrV)vB^V42is#^te17`N+Y^d5fJ?j#>Ni#ghmEJ5q9;4DSbuI%j8R z+Kk)JSs%j|{R(CqR1m5daDLCi8LRj5+M1Q-2A<6H^zk~#lMm|Vi=yxdlJS@XTnLTLkr)Z0_(>Q+H?GEzL-?-1A+lb)`$8B}?-zg8%CafkgX`li%# zK)1~a(|3W*dwKSG(l^VDpSrN2Joj=Zc14kXuZ9b1hukx;%bEESdd11V*pI?}#IbM} zFDO^aY%U&&z^|oikl&yiUMrr)0?h)q%|N?GYEGt}(LjIqIrFQh<63L2M7 z_|uuNAkTn^50`WK8cHH=5FNCn$Yq`Rjk$Yzk-O~|(HIIx$PgL{qSkL_P ztJpJO>iA?PgssH-Oor~}fI-(|fnS-C5S+cGWi_<^=~qRZXZFO&Rn~Xsm*aJdkL3IU z0@&RKU&j@lhTZZF7|u1q^3gQ3sk}PTvWsJaS!76s{r=VDd&zEhOMJUNqCZ6dpO8>m z#_<6eubn{`w0QS$b7)rnalYO|k-zTeaJp5)($^P~w&p#fhoIEN`hEKl=j6vP^Odm* zvtsr6kMfE&{cE)!hacrIs+8m@Gydh5SGLIL$+Q8J(|U1>ku2`K#f|j$@vQNJz8uLc z`I=XP7*%t_5x+ita_k`GIJM1!_8HzoXR$YRqAJi(NrMy0wyl!ZOzBD0bfF$HiTC=5 zN2}7dEX99zw16o>NGRaDFV>hGdn;Pg#Ab1BZVn7M#bz2d(U85pZ5!%&_&C*Taea5d z;AF04I1MtmZbp!o1Ge#dEt}3^mAq=2;^3&{(fDATCdcW*E8M8q*uK!4K4Wlp?>(}G zT7L#baF$dv$%ccaD+>M zfQFI874#A3`H-rSL+Nmd&&Mt4apo*fWA+AdaSrpf+^M}qgk#NUdYw5z+9k(au-_Q8V(@7^P!FI82rFiA#! zF&%}VA^jo}=GUoBt)P(H#hJjso&?O{Qfc>=TQz}?tf)XZL^p#PG3cjxH&q_E=tpxj zd0Vf@%`1`|2|VvF-1De!n9e=QYX+PhQGDXBFVCe<5T+{wJW#FvZhiYDMCKtSK@~7GLaZV;*J-N!|}e z7jgIlLfwANtl6jjh&d0l1Ip~-v$b~*vIJ7!s@$WxUI!(R99zOdOzk7mnG~+-4Q_L8 zMlMT2;4;NAhgL$6(U@u}N&HCGs3<$-P1G0e46atXtewW2)M$Ez@yYAqkrAd8k90=5 z=q{f78Ww2iMi?YCr5hz1VshOKn;pg+lUYBC-gSAseW++^YU&T&0l?W^s^$Ky02FLq z!-Op>f)Bki3=QkL-2|*O_(}=MI$ZRRCR`7LiVGo&O;AeGy?MzKzVWNwfn0rG-%2Q@ z$Ti~$Y2VLPGDU>)X^pB6(`7{{8SkT58Go6~zhLFC)E*TRqr7nyo4{%I$iZQ}M$dI; z&L(*^v%G`CGxRB^sj?L`k|npKcT+3iv~+!S(ba=Ib|#My+P?>zb2qE$SnW*|_oC%| z3)xPVW_BCtMU-AN#N~A}toO$aH&d|$JgrI057?%lob-@oHLYT-m4>EAw`#JNk zfZXmjuJ9q=n>TOV+}($&9gIY-Cu+aVR6`C+2_oLLh8H8*3QXG-rwf6UPfQeZmFsS< zFDgP`&syLG8CZ8bpK-Qyr7#KkW=2~wf}#&BN@sWX zVjHoZac@F!c4Gg)fSQ+HA7tv3iSEklR#F9;NSSHhkK*FU=4LVZv<87DT?vVQ1Yz4W z3=Bc1r*13#DLs`kD-1}-$FQICi}}98!7YDitRG4VDu*|dF~9uu6pA;zTr;h>;`5Q|kRoC*Mgw{S9DLD= z`$k+^6Ts7UzcF>*w=d{7Suc+DcF$EokAkS8l4e01pjI5VGkhxpX?C7MtgPX0d@&c+ z*1B|HvzezHMOsziwzIY0Kj!`!%D_tIvB^8ytFoDH_q$KvwvuSJdwBR0wljBmzA@%M zW;T%8XO`xT`Rflflrag$FYIynTtI--_xs|cNj$4g_orKTNJM?=7KLLgY-a@oVB50R z*39*uCo7j{2NF_J(~Nt=9fgc^ckkW>(o!m0QpRTe25v}bS zz?fb+{(dJWCiVd_CJVxhKXw74lw8OS&(6-yw*E*JqdlCwXL}aO4_$lJT;=N^;~Km# zzQx4&wHX?8$1v1gpKoxpDrhh;F#Lmsm4VtA+BgahIs}6ep?>k=*6ztkM^8`4;o;$f z6HW*;0jPEYzf;y$UWQ_ZB$vhT&*m+r3{}LCWkqFL0DGXuRXheri}$p5*xOyp7K21Spv(%We3f{AHOJ+cO`!ItT#K&M@ChZNIU>%VHSoiI4U{V z-#@){+W4|^alEzkJ&pX81Ky0_ofI|&6FU5gC?z4puz9r%$nt2 z)1d3bqQ>haHC=aqkZsRzZRspD@Y>FF!ug%R?9lbZu`G2)y^u}h$n18+#KIyVCho{l z%s6elxe)z(ICGV<-8KrU3N;=n>H@$52c&4XrnOZfDOU4{nUOIVz>5)2at(kX7Ol#1 z;nmGeS@7c@1Q~QjQUPoY2N=oezHfZIgB}1~T56}3vg$ST*0~?J9Z#BRGWu^FgXFfy zUz1~aRzvTcOB8vK2D1>nH>)f$bd97n5+d?Btyvq?&Ol8Vmqf-{>1$Y%qPG+l4P75w z5(469XjrAMiB-Gs9hF|wPd0Db5AO;lWSS^8xTAw~HR;0N7@aIBDS`7je0gQmo<3ww zBJ9DH=5x*f;K$9=b2^KxzrR1r4&6|<-_4}X9Ji6AyaCp>t)<1cv-2IDJRIumil(=i zD$g|}!^OpYnM#00_HqQ$8X6h#dmO$5(z4g#YGr3Bs*)-I75qx_l1ArR+z6qU~s|l1&jJ|Jjf8j~_q&nb=z=?I|oAclX~v zH~@lITc2FbVI~sknMq#7R=0b*@#>iD@BIb^1yy?MMEnhmiHXvnIH!~?zkIfuM$f}D ze$2~f)Q+>#mn;R!!v&4$)D8{~hwEdAZleNCs3I6>&yzlE$$l>zf>~wFh$D(b;c|*=kb}q!W}Ss;78^xvAXA-QMczhKJM{wt<<*h3UUR}#9ux; z1Mu}q+{&K^)y}Inva&c978YRrIPKwm)GM@yc~mT{T}_abdWIdvCV|cluh?r2EO`<>f@V z1%da)vHxQD!P-bR5ZK%Sj)w*YVnqlatJhp{S&fGlXhBsRK9{F55!~SHy~R2P9}HqG zdn8k&TYtLW!)DVMFOPqe*#vwq0$~pzUUJ{0i=m7XdTiY6-M)sNXY$<(iLu${1i#yn zRZ{rtYko?50)_r`oW!LM+_xgifcKCVIrud&I&M$r3&|KO9;8O@QBXUDeM?0O%<|a~XZH(}=E2DJyeIsiOp@jMX%#f3YpZVz#!Tpw1TycV#I;v@&H` zE}18+yqxPV7XSu^JncU}B~)H9K)G)33!mk^01!n$FF)bK_VlxItz&Ku+{ee~aCY87Y`~fX20HxfQliXtMdm0;sD^? z0eqgWBZ>+UdcUTplUMr4#*?-m{)qU_OgR5jgI2$xl) ze>Chqe1P${mnxMKFWp}k@}_JrHCo^F9X3Q1Zr-IdD}H~^xj8pHTFk;`s@!1sr*tLH z_BYPI9Cd=R{u?Cv?Hl3~kIhtMe0;X~`AZP0LS46}_COqW*q-4B3ezbFAe|t!0=%t) z(MnxCVKJh#9DwZ7_IAJb$*dGYwZMEVkI)zM;ST_b%vzPbmEiZ5+r?HDD6lW4z~(+L z28;6ZCytJeJ~+`)kuA*p;f?mW*!p{FPN7q`;o;!~ZYv^iZmNEKEXsic&@&qY=D)oT zN?@qK!Sp?(nrMoo5>Mc@qX2QP#hi-+z{zx%Y?bY7XIGcOa(C>9ne4G#C3-ROo}$LN zB0bSc+gZzv)>OdF0X1<9>v7s!e5-X6Gh}WI;+Q%!QK;k#KrhWswr8Ok&@bo)%z!c3 z5iy8rVCy^;@pALOAm=p20TNZ+ZZmdeRxw}+GB=NGX&!RCiupi@P99_&jD^`o81@Ul za=zzE&)@N@&Q}`e{F>djL9`eA37+&eM|Gx5iYM>w2>vi9{~b_9P9SDs*KkJ1q*iF$ zm&6V5rOXJBg&IUR z5H$c2{H5vYWu{?U*OVN!_CnqeS)aa!#+0r3kYEg-{BvRwN($oWK=FplmC<8K44D|Gf7opVnlfG{r$E>#^DL|DsK{iWVL zP>6mQMZ!$PPJ&2nl>sh-qcSxet9ac-F<%-?D!*c0&#?+uNE!#AD0Ph`FgYL@I1=)> z3t%=AHrf_ZO)p=>_~AAG?btscseoOL06c6=g#DY4bTe)UbhguJT@tBh-uUxVeReC* zSlYUpQl)@ps1Xayh0o_Jh3YMzkkUzOs;cyN0VNNpT3y%^dw&y=unW%1PE(E8YB3Y- zKfryrk7hlAS^oDD{mJ+6);6%ybNks=DY8`37Q*lEsz+Bg(Ds{Nwn-RKU! z7h<{Va^k=EVhQN~#Z2-aF3SH)6D9DaK>wRBy!dadEdTet4`SHNxkvttVH%r)#?85+ zJZML@P4LAoz0J2uM%Y$Z=zl6=+_Brx%|NBd)ydjNQp$VGa#w{3#=buIvr8JkHOXtH zeCjq(Xj5%B$ih9BCTuO;zxCz>EXa~{R-CK?a`)8)mf?eIodMhdAQVVa|KhKYiZ;LXY{ePUjbzD_ZyYIUM zMI;rGZjn;Dq(efwJ0(OK>7Im?bT^1}cek{Zv~+iO!yUZu-skRp?z{Kd=l-?e18c4^ z#~jay@9+0KE7>9Nts!_vDe{I#Pg2DBii^BK&E}yGc^mbKbc>IUJ ztVM2aYZCQBU$yx=!K?dnhm;|?SLQJHs!THsL1#Ep(vOsB?8d_`a^h5M{4a!F;O8LH zkkNdsCUr&pxS`|>?KgfP5fEocd;HOpJhANdy0qt4*(PDqdJ@l0GS_KD^7%l6Ax1>p z*|qhGHEv{QQ-59IQx1ns8kmT8IFmG5kaIr00;Kg_J>75y9a`94?&)qVhLt!?ya-8Y zddwf#X~(2h`Z06(1bTxt0mCZ{Azpu%W?GMZ>C#0yqnUIG9VF}0X$9!sj(c-Ue*86- ziQ-mQnI-ZHf>rJEglJnXn6%UG%>{PMPXuZwYb>eEA$#!nN!zR#P=#VBMSZni*i~@> zpBiqSEMvVX{&>5JV8Ofky41DJBHzgN^%33l8G+GBi90MOLP4$0o^rxsu5)g{cP-d- zq*m%BtVjf!S?Uf5?0#iUr`Z%@sz%9KX(8y%Io7vpc#2}9piJ_dn;0`cg!IatKw@I| zr$l0wE0Z3q^zd21v@^`3^|l_yU~OgDL~u*N6d#n5tbVY%!G39df?pG5_fzi)F+tQB zieoL0b!`y6XmigW&so@e=PeA%0pG*3A4CnfGd{PF=%6*}j&sVD5euGERF2;C+%gL8 zpI}n>I=F?UopukEDdx*Sha9ctCv5widUTNy37vCoMmY-QOsS3cG~zB?_IrHKTD<@I znN7L{wZhjOwZ*>Pn9nC9vM{tMVD|2wK3A=<#&2z$k{_RB95Kom`XcsGF}`<)+bZH4 z`K#AYp=QBBY1;QzCj?!Cnh5;qLm19);5-;#KON3+ryVz&GonkJW$w!@;3si(r&4aU z0)3paXPB#4N?>o#Hj1A2JK8iH_IG*`?ngy+hk)@@kHTQMrDx-^rYc& zN3i!)yCvn7j~4nm-j3!f;l`FpJJ!|9mU@NOLVFFv)uN^}m$7ZNL9bz}ZrnK$O2dMI%jDvSvC> z(Pkp^9PLTIUZWG1b$YFI(Jm~-BYM&FI@T|$)tp9jclsPndxeKbR+&=A&N-~=dp!Pw z&?$3Y-aqedyf975hgn|2uqi7!E%)lJW8yI<)h+{$Br{moX!n~gnclMR z8D2L3O=rH*oeJL#UHy~aILM>kQ-{kXF0(wEXaMb<2sa;je-p9{Cl=NkD3di&y?b}gy{ zw$Y7iqjf>KdEgZYm3Zv3aC(tOUrCDI()kdyGuTXE!1|N>qcFN4=HW*xlcSL%%zZ`w z?6$H(^2Rw2>1=i=NCdG!E`V!@QL_%ibLfju@x(F7Sz2vSVX)VE0O0(lQYcO-B-z;_?&eq{hD8FAA4Yqbz}q=Uz+O%K>0Y*-vAx{l}PZ6BUDv z=HYoHO5kEdMA#@9`5bAV?9L&0&$V2cKkeU?;Y+6s7tg$iKGy^90e4XALLbA7eNS3) zgFEdE&=rfUrTp&-K_%6&Wu6d#l5W{9VdDUX&wLK(5J*rFD0|b zg9Cz@%@ul9nNSd3TRwKks&Y!CFk)g>J+*a6lkWd5nbh>$gW}Xwy?}~5K87hgF6vjs zd~7-t*82w^1+@F8aOBkADXOwUX82mo>S)-6_l7XL*NkW7-Gh&LZ4~Cf8^AJQr?< z&P@qdd@vK`!jIQ6s6^-I)a9D-iYC&%o4B7<9_A~)j~J07M|)PX{6Qig<=>=NRDYnW zo=K54t-wL?C9p%!xMrrpL^8t+T}{+3ri9)o1$z(Ejr6cJgNRsR!evsM+hV~90$pe& zJEalbZ<$={Bw|vD5HI=p)YayL9M3;{lFUnhKwOzcBWD)0)2{7v`a#q)_E!^2Ap z94QWX;n($ijsjjtO85~Gz{_)0lE>cQ1@YbUf4(0JLTr7w4jl6L+sEvIordx&OvcWA z$wuprQsDooB!f1e`rrm<;aGDvZYs!Q*6W6SY!RUoO$#%8=`Ic4dB^pU$xBh3pCURf ztd{{#Ab%u%X!9M~Ar0DhC5FGdTR#ZRH zpcCfQkRZp{F@M#;^c-B=aV}h3%Em+ve+x5Lx1>7y^(&+&53Og(z<736C7l0+im6pM zJ&g2aHuUT@#hI>@po4OetOVjUJYqs*!>cYX~+m^Di6OerBkiV2o}rH-Mj)l z|Kl&!caG{mrkK3Q;Igu=m>o$P8(ANOGvBb|k%|>kl1j-W40sIaLPC}dm%hCj7%TG6 z?5(K>?3t2wggkgbOXhz(Tn`(?XZKGQJC4(j;TN$#JHmv6o_o*vD_fE)nJ#?FLyH&k z%9jtxPA8bOsqh-wJW1lU@tv;TUszZ1L^$0Bpjfz>|3D{;D#>4>u-tP9v6PyVD5qjA zTEJomKe(OTGyswrTU^a)ky3mALaXgpaf#U_l8IvRK)yFZ+{jA|j5x+?qZ3ZT;$}8~ z6Qz8thSKZ@Ut00WcBptXibr}n{HL__7kc3NV6tV(D-^Kf)#4UYaLPp*rRmCrwsy}7 zp@xT!ZXlDAe;O^+c&AzuH|uhiFjApTYl%+W({PFW5t7f%QO^|jobCw{mJpQ9jo6$_ z$A25^bI{D^E=rn&_ylyn?AB+j%7K<nwbfPY*3;C~e79!_)a}5uv$C9)tabOs|9Y1jLT6f(v#Z=8Pf(P-x(Xiw$AkE>85|!86j(ri5Iks>-DD^qwWx!*I?oRcW3mRzSax zkCWn~?CeF-7%Cs`==IVjLfk7+$05dZQy4-RP0ctnvSJ{Q7KtYFftNj z)?!#HId$AHKwSR%+5W_P?h83Dt7X2=FA`oQKL^|=Wv;3(#-1VlG=ZXNZn#}2b~)`o z4e3a1%;dt;SDHL)Y2T-KiRT<}wmZe-(#fK!rKMVPLEjmC&pC1ha8ciUTU#-vt|FYk zCdJZP^G&T)xKbTJD8#`;hD~gE!5|7_fWyX$<6V-o-8KVB4RKS`YIy+{$UxbLl1!Rn zdRD>n8p+jn$%7P&%)%Xd$IS`2+{s3qoACGseuX{OZT;2p%~zX@q$PDIeXg1&Qch=r zF4_1@Z$xritJWDcEiD#tZ^Ca%Kmh9;jfnHtfL8sN$+9QOoE3xCK^?*Y$i4=Zo`P14 z-OM8Cxw}mEmkuxT)zUe2+z)Xhp}1ey>3mBEcRcU8xNM+hk;gD^gvZNUCi3FN>W$-h z4S^DBFzF;tDP3LK6K5bX?)5rAkG+NmPPf@rORP$j>Y7tieRNgpE#T96Z~nSA0y72{ z{j*QU^V2oLZ`u( z&I=xQj*zpXYE(}SJqZbkL~99jF91w9Lg^x?g^$}-OBk@;X$EVu>e-yS#N@ICTS>iytf zgVzcE`Wzp>%hG0jH7_}`3$66)End_0I7{08Y;`KD>4e%UKF=0U2;rRNp_95!ja!$r zqn?E0$Xa3TR$I9wcmj&%LmFhSl&5hAOd}{UXTcdH)oAeBN#@d=+w~^#OzZ$5*K;g) z$j6xZGcfu8Z8A((a@!9kX!Umh+nc>k5c>Xsq51=Tn=B@y4E540xr1;5ayDx_YG?%S zqw*UL4=Gw@8`W9!RPs&kxQ!;*GEG69xoKXkr?{IqBfo? z@I*%kkJYS2IiG=_b$U%8)AAGifjAW7Bj7Flj@gAk(Kh=$+MhxUt~@S6O}g~y}B(N#-@uC)sx4N z4;a14IS`r?BN?Jz1)V2e!`*Fu6s-pk35Bn> zU&zA3j|}luY$}DUAdbP=dWT0jI?X8@{T&@oAU-~_^q)2N3y)s8y4lQrmNa{pvCVk& zdw1I5z3U1Ua3Gy*j7Qhnt=ZiD5~lO>QON%s%sp{lAiLxxkl40zlW{7i?0VLfB{^bK zru;j1*Gs;$hg#(_Lq1oH3i0tE1V<_n`x4)XY5HzG)wI2%i_vs|9@cHZB+__pk+r(C zB)K*<^0}|<c{FWcq?j+ak9|tSjuoYx%T$vIm*iyRJ6 z<0^!(;3)Wh3*SBID)O?Bo1ukOhYJH~!jfz->in!UNUulCGW0RDP;Wu+ZC8qOcW8BB3|`rdAfWw{fQ3f>ws zp2{fY@G3dUjqG8<>~eM+sB0A{MrVW0!5n!OWE|g#W8>h^*45SJy1TY!a%aJU_e{;i znR<7q@GVTM-gc??nGRI$D2A9Ce0x%hYM{21F8}e(mlL(-+IlW7EBm=@S8Ph%n81#)vt!sm>c#1yJN%d&t0(g4dC3fg z5{12bb$g3@+#`C!;zmL2&g5H&&rTA{cQ<=~b*))tF>N77VQ`qbGY!F_Y2x#g@>Dk| zZfF#L$ff%xaoQ#HYg8X^wcKHynol)!kllZyZ~h^0>kQ;o@A>;UQPqoA+GaxuKYs87 z#Vo*|LG3Nt4zc8^i4Sz8?@sl#bpg_SD@>O5w=wk%hE)ait(QRh7+Lg3+PgSr2ArUb zTvn=G^T14L?^b`2{Eh*(Z5#!$9_qhTn181XZj~KQP7L!NM|HIon#DLFG5Jz5f~|G5 zATFh0*7+D7EtB!UDPkGLTbY%MV}tjudE@$EZ9#8?0&yQt}a`Gdlfl=*nkEBN1E3&?Wz{{DP2M zYeSzCP4LtGAsj%?$WbVp4H-}x&QVZ%a6L=lF2>V)8L|0(%>ibAcS5%}P>ZhkbRjEh zox^`_5Gq>NyFjG~#{FKMo!`+IN_i(@Esh6CUcT==a$1}Rn5o3|ExMi*QZ-)mt>byH z_TQ-liW$^t^z!2t;}Zn{4q|zK-J5(v)EFKd~??(x)#8d z&1et?px93(uA1leZQO^=GqoJ%i4*J-d`7LDMItjImJELi1Zd|rCd4k#vEVtAQ~W}S zeoky3o*Z=BGmS!9w&WD#>Z-^*=s3Y5dV2a_ZLRoo`))Gd(359k79G{KtDwNNrID5t z?3~nD4GasT@X0Y05-(R50+1h{f6;Ghy4xp=P^9*u*=5BeB-CKda7rf`9>t>=Q0-fc zOsgs2k0(^HS~G!-aUBcI8F&M@pFxvj};oWlBjPwD<;;Yg2M z&)Z}TJ=1)Cib#3B5}JFF_?TXk${2kkI%boP`+_f!VB%aFQ!M#iqHR~J#wgDMdK#GR zIaOX;yp9&%ypaD8b!HE9bgI#1!1`jvbGl9jVY@b5d=T8~!#$k(kKzKSoJ(`2cplT0X^^YN4OAwS(^ zUyYmbcnFcDO(8e!@Y&BL?H}aI(p*OO+E)UL{xj5Ou`zK9g{r@P&n_H!+yjpeZQrm; z1v8>c6nTw0k?wFt-}F9u{^C_|VWC$(^rAYB(ejmPHPOl^LHVNVa9sJ^sH*;LLsRtB zNgQCR$k9s2u%HX&8L*uD{`r3B_@>N?$8!wM%9@3CdYPDFarbb=an|1@&IS3U=Ymt# zC?Z0@uDeYc|ROTYL zqNT)ZTTC9!1cY>a`Nr12{dI5Hjzygo2y>C^MSt>)cO|EPFYFWgJ^C`1fd&f@qyEdC zHq(DmG|>Jvt7IFc8LQ*4eDP%8cuHL2qkNjODTm7@USY!cqPrw_yScL@lPfEGFsD*HA~g2(k5s;ZFWUndk;^ zElWh;Xf76ZR$6NRY`^E6dL1p6qcnEaw}FC|iefm?%ItdFqie893I`~w7$o6B@JYUq z&8@n!Cz*+-*={Z*c$OjHLeRWEGEWbn+`mb%+UfIrV@pZxqHTb z!&wJ)YhT``C-@Pv9sN=ZhN6zG+h!|K>d})I*9u)&icF2yUH1jJB_{Si^A}g$9L;|5 zee6(LGhe5s1)9IwKFN^4ir8n}yJEJ%8dEUa5f^^j|B}%oTSIMcCXiKUz7{*MG1t#4_ZvIhTbHV|PrD{-uB&d6;_X4M^0}Bkf+8mB18L#Mkc6^+H&TF*pzNvm z``E7w!+aEpUsbRv_humOx#^+OntH5|5FrxBacVoSM}V@)PWGee7$w3rF7{?(xF(zD zte6BAgth5Zn5g@K*KOsgW!vZV zrFj#5Au1*`>!d5q2VHW4d4eTRAHzgyd(2?nQ{f9#p$>pDhc)M@s|?9Su|ggwWYVcb zon6ovbWKC}wMX3=|BVJk_`()RSFfD5!3*U?)|`%n{-=(c9h}PN4Lq90vh_6jP0UX& zO^4q95v<)=$;mMBphsUKl1tNEqcGjBB5c=XU@*PI3p}aia|hH|rmouZ_qWu$$1PpG z(M`~-4%~vaB*D{AoeY@?WpIn0`q&?(6v~Y!7}<86UFq64s;{1dnK#t$T>s3gvS6kL zogN(*d;T{wjOB_ z;{yfx{L1dtGRgCn=ETNtSDs3nQ$=D;+w3?fIjo#JyF}ZM(2{GqOPN0f5pw+av(-nN zYn-4N-5cz!;V$-3va_~N->7RPiUmakUK&l!kr!Od;McL*f!VPpXU!@?0Mw2-y`lEq z(x9>vyvC765rdOF)q|W=)}K0g z9}d{!di)ENLFCBwZwu<<0qB0G{cLNp?qE@|PMc8s`~Ll3p6aJb;G6|N*ry!vK%SYh zOR6KpGnM}Fi#QteKjhT^j;a4IF}LGqJz~DVr>B64wU&R(#YS=V4u1;96}1}iuJIrI zp)Dvl!vvLEax6aNO!4=jZ4~yE+F(aVk3x0;bP zHg=nuHAqfM>iNkMy<>i`SU30){rR&ssJ%zQY_cZh>U0aMrfH_k_EMae@VfT0PGVge z92zZ3D*qa6W1e~SQPKvI>`kodz zmRkXOd6IMfWjc%1)3Z{%P6KpG9JP{{fDyydew&xnf?C=*?!|c;o)&L)rnX}m1~>@i zDE0tcJGwMCWrq*jfX346Id(yNeH%{E1WxhfGet-&(cBUNLXF?wwsoGe+xZohnq|Yt z62)0fLNtr{FwU^g z=$PrmpB&aTYlOy>l7qMJIXPigr&FvEiZsV=z;|S3hI#2I4xqDIHdyfV6xHQfe46DQ z6AXeqaiZCr5n_QL0R%DxoUexfwcQ0-I5fO=ctbgSq;)$*N0BdyCn2 zuC_V22MCE}Cn7rknuNsTr6{^J?F|MYS4FwWd3Eja#cFk}VjgBoSu>VgmL%?RW9oVz z`OXr@+U?z2z>JSOw~OJdRyInxgU$J^_}t@p;9-E|#Q44Ye3*LQt3U{vdHF-J00*+& zq{!_M4ifhRItF)WRsk>X9E(dSwP+~e`ayVlh51Orn5mQopNz$8L&@J50qqT}84?o` z0DqU}Ic%Xv9_GuEj0%-!GLhVwnt_`^= z)o2;pXg|~UA06~sj(<*h_UxImaiz&vUs+@A@f!ROS*pHiUfh{ln^@8|Uoc9ZwQRZD zXXGMJG)O%tf>AZPe}LSQjWusV>V2U|IA@6I#P(@-IQMlb6m*$HJ6-kSx$FxM&s*>C zjEUE=y?~>>W}^Atj79fwmd09Tu1ZW1A|1SHP6MM0IK;|9<|Xgd2{TsT_3X^}f6c1v z{>iGD)oKwRceDmXX1}*S!2#@8mL{6Tc*fro-b)UmBHFG1cEs$#B*Te49!)glZ0%W2 z6vREhV#~{S+frg;i`T^23B`ui9Dv`0QUED5(Qh?YX3OmGyFyaCD^iL(L)$t#k21gh zpOH1wsSl^)1|pZk?oY#BEZeO=yuFMN7WNxDoQN@m?_W!GJHN10YIP1c{~SOAPDoDs zFI^ypF1~KDz?^OOKZj>e21-4kntM-p+b{FuODW2+?4%d7)$_8KKH?Rdf8s$>Vu{ru zhA)~s{BZQ|e`4(|xR$&zQ|3*Z{~c;8@IChO`U#C}UT%yq0vs|&<~HlwJXvbWE`deM zPs5taGf_0%l7r62wmw;J$rl*=t>kjwPK`4xD>Jvy))59ky5KZhq1ws1>#@Y&o)IIa ze&mD8zl>O3q#PVRhdOThO+@d1G)F@+B5sc!JyJvRy}d>+`?O4f$7=HB4Gut+rJ!5^ zL_k0QMkCeNRhx*qgPT|1^LJIW-OW1HD#c&rhrRglwAmBP;t<_c;2M3U;Z_B%b0=Z;By)P(t$bj<3f7K4{-8}g?Z*i;F&|~X7cJ!RA$d}Rf03W1# z9Zt_U)tYRtlzuSNZ1x2@Tc_rILgjeY0>FMWytHIC64y zlpUI%UphY7*X_s~gGnY{y@vJ16jL48Q0+!tD9cNu83B4)gKt_`(vPZ5Cw);9-E-vb zo;*uH5G6EU7jK@@(xMI(XqRFpOAi9mG4--00;Qb>OAOL zs7Rw$qe~PZCiW3ji^T6AIgR;hy|n#oIqqO}y}iA2X?61%e{)kK?UB;KY5!MEI01n$ z@yw5@j)bf@C^P;H<^i7z8bIm|GXE{*i4CZn@OgIj_9=SK0+d*f{mugrdKfUoTK8SE zR9nt3f{7Q*GDGPaDr@8MgO|wIwFFqwg$b-jn)Bs?0DPLWMcT-JPZ-cYCT{6(9q`>% z5M9uXtv!{qH^;^TgX(Ct_mwfeVN#NHvQ{ARz)w*-@b%-VJgt-9*t z;F*%IFF|adh|)w47h4j80p5Lu3L&@3%2VxPj^=IjuR{B)`L4p2DrQWja9~mQ5%qmC z6!~~OFQBo}t!fppdR^tMfq2%o0!L*iH{wy)?{^M$%>M$QgTR=jNdE~Pr&7OETh0?9TAL+P5Kovk~lHKyAS%{C;$^w0pT=N5yFyS7olg z{x>#z$fV#k{TG(Z-r^h}d~)Nr$`Y6(gQ<=*TKsY?pPqJ@duF!05867x)!uuF0<~PR zzAX$Q0d>z7p4phe3YASm@D`SLll4;(&e+GGMLnwe^BqG#$B!oCWR6&h?6##^8QokP zzCwE3AoJ)!7(Pv~bHYGL*@W9|N&9fJd;Gnl5lA}xbt%OxK!s`5CVi9n?Oo)~t2Y)= zZ=Q1>198O{-GxiVX)uY$2-4O9GYQ2A`{j6{I$Vk2G+U<|V+;_FcIH6g5x|X+MQf7c zSPtLX?@#s?a5Vo(cd3@Fj%0dGOB<|YD#=7)$4sKohAaFItN3b?b&Y51%jP=Lh=%YOy5jC-&>liYv#4c z& zkgfrGv{m`LkYK3`p9J~Bmt!+zgkt6j!Bm*^UXjG9D?k&Y-5~^2IG7Mi*&(i8Q@(bhBO(SrFOUIGL%u=21WPXM8U33-F-lg7F6yG z$^{bs<(s}@T1uy0u~sft?L1e=DzlwLvI#0rV9gt;lrZAGS-3U34+)Hv|GIXZ{8*~A z7;h%(i2Lc#_2uBHCQ5T_2IGgrGuz6V^TYP}kJ4hxq141iYP`jOLByEY*Dp2$mMvIfN~15oY!wXD&+QOr9L!KJX@Y+cm8B*n$XT} z#@9Ch4|a$s7gsgMJqLF{j}ZGP{&^^G_nTJm}U=q7rcH6=Z}QPg?;%%d%~^Df)@}F z+%eSpC{sQ@5M%OKGv#t;H3Rq?RLt@b0@_DEyK^`$!L{;C#T6IdyZDhSiKmIXD2dZ3 zxNBLsbFm*?ENa%cdP4`M;eB`Zm}mBEeHwM~%??D1yPmUUxi47$FKN2JB3Wy31LbS& z?EpfWanK9wU4DL}w2F24V_9i=hY? zNgrxpQmwJ(V$D}H7>=tF5)wIXqnF54IDP_%%4F3(d1!=U-=zhnu?gzStQ#@eVDVA( z-%}AHvNu#KN9)p5VMK;MSW>gKX~t=-b04!C9`;Z|#PE1MHhTc_l5AVj=ov-w*J?0{3HQn~=AS#zDRoLd|bYu0B_rCEr!uc|Os z4h{7^E#LAE4kpMP#^TZd3dA$CGy9EGj<*N-%14=oky9TtF`~|@9xE4jpLTP>l29AP zXVs&>+E9A~O0BTnnZ$XI4wDWM*WR4L(9n3o6dwgZ&L#Wr3Qb5kzmkB=uy- z)!BsTA{TQivenYn3HVG2nFL5K=;di1!*Avz)a#XC$kSvva;!m>vgu_#5hVK76I-?q z4Wh-HsOpB+)vHiGTseF`t4_}$R`g-jKqm?Rm~-Yue;u04zRu-J6D97xTA+;oZ&JYsjU5fu{F zH_d;rm4EKj=$L{EpE|{bMP)Z}OWEtAH)91f7h@;d6Rl>?t32T4QIklmn&f2EN>jyO z{T%^*&$8eHHIs;4e6AhqUc9uNQvMFbeXI5Zidc=`C2}L#b8gEhyjGIPkBC;K!lZBO zq_pvCzkM&N!fbaFAPfIf@QD_UfL&_Oh+eyGqKnW$r5B2mbYJDk)tZ7V*C6c7O`x%5tp zklfGjy(;&@#q58|#Gbo^Ch7NZDx64Y;6F+CJ?rf6h|e(CF680D$Cpnxp$&!) zb#7*F&qop}YH^~J}UjMJ-6 z6YcXkr)c>}M}|e+v}@#tdm!{aIZgW}wpCG^XO$=a-8UGE) z|NqDFZJ?U%^0bLBb5zfXW!RIF2^son{5DxacKV`c=O7(KESi2vbmSVEm zv#|jn^p;e1d~#@Jzf1pn>+IM{(8|_Qd-8Q?7v#dliRCcyYbYGRC(cZ123U6n22K6s z;4|dmDMX%y=)M!of@K@Fsfne;Ee7gR$HR{@Gwr5PyNPT}amouhbwO`FGSYo(L zQZ(1lzlp43JV|aj{ShX>3WwNCBxaI;0FqK`B_^I791BoHCP<1-?5()>eaN?7ocMVG zy*7AB+%T4i#GA5V>uk%Zz+cvRYGOsYgMh9_3_|*E-TGrxFUbKmknSOwQ28yS5N|k7 z%-n_}{RM#U;bNIPytWV52kRb8@fj7~#GU%sP3?1g@5Jh#z>m7aX`7IsbQ|7l_`l?U zS3a83r`R_S7Z!Pa1KBKNo$C|HSI2DncV(TLlpVKF1L^CTSPuaQp?Esyn6;S>n9XGW zTU{We!XIVizYqp|_RWDXpw~PNK=v3+hp3(BCr}Bb#+m9;7y04;KnL5d8;3u|X69-R*zEc4EPvdmdeiG$Xuk1 zxa&-4SjIin##FoI2d>Jd;XU8h%NHisYHh80Q85CjtA@%k=etb;soj=1o$inv_B#$Y z{k@*_MSLVzewiMb9>F_Xpn_a0OcT8ZNl?Kq>0))5koylSWMRPbZm1^u`Cb&R6-qV| zVc@2hOnK=A@`88?#@VOiK3u-hM{i_VUeGiUfE=7#3a4|g7eG9zexf$Sft5hhQ(|AjsK-`v^M z^ijzPz+jKrvsvav_+fBU2*QDN`pNk7w`Sy#x>d&M-in z5s`U9p944gj}q!=PK3sx?tVwsAt)e$8~I6~Gm6L_g^9mwhKS|63nwADYKsRr1D=Mxho3npnT??=G~GyeOU{e*Y%VUY=uz zaOajIP{@p_o*ZE8T-249yP@Y@*kEP2xV}tbb3IzKv;sfmBOIPB zHG@A$!tb9tUtC+j1KK;fA#qT=zYd*tS5S`#>|0-95ak?+r+vay8@ zTA``cCGobx=gPl+o{IG7!SBgszgtDcXZsfsNC$$zb-q>sOjr*;16LZX#^D~6YQ>ka zJ>h}*2IGj;Lm^r<&b#Vq!5@G&mGZE_1clLIBAp_dAdhms4o4qTv@U;JGB#Yb%N2Li zh6%b+6{`#f(8<7SPy%jM?+cZ=db5yx#P{IDsuBQ=$BWg(Mksj5oT z(6#HkXgC`6)k~)V$={q@>9TQCmu54LfKf%g77dMm^1&iUfy;lDG%$DmLKvWG#`6gd zkm9{wzYjhj>}94P!^W_b{IS=ZE(r@6p2rVy-tLUrdFWN3+@_V%@9_1JC{{MMft%Y< zCuGFEY3!#@GlhxJ9UO{oY@+@B5to*Ue~QaLdQME7TFeFv;6G}pUU@CLM__#*D)e2# zP{84|pL^^8@oe5||V5cv$XQ3$yC;(7G(ii+q!T|{i)lT0>t15UfO{l!HU zBnBGKA1lBPdvn+2s;8&t2(2JO`GtcuHIDZCv%N*LF2H>B!){x-VW!+qm5Zs?G@zZFYdZ>*G%IzL;UKwk~yog#aDl0IaQm@c9t$vG8L}p zA1=Q`5rYB$vLtYj!NJ|&f&re-gnJ6OQT%6mw*HE_==_Q2c+OD3g1cJ|A*R9C{e`sG zJ2RDmF|-r&MOy1_?fe5%LT-rV{u2~%|Xu@H7CI1(SPVy7}*`cXIVUA;!z zpZ6{=XJ)q$f-!vIXDBM7$UFhSyYP2?P1KWx_S})j2;+jq6XizxO|IvnBvi(ra8cKE zo2d2L4hIYY4cf3I#Q_YJ$L0JD*-{pV`RW$y&GDcTFaB`=hxu+DYS7hz{_!b@QMCs0 zZ~ahl+x#H?NY%*jD9GmcOXCJ|7#`HWz`G>Nvs(_QE6FUUN4&&boq`PHl-Ue`-1SYs zKRxLhX>0o_`L8%Q^zR`2-#NI=7T)Ur*D;*uRbXZ$m=R`Vx*IWQY9(TVEjPxH7uK!SZALo_}QxO7os40S5E= zwSitG9cd!2_Rdq&i#?5zZ%x4LK3`5zM4-CPB($1^- zyGVLZ#U3S;yRa>h?u+VZ+MRf^KY{J>pV@XrV)U7AHGvq(h%d| zNa8VeJ0WL$Mss|Q%)Yman3OG(+3!Vm&zmo#v4Yo|adu#@fS>Ac`_%Fxx^Vvq@P+~zQUbl3etF;_F5qxmkz zIHkmvQ2lf(wDEjQs82^X+Lk8q&KoVhYCzKHkc^aXT%G0x7P%`uA>CfSqfcf%9ouG zbc)I+ecR(YTPBQrm&7;W+W0Bm*aDa32Ol_bySrUm0bBdH<%I^KZ-kJ#M`5H_)GOdO z&dADQ1P*a5<8^vXmhEmJmBnW2cGvEWN{+lj8ez$ph%b1_Ook|77?XdC(ow3NT5e(W zXWP4f9*|owKdNUjb^@W~;P4P))TytnO=!js77>g|&=z+Anm<3tHMy|> zpPL1|TFgZbru;KJw{c5QbpYIIm2u>hG&;j#H0JtKKL0=XW50g zIpGzGm_}y}MbX`7VyN)0Ph4N9S!#fM5Ab-7pSFyTkGqsS(Fr|mGo6fo0OsEKbEQr;0zmhBVdv%} zChkv1xnrBEsTR4In5xJgL}SLEp-J60Q5NH1-};flA5c=_Y;3dl8X@k-_BVmLdJFMQR@xF?kS| z(p5}L^55ryZ1CE2WAehet144cj-tawQrUi1$?g)21^s9Z8GTv z(wyLAL5VYwz5Wp6TH9z-&l&DOLE z9^O81?mF{&;rOr)s0>IqV56C*oU50ac#tpa38Xh`5HD<&vjh3?*BYbM#>ZHJH^7hL z1u_$d@>S5?@AZj3L5nJmrNX9OUT{GFVB@+*{m=IB|L6|Cv_wJxNJf#Y^1{;q_)5hT zOhJaMB`z?iffJ*Bm0DMvxVUtyNLtG{#ddXFdN#enn;+an7C!8Yh~Is0h>)T`QQTF? z@PoXj7+SSiUzuHOLsY9u`twKPomyQK zsb!NVInRMR*hn6$Q9iU`ynxyK^(p#ZqH*%AbMr4ykWQ`fGi@>2PO7;ZZa$Ka=D^mg z-V68NiZOH=pF%Q~%SG(K7imC!e#8x-Eagi z>|$(&FFDS3`06m=_Jb3~p2Sv!-L&s$za?k+rXnmyXVGz$R43Ee4+olOD$&b zErPeFA^w>aIGDQMFTOq&DpEFef^N(L8;YBTQ^xzZK5NW3SMQhVUMfRG?FmtitTfk$ zd-#ndm8?=r_*KN8*F8r7vv75rxioNu0N5n|503HwWf)J{yk#jf=$DpGLKdZolr4-P zPWNq!FyzY`)Dq5S3q~x!3R^E-x7|0k0M6Ng`|+U~ZB&G&neim|yCXYyt%_E%{gpvz zu5&~SF@Fiv7D@qM@mf~7GxRSU;SAf>NP_a>i!{X&t{t$2e_-y|Y^4v%*Z zI%p|1{8piCYunrRb07&oK4%zw5#sMCo!G?rfnaX;_hLO+Rqn|*7qk)-y|w1SBBEW& zsK#?t{t!#0ZvZov&4mHguKGA78B;>?Q)y=79a+s@PDYzMlW_~4mTfmqkdtumcE87e z!WonVP^~e+2UI}sM8gfHT4BQVLe3%nVsk-I?TG>V+0I`0op-OJ1dr_!SugueM{Url z#0Ncjq%oZX{gY|8KI6$}I0B22i9On+v3w4KkmpJC=*VG=_{&VaS+ZDhD+R37AZrKN zm1#2S5@(p7jCR>`J%D1f?SsX!F7M{&lKq682%QP;2jEcZfA}NQSbTvE<$QgOmu{@b zW*#(|z-C;PB~>hCi8sP(oJb*Mft{E&X)^LzhgIpNsifx59L5?Fnn%Kj;<5rlijR$O zptxEHMUtn?AIbmfnmcsRn|a@5ElCczooxnOD2{u%-LF;%_7YDi-S0;Ex~H@~Cbn0z zWd=UEXs6xRGS5F1A1f|^1Vf`)M~6<&a8Xp5x=7H#jI2Cz|_zteC%d{}OYqF%<`d(4)rw?mrbP!>* z9s%|&wO74G{4X8i)Sd%U`1oqaSD+}7!0obXE!_w-lYlzCF;Oq-osXJ7_B&%V5P$a| zR8eu!R{8jd>weL@!+3|9*XsA~fn$SNFEvS8s-@G6=TVz&=WzOkYThuzkJwXqes=py|&k$?*3b)|JSU7rLhPF351ZSWD^N z?<1$N-(evyi%^tqA?vKCl-f~#ovr`#hV$3^B8R~u6rzP`qW4a+I%hjv9up99Y_@ja zS^;<7tVM%1{WiO$(XY5D5uJQ$@E!*Dljloy9VOK@L0$JpocSa8dvjAsKB)zdbboE6 z$zrp4c1ht(-0yHMeojhC_HhjlZ=+Rk`(W(%He|`Vwx@fl=Nsb(YHFVD{S>F=sI6gj zFF5!(r*m6QQQ?%6zftXXD8vv`SW2nZ-x5PE^!q(w@m{n_2_9k2IyA;ES^AbWWWO6G zY*0l{vd5zG5B={U8um&l9OkI`0e4iD%EUq5`M&m2)Cr>E8}7)=e0J-apAk9TS3Hpo zC%a?G-3PL4mc6gvy~D}x7nB!l+YhpkUeEbxalK{$%hE&(jLkkao)UpNLoL)=3e=i| zGmR53`emB7Qxg%9Q9Czp_KTwFV)xg7ET3i4PpSN7rXO5x0%aj&hSh16y$A(vHumsK zRdpSY_}veVjV}J5-md!_uI}p(kMe2}(V~PWBt#8K)QpIzAqJ6*HjG3>9leZtMF~ML zL}#RE(ME_)1krnEv?xPF872CddGGTNeAm0yv)=Eo_nve2UHg3YXYbEhr}z_tDJx#u z@7`S`0t>jN=ga_l(J}?s{d2-03FcqcbyC)bkR9`+G3A*ZSJScHcq%2?)$#r^J@D#q zqKM_}G!Z&EZuMjJ@QG>x^ z`fJ&`r7{HVVUuYva-pRR_Yt-(@0O$el!ad(yHqQAt=wo1SY;{8 zl1XIvWK3G~YZ{Ug;msWHsTj}P@o&m?9iXzdUN~&7S^#_5Pbz@Uawc(|NN@*7|c1Xso%%+ zih|G^zZkgt^wb9y>;@Nmf7Cmgmnw@hE5s3_?v51?kh2(qmZy+DQC2D$$O<^}{FKig zvd46|w-V`k$-2g-%B=R9t;}eCF0~=TTno=_QMa4Hz|wPi)CoACp#s>GPU!M(DmFN3 zUZuffj?)JHC!Rw6KEV0T?Xm{ukf$5fZhh-ZpYk@2?bm^3?xmIhmur4*NP&8a2>Eo7 z6-{g#jC%WKsVo!cl1yZGEZOQ;@%`0h#1&kTa%?ph8*q*HE~Ro%;da2lnQ19pSu@<7 z@B_olGmC3+2ZXpkUT_by&_g$U!!1EKOWMtrKXfeTRZELG!w~v2wD&1|0@bsMp?6W)@~|A2 zqqz&)T2&3P8TieWJ(;@q0m)s;sh-02BaX!%E{5; zhN18N*hziPx32UdSIxTtd(#SUwmAmkiJ8NjSoH!Y)5oI=@5hb8Un`GYjq$Qx>oZJ@ zin>z76OKL+l?@yDC1rss_v7-nXg*Shb6?>>7AIQl_-;I zsm4YX`m~eTzz){+UOmFDW% z{*84)FO`X1AqOQ@f3Q}J52v9Wsj;{Nslrl%e{TXaQg}gT2y$C|)dVNK2;}E2c}(u% z3;4S5Py3cciPy!UX*DT!1yB{lOR&362_+UX@~ z{l~2HKrVN4g4=mH|Q05!EX~1 z)c}NoGNw0@#l9k&vl9VMNU>cfXx9bw+~BA3!TS5sLV)ulN6@Wmk6v8%B>U14aroA| zIjGeE*VvWJ&)wvs)u!ak^k(^(&AIE>I(=;rkD&_7HT^^Dk)Bi~R(cN*;2!}#WZseLAWc6_&cFP<7RehOZf3dw#rPBuzmgNW zNd1K1k!10tM=xdq)6_r{VyOK$=EH>&E(u&<#jCU~LlGo2VGBuI+v4PrBtc_rf!ds? zi;)x(vZ*3m*RBefdS-=(ujZi$%L@y7#4@82{3&PUYM@4;aKan)GtWMP2#A@0-niq9 zz4Z=6g>R~xg9kDW5Ga1x{rZaJba~jMK;2smgFDOb?Gm5c`*kPIQ@e5sae}ShpV-sS zTf}GOsvsKSG;HiVT3%=wh^@AGx0JY?$a{ctcx&D=o#B3SO|Q0|6A;r~R{G+m3&tnBQsDu*ZgCP)_;dIBVFn>9C;tI3Rw z`KQU*vGMr?_CFP--`yZ0-n3Jzrn~dVRaF6v-@Mp;^?BN3cg6Ipl>patRSql4Ix6<^ zW$octZP1-f`jUD0cTqlHxAU|gaFk6`N6+*-3k_Tvj3v&y&WV(L5W_mJ<5i1myJu>o ziPkZ!LlUW|{Q#GEMi8KRtO3i@;8#rnAtix$bT5PRK+r{SiJAl={ zU=vikt+#iSg5h6yTLbK(z{J;k%V+N=6B8Qz5^bBG)?ZQFwMbx1SC|9xQTV~s>G(Y^ z+~8tUa=I6zH^i+FW1C~#De}D1oZ;(Cn&OxO$@8bqb%^@e0pw>tfrqq06iiHSrq_O- zo$&rX;yUzlS0qa|7#^07^wq?+?=MR<826gUyZ7v#H>%>^O zmUj|)ON%*n+29CiFbR9Mc^4fnsM7rX@_-}kT=e{+HiW8r)WY^j;E=+PB(K(<5;ga4 zCrMXCnb)!$_$)hj`X6#cJ`LLY0}!5KlI%@6NpT? z9XDmU1-z%6IGj#uvgP1N9aRzM0Q96;&l`PvZE0z8(XvB7(X#C9QJWJeejd z9PHTQo2tW81+rQM7myFZSdrFS?@aPVsy{K6AqG~bi_V)**qecajltAJ+Vb4>3(%yi zF%SsuLB+)%=nt%IDOp3U3=wplQm&{{d91FIX!ILq!b$q*bln;R7hL~})~OOr>d#}| zo*M`9JcKfVab1u%8gzTaW`Qo%0wYUfJ+c*&?`}l;2ad~bdkI_YeC^==gIZ#S?5rQ5 zi*8VYW8fyQ?xp5vana-fM~F)U>3JyiCV!ciJTIe^5x|WT9vT2M)Xtu|0#(A7Q3XjO z!@!Fn>#6w{Iaf0kEz9MLyZ5m5YrNz@$^nE#2C@l(b;VN@L{Qh(O;x;ewsg^%S0m^L zFr$$U5LE{fkV^qM_Y)!sc{@D6u}nNgGM?eLx6VC^x>!=Iwb~;?_P% z9qLrUNE+<)egh?(=Kk5Z@V%(?B%Gd}jgxDW!tRqOp`vv3D_V`D96@{A-_3QTh8x)X zRjl`G;S*ELx2}6zdsfHxS2ikqWu;}YQ3-#;C%s_@Nj)aWTWKIwVkvG?Ak&rLGYlr& zvx96Bmm79>^x02FXOsH^gQ=K;&n$%=@H77=@G<%yjI!_-LA9zHQWj?2ZNv7L!3Y`+ z|InvD)4HN^bW zKm3YiNiIeG3s`9Abv~b+(~-BisD4p-@vJ{v5fEtLs0RP50`hwB(h&LDkM;yGf7Q}c zI^HfD|4;c$SKmzAdn?NmLXAj{Q9z!}FlQU>`_D@49jh*YqEzySbN_M=K<^%fq?nz1 z?+aAb)Bh78OVP}XGI^?f<{k6W_o32FAZuWgb!5u7Jk&z;!m@%dg7!Pe$LV*RLsa>T zoE-fqCJRaq0o#op*FHZC5);GytZ@xw{GR*l9Bcvs0Rol|ElUSV-rf~=3eP?*d<$(T zh!s+XhJ)Ds3|1i0fGpN8eohTtuEYl9qnsU-L>bG?9>x(nS_doBR9rc6LG_qROr_Ki z2#wc64Yem@x!*{;0h4D$3Hzdc26x#;(ojF*Rv|ZtV6Z0C0h28<*r`I6`9+ny$}|Fm zm5T5K{zl~ucs^n&GZzS2*CL;|E6+5~8$8b`(@zlw)Pn7LS$x-yA&a|xk_I;is2@Yz9~^}f%I5)3`05G<$PKO zmooUL{`QD-+Oi({M-TklYTsPtGSn(atTsv~z#-;d&3IT#vGqkZ2L;sdS~Zz7OdB*d zVuk-Wh&r$all500j1V9+5?=kw(?aF4qY5J(8|KghMiVy4w4H&UkcMk$z=q&Ks7y%{ z?>bIUoS;fePx0~$bht^qsxn>;4g903hU$L!tLkYMm+@`hQo~DZ4u644K-yP7GkM`< z%g=)c$-Sj#U`umM=LR9IciJ7;8wV2+>K5Okqq-u1mW>0CzUf23+L=JywfIiG zyh`(4Q%#M5`>4`L-GTMIpBClX^wCB-?KDHILu6nND3)^3H6&>9ps8PjVyQ&vOu zRZnKjb8_slv4!6IIoSOWmiDkHo#o#|hT^i%4o8b;)GO5aEN~Tco61W5x6L0c1&#NgBp&eZLHDl| oD)6r{|F6tPLH|!7_nJZ<67BRnQF{&~A_(N6CQPI3fpzHr0>ruZX;J<>!=>v=2(_Uc9U`%-w;f85WZ1j6~bq+U|3CMq&W`oY--1#oOkF7Nxtjx&qa zJ}Ge$_vbcI=mSA0SJ#~-TS+{l+lJt9k;t;^eVw@b+MpOq_J_Cxlsb#T0`2QGy~mDY zf9e0k7D+zBwx)+k#qB$k3Nt)!9O^jG5_hp(vc4o6nj$i?e2&hs`aR)82LLlzadLYR zQ~v~Sb=yBZG;kcX?ftg4fhvZ9gq*wF?l6kMcAQr!kiEx!fX87+UR6>iLDxnA1$3Jk5Q z+nEo<$=CS`XyH@|2+-j`SN(o{#YU%(|4O;DV6;~`wA+6j_vD)6`0(uU424F6(>CfX zzi0O0(^Cgvp`-?DEM2*JxxU5`GiH;aAx3(}CB((^=vjz^t78LGU`1)rOvA$_gQh33 z>OG@Ddj;ED7$S8-9HpVj5n5SQ6+FR7a!P|1lXZ<7r)wha?e@diWCHk?IT+Lh*+Y4b zqt*^?unYs_U-@YZsb%W;Xn$Nuov2WRBnEZSdlOth662FJo*%9x21_*Ye;#$b^2cop zp+lqg$JL_a!l3pqr3roueh}0i>tYNUKlZLEEG|^#F!0Q|3G)|_9QaQ!HM3R1Ki`SL zm$I&e{in4Y*8elY|9$}Ae;nceu+RT}mr+-PF9o*3$vs~bgy_~b-uI!Re&|c77e|N! zslSU0wF*=C$qSpb9$!eY7mU) zAxxV*XkNCbh^9YaKIdZeGs$VFV1sIHpyj>FRgl+0QT{xmFW!7o)CIr&sZi4?4cE$M z*v-B%%{;mM4~`L6uyw*jPkOiT^>QYl3GV-*_i$)RN3~-i^bj%9@slUvV$+KI_{@l! z&B#a_Wu?)OIx0HZ_|$azGDD4NtAZWRXgcQKyXl);nTHtVE@?#s~5C)Ey+`m zv$R$~EV(X2lSv4OO6eZBP^?Nt7Ht=xCJ>+Y`l!apbHb-9=$cO3`^o1m<|5#nr6kzQ z$7-iz$BFO;v5a0vMpyhJS3aKBk2hlzj_=Kiu>Pjhx* zc{h7)-Y>~f<%fW2d|FZE)ok7qbiB{Y>wVt|4f&Z=mh_uviVoQ(ThrxxEI)h{4q{(l zST6eAkfsLAq>hbjZn5ttqJFzrFZJ&BH+(yCEjZq5Mt$18(CR7}I!?)+`aVLj(AU34Hos;5SP=GFQ^IeHwXSrZJsV zqODyPiq$s{tOg?g^IB6lAe1Jlm64{|DfVml_>@&FuCLhtXqToQuT002HSuJr{F_lk z?sqi@*V(H9twTh}EZ#f$vT)0D zO7@j3e7|ynjjM0Yx%TZWBug_<_Dzu@T;{9XOyNt)RHveAa)Od)% zuICkph7PtF7aYIUw=sM3#54=2JAR1Hz6x=%u#D0tMhV_Rcv$q z8k>1Z@|%iT^HVCAhCy}l<^u`lhr3wC)ry0L=HvSvBb0l}Jq<0bYW9ykm-Q*!I*ht4mLH64~o~aLRcoHAy~mjXg_1qooiQjJ^BX?`BQp~ZR?L@HOKXGGitTq z$mk3HkFe?kpew&`22D8f-K@grYvQ zBW7^3Q!lQ!-kgVm`yqeU@6QN{U{XT++_UHBw`06O6UzLUKag#`jC5slvTj7+EKA@4 zI!i69TKXmygWLE*5cm!q`?!lZ*A;p8*>%d50CgDmJ@npu?`jQW@C3`^coJ)g=h>y} z$9iGJdU(NScp-8vn3}!uQ^a6eQtq;byiZQa{EN*_im2FmJ9iqUab>>p=H?QO<~?wR z;{eSe;+|YSTs)~nz=5mq`^rpV{5|E?#$a7Ag$_(O^}&lYDR-eG!^3bWcBPN_sYzKS6NW=Ocl%!#b1ycfZ9xgP+glQmeoLuD4+)x$Q0gf; z!gG9Q9GNhA6Gol$fJ!$uq52$8Y@NbvyS5LLL+m9<*iP+t>7c=Lfs0f$zOclO>cv`; zC4DEE_07CrUf;+D&l=ADj;hS!DFWxX0-_ZC-Q9^#o|}HmE8BU&Q;$DjX1$X=@G zU(HfqV=In-2Y2m7ofT={w=mQv-o)z`?NOYj-v@|RVoA`b)-Aq4Gv@xxGy<#WeTBVe z$BDKQ^9L4NEQJh*2=-(n`6K7JnD2qV7`VVdD+-R+Psvs8VRGPF<$Z>CruiQwPzFPn zEg}=-OmNGVtyaKrr|bQRqVA6;Q{PAwFiX%{7BqEa7WuQTc;I=yTpy_(T#Oub z!&}GKuCEOU4<=b*0P}ubvVbVJ! z^WG|M)PMWGl-}=DVOWeuvSXNE%~FE&zduMI~Lm} z(D$Go#mPs*!XApX1EOy2t3r9P;KWMzCHW23$QLSIE$!3?>l_qH$=LZ4G-lEc4PK;% zs6WU86#9{(#aR-V3*(*_w00QtepfH&dqK@!Sb06%HW_h%Y4A83^ZclyV67PXk8!gb z;O>Q;n0di3?oTpM2x8%g8_XwX!^zT5!$h0$cC^}(kUb{-y&cL6fL3xXki-Ae! zU`DUUT*ao4&hb@+noGk!(OMp>g!^Q@1q|nK$*87<@&c|r|`Enzezq=AAaWUY0etN zZbU&*jc9(Bo8J6{F+6(W2LPuupzN=eN5j2Yr&v_nn0Xv7|JJi9Ef|?K=|!Dv3`2KQ z-t6P(j;^h)5HOuA?f8hl?)n zVa~5s1?0H%ffGNKeK@0#cK3#x53eJS%GA8I=;<^gsZ=`ksundeED7XXLq-5`^=)+YXZ!> z>U(Yw!b7nz%E=7?Pod9NyX$?R3drkH`U|YK6`q)iT<|o)F#gHR)qtG$X#cHuDAfGn zEV=t}6=$6IM4-i-a*BdLH<|I}yIl+yXXC>tIurFHV)E5~HtID6MkA`4w)3Fk(qlLS zY0lS^C07tInZ4Y&JX;`W5_d?%;E*+>!nLUEAM+VV0h9(U?*nWz;i>LTD(|20@eJaK z3)`aXR~@#rc*pEjx=&<#KnpCQF*8DbH-IW9Tg z2x9>3p=$vH#!J)hv@b4oxP_$A(1-@lw3gzRdqgWv=$7N9A92>D8U;+}u|>L^G#67o zKMUIjZ6GfeGqC#1+{Fv{fq7But`qV!^^q#GaeJamRqEhMbQ*oh7fPaQb<6@tR%#oNJVlV1iTFchE3^;^SwQwFF~LrgJ_ug`wL-4NN3q!d7PUi z6a4VPUZYTbTi57%+HC+HzuH50Y*2Mp!>?mdtVi&n-sqmQ{VZg^p)ub5mnA2@F#1nT zCawq1&XPF@q}_!cayG=`wCfuHyPCs=`65Vp`Y-b;anQ)uBtpl3b8JJfv~loAx6gkz zf#T_oajM-;rgeGlW_ow$*b{Sbj-Jb};aKudt$<%NV z2v5TFTvCR1Z#>Qi9FU$@OB7N+lOKI=w|g-zjv8d){;B%f%23qFRGJ%eYmivuNnQ6uSrwLfh*Z=od-O&q(^*VkK^gQB`Rmx;T`6f@moZA3%Upe zL9q=f);HSU_8&N@^LQRU{pU11Jz?Y;V{(mkPxno*OXr$AOxq^t!=HUr%QGB07*yF(Pb%>pvCd6=mUXFdf1JT~;5f!R1$v=K zsP2PeJ;ROZANzaI&OcRaNNfE8i|CX>+WNn5;$I2exoSVYZ4+~E1YY~;xiA0JIy&`c z<9Z-*m-SMY)oS#(7b# z0D@Bf3EV;3y_Pm_`%}q9+zB`L1Mu+SNk^Qn`^ULZIZKQAqI-7{4gf5Hl}B$sgrA%6 zCX!XB-!Eg4#(p(7o#yY|fBWtmRJUn>)Nlj8Ej7xJ&g|vU)m1q;N1D9r!MW_8*ME6_ zE8xe4eQvKvYjqdN-<%w!AVOe1IXGN+4-n3fbqdhxKRh^$czhy*KObh>Q-l40T!B^M z)O@nMx7Vc9_})B@T#2Tm{fkF?)`f}AGyCV&_V4oSb*qe#7h2L$vHHg6vm^27#RrE2 zN#SrEGI$Cf=#p3sbdVs^qsl@MM~R2ZP=EdHz>SW&ymZ)Ud-z$SpL=GZU5U~dAPs-? zvGVmxhfcH+QvP52Z%u=ty6k!c;A+^-2=(lJ8sgpvu*KU6?wTj$(vm#6BK5|x?Wa6M zya#y|UnZ8p(#wzSR7+LVWW#NtDti&73X^#Ab&Q90Ga&pc+wl@t@0Imy`>ZunC^g!| z?=#QIhWDCWzo+5Q2!8__!Y#^kY*MQ(ev>Hx2U_CJJz1-nG{!O+=% zL{{Ysu;H${`cuE24#s-+-wVFpXgzpil>2xs3f^CBc9pe)p}XLr(!P>n9jgJ99wb)wo|SwUa7S zZ8HKV(iQtvhk<^sYt+S5+CLhL;~zVwN(1sC+UPMlF06_0RCsG~P>f8^gqx4(T#nC- zhE7t)c^7ypxQv}xvM>~b~na<6a$M!4)*lAj8%a^iVQ5Q-71`Q;h_e}lAhmJN~Iq%W<<7OIu|6kIn|DEgvKo0hQ&yfD# zvz`AR*7mH3TeX&Nr$mz&Et z>op^}Kj-b>;81$XupRIPHrKUhP(M-OK{i_lD*@?e>JC@6>8!uU>jPZCUJ9nsGi+>& z+h1({{{9p2`jWeObuP#x+xNb%zw1wfEP%UI< zSDMLA0{S;BsJyB#l_@2$w9AQHq50YFEHZ38E#qDNSWUg)aF&hboX_#|6HhVONXk!r zkFp>-$KBSR^TVlg)h_Ex(#LL~7%mrX0br;^h2!hP8w|{Y zQ)Ix;Rp~BsZ1;RE9`o9fpFN^DRT%O&$3_4P?UBu$PJfbw7Cr@;tg0$*Jckir(NXI4 zr>AEvEu(#7?v~JPZ=$Q%Kt#0=l_Kbq5_VE3vgga71X0b`hyx_9Pd+3)=Kr)&`iWe` z^9RRz|7tE3up-sRBr4Vj5%d<0OBHeZTMbijaw_{gUMz-=v$MYNB%ZnA{DV?R^Oad8ve7F;Oi=^QAq)zaR(@Yc_&ffxK4%m z2b^oIzZ)*kBp}41E0jLB-t^6PP0h9U_P6;it_{+`X>?dEG&)Iw?rxLWnh4l3FBq*+7f0T_R ze_;Ga?46^Nn@qlj)4n$?+rAnGH7R$!q5PJFF(fqZ2FthAcwv<&-Lf_Q*kdjKv+Z0R zMWZ3~Gx~+unC$8pM=e#$^mKAKV7FRpnYIKfM$z4j9B6HAg&)sGp6sBulTI(TOOzCo z1iygdc&&^_e_!%Gcff%49tJyqeSWp75D(dy$d6eS3XptzPAPU_Gty-ss$hc{ZG2hUXhf4XS`w{kXH~nska(C z6OBOpyjD@BWr^X(2KOk?(q%q<`V>3OM~oHb<$wh)zTs+$j4sE*VeR8X+P%h-Vb_^r z$Cdr%?Po2M<&Ii|@!qWdxX;xP^nndCAAJn#hIk{&js?PvHBUY;1XX+4OXM zXXoLYPoJp4c?C$e?!snzsBFfU*VobCz=0bN2ss7CgZJ+Rp_Q|JeTv}TeUKn(&KwaD zk)4}+cnxhfJogF?v3}Z8Co!(Bq4BsSaU@p=cDCah&*YDL8eqUdZ#UVH_@aG}T0$an zw%$!QEnR&13GzNY;fk)A-Dn{Gc&+U7GMY@dr*B`8;#l#eAMEau@;scpbBrz71326K zj71^-_3vNR2udMICnxUWVtZaYQp`8WRlAUhMjIj(MNcdk4$dznuOy48sFQsOUp9UI zYqiS!m%ktICt|ciN%);k!1jdh?_}&-D$(&*8OjDq=op6F$1Juzv2}uD;5L*fY&I6 z9@IXUe)A6>@2T*$VPWNsa^OvGeN+=PB)8UddWjZeja1WUS(l6)u#)%}qUG=e> z(i?Cz8V<%H;|~u`TKfBExApIlx(4)WJwmyXrc(RuHrjwdcIE-0iw?+}-g z5*8GMu7?=2a8;I?6O(qcdpXVC?D{B3VjX7Ft z@M4!`W_s&byjb;M$uB5hH!ysaa}q?NTXKIHvXX*w-lJmmRIo6gSfQ+WNeQCS{Ba+? zq|Z`k>B|ze8D&;v_`CW1<>qOEwTD!_o)VT`B_=|KtzG>YbqB!r`sGhlLX1?DvP#sP zs?Bt9Y{ySk-xw`Fd3q&MNenf7%qN0Ys5}jM%n+AKr!(Vou}by)mdUp)a$uU%-d8WD zWAbgN`saHEcDGr5mY0|*5TW||Z^8Bn;ZtgVtI-PU%2{@%NqR0p zD~(}#O$_Rt_Zgh~HoL8B!EZAl;Zr`jf}nW&c&)F={ij8_-q(Nq=Oev#ob3!V_4c%J zCBNyJMtT?bEBx>x1ZQS2=;jqo@YXuyjV9ENR>n<2IF$#x(ECk6om+vP; z!=cmgp9Q$&x8y*vv)YiNnTN>{4U20##>gdBFfp{F^k?aKHXb}=p~vFQR4^lLWN1Mu zdUZ3c)dk0~U6bYc0@(GPn_-3$vT!`O^)blarpWcLBX8q%DPjVL%s{%jec?3}Jjw!( z7l3Py@-+=oxmscHpxUl?$qDiw{SD+GlVs#?)2@Wx$-7&b44D`8|* zXs&(#JJ6v1TtjC-AKDP8Raoh1j~uhRxthk*U?$+1_d-WU-yY=Q;D}75x*-dfL4vVP z>obL=&jIx4=*a!DbKMaJtM=Rl>ZyF__|JHksYqlsLJ9di_a%Q+*^_4YzLgyW{9MIFUB? z5_>yBV+fb4ut?wn5ANpd33&-YL|5GiNU%E4i;qjn_4_OPu%R%|gH zCWX6F+?i1`1SXqS)B&v*P&-j%6iclc(Wc`_fk%-IzD)ITceix7mU;c--)^&>4B~fE z0VRzmZCGjPcSWm*)A#U54kEbbV%cxT$i=NKlhc{JA=AlP8v%$BM?&{PJ-TjVT3np+ zbc6wP_Mgver4(53NOJw)`h&n52u%&XS?qk+MN&275o zUc+EYaMr%BNUzv^d$p0{Iz_cU<$c+Xc3R?H1B$z;NxbOFTj*0phGx@X!iQX9VG}wb zq1si$-kps8L;;!*2M$?s6n%Nqmn=QG znt0k{87VF=D`!ynIe&~-$_N~`2YbneSJ4IZ zX6NKSIM^(@KNt^`U;{?dy{5|gOz{~V?(?p7nk@(p9djp|JZRO}^uEW$AfYT;TDo`J zApU#aVsn4ay{;^KdS65e0%an1f4Objsm%nt${;R_3V(qW;3r?61{P|1gABSL%@DJu?PpUhpD z=PByiYQr8H>XONpsKS1oVPuA<^MLFcGg-w-eO(PMA*sBM)$4!aLv2aaz%Ma}xVTo8 zpWNQuVBkC}ENd_V^dGc5<4Ida-h1~?xo*qJ$#ss8uLo{3`y*`UYh#*m0IJ2r+_Vle z?!(Iz4+8f1=xEG0xe*jrBm|m^+y?b7zii?M;<}@v`iVIgw3Iu$GEr8{hGdyoqeojfe;t1O&jOB~b1}9iYOU3F3!_z4Aqk_JK~V zXQk3MQ%5-Iam#Iy?x{%0?L1PGJwDDt-1J+h2r>>aE$FjECoL@v)<-P1Nh2%IGtRN{ zU1}BLOCWzMoh?UmWb`(M8bu(MgK0b*uuVLOfjm7G2L}g|KD#43TfX}Deal-&S{nc9 zujI%k!xXM;U}fuKuL?{~H4^X}VJ z@Pqw(9m@0V>G%?&k7C19z{0EbqsMSVK{C z>Yiy#u3p+REZa7sbZ={vYO2HHLls5ukdqUvf$na6Lek!%`47>(Ox7#_e^~ zuBCwBganjd>Ga zd-avP7B_vJI>X2O#Ng{p0_bs!BIu7>$K17~b6+fO{`nwQIL?y64N!@<)g0opMb^Xi zyDZb;{xLBhYR$d9y)m#FuB6Woe~TCDQ~@^v5Xu_h<;(P9ejCEpp{#B}5{h@*T5Qm~ zV#(2}aYK(p9!E#;B<^r^LNZo#8*_Yk7?yq^chD{4au03CZ8?R9!$RB22PR}97D<6+PH`NQc zw@o+6clX!H)bmv4hf&#>)3_DP^l1^yVGQ_SVGUU^to)lpR|H9a5Y^2yBO|J~yM{0} z{c2QA%5$=N8b(GUKwFz=sv2r}$s!bB&~RdTaupJ3%Z&e$__699WysA9ylQ{q=^Fd|;1M&wLEEtB_7P*2X;B)7kT6Fr76C3Uq|-oO zVZ%jOIVBA3RjH!(-mtLi4o1tA#JJ}6twAKigS(U6k?A8~JQv1MZ&%2$C2jIrZT92m zhad?nob|M^$A9QCT1>6>NbL!HZq7Y|>Q1@CiaDM9W#1tWWlm#}HmS zGt^W0;|5{~oDz)P{vJN6_E=5_aS}B>eJq6tY%H%YI*n4|zAObiUP4YhABD!>VRhbi zB+JT-B!FK9rPy3sbwFlp(DSZgAOc}quL&<7u?oDunw-Stdzm#eK4Q( z-=5OaCcgmf|MJ2;f?N!~ofwK$7zqs4RG&eV7Ow!G=770`KXB?7VZ zJxX3&U)}V(c<*Z*oyK1yo1~|Su*RAnE@JX$z_zkAeWdGix;A7K$GHIw1ARJ;UzBuE zjC=M6NG0E|`EQ@zT`qQ~gK=Mgs{g8D#-PWjFSVP>W=v1)d>LjGlNf7$nTbsTuSRWS z27LW(D#HUF|8%a7xsmy>|3CxJnM`TtyW>_x$S;X=-q1K!^Y!A{fBU(~+Y_Gxz&G8L zyx0?gX8oWZ4oIc@So7w78r?}Da*r2E*)v(b&MII(Cw9QDCzCiBh>qP9^t0tvo$Kz` z`g##C*4oaM5%@-(N0k0JZ$p-pnygy?lDOtAX*hWZ&L#f?!{OF8l*{@=ClISC*=`yJ zIoM+1(4AuV>gPwy?}EXiecK8wWYBI5*Wx#`@)%X!j*CF!3t^v^yCN3%+k-A07ij|L zVG>{%W}u--S;;o5vYj3%%L(0>EzC_7Z>`hZ$M*wsmtaO84IeB5{#w_UW|*V#mu(_; zE`##rMtDQh9V-L=#{nkhaX~v4`tb(@Ic`PZ%o6t-+kHjQxb~4xfUr8~V=(GUtHCK< zE{|fJWL0Idg86LjA1|Cuqb=;OYuoF$2hFw@sYsY!LYt5GaV+%siuNb#2dyWgvzS6k zrtS5bU&fWp|lYS69cm z`8!zjbOp7SPP-4($FDZub*Y4w@c(t&9Zs|axzL~WdljPVyXJdE8SH%K2U*Kh)GRE? zlVB!ya}bFE#-xgT70${Fb&cTrfh&w~z4oJdA4c{)*4^pq<*1;p;YP1&o2KT({ib&# z-*Kz#=3CDwM1&j;a3TFk(mJlM)U-{V%7)W5y+14DDFThec(fbs2Csb`DQ+*%XSF$? z8(`g4hh^EFi?Fs+$c3>n517x$UfHaHE^-b0B3j7QJY4*VG4-V8=x*)!#zUAQr!9rm zhh)0yBaMg0E(sx}vLr&o;V!2uaThtf{hW@G5oCI_5getsRUmyMrKd-Vjg7rp7Z=A# z8g6C*Sg5P2ers-~V%EIe&ucY)wb&MbDFRDSWQdNAIXpiubP6gQ+3xA>V;-k1t;58` ze6OU`J~C1+BNl7zhY0G(tG2{qc%@=#JU6ERlKr64Q{E3`86}*y=mmJvLW?AVv~pe* z{`FOVu!qge;PzP^LAGR%W?fk@(_$z&iiQBR^zDm^%YMk4=g4LG%jXnSbf-&Zl=8}G zOJ`{r!PFrRE-nq9g>6TtJA9vJemHtRfXV>r0X=DFXt&ezQM9b!B1z|T^Q@kk;|;`g z8(0HiV6*#75@RS~biM;paps(nQ^NDYFyaK$wR>FpgZ+|CrTp7SW7t6Usxy5qMgZ{e z=(Niz{yUg7&`@bq0x66dt60$y7Gkh=AupG93Nx_VK{XNj(OH!#@vi$bGOwU9scvL= zy!Io_3y__t^qe(P%TfJ1HJG?2u1vu2Bn1CkjEvkoCv57f-k3YF+wjUk|KSs~Ebtij zlv>JEvjqR0=F1%WZy$cPw5#<8`2jQzV@dnAa}ErtAc!z6J$zjNPJRL;Z&YUuYVaJ! z3LIQj7Yj5qw3t+x2>iW-xN8Cgz_J9eV0vx8QBkI8uHYm7T9`T&2iNUEJ5GwY_mstI zPHwKY@W~rZEHpoJ+_GVxi$(N-6d}`byJ<z7WY_3m`1s~B3YH#y+iVY%*w2_H_*`zNoY!gR= z*yRv5 zz#ZJgq|dZ@XS#Zj8yHad94T=pSZ+C3tmQ%Fb3uK7(T$2C!=VtCGMuXn+D;I3r13+0 z&U}7zb9o$$Fw6b}nwfU(E}2@@j!k|+TASPO@G!ABT(El9O}}>B0<n~Wn{TUD&kMoa_!Ll)zgh`SAi>ucl9`!#aCWv9 zKxCL#Rs`~NM}KG|adB~dL558!nSV7#rH`jaDTy~&x7;*W;JzxbP;z4NyIAv(y(h^V$Y={i@4JbC;5Z*Cc%oQ$k-Tuu=4 z;@8#HZMZo?hEW+Ea*kYHUTV#2XAS9W!paJ+e#HLx(J@o&%*4u?U0V9Rrl#=GPC!7w zZ_7bSJbe7%@No0iuTOT0%u>oKDtOVb$-l+K;9sBbcbjqlhR%!2gw+WDkTq`xmWR=B zD8rO<%WoQQoKX0$P=R3HkkWR4@cV13BNv&wM!qzr=5&+&fF1dDEA~X#+Z>Ib%slsB z!O}2>tQ8d~@9%R-{trG9oj?`+_YxlN>>L4{3O0!wDW!arHHfSRv*ltgHgnTi2fI5u zI9)bWXJ%(>ulMQ^O?_kn_SpcKpL+cg)AMwza}1$h;&m+o+cWF^!6bS<<6Ki;KT-`f z^0~j=>exD7>t8+|lt4G0sd4-#T*F4s7bGOX%G!q;Wyp9A%Y%hh#Wc~$EwR{2$7Pv1 zw_SM`7hdn{v(>hMXHqgUdTvWO)dna_(A&Q0j-a^QDlD-AK`a}fU$t_3J|DD{t*SPO z=J(}%&MWelQZgPcZcD}Ik)psy`FUyd`1M7GjY;5$x&tTjV}%8jnW9uwR83b~ENQ^q zrBew)w>*%`v zDJj8xfVR$nio~sdXy`NNwZ7jeuDqXz{Xvt{tbv>Vkf*nf$WCCWZL0gXI8Z zZT643)@b& z@zL@utgL3J0!h=4J_WY;t38FRxaC`ZM7r6>?g^ zP>5q)1P?E($2eB0E$&oD7f7Fxa#G{t@Y{w7qtJWX0k3YUl|Xvk`1 zsWY^~es=08DJzRsO-+q}A#kxhi0AV#9cWX3PBuq`TsClz*9S`+y?g6GV(1wW`^x2d zy5(wb?8^GOexuLXj_z2IZaZiOf2}KL9J?t9;>v-0pFk`U6F#30fG!sZBd02QjQ3#`PWLKPHDmbX93cGx$0ru==RATiS1n6ww71rGT}L zESVoJ?^J2x%R*I|f5P(6KX^SJ9-Dt6fWSmOpMwZk4v=dh^g7r2lQ-uYK@KA^XaLMD zt&Qe?2&0mq1Y+eoL0@yRQ$G0T&!3i&krD102RAqV+w1dnP_XT0+_Qgw>g_k4Q`+0x z`v(Q_xNd1Er;96r=7@-MbawVz;yM&VDmv@X2n#1IcSrJo%B|3211XZ7iQj&pnDyf1 z-oH0)^M4AzJsm&U9v@U^=zNr`sbz1TF4rTq18_85(9G5)qVth)nLO67vZbb>$!sgr z250<(=>svpEinYzcuGhs8({{9P|Y#t_Q2;Z`*W##oyQL&N%=ycO_za*JeGQ6g+-K^ zSJ5#sn=_YN&IOB_t4Eoos$st@@|SV0=c_ zNF)Pw$e@WrwKJ=3ng3j)m(hBCc{u1hUsKYf+SQcKjRo|uBEW)hthL?7j>L+G|BChI zReo`2sc?!(&Nz8zo@2z>zsV+GPk1%WvkY3Zn>q!6%P0i34U-b`(Z1Ta^2T>P3#Bkpyt zp@iMYuW<9+60O3+^Yap|N6ylO>>uB|xmD#D60v_=xZZEBmd8>N1r8rW4C-9+-D)+x z8R$n+(Vk$8CltKFmx1l$u)D+PK{+#W}1MB zu$7e+9m@5!BuiaqH@7Y%vhbryPFNTg^1&P3``dk>)@2j#AHcw8P&5Yp34wB|K7OS5 zRo?17k40ZgH_#n~(61hD1~Yy3g6VuVgy7_U+Kd<1)YcweEJwz(eFw3q{bcMH}RG_rL95v3~%TnMRhB zqapNkcY}QkXqlNq9y~%@*xpvSKHE(Mu@IYH1(?PbeLo&WE8E+DOG-r4-Hs#C_s_iM znVHID_5p>=9vFa#? zlRYdFAF{Q%y!>vy$@_9SG9*{G=(CtqXIonvEBYfyu5xN72$R6bD*5i6kk3ucY9&ai zE`_WIu-lw&q2MP&vY^WhVo(B-I6aQc(%?sOO}A(I$tX>5lND&PfF^Dyhea-yQ5H2<8#6&v% zdqX;Kf5Qkms|FB}s5UIC$jj5v)91966<#S8=VX@cHykVfGaFl5*8KeZzEqKTuf5uC z>s&)MF)%3oNeaL?A^fjm0zMm1i;qBHn5xWZhvCKvzi z-XaV!Pr<}=Z!F7XHI(Y#dStSqK(o>FyzT7hD0SIXmkB4;{8S72-|qHyzj#B>9w#PN z0fFk_`M2-XIq=^-l|F2IYIlQfz9mG~z%U)DunL(b0iRNf{9zPb6`7LAyPMD4&@JOJaM&4N6ef zCCYRfGXO_j2N*<2Kzsq>-&N&x06Z*F0yl?F@udtZX??JHZ_CpZKfgy3wQJb^e=hW68->&n^I%RH$gh`j{^*>F7!3If0o5H*4FEaB{lLs;(5 zHD2Bv50VHu(WRxOSyk$Ry3s*+egn2_K)UPxYP{KMzDaCuZtkBubK9`4OfpBt9Zr}= zE`qBBcOlZdSsq+7E-9(CN0C-x$@U-5^?_8;!w&qHFJBt-k|uJSW6I~?L7`AWR-Kjt z&BB9&gBB}ZZqqI-FdzHi!2_^HC?w-ukX{qa9K`Xz-FEfBc=ql+@E&7tZ?C4V?g)f4 zsG5k=@P5WEN5|nQJIcrS-_uGg!7R|Tdu2KXdK~<}ZR5c~B1vRDaaB1j6WZlro3d z5Ag0bHkJ%+KwVNlr#8Nu0u}@Q3c=P++>k%+l7$&>;vbF*BlVWKcP0snzm`^v2LS|F z&DY;*#vNs3eE-P4em>KdH|Y=C>uCETuqxpZ78-5s6XCE*hin6ucKc*Sh9gWDt5Ifh zUS^U6Mev4sj~TaG9?$5O9i&p2%1;^k2Fm$Uvv9~tqmuh5A6JrBivHZ6VDq%nM`&)% zZv*A8mm`Hx&(vU6dmR!aR9x7L0Z>a(fSLentSDtY{eCM45fzM(%xqotKcn`4tS79U*B z%}zVs|DCq}m?maB?F|sa1c)BqG$0$b&Ul0gDZ0y0$sXsiJ+7)BK)T2*zQfp};E+_} zrXh}LS$1K6TXWT^*5eXhe}OXxcw2{89}Z2cJizr9PNRyy?eXdq(Fl-KTQ1v@oE*$E zonGUO#K_;aeo9UHLWChOghJRe1Vsjmib{M}xRxO*Y=L22353+n-^zGe@Kg00`0F8- zX5$%u-$WwFh21bAF+=|K$%eU{WMewZ zG{BNoKBw%$C;(;ZOOu)g!rL#@u#=^8iR#7W)z%Y68E01E2&4N6d2pOL!@aONO9(>W z|3Rt6h&=%A@Z2T58W;M2((e$Nnc2v{3lIpu{q$f&qioNg-qzXv0J-JB7kC@m=7~8J zp%JE+TWq2tn-2(Yt1@ORUDj^s$82Z%onxrL3NL~50OZ&Uud|2B5qeiA8!fZdvkzPH zYaYAp2z}MdT01x#ardg@vT9*-*_{|JYwQ(o-vWwQ{H@5XE5jF^Oqid=mslRyiFth5 z^2^RPFT06kgMd=vgp5#9^lT3*y~$DMf~y)C0`?C}Bm^iqR0P_R(Uz8WI{eP(fz`GrB2Xfy`8VcF0Wr+LdDII%Yb2!dO>(*Six?u1?ZyS@EG!iV(>dss~I6uwS zb;pf}(5tJ6q!B(7g(yd0Lj*;H7M6A}eEqciLd$7ol~bs}1x1~o$YyYIJOZD5%km@i zeI%Q?IZ-8Km}Y-*?fGJk_piE}(yW9nq`&R!fdlNxE5W~!gq`s=dX*TJ6k4xnx zvdZqn36Z5=ygw5XN}VesHplV_&?7o@nwv8oVH_N(bsQM>lLqy$7zkrD)v?vhRcLApDYlJ1Tvt#o&%v~+_=vj_p{ZltA~GkD)? zpB>+>@0`CDT-RE2%@}jc@%--lepYhDi5KX!qR!Fmdz4bSv6ISsLdmIz@IATU-_ zef5NoJ7!Z=v475GuWQa^dM7uos=t?!caO?cz1kK_M0YzeTk+CrmcMkN0iJk-rHW=% z%80y`-8AOKbV@1_{g5_ee6}*} zuRkXdfhGw`8s0(&tMXYsZBxS!1WB+22rd=V;Mdh2Y>&60@eED*T%sfH$s*Hn=7bF% zKBY}Bfnw)6Lg#Q5LT9$~G2-8as{Y>yYZJMh{Z+hTS+Ba)8NNL;5b*H*{wR`Oy@o)J zQw`zT)2^ga2afVdaU=n|A#5n1IY>COr+Q&f785obCbL-3heIlYZ<2>dZU2p`_W#`_ z3oS@OEE~TD z2&Ha$yVS*!v2at!IjR?xedb@2gdT~z_w`t7yaP*HtTsnTA?y9*R zZ2Pc#a9=pX%+$(+163cB9G^X{gtYy&tt8;P1nbKG7|A_RiX%t+HuCSu(W2OCxOgVB zl{Ky9!1R4Q$F$a+lxmXl6}4-W@Ey@^W7>+BsCfcMs9fHwf7jsEU->rEp08=3+x zZl1{)hEJ)ku40-cnEt{ajy#TpZTUsOKy;C(^?Cd12`Z8i-d`SS2>gLS=cj-F;*BHB zeE;$TofIFu2z6+zGfR`ugq)5Kt1-1CeKR2L?+IGJk1UkuUB+jBP9J?5F zS1};YZn(&Rw^K{zg!uj>?20z2YU?rHOiT$P=MD%kA4GEzQdZ=LxMMx4* zuc8CzV-%8`@6*&aeaA0q68HCtY{3uNTv2)quqUrf3|YS`CHnh;1(peOocavKYkWgi zo9{|%yg}c<9$imF_6k|%i)KbdmyX8wY-LWGnAK|sWKYYYBH!-`lB7S8c=by=*T2^F z&$lfYqI+%RizE$SfrEn%a>(M7H-0a3{H9TDTg*^>jfeh^&#2Q+3svxZD4 zf~Y~8SBS7rDV zUnigQqnV<3q^P)X;mU|k9DI31uUiqL$!>nw2f)S%Ha2!5$B}pl3SwFFB`bTO8k2`X zxXs?ItM1MWNhl>U%*2H1XS*y}hYtiUk6B+%-Xyhzw#=_*(K+y1`R|3@&@9ovrbT95ojbcQnQz_O}moEEbkQ{S1VYh2@24IgihTN}<@ zh8AnohZxHw!)(?h$-!oY^T#y$5<|p(k})ps>>&L}<#qlV<^{dtYU1O!wXqQ@P{y$X zV^JuA-AIhN!|XU`E9mZE`+4eD^-yB#4IjDUZn-o=U@?Yy-2S2VK`5GBy|5j3=xc1q zQcRp7xVgZ;)vWu1_z+72NksRX8mAvc=A%!)RtY|Sv|_v3&;W@j+`9ey%@Z04Gvl*L zD<1g{8@{H8SW!MlylVBR_wP2tsNI!zT{$T&z zYcev4_jQ4k8i+!GOnFa(=YmgEmNaSrZ?Bv5Mb(QSIZ2~ms}wMcM846%NCCO-J(&{lvBPf7UJ>&2>Q5^k>%dO`Sj5L{J~`J|xJ?b*0cL zVvtY2zb(H}JGSY^kUs;C8ck~SXCoNraY~ugpK%nLBc2qh6?KKP<&U-~soh?ilj`s| zuTG7Z!0&F_${Jgxw-Lq(h3H01& zOmcEO&Ras&g(@Y7Rd8y@-;VL#DVD5@%sW= z)s(njy@EH4j#g`o^Mk=%Wj+%ic$Z-Q_lDc;S~Sk}fxt`l5lLnMELhj!WegLDx?AD3 z@$o8&{g()ch?O`Pz~g7M+i>#6YPLs=IRy*LWd0hNi<>(ZtxbMwy|7nv#bECA*WKg1 z55AWjot;Nxb!f2To#B_CA(~NRX2(nE1}7+O)MRiv#&SA`1!mB4okY6dA{RWqTXAE8 zgW5-1>2>MP)EzpcH1B@4|A2XOU}CEJh6HY6yXkv()zKj;K*)ysc}+_AYZLxp2B#hs zBV(GL@ldZ4uQ=AO!CX?T^m!Lw=KgGTa%_?6%qpS44nOAZg5`-Tq^wuBmR@r3EAHU( zaq{8s!0tpvJPgyzJ2}3NJhFD2t!NC+v0;$f_MyJtMJ(328t@kb+$a&Jq z-m>`y@GeJ=ljgHM`G8w%=Cm~8Vy)K5b3YejnRLmOu28!z(Uwd4C(k*lWYY|X(mY-J zk|XqSG1$$2XBdZWL2*Ovah`$U#3I=enNs;-Rc2ENAUYuCaZL(?7_|Ll^WinO(zF*> zqjd@*^DoX(Zmv!rEM8yOBzx%U>M9p#Pi-B2i{2Vpqk|=|Ubw?>nKY8}%LN8wE>#t+ zFio(uNcg2Kvbqj$`eTz_>ZfVb?JHIL$||)s!d`x<_Zp*w_`gdUB_CfLtw+S9glJRN zHktAz<);Kc(hI#wDcAFmw9qCJn4gCMvm87xG+G>Mt*8v)t;Xs+f2DpMAt8L;^P!EM zU5@5hKDAnD3dInhqOecDGFm7G1_na=9y}0zCvLy7rvnaPqkdqrj;rUF!D>7Zo-2S6 zmye%+6Z)&oX*`h(|HEd&O+x%Ob-MacXJJLS;1P-^WAst`xcC&yW$lSZ-iC2WjMj{Q zZc4CJ+F|dSU9kKURN{xicno0SXi;;s-x~i#?u$uRaXL(%`ZP0Lb4cnssJ``?8{I>_I}odKoX3V5^rlm*ZtLk zQE<@_f0dg%7rDQXK?k}`zRnCs{pp&*F;BBhlB`Ah&%H9t=* zE-nsocnY*Q)HssxYF}TGmMu3*GEwRE@tE*%EW?gxaM7{}?6jqomH5C2X2i=ggN|;J zkxWnCtgP(Bi63MeZUJ5vb1E^tZFZMn!^*TP`l8FfN;J0va9mC6HUu zf0q!3fH=`P61en-9&$%cGC)WNrgp+@PKzw_vkg=EJ5$wCO~fA^wi@DCJz=!5bZm#w z;pZQ=90aZn$&<%R-W7NpG~QiIOj%4fbrwo*`Up-|yJLY%F;=8fIV^*uTqp|WB`$E0 z2{|CkrPv2IC0kmpAF@?$!3{6gj&kC9dqE76OhjO*av%-o@bg0gDFI=_oGDFPp*yeN z#R|vyg1CNQo|HA?W+kB5yP=QvuGE>r!{BKqu9vnt0dbgK^2pM$rOgb+NFEtVv{c>j zW}Yj2rCyqX$pYnXyA@Cougf*x7y=!%yrv*9!{<-X-v0g>sAy?6mJ`-IOJu`^)7<%e zc6tY~x#`^a&WNPq7-|)@&vyP2>3MLx0FOVZ>3&lsc^Z{s-7C(>NHsJxwDs!vhpU?_ z(Wz!DsieMWI!%7yVN0)F^~Kw|DU6=%z)QN{60Sj&m>~U(PA$1M^73pu+6YSZbZ_n{ zH|W+Vx5FvJ*i1L@Oe{hNolb*tgD;#$3~?EB&A#TUHmb@1*XhqdlBRGzk~X1WYz;+8 zw@BGu#;}Xz9GDg5)?u^R7b)q?(H^}Alb7whE)DDa@-IIJX0tB5J@&dbcx*ye%IvlJ z%Adb3*Ot#Zj z@MS2kZ}=_@-`{B?i@x2KmX=)_TGK*p{iJ()dF6U&+{&n2s6Wrfo=A>cI@=ei^t1to9|Irv0O9!3NlU8#oj#gAGk(4E9WfButeMJ)s6?9E zgcLtU#a91{{!A{>(bc)Mv+9>^FwVqqLCRhIDRv^a$>VI#A{0@v&hL&x zwq4;b{M=pf@`iNU3T50~N}TgQ7AP@BgW^9qv+Pu~K$#i3Fjlpm?>?w?Lp<0xWDpfw z_&EE8^HDW=k7#sKS5A7ox+>)}tXmel7l;IcT#3>F&C_9pDiAF=frFsVx2L>DZ>2v^ zUJvytSDPt!olw}LGEZRtfd5DHr2*B=+xI1}Uidv9N}Tg~NF9p~7Q{y}YPrh5%I}Q< zFA-m1h^6T3Xn02xzqF)o$J#e4p`st0vzX+)Bj`3L`DItYA*rF-r}!{c07@Lj-e7iF zeJ*fi#kj!;X}^mi7n4hA_lFP1dBm5^8+M%QG2}ss%2=|_|TwYSeOq3P_D2&VLazXn__ZJW75A0elMfckb2i-pfrERPp*HV|!yxI*j zm22p`cXN;^HH>Sz$aJPjfaLLBav2LbW$r<%f>J$%?}`Z6PreD^(xyJv=kRz!pmln7 z>7JLf;cZS9J_WfR6Y+;hoTcqc(KV#lptyYv>W7GOv+qm~=t#w5iPD8r=2eT#j9~E9 zQcirZKYE%R1?I6s%^ut+r~5zqtpkQ%<-DNd+Bh~LO)a`Kc2HBS4(F(O6eD2xA2So#I zXs2p94R7vPNeKl}bg%I{tFRaUCuyya*i>E+BrH`Ey6VQ($-KTc-oy43N@e;h=c`w`XZ{ERTY2QW+Zk!qgi zF_$qjoB~rLc<=jH)C_%oVWilxKk11|JIq;d%M+s%8OeGETHdEjYjPHK*O@;0wROD1 z;}XO%G&xPYFW{17c@+k99G_&!e4VgZRG&gnTPKAJ$BDqi6Hoeb5k*nC&p=*`N@;8%I@gF<|KRA%(=J=Z(`iTaWd?EU|AU9?|&X-BT#A_0wE2F_`M}b$9bEq$${> zlM1wl@k&&O;^sheg&MPGKRb&!xxCh2mn0cChd#HZNm>w|IBL^s{ZOdk8UtUC&UP^ zxEW`T7kgRO>~ROkmQk^A`y+O@)1QG^fyc;OgiPXk~6cw5zE6+*I)XpNKcDT#qI4u&aX9dB4&d(x`( zOdDFBtTzK2zHz^fX{^B2RXEzWO_?*y0sO0W64?1g-Dv(*B|z(;Ci_d3$9v-qJzKN( zn;52lG{>!ZJ1#A;2L17GRjqx@Q4aq`fc1235YP~S8rSv1ODDL4cI{NvOg%;DA9f_B8t?jtTA>3r;^;Ir|8-{I5EhA6eX(RiUmD(SROz7{okOL z%GRFBJkkb!B;g{qMg2P=tnd3h8W3hGs>$z0CLE1aR|*E5_Ugo+8q}sHt>S|n?|<2Z?y{{@5UF+ zMn(~O&+P~V25I?-V*LetUG|zTMBlv|F0;1Y3hjGEk)D^bEc(yi zaM>N>6Hb(5zP-tRq@$;+QDsO#Cgy2Yzc-!B2KA@veB6D6YoTGD(Yv`#iREVW{IqSc#GWCN<~>MyXaF=Q}M4lP5ig) zO+T_Gp8w6)gWo&Xx#24{YrQIzAy5q)g&|-H$ zXtL?dx(19b|1Jj=<0ygu2$`?{&vmF6dos3Jv}}0 zUQxh#_U2lL_Mv|9Y>Bq1DH^SJN3{!b#sGnX!sx#AqW?q4VYO_bj&C``CuLU=;JD3M zVRa)mUaTz!iu_k1?{%6S&{9=RXiQjNpoQ0cuB^m{0U-QZBfESSIy*h>8kAM}oDXg> zV&}g8AX3$ADiyV3I)8DA;lwXvn@16&aeH$O2;Z&RQ5<-V$Qx~=T?q>&f|)XuX<-RG zo|LftW;VY8!dqidhhAGBXuel=gA3Ot6yN;XzsJDLU*SCUM`yr0&*4e7dnLVuqa%OiCE< z`(pUQkxC#?T3D&E=}<%hB+Mk97O^bueG^TTQ&JTa)JqZ^4e<@ctORUly4tcmNM*ew zJ+L;CmC7hl+IZ>(tKlXEC@Cj@^IkN51yyK_QSvs#Y4Pzir5>2azkAqH1K^>0*8J~b z^VIctdPGD-Sviob-co|a=d8rIK~#|Sqdy8EQ5~s<{5U$hhdMRRWq4dJo6LZ{=Lq|w zi;C%j_Db=WaudE7b8kW62lKk@9PejiVHu_@kxiYRs^N4W)P^b3La|0ORME+t)+`{P z_Vef9gUQ*UuMU>ONwwgHpb7*vu9(DY(vnrxM~UP)1P2WHeYI%j6N9PehO7A&vrUOV ze$c`&HW#oo#Q1^q_)p+uylX+BiEK-1@Yhd-SVmjK`*>SzQoEcJ{q{MU4h>o?YI)fnX2^ z0uQ`Q@*CqHQaADi#G6&*63gdbqb+7ye2Yt*C+X2Cun#zMA*Pb=w>W#uNbAr>Q?l?YP=UwBrpYj~n%{*EU zM~)pzLNT%Q0Z0EdQS%tOWtljHo;e+#f2BV{@Zd4aiPe8EAz{=XucuY|u<7?gS1VOz z%a0#FERfsx=G-+Jt@uL;IV+xGY_zv00?aQfD+@+rOv53!hL*!EI>p#&FcTm1O0jN9 zl1$H+x7ICT;6{h8FYp-BEW1}}M)9x4xRR<}KtiHsy@&x6puij33f&xH!|qWYn@Kxt zGcj`_fz=WPFryqmdbmB;i)CJd>y4rh_K6MXS*F7cIyHjm-WhfBv^dHKN7+s#N%VTp z`dXJvG-|k>vr^uIj|z1;ZJQYM=Z(VS{hqn9q<}q<)z;=~?N5u&uL&cb?rvbGOFS5u zh`UrPt$^5(N&W?f+uGSx^;NEs99eVN7dhPo$oRWqh9l-O;*T76T7OmkZRu=r_rvZR z)GEMn=Igut-MEugv*wem;i^jB&~@a(-8r_u;@e!wTURrYOy)8}*4oe)oMt_sRV|G9 zVD}D~2-j&^mmy(QbL@>guYHYiCq6l-&yX=EBur%tnW2 zFrc7T{Tj+FR@%~%sOL_ZYc5+-O)2@w!E|0%zqp~mICF*U=wxqaN4|bJMsx&*O=yJz-bJ#jCYfq+Cd}tzPZ%Rc0EUPspwjhSOfeDvTkq{=KIb;KvDe;-Ms}#Wn{{u)_Q1furZVQ}G?)87k5{@jT?=cBF}dP{4%4 z)&Q$FP}mwwVj0MC8ULgRi=ZoZZeYXd7BkpuXp7f#A3dA{^M2xJo;*Ny$sYfPpqF&v zH1vRBI&72TaS^-vd!u2uRZv4NRMH>21wXT zz{Pv)F|0XJW3r(NG{-p0L^APk4a$ATBDE(mA1)s+?poHIgP2P-t^M7tC(OgcPVYoE znf2@aBL(PMJIYRPtbQ}YJxIKmLuyf4BG;Ou^JTGdUOpD;E(I(QGJQh>;&kawmNTo< z@#4eV7GzAD4o2{@Z@-a!Ss%8^KeGhO$e*O~@QX`0Q}GA@Pn84E&N-Uy<{Z{%cDnc} z{I?)5@EL zFdwgL7qsTeR4P%TTT+GoPj()xGz-YN`-zEoY+oz^bkQq*&FsiS;>8*zvpuf)j^z9v zocD3bQAGbjhQ6Dh5x+@#4lcYgnKS~eN*2gAf>2w^&_JK)(KHLiy~td(qK?*k&BVg9nm&CBs@=%R-@d3WkEKnKdV!Ufn^ZL))KX)I_C$zXZF{Ej1qkv zi9?*&jub6s2R6# ztf_{j1PPT!D3DQq{@|Q zIq!##t_72Ek|Kr`&t^F)%{Q1ezXi?b%^1bzfi-Qo>BCFi{h9h-Ccck?aQ{K_5xGsOV_V!yV}cb z?cDaa=uhLb5-tXqiFS4qDnB7Y*8D$a5Yyn9V!!@iIGD|S!3`C8Ke2_+Y5CwPJYcv* z1@U$E$H|5bt`+geKbxB#jc%0ck3o6-P2-K&c8MXMgXS?jtEs>f-~!HB>2@R#&^ku= zSuoz3l=TvKMXX63qd1&3TcaJ^; zvoDjAslR54aAw5lt2&;EtO2+YV04+-8w4ER)gn>-XS;~KXwaMIeKsl`bf%M5M3Gfav$N8vsM`Cxi->vcy~9eN7n#b`Hck#1t}8r50;Ytl zZ(p4=DN-UK5)`3aJ^TL5PPzB5E#tXarm9LM#oP7&d&X{|N@!P~;>56JQqQED@Am4{ zjJM^6CFY%Zjs5b@y9UVFg;H+?xGsObE>-03u^`5CW_l`^{?OXkYkn^$*F3U9y(<;1 zRa)MtlGEAH0mH01C^&K=k;8<$h+}t6_oH3Q)1pJg;>6pdNW!AGv-e!Y+1uECYvJRnMYE*K+M)~Xsh%zGk@VrLC7#br?R@EJ=&rU>t7H;NR<@WcKR%>2X3KI4d_|bGytQB8FX( z6!87%2Mum1hj^>X7ZFzlCG8ilcpT(UC&(nlWfE zMJQ?gHMq2)uM;2XKl=Or?|A*;FRh~g1lFsS)1+fxg{2r>wM2Wc*}V$GJiWf&&@>*@ z>fC78?Nz$C5pSQYIYZKe4k?2|z9RzCnUgR0MYCWm32H4O36g@yDB}oP%M1_bhL(0s z=Q*O_t6Y;s7y9znhOD2SXoorrB)6StIWzSA$r2@vR}!py;n^Vg=rcHx(U5LXL95?& zW@|~bZ}Y&ZwpjzN9#d_IZ2`Rj(tEj~>1R^+1KL;>BXP25_4jd2_i*WE!+No*d^?<& z*Jv0j7Nb3PS^T3L1~|0M$)#lypQYu7$CVXA@VLcB?#9gDQNJaOor$q6ujF;P%DYea z;<5pwO&@Gp+I7+~8byP<+Rk0w7q^x>ikf+B*m)@B+ybIM#H-ht5t1$JS|`pvXyWg) zKXOW0yIkL`!@!n1a1wH%LdO!*y$CbSa8;moMH5_~?;ny!t9A@p3{)D`LMKms zN|O9+{%$=EORs-lly@;Bh5)9>_Z@50BO{LQqdE%g`4IE#J*M5PGX62jpI zQynR82?RYR=jV3ZKtmCVB8R{;50;^+|F`aRqCg=Lh`@Dj{3yO{JvE-)1v<^FlDP=c zy&VC&fxmL{G8JhHE|g)(PDF2`78mFy>apF!^S2YiT`W{vIke7*MDi!@pRe94AKsLz zkxiTeYX!o8FAGHCLD3)F{!?K9$^Lx)UKcoR1kxs89Yb^HSIUJEmA#tAXKXJ&!8G5X zFI~4Pc(Cr_!D!Fj4h|o!ufVLkyDHBPG~ArcEtMv=2Hb(*-Jj4nLhE^YMuH8b;=Pwi zET@5ju!AGpT8jw~JP3fQ92mB1Uw*u)5s9;FU)YiXj~oHwk@&TLwUB$1lO@y}lWGP^f%J0cIgYy{(@>YGyqiHH9@z zHBdp9!Qi zfI~~xW4PxMsktLFo}%~pq(`Y1i5DoVpZ+B={&zwGxx^>O@w6Ur->?1pY9rQsrRWXZ z=hB}XQHUzI+h0?G;p0jhkN2>)2`K7QE+42Ua~CxSdubUc*k6hVTJWV;yFEvTmM-XB znp|JMoIUiBHV(5q=IZ3EnEKe4P#2cg@}p$tZwS6--DeHf#D3$C4z^A?XZAHk{!Opr z;g86le@2L>qt@_WcToo7GWWEJmbfZJ20Y7y24whY&nXAdJHaZ01amLipg+?msyPuzw}yXUcE$U_ zSD4{-Kb9$^3REg7%bKniH~T@ck~eVuG`sDGc^T9q9lAC4Xkn5ph1WR^@9Gpl<_A9j zoj>?Kidqir{Xt2r1qRHL&(yH1I>`SVGCVKdVv(4Y9C_-f7py>g?J7DvQO0I{i*Vom zOJa2Uq=|o3XT3jFyh2Rm#B2|BVuE7l0A_W?C!?kO^l&545%`VeNCHXA9S0n7BiIM{9}$^7Ca->% zs#K~^--i$Sri0q_qkvMNl_N}wUHN19BXtM*s)E<05eJY0vH>~<9^1#fjSo$@0vR=h z_q`CmsnX%MN_u0so_XF*K{a!Q*MMu>d#}NfXpMljTlatSyPqI0o~H7K+|4pd0{{O@gEsU(I<)^ggSJ=h0W}Q|cKHkb z`9b7#_3(5)kaeDconMbCAR()k_a3bj|A}#Se3#2w%La_**VQ&p0a~v>4QP@tvqA+- z{TGzc%XVLfpAJfKKFX~m-1)Sh9eDH&G zS@an+=Gxj#Ygg9x>)`hzNK#UWN)BCI-RzbYfVH4V5(6z>wB zU^qveBpjNuKTnV8%jlJYO|rUD&#$FIN801m5&)?y4UaKm)m#2OIx_l`)#|d*jD-ru ztsm$Z+xT@seQ%-3H5f4HC%8_StkP0Pk`1a_83Ol2UO@qv>8K^hbJnb@<)8nPkqaxp z=(5~MNkKj!l*B|f9~kbhFD8T=9QJxD0V!a%`Y8%f`!F!?8AGzkpqC%v5#Dv@hCxeT zMGIg9@Ut&p9{JT9SZ17*l$0(`uX^Lm{T08)=ToKfI%a5&6{@;& zT21$7wu?g*#T#&zbS;9Yd)eeOaEu|Ls2ZPtKeOyJ9G@sL#{-%MJ=`t6L|sCClKb*x zS7t8aNs+tz*-M}jS%Oe`)Zj~AL*A~atl!8YLF1xN>+n|zC0JK8<3|6*O904t%Oc~) zYV)zx;2|+K;I3{Gye2!{pZ5j;7y^PH!U3?A+BkF4<;It(8!62Xl<>jC!(LKx)nS4d z80a90QzwY<*3|y$)N-EqkP-NZ5%hrqP=e$uo1bk`CIG~a>Kq1OE{E=#dp0A})kjoI zbm8Bz1Z%YK9nZidI(2Yn29!&~DJQpvIM*Y*$sDi1O<0Jms#>0J88abhKhQ8I@q#*YN1R^uM1aep++vC-mA1KDRpzo&7((&bMD-x|Ne zIcmwY(|1(=;&aPAzSm@BBe_cAK*%z<5=;jDaXtd4Z=EB&m`z$*dbN_p;DLGzc}!36 z5{BE6tw*)jP$drW z^fTSo4@zm52V6>9KBO{w*KzOPjXC8VCGC*Dt5qym>`ojdT^TR|Zu#T=i9p6wo@d0i z-oUq>G5Tk+!ZuaLbHAB4Ci9LkBtQQl(4WW!NYgY%^GE7zvr~8?%LE=F`F{X$5bg7` zk7B;sx3|a04xZeV?b=sgcQ7_N zL~zxae{c0$*?lauAkb_v&EV$xVg8~T+wR($cp zy%nhB59&aJY#89!toefE+OxB>--wcuTj#N|y{s z27Li%ls|2|YsbwVh#~YF?PTAS8dY1i-L*k7BxA_P&TI0LnpfO<2)TLINs*7OUhM z;#?ZI9SvPBqCbrNlWx2I7j#>SD{(NDXL^7>&_M^z3HuPL8POsEk!5p@y6PdcaI(_^zLx|azzdkn9*veMyHQEfBYOQx~;IXwE zL`^MX(26z1&AS$ge0Z+D{{%F}c^P3OVBZvR<0^$x^K(j3^R zZl$BxQUvK#-y~%sJmHq!8O6(>Q_ltL+n5==TC;@FrrF8K3DwOPt1*LaiVj%<5YYex}m*7uC|KHdcqFdOH+(W2sdRP;MULp5d+fv3B$Z|da; zpwGPlQ0yFbZ}}c?2@dqBn8Omd7`HbcOx-J8VqS+&1*KC51JgqcM;oeDgZ9&*)PcMK ztzxLpkMZ0W{E$V)aE^SlCOhGrs8FsXi1B;@-G(YZjN3WW&>Oid*1r9w;%4b3pUx5SH%!p+G;#lxo8WDEK;=_4d_lV>E%W#M zOMv+75lz9LYmPrF7Vr%`P;W0+^G*UcTdUVuq|xgMvy&!4<2;plHQgN{06{j?7pg%V zz19%hd8J>nNmB*UW#eQ+p=cob9599{0C>fs(E!{QAQ&MLf5`r|m*%?O$+1Vxt=t=Mab1f_87%+qk*f0f7d+ zz0`74D&7oHz;{@2x5-UMT66WwbZZ>FqR;vA+UVZry%?!$aK9%KJ9$k98sVkEqiBeLuwX3=Z00OSu<_&HUf+b1g?-=7j zwt}+G7>7}O4r$SF@?Bb~5)M|aU5_CvPp#?C^3gj4(2b%eSVuEc74&&s1@^Q6^@ z8bxFFn;otJk9SlIwL}27h4S5j*YMiaoe>ot_!bMy5!$iK@qif&N7n1#n1=-)B+UmN2; z7A`Ee8znYmC16iGG&##0jq|t)_ka3azRkEAsF|h{SzdA2UW$+%?Vmq9UV0UgD#@pI zM^4%;-5*#b{N8Ejx148yuXY?^YdZAZ+~FJ=AJT9pz9CFjYdsmj>J-5wN}4ImA9q4 zTtq@ll#zfp1RltVPchIFLxeclY2W?5z@4NqQnpo7!aqX>7W*SH$4UE1zWQ`N_xoFl zZw{`p_6QhJ2MX5@egIo}Jppzg`P&)X6K=;nR*-%Hb%@&*LP_%2Kt;(8kezZ=MxR`J zHs4z_t@G|WTgYmLwd|Mop;N?P4W|4(PdFeaESeCuR&x^DuCH~^2bQ9r9`BpZOG-(P zXN+xeyY4*$+gg#eKv*%M?^n|cLa$B@`rzxfw6vaNKHpHCp7r)UJME>5-A+HUSjb)M zsR6)K3ZrxkJSIKKJn2kusXMU#eehmw`9rqwa$&a_T|4?#0Rr-V*dkKhW-^TSG-T% zQlTHZsmB~hxP~h_?FjY)PdB`}7$^fu|0O{F2R}ZzJKf{sD?I$Qa&8^BgQoEciBxEk z1ueJ&`zccJXY|<4T-9c)YBi}p)txi*xHE3H_0_oy$mS&_uc2iWbZQ*K=yx68{a`HK zLKvXUHYpOS1ZCpxZKUCr6t3|5pQkr`;X`C>_Jb*$eHVjGCKActR5cr2^D9~hM;C9` zX*^xKWj_s%R5!Sfd>TmR7r~n6o_F#3eQt#F77G2e0<)j)FV{k$zQGRnE^1A1z{0=)Is+(ydz9B)te3*CvOq`rcr ztqF}1)r*TM<#n#Jl1L^Bmf=XAB)!ScY6xmAQj8c;hLxJXn;kPgw%;&qVz;Y{;~4cZ0V{fR!?5ppgshV`*J3kW!Btf zYG!yZDU!#a!s=w_zU>oQSG5500`d5`)~bb-zn?m%OL&wtuLh=D7*>SBQdBrC;oNr8 zy)*=ugQ3+6au4gKwH1{lOoGvC=wA2aU9&Ob#Bq)NYBZdo$9?BNiGyusUBg?9NfMDk zLC0aX`rV5?%^dG#?gLMEv-O?6||nCh*8vX;+c zQ56;8TT@sIG3j%RMFUxHJ>AVm>c}M&b>C)kc$SMRQO>Fx=#Ui8l81L^3*LBs{d*2) z;wzASIlWbr9B4Kkd>2EmgN>bAP#YRUZaLeaWfW%TS!G-#pn0^v@Q7t)iAHi~?wy2J34&3kLK(L8kGfm*iIGD2;@fg2r!vq2N6g&8l-7_PQgs5n`SJ5_>m-A9^d`zqO7f$rMch?`gf~MJmUlAFU3z!)c zI+1vF?0%h!uGZP`KeuA+c?pG1+B1r0e?IAEdY!g+Eil8)s%iO(*D;yt->55Dm0VbY zpH&{8NuYjjc5Ek8kf|U))~Tenb#v_fsBIw#_jG5L69?yMUOFF7^}IVKyXu`^1LQlN z`Ez)~iszRtM zhElXOHCoXkbs|U|RYMv>%^`NHgJaedZOv+yiW*Ziv{kf*s4>(Ks!BqnMT8K!+gjgW zcb)aE^L^{Cwg1?A?ceyk@AJOz^ILmuQ+~ew62o`RD4vm~$JWo`MON9FluhE4P3G~+ za;P(~Y5j>~@=}CxwEWlU4T_Itn4!Pb+_GlT)Eh7s*#%Di;8Hq-K{O z%!7ggB{?n8*Q)Md-ol=v{iO-KA0w49s>q$tam$IOAnEV58i-Tp$Y1$H#k$L`9)40f zkKD;BmmX8)o=Cq7kU-ib4#tnkTq2)^R-H5z*&zqMU<4Yc-q?{3DMN`@Y1NG?yzc&Q z^s%N*VQ$B)gZ0|5GBU0PqYZE|4nVUl9uPcL`RMC2y8Mc&WAI@9SZ@B9Omg#Az|R|c ze@04HX}U^_`&12;s(<^`Rs3 zMV5>9b)Z2G&MP3vClA;SeN_}Ld`1zH=ESS(?p<)hV$Zx%b)E%K-66AF8P_I@$hUsM zqnl1O*e!;7`mRphLfQ+~2?)Xs+~lJnYjR!qpr1(Nj5#}Y75$~`)2s9J;PKS($)VQB zoLNmR4Q7v%seN?+5fg;p%e|r{;!X~qTx15k#3YOqpE5e@ShdY`q|*XvQ#-*=pc{;H zBm?z+L2X1>8=wWBB$uqUZQ3N_OJ{xx6u!M*(30oikbITAeM=wi((I66z`ZsV_uHgC z!tL*|?G%7^5Iz32f5>Ypb@Jvgi1H=!3hK}z*Dj2mkl1EpoL2hu!|4MFycfr8ZS0*x z-EurKin2W{g@ck=^&~M97S@D#Zl_?Y+;7+R#dxa$V6!DCY1jZ8@V}oHy_lM-c}t^J zf%k~)XObPeuj&kp?Zko)m7F7r+^xnwSK%{#uE@r+y_hrAk_2rglv%BFL*2xAUk0oX zQ!fmNfcV`II0LQLv-iylNgDLw zA}?3Q>?3uHIrV5*pyNh~a>lLCg{oQW-g7}IWe(#P2@|X7H(OR7UDzwqtH2qgryHlg z*0#2mw%cm(X`+dpu)HaW1$~6$#D>I%Y-LOW+?!`x&+@xaz}_`FWA}%Bitb7&1N~&hc}>*<$4` z2P;&|H`Bn_ZEnL!yNsO6K$G%$Bj>R#JtMP0W}hyZG<+l2i0wOD6nfRY#f-I*(5eBl{9m_|eJ=7)EuEK?@)L8noB17?ImCd8>!6F# z7&>0c@HNYsByo%NKH__rU_;%+R6rt3WmVdRE-|_0vp`mG;;=8akI~~2l9*ifOOBB1Pel_GAFx37_j(c7dkMU5*L9M?X2PF_3Dy{*<#O3T9mZ=t9PgGhNo}w z0g^}3sf+fGl_!cH^e!Qc%^N2 zcJ18>n!fLju}SG|udKsNhJo@SVEm*D2%##{rhBL4W9MipKO6q0Z`l}mePFQ4XXbMb zObafMcy4O^uOP~l_IdZWdulM1nNj9*b=RV=R!j8}`_xXJ^ts+QN=zXX-Bf<0QlIp< z;dt$V`t4P1bO0yOk-ukl+~LWq?H@@qF#R&8&ZEZ6N_H%Nh~>0k5m>GQsKYcz1w5~2 zYNRAfjf9k{lDb$A#-U8nv>_M{J{qL!fPpbuJj(S|{H4Mll|7O&udQ2B%W)7P%?v-; zMK^Thc&3Ftza6hO^Fp_l*nSOaHU^9YGae*Y7c7?dzGgq>XBn9GKb*u6XL9($?wY<` zKM7%$?SyE}u7?9@I7dw+r@tVH_ah%-7J_FvW2~jqbDWAsDlbI!wHr(a5BW=JFfk^o zp$$@{-G<2zx)t4D#cz)HinskzoOIosOJuDdK=TY;K<)ka_-txeb zB_8j}eDD5~&6#iuyu-nTO1^qiSPy^RO9&dT(*ZAsS^Qn@$*y$wYCLN zc0?6u4YFNU! zb4Oge;(|YOH>nM)l_JUZ2OUwDZp`PTs@S#^R2?_hkjd5>CtsTv<^tg{JKsmSUB2I7JvuKL2!nn=gXx78>IVi|74??EP1l|fs*ZjJQ-gJv1y%mM zFecf#2oleReDLcvt4gYlOqr2>Etp#Utc*ql+^7j^u?Jw#EMnkrZVykI(&W4H93e^?R=nSudp09&soN`2VbS&k04 zqwmDcu8@JH92>$uYm~?o&&Y#u*?TruvzUbU!exO}7LL_j6^gzb$OKQJGDPcULomZD zbfar133>~$8I_S%{*FE3zkCequ{o+glNst^wo62hTq4E*$1=wb%23{4El52FOk(;F zwjLd$zj^^mb4QIxRIoyDq^(z?!<#I`HjLDk`o~m!59^rjx!t6qh!6E1%++kSc&HwM zOIBff4O*#x5^tMLrf`_HCLV?9&#?J3x;xL+PYApE{2VxMPjH5-E2B5*ItmCk7w3(R z0J*Zor@s+8{qmls6-B#_b4n_vO7_M;)Of>1Lwx{hJBVq}sOUzEMPUTJ$KA7l&&xzEQz0idDz;q1>q zfyb={BIBD^Elh`+vzwXET+;V_%TczmS$M7h1T^!SVwyT8boHI9Oe0GF%IB1sZ(8n_ zl^~X)l}L|7pMOy5m+$os_o4m0DAVWYrmKEQijEPfJ4t%RYbog;KMVLe?O}e{d163B zO}17bg3bPmSKdraH!v9Y#r}bW#9_(Hif!B<3mfJ3m`ZuDR2ULwg4HYro)H9%^7|dU zO*hK+{X29bzcwQ|=WOBcpYo-fJgv+@8@3nE8KF5>#()20M5}JmF@|Ns;9>d;w@Mza zfU3yv?oQ|f2}#6;4FLgGDJv&kkGVtcbh|jj-Yr*w+yVZaO!yB8ka>1LRJ-+%62#di zXelV`?0z>;b-+u`tT)<!336R)1o7__fhaQb^b!w zGhH(ec3lppEqFrUvJZVxPq11v>`rL&_V%sgGLS3H9Q-?&3spWZnFd)4=BliKhR|V0rnrs zw*Oia5dZ&OApX~8`s0a!KcQA1MOMo~Adp8$i}Q|uqWS-+$<_b={vL`YQ!NeUf5S*o Q!PP;KmM9DAh3hf@2D?(ivH$=8 diff --git a/test/goldens/screens/dashboard/goldens/macos/dashboard_with_balance.png b/test/goldens/screens/dashboard/goldens/macos/dashboard_with_balance.png index 066064d918074d73bba09ceccb100ae848b64b3b..bbb828a454add12fcd0ee8970787d81cf1f1bd57 100644 GIT binary patch literal 28703 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 literal 27711 zcmc$`1yEhxmoIp5g1bAx-Q5!`xCeK4cL?qh0)${81Pj64-Q5Z9?oP0G_`3Vm|IJKS zbQpgAd2mk;e%SgXd0RV_F0DzK%hXvo!vPB>Pe?Ys4 z%c#MFU%v3B5#VbG7ZoXSpmdaI9{|VznRjApo*75W9-de;ov>#fJWWOED`cUZ@NXeM z85TmKhm02o?y0l<2nuD0y6%dih%#)_ugD3aC7b*yd)h(`0r?4dBO?u2l-_98D)JdA z?W!`}H0kzMP}n&?E3+!iqaZW!O%PdNL_}doa}k5+yLTgq!Ji@`-r^y>d-t}(kc@)j z4J;Hox(q8`M1*uu2e<$O36_H5vmq-QdSb|rA`POXlyDyR?)9w_qWa?D^A1CUnEkCK zwHb1X9~hOrCm59P^+FU;x=rL=39hzhi4Jl)AIbgUt#Yl0>Cw@Z@ndn24k(D9eK^rY zvScMcX&4+==koJ!X2SWc**Q$$sZ&z?V2>d|(ryV{9nD*-3VzeiIKHC87Ff~24DVR| z&ioUK&5x-BJx_;bapo>np@p?PVSUGH>q-v&ckfE#l!J{9lU;{bJuv4u5Z-o42&ria z3IfD>zk7_*V6=U!PpLKD=gTUv{rULSNW(!<&@Vtoi`KIwBcgvk1_LRv&V|im*DXU~ zFcHZ^Gi~!pOc;-FOJ%!RnXbX}GMhI;UgJZU&wSeE@h4$CT;1#DckiYhVNt^*c&b?4 zuBrA088ygL*YEX7Jd5ISe}Cb6Ilu)T@sq>6d>qIq4H)omwF4ZQm*$2N)TzN!B9S0< zrM_Q(Joah3Aw8?;>x2)uto=GR&YC5vI-8~kCf`#f!ymxAno9*gKU(dj>`j!sTt3I!70lCZjZ^dfmT z{kid!LL8U^wf3KpUL2nxeIJ%l=bN2~y+hn_!!0ZEUR2K3;Wew4~O)-NMna5Fk<8?G!!&_6uY zJ5{1ROf0pxw6S4MiLRa@-P&{Icio@FAM$^GW8nXcABIE{Bl4mhP2MH7-aC>GTd0iu zix*zC*d=F9GoaYs%S0-^vyapdu&|&3+S96bS?xEr=nGDln{a>p_>q`Q_2gFR*QWrq zw?4tW?NZFTP+N3fM)C~w?VaaoZ62GO)(jHP;;~-T~a`smKGMs)86*@w?B@z!z z!v5?MWi8awluj6if{z4zW#2avJijx+6Sii2{!@X;k>#~uc+FlL2n0Yw-$idH6)0#5 z_^k@JeELlM{2i_f0*H*1iHL4ONgjlKIa;FPGv@Q(cne6!QN+-((>E&5N=#?m@oP~$ z*Q~&POcA$#G-Vwyx&Nz+wXfogKqo9LMwF788h(!`{J!l|@HlxPN0!gJNJNXbIuU7h zLSVybbGSbgTuwXMH4LzK?LC}zNdRsi zA0Tqhn~|A$)}<(7GMF20@{rQDzH@+3!;g#0tlt?^P|!6q1Cw57+m$i55|S4RLmfjp zx5oee{d<$YRUG3rA8?2LE|HgH<+>F`UV@}Z128NUNPFb-5D^hWP>BMA!%?EUZjJ>4nf3Thj`ozl-cI!KdY-XTE8vAM zEeU1_yWlvq3QJ|&+zlkbqo8!QW#8eds7#&pXc}y;b#(DMJ37S9L0d26>t|)X>ZAX& z8(&tMFmha6+}ZWH)tUQS>l03X2&oNZm-7Y(0Uz3t7(z5AZoG?&i-fOVLx^e!q~eLY zqEtx5Y*9xg8e6T0g8JdJ zPf+FdU6L7sZoOn3fu9di7&IA!8@$fBzHr+uu{|Wup}Ek(8X4Vw$oFIQB?AQ)QD$HO zPrzF(dFbdxROD3>rVn(rw6%@rvV^{#4^N$)Sw^>`5SLq=3d?1%qF9V&LIGFDey3<2 z69tHaGm5b=+5TbE)zz{rg{kW4b_`)#3% z%lcF)ab;B%vc{MgYFKm*0Qh;|LIO(-K6NWYN6W=DO3pdjd6l2EGowF2{e=P~d3t+G_a^Jp&~*YC*4Fe_-hMT> z!K8b0sHd$WsG!-vjqX`T2*sxeL+L;jVS*XVXoL*R?Te!YwSNlJo-4_> zLjZQD#J;Ab_1GXyTVq*Y&wB_>kqsf-y0ChO|Byru)A<+*>r>EKbM**2^71U;axQvj zIQ(*_>U)20@s-t{vGwtQu=!C8_TG|-Jl0HG&c`3=~FbE*frQ#kKCMKE0?GlDwI(w zh*K(dsW_^`h9~7kqfeI4QxvRQm@EMhijZUW0 zHtlGoamDZ9W$oY~>gD-V=kac83;+TGM25ZB`I?^(G7#D>hDLR28Bthx{I|}GUW10m z)x}c?pjE3GEnY55&4>^h#w|$JEZBWz#d0b?)>QY!zXx|44h4@a%NR0{E{U|aXv%`s z?r#+kxb|IPzR?OwGKNIc%x`dZW@)K;!A`r8Da+5C-~Y~u4s?nHsES1KFaGylp=KC0 zwA43SRTBaegljhOij7vpJ_EdBXbGRcA@FF<@!uXNB_={}aB$53QC}`o^~)=;5Y3%wADO8Uujf>Y(?-TO z1vTiPCFY=(PS&1gba*%@Jw5$xMnvI0z98O>!C1G64YQ1XZEnI4Yi!Gb#M#exgJx8?$A9ea{Vmsq!9ZbNoT%QN!Cd;g-e|Ts584UaL zrr-1Xx-g=PWXMT=s{Kn7Ealk8^;I5`e!} zGI~Z638_}*G6$Cw#z>-usjyjj-Q9R%xa$px&T(_!Y4Ui)9PBM&se?HR`!_}~vnll2 zuoM0-Bs|dn_1x(Hn$4G^0@yoV_uHvCQ~PIsEV4T~JAWt~F17`T zbar*IxgTlSf~km@+uY%`=XnoqVs>`?M6Q%cXUH4ue0)|z0EI*_!lW-QN3%@dO+TFc zP$&7v4+!2 z!iQ2>2wiGo^KIrz$1OSUo^DsEy4wC8Fq}u^)uyl*paA7HH9bAixK1}mf4t{xt`BDB z8(e6dU0iI`4Jdw8#tNK{x(P$B;bE&567Hg4F1d5NMCWX0ZNUOa||{ovit~B30k}jdu&l<<3Y~LAE~vfXCgB3diSa_4$bFb^(E}=?9j22l;GA8hhWB6hlY+$&Zu2oIQbVLhztuG`%Onj2PDuj zH1vV}C0{Q1oENBnhPiE>+6t!(ci26ApJ62Xfd?e5*^Kb zs}eq!9e5xF>mX?KERmsb+AjzFcio$eA1f0?U+0jBx@q(SgJ9Q3g){xN9H#&9p z*f=cuj3?8NPfwvsOS)oc&|KcQdLEzo7mDW`6Q;!Zjl;3h%AOwM88sA zHW@h#WEZHI?d_uER8;TT_4XAWCi6agUQeF!042>>CmGghUqJw5M@FgZE|Ms((?$px zd=MHgE)7a_Y-~ySQ&dz`NQFrs_zamO>d1BSX%0L9e%0?bQwjVsO8>3bq(S7qJ4(zJ z7*Ti<<4wQ6HIRhIW(>VEoE97mhx)^y&40ea1nzf92?S7Qzbp<$Yrxvg@uF6%FP}!K z?i;X70JMWdcAFKc(HG8G+pe|}i@ZE?7#kY{fR&Y%Wbw;pya~C}eD&XNExgbdf3qFqFbn?3xA2?%MU7L3nyPq5H|w8y=oI@3OY5 zS$$Mg)W_$C)7Py(CnZ4u7NZ$Ojy5*$-a&VEb^?IEzyIDs4FeEytTp?Ar6uIm8uurV z-yPSjepjMB&{O=9L6FX761H_x@%;1v1yCzwMDVP5LOD4(Il8-t$x+*kH%4ezg+1lX zlJWB5x$F#gfD&3^JwspZuu1{}0kLt{-Tm%-6*T9rq4!mlm4Tmi$O`1s!$h9%B#1QU zGIO*`N?GM{C)5NK!K>;H5a8#ZweqW}ao&~%L=_ZJhDS!IHfMi-3BI zkdc>vZ)^M8bP04ZpFT%#(I@}vY|fM7?p6ESEM;g&CN3e-^CIhU7w~kg8R`Xcg4KCT z`jx#?1&R*imypE#t^m+{J*$IV8=$TI?Jbs4Wr_76xK(&to5*aLLA*ojeg34?;3}Wn zYbe(t!hhj`_o* zJ;AcDu>3Y?BYAmPeR;e2!DFX}?|7!k!vSbdVbU>laA2{50(~aG??n{oyO?XI0-i5P zGx*(Pi8Q^TYc0k|ENisCZ}oKyB+(pJ$2B(#0(1XK~L75k@RrTl@NfGZ#0~MaRmgGz>UYM zA1)Y_bOpg6TDP1`8Wd;y|E1H_(_?epQ<`5~+^k#m4=yRO3)P_}>>M3M0Zknc$Y#>J zHlt}!a^XHRx%(zxF3R-yxj*O+1+1rOU5^*A6B83bW$I-pYYyUB^~HpOMQYFBb2(_hK6!(P*kzIJ z2Mged_&0kJUijTE!+n9?1Al~rm(kVNS1>vRa`V+Cu{x{>4yW@dJ_?71hx62f*O9_j zYU!|4uMGTS8rcQ;`L&yywuaL&?;0M;K)+sc$kK@X(haa4S?q0$HWDOM-YLIcW3ML zb=e99n5WI(1idOv1(I8YxG5;awJMB5J#Uro-B@(%%R9Nh@ww!0`C#JUQ2U;Bzy&E3 z6uYKlD38QUYO44Z%~+e7n|J>C^HDHXO+#53ot=Y&&=ps~-(MK?oa422I=L$iKjsb- zzkGpCmW6?XgUc-`;HRUb6CH3%S@6{WQy%ay3=9msyu6~!4jE8`F)>J5T3XW&-(H`g zqN-}-;IL}C_^pu2O5?R9Az$|1tx5jfJLJCqykWs=P2&v~?RZJ?L&2ndbE{~oysGBC zXrPEB5^0zMF%$&wTHu;6>0>j#V+CuDI_xhMn>6KKLmF2nGl;p8C~&fkqIY*mp~g`dD?13?K6M^*s3vhoBx%0WoH$hUBisKl(q zNWX{Mt~R-{U&u~ZVZWpQZQGy)vAN7rP9-0TwLrLyL}V< zTLP@_I0*6Uf!&h>{Oyg)`+i!W{gT5XA_u&!{0>?vB#Tj;+fdT!B9oF`uZQK^^MTH> zw_Vc*OY{Yr#V!oGHh5glyi0`R9bim^PluO5WDjP)&Azfd!8N&b_i z2Ayp|hqi~-pG!I5Avy?bFyS@YOmbpharTJp2uYh<-SC3&hJ2NSGD77rY zEL8l{$+Gw_U1sz|Iu2P@P)G4KjZG4L1sY~Aho8q7<+5*nn$DLE1$61Std=-uaTWWg zei3lBG}w17e%1{h!wDcy>5IjWrFwZFa2WA}>r-6vF`2i>_KgV;;;!j@oS496b-HL- zr%y&UW_hpKpi4oaYW~Hro`n}aphPdejmNIziwr8E2{xE8pNY1$)_kjdfqF7DaVaYc z2x!|dxSo1`f(}C^q(?bjY0=5MeE55}_T;e}wLLlKp;7biPs1B;6H&To?sQ;yZr^yMM9 zFJVF9)pY`W`htNV4TUuw!S+lReE(bD;5 zV*HMU7Rwxb=d|Z71v~joZSN^pkktRuR!n@uZjOrKdyxh<96h`LFTKS_(=Dz6AmyWZ znpujD$46#S?_=;c@a6Wb-j1HkYqoClzdY?fX;S{TC*lbPiqgqQhW`-L;Fh|bx`9DO zsr5?cNBd>7G%|R|h=>T;GlKuLZTjC%(Lc@De|zdygBQ$a2*#>m_94c)lS6&C;dO&KchMflCvfGI+2;LjS)b zf3)8kA;EL{1d*+|x)Ry14v43UI^20S68I7e8k2$gLX%rce$#7r=hd*mqZwcPA6S6w z;9cdP6X2@XE<7fNj}ShT56osO%F3DbVNCZI*Wj{X&?B0PUm!%SZCD}#j!>wwPaY1m^B#m{^T zs35=r6}R7v-AUJGg;|~MNx<1%sblzPWp|{1G*pA(Nr2tVxGo}>lhN?`Fvv7*Kr91o^<%{5fad*d7M@t)_Y%a z18eI>E$b)7tNk@R&EO#)TOP1ZOt&Jsz|zuV%QCC2D9`s~`_u#7xDTCKxfA1vkkm-W zk+t@58}-)$3S@2H;aMITe#jhJ(!L0#M35>_LjmschxC6spxW<5exRr71BjtSbGuic zV{|Yx!{%!HAb_~xJrsj*pN{jTRZT}aCRCBw(+KoWJ!LC$qjchBr=#uS!;YSmbUo|J1l0MZjmIh};znW3kK(4L+fiK`7%E_~lp#Zs&5b<`NmcTuryOE=d|(xd zTuj*V@BsMYOFC#RznI6)*Ki;4LHfM_Dz0apoBhE7C%qH{%GIBQw?|W zAF9r5t~3(&flG*WnA{Dl}C0Z=XdkZ zctV9zs+xHdu?KrGN8-c^D~%q5PG+X`;zDZWTxEB;Yqy^%L*Cpr82gqZvR~Bjej^i7 z$)4%MY>i9N!cU9BqKGWtoWQRf`u=3ZD0RMVfxE-wVrGP-yg9eHL}_*Y1!f_ayl+3J3V5u)FJDPRjiT) zXJ`f<(%y5I3Z<_DY*wDx2vgO)$0}KNh)r*DYkT3E06;?$`{hC;`D<{DFB1u(Q+Vm; zD(kjv#n4dkxKI8!UzZv@+bWNa>;^c^+3{N$-vo)5khpaDty$;(t}i$5e;b@boi~SM zpZAmUM$*XyrrJfyC%YC1Eaq{D-i_56ybf(dICGZ(jn9{+j}Z+i@M37=c_vNyr7063 zE4$%w&m@kOnqs*#M%O8n{lf#rta>(!%xb@5OZbA8ZOflGi1$J_h(&;yeYNxIu}B@h z`lNzS(KQ%t8LhEUJnT6ML*>`b{0KGP1J%VpPBM)bJc~BCN_Kv|hYijP#n4FEQQE~> zlavAOyqY>@dogf!WS10%-Z@)Ko;$M4QJ30H?JW$z0cc+P)IUn!w#zX~i6S=4s-&&m z=zA7A;K~s%qh>w|eV;mXy6L<|n5wBg7QYo-m_Y_nLGFEKhFy6Acw47Hx~u{v6AK@^ z^e}UGQ8r2`glzvWao_=jASL&}g9$?-G_|WSL z3}z@y9>54Tog)Bi>B_`s14;q3TkMakqleN%C3h`0_S&|rcgc@~n9vZ%g1X)7$9@YD zFZOmG`d{W&3Xom@Zb+;qkY(qW#wqJ6O7~21`IJ9MW5s1g_cR8?ZBEv_14O?&EXwX$ zAuoo~JJ)JZOiP461Pl|NBwcDO%RW5*$t4a^M1U%_)c z{X5NZTlZeT2k|I&sC%>+U-A(PnFcdNR(fk{~HqVUn4`B?8tsoPhaoqqT+Ezsutz* z*bw9n%2&{0g4r%k`3EEl3XJoJ|NjDDB~CY(Usb!_I0`gHi9cq^s5FD8mneSjSSJ)r z@VLzoB-f~*@DNfh*$%JU=Y}-NMv{=wBVAO1SsxetVjbRMof${ZO)_{CczL}@6)ysz zo8|t_IGeQj0LtPSzfT8)y-P??en+gF?GR9Y$3`W#D{z6ftBpnvwYDP-e(CR7x2&%2 z_T>=#`O-UCRE7S0O*hi!;Sro!M&{lzm)9eHQ|r)G;ipr(GutXpfJBfPDDq?SY`JJr zdTfqcol6^wh5;rE(I&VkQ!j@ziOWiFzBR1x#_rFz4-ecQS-bOb*S77PBIH>tqh{(n zKaFbKnTLq@5;1-=eX=#!GzN)~Uv8DhKngHW=v!>sR4&{!DqsU?SU-l)|BAM?RCs9i zz>QMtFf^-n9Nmi&CKyX{dK}xgnmR>jlMHPrp1`q!cpH}2S&v&c-zpz`yjaPl@sKiO z*@2d>AGph7AmOWFWb!+1@QrJ2=LWn1G`|kM*}d&*eh)}u%yR2Cmn`cZ%vxGo*@lvD zZH16acqyU?1A zirP-8x7=MRNz-mIQDME?aBuA}kSS73p?W@8Ya>}=A%UOk$6#_rlU-*pVtHfmt8$Co zaNw(G%!%3)3@OMXunBKs`W+KQL={aO%qfXvDTtL9b4j{7a`s<nAum@IHV zkD2@jjC>nUu$`Bdt?D4+TKnLH0=?hxW&|MPj%c#%dP5c)~rhdQ)|b| z=e@e29ZV!Et-YC&AEBOz%AK7PWR3+_mNDa>h26{T)`B^0S9glI>KxRf2spgGzdtfR zceU_90H=a`l=#emmIpcVC+1j}lNB_inbsM$1GAG;#XPbCX^UZXJT+8B9l8At?cICW zzkG%pes0-iMO#?=XHP|o$oeRYbzau7#}9L%0{zU>#x4WIs`&TXkNBBkxo@z`qKV}D zd$T93`f2&1?hZSNO>vge>l!%J9yIzaI~-{ggJ&i@Vro2dwfI|2k~dQ_~q6fvPX* z^@VOjI?X8I<4lW}yqLsd9knE4*b9jwcM+W7@JN)8=xH8!o05_Y}{ZI+|XXCN(@B=6M*FtDBIzOtYlv5j28@RxUPK)+G?f z`VG+|w!$)Z|8((;*%1!R+yOzn$J}b*(M4vXnp8$qLWp+d;7nf0GQ|6=H}x zOgxW5;=UuV!Xr`S@Gu*u%q&bAGZv;`d7+?l__q0T>qMKNGh#u~G!V!k!v_R{r-J~5 z1l7JS&wXI`3zzi1>sCT=^Cpt*?@)VayzZOCSgaESx2kdYsH&m&+cA8iVQa%Zt|5sA zzjPw)WcT+!n*H^(6L1`ooYzoHlI)ub#K`M(4f=`AOyfA5-{8jx4} z&{e1i9;G8#Oz&scc{h;+%j_O=-nKd(&OwfNVK~@iso9b?vQ7)P99;Bc>WbjvjN6Rl z8{Z4jUhP^|S*+upZ_PK-j_1a<*JOIQ^#SD}&4{FbY7Z^>M{lG~{)5-yLTioLnGMo| zgK7HK`k%OB4tb@aE6bwil=g89s{OrQ7F*ux$% zB0shWRh)WLZ&V>vJXeg$YmbHfvU!+I=?Erg?&5r~XiG2X05wiMj+AIEq60!*#LknA zYfhSrqt&I(#^5#u;0Fy^2k1*v==SwOz}| zcv%wPD$DMy56C6V$2vN&s|1l$yBqQo(dj12GY)yJ$4BFJnRp=E7F_yM z)7hNgi)UwkXK2~mf;sz^p9(jdv{ilger3^5;$oe6Nak%Lb|D$jWJTCTc=D|Of=DrUkQw(Tw>#bV*q zU(c5oqTxM%^W}HNP(-b5D_2PUMStV~$pS8w&HFLsnKZJ3bM9OwRJAo!obN)ZlLP zcNyIBASg6Ruke#sw6R>Bq~wSD0MprQd!D_*>z?8x z9tVQ8I~d;He;}#KnDpFd>xrk*FoNNNqhlvcy-j~3wbjbLBt`xBs@1{&%qW!>a@-QZ;B_lKsbhgM6&S&m9402AF5V6j|6y z8>{gb=dQ~Abhu5b@q<6rZ(6R;w=$*6KugqdbBF1hR&8w>M&3(2mfuV~oarjY=a(u3 zgRGM)@jGV1zB6$~>xG_qcFS@zLRbw>S!#7tpSsO)^))+nvMhaNk_aDH`y7fa$H%1G zI_HB162}VVb`Rxqp`n+9ow!i+=Lp$>_uXZQ$okI*Qb7}=E0A?AQESJBclHj1(UWpv zj;n*&d!;dKi?>@YIUwX7B6}<@a#=V%eZc8yMn7VVQ8fE>Y}P9cB?Temfp1MA3pT)j z{ZRn++r#a5z8VKJjMY6#VMPj{pLu*E3F>^Nm8pW<`mgYpxL1V=iynDFc6YN|U0}aM z4=!oI_x4&K@UB3{*fEO+!FGuszxRB_o