From 914565eb724bbda6a6cc9c8ba166f90761115f67 Mon Sep 17 00:00:00 2001 From: Serhii Date: Thu, 28 Dec 2023 21:20:59 +0200 Subject: [PATCH] CW-539-DFX-off-ramp-sell-provider (#1229) * buy_provider_types refactoring * refactor MoonPay and sell option flow * dfx sell flow * add default sell provider flow * localization * Update other_settings_page.dart * [skip ci] update localization * [skip ci] providers fixes * [skip ci] ui fixes * refactor sell and buy flow * handle dfx availability by country * PR fixes --- assets/images/moonpay_dark.png | Bin 0 -> 23214 bytes assets/images/moonpay_light.png | Bin 0 -> 20800 bytes cw_haven/pubspec.lock | 9 -- lib/buy/buy_exception.dart | 8 +- lib/buy/buy_provider.dart | 32 +++-- lib/buy/dfx/dfx_buy_provider.dart | 64 +++++++--- lib/buy/moonpay/moonpay_buy_provider.dart | 117 ++++++++++++----- lib/buy/onramper/onramper_buy_provider.dart | 54 +++++--- lib/buy/robinhood/robinhood_buy_provider.dart | 57 +++++---- lib/buy/wyre/wyre_buy_provider.dart | 51 +++++--- lib/di.dart | 10 +- lib/entities/buy_provider_types.dart | 119 +++++++++++++----- lib/entities/main_actions.dart | 115 +++++------------ lib/router.dart | 6 +- lib/routes.dart | 2 +- lib/src/screens/buy/buy_options_page.dart | 108 ++++++---------- .../screens/buy/widgets/buy_list_item.dart | 7 +- .../screens/settings/other_settings_page.dart | 12 +- lib/store/settings_store.dart | 56 ++++++--- lib/view_model/buy/buy_view_model.dart | 3 +- .../dashboard/dashboard_view_model.dart | 35 ++++-- lib/view_model/order_details_view_model.dart | 39 +++--- .../settings/other_settings_view_model.dart | 37 +++++- res/values/strings_ar.arb | 2 + res/values/strings_bg.arb | 2 + res/values/strings_cs.arb | 2 + res/values/strings_de.arb | 2 + res/values/strings_en.arb | 2 + res/values/strings_es.arb | 2 + res/values/strings_fr.arb | 2 + res/values/strings_ha.arb | 2 + res/values/strings_hi.arb | 2 + res/values/strings_hr.arb | 2 + res/values/strings_id.arb | 2 + res/values/strings_it.arb | 2 + res/values/strings_ja.arb | 2 + res/values/strings_ko.arb | 2 + res/values/strings_my.arb | 2 + res/values/strings_nl.arb | 2 + res/values/strings_pl.arb | 2 + res/values/strings_pt.arb | 2 + res/values/strings_ru.arb | 2 + res/values/strings_th.arb | 2 + res/values/strings_tl.arb | 2 + res/values/strings_tr.arb | 2 + res/values/strings_uk.arb | 2 + res/values/strings_ur.arb | 2 + res/values/strings_yo.arb | 2 + res/values/strings_zh.arb | 2 + 49 files changed, 623 insertions(+), 370 deletions(-) create mode 100644 assets/images/moonpay_dark.png create mode 100644 assets/images/moonpay_light.png diff --git a/assets/images/moonpay_dark.png b/assets/images/moonpay_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..872e322e2a5f8ab6a639f5949dff97ffcd325e00 GIT binary patch literal 23214 zcmZ^~1zeQf*DgFWjyi+E0D?*mUDDl1hjdGKcT0|-gi_KWASKU7x_xO8$j&tw5@4eUB>sr@}iBMIRy@NxH1A##9$jeEoLm<$#tN#%YnPwh~Oo?<*lJA`zGn?D!PYaU=4TqQ3>*cDl(D3;e zQ>FZURLho-3&QSd!(jO1(_ZAH*%7i!$RPv0jQ_C?qVpgVcc*$dHI6gfSo#q|@#~AO z+3=e`s1KgPZ@TZyMho*4mN8QGOMm7qNmHi&+7@yeR4+Ym-!&Ve5>vw-aBuNi<3Tc$ zThg~MRF>AtoJV*Ct8Gm;>e8~w7Ws!7V%XC9!VTw-XqUN`Un}WPkD_9}Q7rTOu~ci+ zVu+9D2jRX?TT*9r9L5oK7rQzNuT9KDV8Mi%p**(wpPrP*A zrXrT#2;9F#{F;_cyY7!fZ;(zMeIClV-Xkbxd9GJ!Hogy<)saQKsv1ubziA-aMVJ!x zS^Pt&?*<{e34v5B8rRle-z4`v4)IT$cR%xO-W`oEZ7g;&9$!EjH0>^W*ME+(OB7sE z?~D|8Zrf;4x84r)wJGr&L0Y*`k8#Me$-fCXBBhF7VSh-Oi+)4@S;OMB#i*ddU7nsK zDtC*$rqMB9D7AVP->cSFKfND+y0ErLS*jiGR>C+xI0-IvEKDIO)7%vHyJdhJr5W`@)Low;}f|w=CDi z;9kQeRySieV^<3IELpoT-)(ODt%}?#oHg5IwYe;br&BD#3>Gg$IzM(=D60GM)~ zWqanuS3(eV->~IIPo=X(NkD1v{UY}br_!2I3%+l2T~l`Y9!f1Q_K@m@ zW6QJ9&HVzeKt++63d|?>KMumz&HRo1xkc}Zb-IK+Tzu88w()1o?N@dG-R0M|Z)@w) zen)=%yz<=)r-GA-==Lf$n_<2AXA9|Q!QHxKn^j1qA18mOv;4*R{aN39OZTiB{Act+ zhfnJTaFZ8*Y|OvU4Tsa zw;5fYo<9rNyEe86Y0SR7c1i1VtJ{2{9{_}urLMe{k`jaod`3WEP<+TW@Cge3K*DSx z@PD5n5IXQT1Om&3{r5M}wd`yE^ZDwbg+`JW7y`G|)b-F+dLn4f`8g zH2@;)BM3e^T6&nGeH*4&Bh@hAi&1X$;Qda0!Fa7 z`#O1;`mi{;Q~zg?|6WJR(%sz6*2Tlt*$I8MuBn-`r-uj?)zyps*MI-nr-!Z8|Gde` z{lAU{4#;-(gpGrho$dcN&C%0Hip77Nwf~vMYmJYg7wvLug?pJfT`FVxe{;yB|uQT=je`o%0PyTDBFx!=!{x`Y( zM=Afl1zHis5oY^e8WzR*&3%mr0uhJEOFh=~fo{G5C6*G8 zrDP%&`KcB%D)k-Gq?YlKM{K{yGO?l)qGwe47{6zC7o!#tmsB7U|cqq+0 zH8|l-)rTZohsj!bzsifti$%uB7xslqO6<0I&MsRf7K|%IO6$U?#UU_4f~h9=9}9hR zxkjb7J8iE5`81xWPf=PgXqRdkBu4FbRLq#ii16A8L1C!EU@SS|3gs=hx~w>?H7i3j z&*;qTz^`ZQ1H<4q@5Bwu?jQ@X&C`DTjl+HUU)5_C__@g5H50ls*O`IJq{1}-$hHFW$v|QVlpF0>ls#+-Dh9mN#A@tc}JigD!l2Ehf3nx^s05A9CDlgpN6;qHg{zb*fGUhk(= zO@VY8q}=f{lM$D2z%?U#Z)4_Al}<(31b2Xo7`nb(euAtMaghlY-$0@Vs+>Fj3Zf~U zsJCUA)L@jiR4Il8oXHvPOEh8hn zB4)Z$A()r&dYcf#-Fm$JT>M`;By>xB@Eiq*X|!Fx$s{Wb-`vmbS?Thx$b+{Kj2rTi z*2_g^^s zR>f06VdGSlMudwM{Cco+v%1&WMhNu4U(5(n?zK?imm0zzW^(dos3rnafSe*-^kx=i zh~xbLq|FUOxZ4zdHH_D_y5 zFBD@>Up8I8xPTy5*(F5ua_Xs7Ewi!LhcF{>mp%@UXy>lId9ft?O&B6VyF}> z{sqyB4ht;fm!=w&qLa6uXF3gG*J*;j(QJb^y*g`>a($ko6eADDA*b9DFJ=GSMRLD# zplPd<+{HD$i}ZL?5{I5GJaX&ix~ES!t>Yge4uyHnT!b&P(Tuc`7((#V6{&;SlRrgh z-a*K{-ZW+P`*GwK`H^^IjUN8x$&!FTQ0_7{zAZVCqPt4rh<9j{gp2JU-Q#+lx2&4A ziWBo+`k)<@sxYYj=5L)yZ^dli)nd2_&GBnU=Mxo%h+z2fErlcJ$VF`jeRVqg#}Jr| zNy)PFy&xt`U_UPktqR5&yuN#y0dHzyHz+&mxThZi2ey|IY&daIwj898t)pXr^N>IV zVyf&%3|RsM%^SdBPF}ECi>0buKsemlvz-W#U1-Z z!I)XmOE3|IsDi@U@ha(?C{$aOVUEm3c<5NlVD_(goN$`no9q2Cgu`03^yNrYusA>b zN%ngNBW6q>O9oRBZ6B~Fh6ZIsz2!FLVs>$RQ8+pbo56%u4Gv}zI2P(Ip&gkEVTCZ> zNyl5`u5}})pzZauubG#;Sd8R-K?hJI{07F6^E;7r%gr_pVmedl6tEz>t!y?c(#<{9 z6|?Lac`A#gc>?fbmtZP;@IWTC?WSXL3z8A+?|t68L8XXY`IAQZs;qN5fL~SUN(F5r zCf5taN2T3z!*G<P~qPSF?_ z77TAH$tk0-cR!@+^LqR~%-4R3m4;wo1jGC>DF#^i|1 z)mf7|e3C(kXFr1R4*nRv=a{B)sw{_sh6+56vMrZOkp1;tS$d0_1OP@P_3E<84m!xE;UcP=EbYH54=->}Hdh*^y zrWRe>HP~zjYY`+;>=8wXqetPQN!|y44+0_%IIj;;4sbYGAl=wf#nj?%wwTRDr`&~R zA%)9!H+BzjvikEIV-YTlLNp_R+SHGo@{cH(_GqQ0rd1jfu49e}9#Gv+`bp{<@|Jmd zTlqxTq>HqMcM`*^PPdrf8?cg6N46%BjRW8KR_Sc__soLucR?QxlHIHvWiU0pA!aO{ zUS}Uol|$-vO_c(^ERQ9>dzwB(<~=1qlgXMlY$r3oVdGAw8rJtjzhS@fLCUKLaEG{& zt&}y&qd$FJxd%TMlR{+%1n}|B63~1at?C6V>QV{RQ!*)V%mX}r@%otoQ!#Z5RibY% zb6u%s_yt|;EN0pwWo5^t2A)IU%Zf|Bsex%S!*zc=DVvhG%%V+Z6%vNy^bWfANAb9E zgmE8m$QCMj#!d~0@V;Gph{t(L1B|gC@G;nWTQ<6P`q_(1 zbb)dJ&z3o2_YbFKHC4$csXQh2YcNKtoj89H$z@?`X;Ik;EmljNCR>``gW$}QC)1aZIP^Tr|Rj!JiRg@;m~9AyTy9B{}WWSVVlH$2S<2ea)E;GXUyrxG&F zxt|#s2T?D61o#rx^Q<9_rZ1%Sn`t+B%jVKeIM~&1f+KM{f9d73C;LkBS=SA!3nkJO z6X*+Pd?Ul%2#+*q;ph<>OhBR;W=Lz%o|6{oPmc|s_Rf28OOxKYr0rmA-}i+^)lC=> zc^=|SZ6YIobgw{8FVVytwH8aPlQXBGC6d#7+lZ3mJuyNY%Ll7tvaRhp)>>4^%YF`T zOfW=61qspgy)Wi&(FtKV4NH6jHnoG8dJ=j$(zNk+r}&Ap62d*$u|l~uZ23;{a?rim zo4SP3k`NdQktS}hwG%sJ+c4F{^0V)YfvC$hy8usv^{#lr4m?%Dd$*v#YM#QaikIU( zZT%GMZp*o~A>1Kl%xV{{kpY^nK9Ft=nkykNpnRldUdQrx+`n%A#^PG%hP%B&f?r9m zZRE{U1MY5sn5b}KXnN=hL&V;0;X@*8zs8;o^L6=zj?(2dW~x3>w|oX*Fx7B}gU)1+ zEg{}&qE932q0{eWGFmQpnjQFFNZJ$UzS})0;vW>QQBdx%H4DC zr~1N{8DLjpoI|7}%@w(SD|6)%jy!OSnU*XmB$x?`JY)#`D#>B-0s=?J(!lD~7i@*A z41MFN1_-O;8D{-H2dR_lnc`W5Ts_x>DXyf5+_PF2Cg`I9o&`DZMloU2v#^Ko<_z%F zXb2R+O;!0+|HiJM7B#DqlXyzHRIMK@!tK6eRr31jc6*pH3cw+HBp54nA|k$g<`uGe z_DBL^$_jh`jWwd0FR2u7@lO8d5M&{{~qa-pE3taQJssE*(7+) zidb6hF?B{5UM;0SV-n9UQslnwN?V{}$r;)tUQ_ZtqtNgmr`7N|<<&C5!D8^Ig(<%T zdoq8kxPE4S@xFSm)>b>=ve58JRcaV+`jw!L#7)Bb_%&4Q#g--YBA8&NP9qukmM#8{ z)Rrw25?4}!V3rUm{4sI-v!P0%j{vnX(url54KeakN$sE9Z`>igQfn~0zGNepc1Y#A zu54+fOiIllt(vH&@ehXGY@H+|#npx-s4BJgZzuALQK$#RmESPc-jhr4TmQJAv6fgU z=dJS^s22Z;NsWKa!_Fkysh&3mif!zQ365z>>7%bcODg>4qycb+Mm@l&Xzo(t|IosW zqe7l*ungc^>c>8}T7SX~tf!sy|E6R9mt^ zn^~lQpWUnM#fPh-e38s9F$kdZ8&Zy$+~xM{e3Z4VRx2;HVe4>;{wH|=(0>#3xN>Zt zx@hMsq|kOvB3Jp``S)EOZ!u^6y9g*H7`5ltg%Y>Ytxv^c4DrMV29&LA4_+tW0J8=$ zF+rn5NRjb%16i6#l@~w`9gk*$v@Fyjy&&h)#=hno}|PaEo!g%Om1K%Cr70!ZT4 z_D(BV*EES?7SEhn`VMXs9U;JxIQmMj&8K|cTLyg23&IT^ztkJdx^Wtw?b|h&hn0!z zKC_u9OV;m8 zL!e0E(VdWrMddp-##i*I4>4uqGgE7uYV*9+|9Dp%`FCqxh)yxIyS8J}ALb z1Oa`s@#V?n)@H-vL5q*ds^_mS-2sO!i=Ow5{j0b>&>cx&W%}>SWjHPuc7;ksBuh#Pl?AtEIA=10m6h4@T6;oirq$t~rEjuZ0F zWnr_4m+TRH>M^TQbbe-`dO362nw!Xtk5rQPC4r*Fktqmc?|ILy`dKG;{RXEywX7Yt zoX3k3TQ;iu=XfR!zCS5NW+F;;wzlwpC}k={$gR;J_u9;!8A&yo-2g^~hMrSzZMAHR zdhd_x2>JC14t>a6zBnEZ+8Pqt7A9BUpNQR~8K7<>#EY7XKv|LH5rpmy*`T{|fM1Y< zy}HjfWPp<*mC44ZeG8QqQIflt{e4NAWdp`~MJ%_{#==zhU{;oOUSa*1K|Sad;gt z0-^AMnn)2CwG^;I@o}X8&WD>@vx^sAYvb>WNeG@p4IOVgX)WdCa>zxVHHJ|HPTe84 zxPKjKiSHx*>r^qLKYgfdK;gN9(=yTI4#C4rJj)0Vg8(dm>TCq&*Vp$g=FtLlo;eP3 z+GS5yqeV|_tZySM34Z31Rs}Q&By~Qb6g3+09^A;kjO=sb;RLolO6PKUe%PX+EYKaW z`B^}K@IlvLbw zCJmcgZHGqQTTMTB7d|JM!2=n*#MqM13a4h&1A?md@GV;DwGYwixSd3ZBPlGoTlHy? zK|AZ+TFd^VEnr6O^qD?pe?m9PRI-)D9M0D!e>=baeW;75k(%D9?Z=aqC8Y5JsF6re zsgtwuYn@x{r`FxJ;eai7r=bjiRt2rQ+&}CR&ma-*D{MVotK@rxuxGV((B#I~FevOZ zrAd6)lwV)C+b>7E($}d~)Co_p<;@G)Zoir6UbnInoDZ~68ytR;_SUbpN< zH7#}4v)_yp?yPOl4QOQ*{tsEjVSJ-Lxk#e}&X0zjDEZS)dvd$y;+)8-)4uTPAV>R0 zsr5CFGI<%=*h+XTEpJo1(#Q@x-`{q-c*zb?1q>L)in&9=+b9}v(pYCd`BLAt(;h%= zjo)gbjzNppCjM4Y#n!oTRGQ;*vD}N^C^_1`HmRRIaXFzJfIT?F%UiYtrKb7PE+6$? zjB?b6RwFW429MSC1SI50oA7GTg9(edk~?pifXrf%rFu1=$b6PCPU{n8l#?0Jzn|9G zt!kGW{A%{H6u@vkZPm_QBe%@M6}HWj0nVx=FBZ=-0C&!p3AnRPT92T9oAoYjrRHmJoJSr66fR9wAH+3xH&O)?>6Q)ehR-Uh_NIHro==)rKu8Ok`&J0 zSG&Pe`0=T&qLMb>u68iWbY?<)HMrsG@>g!cb9-UF?fhW2=&zW=`xcL1%i0x2n^%6p zYuvSbcq=PVDR-?{MOtSc4_xPh`C9KJyj6a0TZb@lJXnqOuuQ_Moj-ob=hyyr5vUv) zI(+Hpx|5bLo>zSG)YCNSP)v10M+-;uE)*ngO!^WSF-a=lqL9KzOCeEw)%lFD1NRE2A}Z{%NjG_fM@^>?Rxht3U(Sfjhi4tX6Wh^^RR?&p!)QFiyZNXT zA#W$f&vFD1=#SpJor5_YQIJ{)#WU&BKEIKFN4V~TlHTC1rd(61 zosF;p@8x)`qeZnLUL9wz{2 zJ9rYS;nTY2q9ZO?E#^n2OHnQJm~^=$q1CtJcgq)<9BNl|@)nF)dy4yds2zUx9pSGH zzO|BX%V=v!T2EqfTQX|ii_iFcl7a$nvx1!JqVn5ndOutBam^mB`AWRTYH;p-R$!kL z^#U<=ZFDQwI!EGIg=+wBZ{O{<+QEcC?;Xq($_H>M)GDGj>D=7H@$rrn_C{65p{B&c zR7pH=?{Jfpek7RQmj~xgA*kfRAV<;S`bS^xUdyxO<+ z*iTU;)aHsaciyr1|AO+`-L=HL!oJYEG$ln_r#$0kb%L)RJ-kU(k-kt7yQi=2uHZ``WK@1x))JtLF@Xq*Ku$(Q}(l@qkfa= zWIJ7jG)TJO2U71{ZEqRRE+cI9v*RtUGYv?XZnMk^nE*_^iZI0S5mSY}>ao#qx`C6J zob=n5+OeNBc!69EVSbx+Iv!Dqsg|`bw(p2a5l_w@s@Ghb57;|1-jG+tGdkl_MSuMiIhDcurXdPH=NVP*Y!`uaLhRZfCcVe_P@nVuT1&*BQP+ zXlBg3W0+$3Ho{HcxGawyjPZis6+JSszQuqlR>aO=XP%=FP!gWS-`=0w$$3tOKNQTj zjyY2^0Gy{Y^&bxr(h8kdfv7|6M zM4R7DUUySB@#`xQRk8`!p3gQ;wSSQ?CJ#E4PO)61jeMj%+W2k?h8ZJxz+}U^+T?LIF06&NgEQ9Gy2$=w#zzIGZ9#i zB~R5t!EVPhQ9+9?A1#uj&e7EtnY&=$IPiLh>19FB-zxyua~V?NiAiP}oVS6HdxQqQ zH5^s^jrl=nRJ<>2roDRKmm|0pyrvpCwL)ax6RlJqkN0;dg{w+Vi}=%xlFaAy0LP{? z#YxPFezwp{_TC08Ga0LA{Wj6y2i5O~10@ser*gP42kI^F6{m~OWV8Yk?_cW$c@&x~ zaYI8xt7po7o?$3qsGqds{_pW}z)C;p$lN(b42-XCu>F`->#L8H1)j_PkGo<~C^y(h zGcrZoAJBbAI3^5BO!>z$q)H|s;IW=9rK}}SbC9Ex%Xl?Rd;TFU_#vOKK@nZ?!N_ju z`#gc#32HSRn*ty*keW8mOH9)KqwtC_Duw6i7FuzVVWyb)->sv?)2*?J657FDn+Gyf zR8Yy;Mbz4j_mvqmeev9!scU_mclUX)2;RKmrBAz-)3^FSH`;FwmJ$ifl(Ly4h^cF` z+Z~q|<&A;o&knzZQ8W*x^I3;}=%Tzo$RJcS9^*Ju>w3CT6SF?@E(F}Mb1&(wd98&| zx(CUIUa6<6Y(aXylq-+quv1ih*Z+jJeZ0{wrX}1l7s*VAt0$B4V#hL+V&{LqZcSTfd9rn!xzVv2Hjja`G5##>XXEPwI!-aJKz>_A-Vr{4tVc%+@%d_g6)<(rN&bDyn1Z7d9 zr(cHE6U&P6zZ)~IudZrJJVhiZnoL()IxV#NI(>M4J=KQFfqI4i2Fc@eU0gCjZUV=E zx%Pn5Posp34)sC5!*D6K)`qjT^;2zGZKf(sw~F3pnq}V`RNnDM?(MieVOC7>WS`~{ z^!r(&A&?kL1XIHE`SI3!4p6T-V0oN>2Kg2pm=sG7z1JrO$v`Zh%Kz-IO~d8IY0pfS z!fWGJAJ;Xz=kt3N6dM$&jTZ#ki!qh$vo$C$o?j!9xOYkvelx~ky=a|TTv>fB)XLdo zG_Sk&*F=n=dR4Hj@?yKa)LWyn6rOCYI}&!=Y%`nL&}%muzbXq2z70J2m8!UO96{^6 zeihZRw$=X8r~T9=Gag!ioC`eN+@QF3?(XUc8FLuoL$J^woNfXhdrEA_t-@>dj3)tS? zX51_b{*e{5u-UTRjtv*ZIO4CpG3H>^OYzjRt@IBPJh?D8y@!}O{oobx>`R_}s^6yr zWsY}lu!F7I`FL`%K>xKj4Q%q~aeI^8B5UICk>@FiWJ1i)kox)Jz*D9iB z9<`yR@GeiD0@~v3zh2hRym)zW_@v>1`rd5e@w)M4bIaLo|2!6RO|qD0%5DqT^*~Fb z@KIt`jq=9O0#xkKfasuKh2e`nU&;B*iPgKiZ`hdd(Ieql@Ukk6Wc21AJu||GS3Zu0 za?azc()H6f$`02L^WWhTlSq^z{kv66+vz44*cZHET-HN#0&Xk4nprft4DboM30_xR zI`J1$y4wQp%NHNsk`mZz6Ewf{5PNalErtGo9ENivdxvtP1(`hQKQTnQaHN+;Sq*1y zHno-L1!t}-7M&IqZQFzR7KQ(K>AQMp(`UZ{iBXN~WtE+O-!d*Lyi3D!^U|*22JT!t z{2_KJfLWlO%#T+Qou2pk9+k@Y`1{X%5!69lML^lo*BN4Yx@T1QekUgY(AFMT@Ov$E z@>1jn>U*%xs3(08V9{wOgNJ(HRT8E-Xutb-SLn7{wCUGbG4%euD1?Wsy+zJg$~bJ4jkS3PvPBYj*+@8d-;s`4<}ad0kE#LvnoXr_I> zz>l^oF1YS(Tc?$yts`-Rci&B1@{H8#{SxDIJGC=;V*aVuM!dd=ZN~eKyNz3}jLwNW@n;-jLVL}wB&V&L=bMQXgMf`M{kIxpKHfXK}!JIh24PMZMezVH|v<+M|{ zwzoRoQlInR8ypBQ?06i1IM2GbQ5N{rh4*cN%s|0LxZAFvj8Tlk;Pg6Y zVc1&RuLhSF=ktyY^Pk@MeYTx=o;g(=d9}#Ifh$gZ>$@@$2LeZrt+^(euetuIv;OSH z1N2EwkBS81V}HJmd^-}9In0cY|3uuKV7v8a&MWmxg~+>aG`T88O#&#LzU9V<&iHl{ z>THZXvZ=BctC@D-qd*ip*12DEDnbCdiEwf~g#j`UR#4mdTf5_wM=dP`sI`h&1{E)+??sZr#$Im5#( zqj?WRTQL?YhM#@g9+l91vd-TMN=e`^eedRN1SqVKeyt5&i`q*;4&Ix+*ptxxJL}Z0 zwY__rFr&m}Nw%z=A*tonPe1>cW$U?C_FB(vtniZtG`-aj>zmS3%NUR;#8vg>@r6 zK;Q~;Z<1TR)G?1T*_Ztq9H!s+Vh73H(uKUs_1ou&`fjm;r0HxFF;8FALoPZ3kTE1P zuuLGehRxCplZI^=ZIyH>?pd1X3;)A!W!$t%8gt?F3OnuDFK>7mKj$YKJ>?-e zEB67M;vpuJ&pETE$ZGT9tyAmqOiea=8;(2Hm#tH6$ARZ3O?cEY7VxGq`tJUsSG5te z>`#}4Ti$uU2t5{3I9h)haZDx#tN+<5V#8_sCGY5AOm`e5d*Ip8K#0~;RZ33YW83hk zVq?Ee9vyxCbzY~rpQ0B>-wj89Puo|td36JyEW{=K^D0OE;LhnZPSGR#$k5}BmznqR z*A|(-rQRXf6EcGTjg?=!NSe2&7RNF}%Z@Xej9Z39PkmS-&rrcwYxqJeJH-@AhSJn1 zKa}TchA7-9=-ku$i#&Z3$Ypi+*gT37#sEicwKYlZW0LDGzv9>R)s%` z89<2Ya6U7dE8F81(NX+3cexrdwHr6lQ(6JCP|#sG@w~ZAiU?PfGW4K*4N!sKypA*X z$Z{)b;^3t*dd4-PvLJOLjK^PK>J-{U8JBWVUvzI+c<+f(SfuS~_vl8MeuLFX#=ZzB ziK%j60`ItQ(G4_D%u66|@I2|-wjpTT8xp$mML!(ssbTO=8k2yblg$0cSHtlz0o5}t z%zI9qrDevNbMBKy?NXV+Z&7URM`rI|MzxkbNO=OAI4s4b8v5*D+=`PC+(zc9aQ>7S zhYL4w{5`>+Z7*b;^f>i`93$uO2~~2P#@JHhZ+gtv-}hg#Prk>?-98u@Y;^tpfU&1= z@QqV(R7RP~uDI?Oed0?Px^g!3FK_4qp@#{}*BH?j-W}RE@C_b9KdK4xc1m$HkXqno z54qpKTbE9x8rK~^G)fDm&wWidJ;Sx-DmQLKBBna`@nTOa$ADOrS_(_&Rp~9~OUon? z^_(#ZQR_H6F_^~F59nj*s3Tkm;gBrW|CZC~6s}Y(_XpNFk@5->Yvg8Elpc7AHmM8rO;12P9ur zAmvRgSfv9@MQIx1hHdaF^GSN5$@-t)8?_@UWyAyzmN^DYp0I=d4F-j!kvXGK0a>cf ztolq&MLwBE40{;^biHLF?5g0Q1`~1f!&@Vb2oGEUOv(um6TG^+HE300==#Trc7UTzCG^S2%Kc39Vh~_>v`PTWuX{=DKm54BE4T}>4Bv=eO)2x!B{_5VFrUXUQ zn?Jv;Yc=6<9VSyx3~aX@W(s|O&4pSGue-ITsf=A{X%KY`NCzibq1|DtCo~=EFK8z5 z-kAC}B)I+v)O3FTCYIPl?W6|p+pf<}^jz(O8urogR_9!};y&M;%f~xTYXhm{eDGUn z6a@M|LsBP`+z(T%?GHJk-c(Y0z*NM}Ye_`?8)w@C{C;|BP{KQ@MslQY9gbv z4Ar9D7f0WPFHDN8-4!r3qJMQ56EZGe|L3cUnCG<{w_TXEOXaH5k8WUYV~pCKHQhJz z`LNkU&ph~r79C5gN^j~k`b5Q|;p+`40oeI+A9Dq0ju^R{g1QSmy*YLw3=}XWo!!LU zRKuccI$3*vduAZH7?r~7m?rcqFTPlvFHs^p4Sunk++k6$Ra$Uo><$Kj$3ISfF~c(} z7kXPUk&%Sg!R5Yz%j07IPkM+I#Fe`qiiQFsilt=7hBn=JFiyMko#Jw;OfOz*=v>~R01s%QZIws1TA~uP#7b9WtL%Ipa!Zv)(!Qp#rak5;l^})n2`j%$_q8R ztLoF??~;n%mv2mjiEqFF(c>tQ)lYRISf8I1AtYq<}bjf|VHvjYXa< z-4qi~khD_v{6lH7*JbQEuJwoGfgu^yRFE9!sVQg5N0365D%eV z5=zO0K&NB`q?1(L-Z8T-1H;UU6#}}^oJV*1ZGRSvr#kLjJbBRU_9VV`5`zTCHE1i`Qb~jXhJb7_$GHZ zSL#_ZdFViI6bFHEL#ckl(D>L`BS7zyr>rj?qm|%SnRJjRFa$M;FDtz51qon#!6bpp zT@m4K*w>7r$M6jbeW4_viEChaXnpnDCl0j^xmMs3E=2`LB{_~c7}bM*}tbl^}VNnu;Q!!x_1ln7yswi_c5UQtoIDM1&TVBgl~V%9_N8 z9Jts|mV2IVcLcT113Pg5r#Ptk1w{3N;j}kC2Xr0=pI>w6A%()^L4VTeR1TNbz)lr+ zBxqwuqr!4b-lII|iGw6p2RPJi&9}7hE&6}$!M|FaK_(keGAEFO>V8v+3#+0AJxTBB zY8YX7mRTn1x35}1dPu*zCm~*0RDmYG(=kn;j=dt9NDKttM~SV{xy$n969&?`p}~9w z9q->T+yIM)L3)e?YUN07!VonoWwkEfB(fB89)LtSDU8%_b%e^16tT0Dj9XS~`tin9 z1+|$>$m>XzSQ_o`~t_gUsCLMjPDZAFo0HJT$GUX4r22!kb}Duco| zAJB-S8G$q@p6;evXa0G2FA{JRCR!|qE~CJ%bR5`}WJzUT6fO$PAV;{|eike{(TgXZ z5@3U`j3WxVtPnnEEXPkMxgND_a9D>hZ^ZoK3}AY51xo3{Nm5>5zv?iMpz#E)3RN{X zKOj)yLeOVbJw*QJ`;&N_TLY7K9M*dsok~|&;AXzAETrS*k%>{s?-sP(yg;gr07-!;HwO-&lEi4 zwlIuF2<}e>RwIy$EE2qW4S@^i4kC5~wG#o+sqlx%b7;{Av->%~ly8tj-;lvX@s_eI zvc%P&j3al*Yr^OXOG?~X^y@u@ytn?U;^YKq9FM@#^*R3^w1Hm*Bh^L6uE}iVJi>%Nw13Tw~M$30Zs2lNs#$xh95bK|k zyTyavoSk$?Pu`*wV+Aod7Vek;(kEC}8vzGboD))b@8Q+fueohm`~H8w-37Tnzg{J6 z(Aee-BaIIv$Zxenv70WK67G4p9v?M0%|8vKa|71{gSV7&`!>tU&X-|z5p=EHhZ_gZoF7)VbdWL0xy;f zHm??(D;n2WTW^s#s$4uSL2T?A%ib?-cLPSK2E}1OOyfeok@(!E~W)z7{B%`G1Jh#4n7etEj`P zd66u3y5uYXKLByL+nabIP!Im^o!h`D^sWn-UdWEr(N>+^B@?6>W$}LwaAKojF|ewR z>?0)_RwqMAKkzz*`6odJoGDm_2mzM^iV<(TM|J}wyryS<8pp{J*3IfzqA2NFX+b%jk4RKD=3cbr|js#f|LyP)gfcU8rV5+8D`_n&rF z^42jhdCVz8fO(g)^G$`Kt>n3lJnbPilI5n!_PKlnMC`+AH9bXGkF=J4R2Uf-PvYfej zz(XInoEPW~2sR(3kG-eC0%Kccl1Xu=VLDsc)qAnjdjmAtqJh89Z>&TP^lPQ)r0gen zIf-Q{q4+@tJ6-Mr=(0fHhTSA}5q~fa3fj-nCOM~gO&B_7^NnaiBhjudBPb^-?4i&pkkp zHc$*?hZUa4gF+)30V550teQW7LJB|aD1X`6sw*T`mKD6MM6i@M!cDDy)gl8%e97Mk zMoxto$Bb8)^O;Tz{@9#3esT`?cTU6N$&!wGP^p6p4ovC6l_}j+S(3Efu0I*ypc|Mb zGZ-pYDGc4d%0pjhBQxY+7&Kz4%~I$F=n7%P;ox}?u3BX%e#}XpvUD>YF=&}C~F};b+<=?sJ?6ACMk$|kS-hDRpXTc zic4kwG&sn;_+$Kuh|r9VzU?nr7)Azi!T@9^0fOFZv=fyk=~D z>#LFf!O=8;qij+zy5ffW#TD7?>zt!4eSdQ-YZYZezaTO|4m_Wh2HdNq2uM#Fy}v~{ zD@G;1q~bp3yKHuAE|;t(R1<90F)1ZtRzN7YFt68DISCXrjJXPM&X5HOPwIge zy*^VzLA&HjafC2h)Y1cHD878olcjfNpLl2oct?BJ%&u2faA2y;dw3UmiNGR!z@a-c zFf#bVyn>E9UTNuTrg+|&lkzpHf=x}PV6z{G7-WeC5O15@0$WQBCettN*my{NT{8yT z@?}CLQp;E~rRE_8n>OThm*4@Plfea<1F&Xcyrrq#(3pX7`jHirhR;1^`^B0n$5oyF zRg(oi5-qjbhM>+=QdHy$z$f1l4UP>usn-V6TV`gSPJ(6=y032_Fg#k91P|_Q(rE#| zbW)BINcvlgDogPM_o=jtT7sKjuk#K@+yl%cm`o$wO`uDTgaekvP1|SX9N(GY`8zHg z{N~jCQw#;gEg=qkm@6fggD^R%a|i}cYklo_oYtD)7~z3DRSjshSVZO%Of`Gjuelw7 zRtSY*DFtAcY#8OYf|ji24R+&9+kw0i=o@h5k?`XRrD?q{;dO+ZnE}u~7%z8`wf&+4mi<$m; z)yAeNgN09%pH!=HK|!r{hYz_onm=XqC0sy%WK120W^fC>A+@_-5fpG3bq0uEAC*ZD zD#w6`6JIt4>zJRWxf?!>Ggj!EC~Bv`lHrks=L4FpQc##72ox8d8E~M-=dvV@OB+iF z)1s|Z%3Xa!5zS!TNFmcqf%^+it^d^BD70!g&Oh130@Ra|T7ro0M&*Vls z1~gQILEu09<^0}_DyViUd;XK#nghY;t6;!L4oVy;;g7il4AePBf0cV^@cCml+2Fi* zl74&_(}*|47-G+D7Fc8Ok(rpIBxk?)r^>3f%*im~@A~Ahq05KsOMv6lh>kzfaJ|H|Y9L^M(e|d+5saJ^zK@%^2m5eT_X#Xwf zwUP4V<{u^{c=;Rl%N+bL@FHvG>h}W^+UktH25Vg(OVEGehz(_IG*Zgfo9--<7TTf$ z+GLY-gw5)4cK>}ULiG?xV56ZzM5xtJ zZU!fxw2a<*%M>)qkCKk9B`fs@`|c(z_vn~S}?hkw+0zkMm`k=`=!R4_Qc_>vC(8Z($> z+m%r^wKfzu`3lc=*q)xIchP1_<6cs);oyRbzuip!15IXHP%equts8Vt!`PJA2|& z*d-{h9US~No7`s!YOR5fkB+r~P4tviQ1{6@t&Sbn58$ft+O;Fxi7v~Ni+nwcckc>V z^r8;8FE6$e?~5xVx`KoEUDwy)2%2y#?)h$avf{LWIZbc<$WG;)vnVrlrFqX??@lCr zV=9-mwl<^*jWUJxka=hLo&D+Y4HD~g47If+@G;J{)SvsDy!0b{`gZg9{StMfaHV>7 z%f5S)ZGMMWO3uTpyvpCnd%l^dE;{_dD~3E0F{@9{WhQJnwzYWL|FqHN-Bs)LEP&0Y zcuU^T|FsDWz7?7z1K~Du7e@!Zv;jXCh)UViVl;kULA_RL>V0JP@P&o4NyphL8zmGY zg{o|S%|M^S=xXK-3){(^TE&%6VsORI zMnHmh07O`&-&g}B!LL_N4mQ`Xit7iU=-)dNZi*_51*s7~#T4T*_Mi_A?F;5Z<5ZH4 zJTLv+I*$J;W0f5M*Eh%W-DY56z!1hg*K16izzS?@pgf$36(NGeW7~H-JY^dI){M0p*_~)F$ zWver*8$}xL1^xCz!LM}iE8A@Y#!i^PwWY0;BR*76ZNKgoBVb)h4qJb>T~BpQC154{ z=&vn(JJ+!A*YWZ3V@;}Vajeikrz>{$4cBR7s%qg@E*v2h4-VGBU}}V);ERb$88ms= zu0U~k!_q&kzm8yW<#T+LYZ}LrUns~{*NR3k$QG)x(;{HQ*BSe)T(UbeXm>RNPI##m z>Ys>8EoHb5xq8oi@MraSX#>=4698gQI6>v;Zo~5fHW$;0AR^|~uJgx2Zzgsf-yMU7 zcuqHE`Ub@2yCi|_t`>~fCc6G$D-FtiF%iZb6@dx8mo;K{|>QlgO;o%whhC zy}`>RTtlyVFu+mcBYE#@qaugP1i?0?z#yZ}R?0`0k|Z4Il5P&UqUFf`ChkdddS!iK zto8xr6(yWVE>)rI3mnON{Q$2~$~WbGKr|P&CeU%k>?wHl)A8bixp7B)EySk z9f)9?pU`MuFK;1Lyc`x;=0*4@4$qeX5^-z$IIi8)WHiKcL_V)$9;u3E&@%7$q zzcgB0hN0u(VU0Nf4)?XKM~tS!*95d9qmLf{aIRDR^J^#Xt}z}liyU3%;?U3~E}e-4 z?bViX1pR2_O=O65KHe7wq zHb=fNGre)213@bs3{dQt)3EOal|ibNn_F6&ha5@6jIQM!)UuAeA)rN>yIyh2($)zn zn@8Lr-RV_(m3Y+|06~Q?(EHZtU9G#FQU2A{v2(GQi{MHy-@(B%s6G5gkGLK>gj!;G z0hps7XH|MNku8R^xcW72D`v9}`yAYYCSYxsucd&BlW?)~Czxj1*|xtZ1{LF~QEw=U zE=gbWD(yF;0=^40j;o+fvL(Wv_-PxOf7z2r%(lW=s_xsIRL+J7Ua0;a^A+7TXU$e% z`cWqS{pkIpgO4t3HXy}M!|Io#&%6iMs@6FH-1X2e=5Stdmr^78O$qzB0z{~tjZw#x zBR}!=@n(W`q+0M82R^;55S#I#U5V0h;g0Qwk0s4w6UayyG{(Kw6zSOVGY0Zf5ZL7h zU+nDD;&1dCb<}+M(Xu5Hc3``xme(u!hng}LecVII+qR42HYb@%gdA3RHu3QRJG;%1 zNqw;d7{75^4Cdpq+_8B4_vGMbhol!&X^0)rt@8sd$akQp>B#Vt`Vw&md&l~Yy@7`E zSF+?jciC3iL2q7uZ3a>R6urVy`$X+CVMfNYM4v^04jBn z-9+!rnEL>Pz~}e!7*81-Mdm3}Ew3t9y_6Q0?B?7CvW#w>|3%Cn@D&~KRdIQS$9OkG z4XNq-*#NlC)(Chmq~R}hRl<h0#7d(j`_wa080cna`B!~`BJLV2-7qFNTe8=P zEC{GB!oi@~NC6j9{w>WQHZ0ZPw=8;#qy-3!4_?Q97YD@3a zrk40wchpk|#D00S&)zq%VP|M*u@~zdCzo zyu>AOX-p)YccXg3VYnx@e8NwDo?exG1SBYx>1K=z+aV@z>-J=%;;0~oKSASX(xW3Y zndDo)&j)8q~Y3Cd$>+={NMQpjoaub=<^y=;NK0>{(JKGOZus#H=Oq1CtMjC=u2 zQ9;II{?(`<(%u~5Aa$!OA&6_jGuBHJBVdBjEH5t zwfRx1t&m>B{`Oo}_qvd8?bsjAWo2b$R#)&ySU+>8Pm}5hFlxs{5gi8{4mZ_PFZwqX zl4ZG~$9{4GAH{MIkkE69d-bdMXP;9t85jHbtkAXiM~VH-|6n?aY*9%W#5%1#z{GgJ zG1pIFjzpkQ3rx@vIQK4ghOaq!K-S#s8J-#mB=~~sj z>db5YFY0er#{#4(6+ydfxF_B5Hq`BW)hGPdV#@&bFzpiB=Wvk7;NQhIU>Er!1xc&h z?^9wobQq_8mm=ziMrFac-~$$5gz&qUb1?KrXZk+jIbT9@yPKKwETQln@mhaM42^EA6N|g27G&Aa= zY;eX|qdQ-oeqR_l?LL3TeY&<)wPj{|&(cTeKSgL#2y|d_tfe5fywcV!ik@Tnxwe># zl2h07|8wt#%{xRSckW?RC*1R+=z@mF){Vk;?NS60&i^g$Oe7(&vq5$#_k{1|4xOoO z?#qkPkUt^5 z1}$0lnp2eZ=ZtIQcy}SUacgb6OtzAXB4`G z@2p>S5@M@Hx1MA*bQ!sRGT*cY!2e+w{+b2ri_x}HK(qZ#Xt9F5A&PeQB z`&rGEPxwM<4n2x19~YuyK1{c7T?^r4p$+ znlT3ocYP+Aa@|}JlL6PbqI8}tj8ufx8a_B3fUN5_MOAuuTQHjL^z6}7_3|sm z1uPNyp~zzHGP-cX%Wi)O=QsrepyI`gowxESO{9|#FR1;u=MqLP@uz0bSegxOvljuq zc{n{d=fLBbZv!AZfHAlQpqmN)Xp+C%h>en=X5}Zmg;b~G`bvQWD(M8PiNZ19PqOcl+ChP*q5zP@8SJyc;S}S3;GKsu4LK_%CMrZu$80d)Zt6 ziH<*V)1JDS_th|qT*!%@Meu+JLDuY9CAd@fx`{{8te6A%-eBwU;{Z`+%^>Y~%-Lx# z{lK&tMrg5w8_32G11_x{Ar z0tunK*gb=s%#toNbCrmyAsJ?20ZPgmGAUdZJvPH3~3pev>X!w=74rCNfUnnvXYNC1Y`M0yhB z@Wm4EqPAKd=7S6~2A^{)CrGRF#`Xe|KLHRfgdNADk}q3NIC|#Mt%O0MYe47RyDgFK zGMp=Ky?{G}lJ)hK!|8QNXZ>N*h%LJe_1d2I5Dx1(qniv{sFqXDu4u!9wufeu)r;htI*I=n&7x;7T4Sk)y4Ts@*0mz-k# zx&K*9Bb}X&ME;NooX3%;OLpUU7s|O=2Z8Pt#E;gp_P|cfubqyT7gR_=e)1I4*TvX@ z2Uj*elXZ%3LVH=Olry0ASyZvt=E|oL7d`?Vo3QXezO<*-E|2L=RQ;bD$RL{9qf&4@ z-=S_wH?yR9csRJ~T7a2Vty%ZfBWYG{yyS-v_+~wtTc-EdfV&! zm%H`HW=64uHuvB(l`2o;B+r88$zS){Vl^bZQ+32RA8K%68l^fwReaYQ^{ctdoiF_O zi(%%o9{wP&ov@~J?kT6my$(M9xd&hv>hNCzF9JGAL5qlDWZUUHKt*Wx|B+(ZpDL>>dWiZ;V5gKraYIh;0u=2bgx(xwDbzYbQ_9%z5GUN&F2!HgQK5$;{p z?iQa*m}xd>>ZYi!9sxC^1HOs#@CYze)9a7r^g)C{%J@~J%iA-#83r?c+z#? z_qew9kC{5sG9g%0H}mIExWHT>Hv=^g2K=`w*mVH8leHttXw2J~&Lhkh%?lIv+6w2R zDo=C8NWaqaV+X95wJpx^ z-hHq5{MgkFykSSvug1_k!>%f;-UtQYGKF?k_BHQ_A;M|yb0hNM&IeJYk;W+9S6Kkgsz32Gk#*8vY|>ik(%0A zE$~)53})LpiIr}NFv**nSGvI&Hd#y`gT3ul-5 zXA3CXbt?5~T(~!E&$(-{5}{Z{syugsEAl1}O(xjKvR3bYt@5=uV>mzonX;0TR*#2$ zpbS!+R)m}Q5kY+YEZ?#~m1QQ`4x6-aK~CQkOkdvn_y2sbPq_N3fSJ~dSY0((qc0o= zOt-f5n&zZhN{`qblWvX%q^pc{j#e|oI;6M_b}z>bivfGX{TBo^vBQT;8i7COFpZZ4 zwowVM`R{*4{t~pg!F7t%Pkf6~^%3*->jc$~(YjaRj3;Rs=Td z?SbuX5iE=TA%)R)aXb3#4*c9PjL8$N7IV}zmd_jT!C01CWrbHGihhqECpNZH%lM%2 zV8!uSWrZ*(@)p5==bxq}c}_*~qb6cN&C}J|EyO1Z%+x0DKmk$&<`^BXlP%L?-L$`|EhSE+>8fQ&$|@w- z?b=~vPV>O@fl~IfeS}p7Gw}u3WG58}G!o44a1(ZnwG^#e6vtWBnA?+F<)n zx0h@yu)VdjS0=w0+i$=F7}vGk_Du`8QcYyOdCCWoGw)`mDnVtas=*slq!sLp|4H9x z2EdKa>P`WTAMww*8nlBSUX|f61WfR7RFCoP2jf?oUP!fQ-G7s`D*lZZT;uZk^Q9aR zL}i_jw?xQ706*_bJIkuod&KdUv_j2Jo zcn_RJmc4lCeG~uAkG^*U7y*JPCg6Kqu3m&9XL1?gkH+nIjTEXZrE~$wj8OgCq^X%t z^x=sH##vxm2YAWwfGhTFlt)rx$*PtjPeVdgIlswk=d&lFM?l6M=`NG>f=T^woJd9d zqr~~0sgpY8slQ8?LeGshUKAmu4a$C;f96o~EmNzb-GO_xS7}a}V2+`$cN0ZDt#q!{dfn z-tmfx>|(LiJ~;pyoBk6B1hF~H_#r8@FjoG*5$^);o_HB~TE)YlhTw;8-kSl{OUTi9 hh8d_e1+nD=$V|w|6dO9B6L`NKGQpS`ywP{L|9?N3T;%`& literal 0 HcmV?d00001 diff --git a/assets/images/moonpay_light.png b/assets/images/moonpay_light.png new file mode 100644 index 0000000000000000000000000000000000000000..c76ae6e74ec7e578874528b4c670ca4829be5647 GIT binary patch literal 20800 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 '${description.title}: $text'; + String toString() => '$title: $content'; } \ No newline at end of file diff --git a/lib/buy/buy_provider.dart b/lib/buy/buy_provider.dart index 10a13ed94..93f4e8105 100644 --- a/lib/buy/buy_provider.dart +++ b/lib/buy/buy_provider.dart @@ -1,27 +1,35 @@ import 'package:cake_wallet/buy/buy_amount.dart'; -import 'package:cake_wallet/buy/buy_provider_description.dart'; import 'package:cake_wallet/buy/order.dart'; import 'package:cw_core/wallet_base.dart'; -import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/material.dart'; abstract class BuyProvider { - BuyProvider({required this.wallet, required this.isTestEnvironment}); + BuyProvider({ + required this.wallet, + required this.isTestEnvironment, + }); final WalletBase wallet; final bool isTestEnvironment; String get title; - BuyProviderDescription get description; - String get trackUrl; - WalletType get walletType => wallet.type; - String get walletAddress => wallet.walletAddresses.address; - String get walletId => wallet.id; + String get buyOptionDescription; + + String get sellOptionDescription; + + String get lightIcon; + + String get darkIcon; @override String toString() => title; - Future requestUrl(String amount, String sourceCurrency); - Future findOrderById(String id); - Future calculateAmount(String amount, String sourceCurrency); -} \ No newline at end of file + Future launchProvider(BuildContext context, bool? isBuyAction); + + Future requestUrl(String amount, String sourceCurrency) => throw UnimplementedError(); + + Future findOrderById(String id) => throw UnimplementedError(); + + Future calculateAmount(String amount, String sourceCurrency) => throw UnimplementedError(); +} diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart index d5cf50d10..18ac081ea 100644 --- a/lib/buy/dfx/dfx_buy_provider.dart +++ b/lib/buy/dfx/dfx_buy_provider.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; @@ -11,10 +12,9 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:url_launcher/url_launcher.dart'; -class DFXBuyProvider { - DFXBuyProvider({required WalletBase wallet}) : this._wallet = wallet; - - final WalletBase _wallet; +class DFXBuyProvider extends BuyProvider { + DFXBuyProvider({required WalletBase wallet, bool isTestEnvironment = false}) + : super(wallet: wallet, isTestEnvironment: isTestEnvironment); static const _baseUrl = 'api.dfx.swiss'; static const _authPath = '/v1/auth/signMessage'; @@ -22,8 +22,23 @@ class DFXBuyProvider { static const _signInPath = '/v1/auth/signIn'; static const walletName = 'CakeWallet'; + @override + String get title => 'DFX Connect'; + + @override + String get buyOptionDescription => S.current.dfx_option_description; + + @override + String get sellOptionDescription => S.current.dfx_option_description; + + @override + String get lightIcon => 'assets/images/dfx_light.png'; + + @override + String get darkIcon => 'assets/images/dfx_dark.png'; + String get assetOut { - switch (_wallet.type) { + switch (wallet.type) { case WalletType.bitcoin: return 'BTC'; case WalletType.bitcoinCash: @@ -35,12 +50,12 @@ class DFXBuyProvider { case WalletType.ethereum: return 'ETH'; default: - throw Exception("WalletType is not available for DFX ${_wallet.type}"); + throw Exception("WalletType is not available for DFX ${wallet.type}"); } } String get blockchain { - switch (_wallet.type) { + switch (wallet.type) { case WalletType.bitcoin: case WalletType.bitcoinCash: case WalletType.litecoin: @@ -50,12 +65,12 @@ class DFXBuyProvider { case WalletType.ethereum: return 'Ethereum'; default: - throw Exception("WalletType is not available for DFX ${_wallet.type}"); + throw Exception("WalletType is not available for DFX ${wallet.type}"); } } Future getSignMessage() async { - final walletAddress = _wallet.walletAddresses.address; + final walletAddress = wallet.walletAddresses.address; final uri = Uri.https(_baseUrl, _authPath, {'address': walletAddress}); var response = await http.get(uri, headers: {'accept': 'application/json'}); @@ -71,7 +86,7 @@ class DFXBuyProvider { Future signUp() async { final signMessage = getSignature(await getSignMessage()); - final walletAddress = _wallet.walletAddresses.address; + final walletAddress = wallet.walletAddresses.address; final requestBody = jsonEncode({ 'wallet': walletName, @@ -86,6 +101,10 @@ class DFXBuyProvider { if (response.statusCode == 201) { final responseBody = jsonDecode(response.body); return responseBody['accessToken'] as String; + } else if (response.statusCode == 403) { + final responseBody = jsonDecode(response.body); + final message = responseBody['message'] ?? 'Service unavailable in your country'; + throw Exception(message); } else { throw Exception( 'Failed to sign up. Status: ${response.statusCode} ${response.body}'); @@ -94,7 +113,7 @@ class DFXBuyProvider { Future signIn() async { final signMessage = getSignature(await getSignMessage()); - final walletAddress = _wallet.walletAddresses.address; + final walletAddress = wallet.walletAddresses.address; final requestBody = jsonEncode({ 'address': walletAddress, @@ -108,6 +127,10 @@ class DFXBuyProvider { if (response.statusCode == 201) { final responseBody = jsonDecode(response.body); return responseBody['accessToken'] as String; + } else if (response.statusCode == 403) { + final responseBody = jsonDecode(response.body); + final message = responseBody['message'] ?? 'Service unavailable in your country'; + throw Exception(message); } else { throw Exception( 'Failed to sign in. Status: ${response.statusCode} ${response.body}'); @@ -115,24 +138,25 @@ class DFXBuyProvider { } String getSignature(String message) { - switch (_wallet.type) { + switch (wallet.type) { case WalletType.ethereum: - return _wallet.signMessage(message); + return wallet.signMessage(message); case WalletType.monero: case WalletType.litecoin: case WalletType.bitcoin: case WalletType.bitcoinCash: - return _wallet.signMessage(message, - address: _wallet.walletAddresses.address); + return wallet.signMessage(message, + address: wallet.walletAddresses.address); default: - throw Exception("WalletType is not available for DFX ${_wallet.type}"); + throw Exception("WalletType is not available for DFX ${wallet.type}"); } } - Future launchProvider(BuildContext context) async { + Future launchProvider(BuildContext context, bool? isBuyAction) async { try { final assetOut = this.assetOut; final blockchain = this.blockchain; + final actionType = isBuyAction == true ? '/buy' : '/sell'; String accessToken; @@ -146,7 +170,7 @@ class DFXBuyProvider { } } - final uri = Uri.https('services.dfx.swiss', '/buy', { + final uri = Uri.https('services.dfx.swiss', actionType, { 'session': accessToken, 'lang': 'en', 'asset-out': assetOut, @@ -156,8 +180,8 @@ class DFXBuyProvider { if (await canLaunchUrl(uri)) { if (DeviceInfo.instance.isMobile) { - Navigator.of(context).pushNamed(Routes.webViewPage, - arguments: [S.of(context).buy, uri]); + Navigator.of(context) + .pushNamed(Routes.webViewPage, arguments: ["DFX Connect", uri]); } else { await launchUrl(uri, mode: LaunchMode.externalApplication); } diff --git a/lib/buy/moonpay/moonpay_buy_provider.dart b/lib/buy/moonpay/moonpay_buy_provider.dart index 0736edeb7..0831e1313 100644 --- a/lib/buy/moonpay/moonpay_buy_provider.dart +++ b/lib/buy/moonpay/moonpay_buy_provider.dart @@ -1,9 +1,14 @@ import 'dart:convert'; import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/utils/device_info.dart'; import 'package:crypto/crypto.dart'; import 'package:cake_wallet/buy/buy_exception.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter/material.dart'; import 'package:http/http.dart'; import 'package:cake_wallet/buy/buy_amount.dart'; import 'package:cake_wallet/buy/buy_provider.dart'; @@ -14,33 +19,41 @@ import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/exchange/trade_state.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cw_core/crypto_currency.dart'; +import 'package:url_launcher/url_launcher.dart'; class MoonPaySellProvider { - MoonPaySellProvider({this.isTest = false}) - : baseUrl = isTest ? _baseTestUrl : _baseProductUrl; + MoonPaySellProvider({required SettingsStore settingsStore, + required WalletBase wallet, this.isTest = false}) + : baseUrl = isTest ? _baseTestUrl : _baseProductUrl, + this._settingsStore = settingsStore, + this._wallet = wallet; + + final SettingsStore _settingsStore; + final WalletBase _wallet; static const _baseTestUrl = 'sell-sandbox.moonpay.com'; static const _baseProductUrl = 'sell.moonpay.com'; + static String themeToMoonPayTheme(ThemeBase theme) { switch (theme.type) { case ThemeType.bright: - return 'light'; case ThemeType.light: return 'light'; case ThemeType.dark: return 'dark'; } } - static String get _apiKey => secrets.moonPayApiKey; - static String get _secretKey => secrets.moonPaySecretKey; + + static String get _apiKey => secrets.moonPayApiKey; + static String get _secretKey => secrets.moonPaySecretKey; final bool isTest; final String baseUrl; - Future requestUrl( - {required CryptoCurrency currency, - required String refundWalletAddress, - required SettingsStore settingsStore}) async { - + Future requestUrl({ + required CryptoCurrency currency, + required String refundWalletAddress, + required SettingsStore settingsStore, + }) async { final customParams = { 'theme': themeToMoonPayTheme(settingsStore.currentTheme), 'language': settingsStore.languageCode, @@ -50,11 +63,15 @@ class MoonPaySellProvider { }; final originalUri = Uri.https( - baseUrl, '', { + baseUrl, + '', + { 'apiKey': _apiKey, 'defaultBaseCurrencyCode': currency.toString().toLowerCase(), - 'refundWalletAddress': refundWalletAddress - }..addAll(customParams)); + 'refundWalletAddress': refundWalletAddress, + }..addAll(customParams), + ); + final messageBytes = utf8.encode('?${originalUri.query}'); final key = utf8.encode(_secretKey); final hmac = Hmac(sha256, key); @@ -70,6 +87,38 @@ class MoonPaySellProvider { final signedUri = originalUri.replace(queryParameters: query); return signedUri; } + + Future launchProvider(BuildContext context) async { + try { + final uri = await requestUrl( + currency: _wallet.currency, + refundWalletAddress: _wallet.walletAddresses.address, + settingsStore: _settingsStore, + ); + + if (await canLaunchUrl(uri)) { + if (DeviceInfo.instance.isMobile) { + Navigator.of(context).pushNamed(Routes.webViewPage, arguments: ['MoonPay', uri]); + } else { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } + } else { + throw Exception('Could not launch URL'); + } + } catch (e) { + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: 'MoonPay', + alertContent: 'The MoonPay service is currently unavailable: $e', + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop(), + ); + }, + ); + } + } } class MoonPayBuyProvider extends BuyProvider { @@ -88,20 +137,24 @@ class MoonPayBuyProvider extends BuyProvider { static const _secretKey = secrets.moonPaySecretKey; @override - String get title => 'MoonPay'; + String get title => 'Moon Pay'; @override - BuyProviderDescription get description => BuyProviderDescription.moonPay; + String get buyOptionDescription => ''; + @override + String get lightIcon => 'assets/images/moonpay_light.png'; + + @override + String get darkIcon => 'assets/images/moonpay_dark.png'; + String get currencyCode => - walletTypeToCryptoCurrency(walletType).title.toLowerCase(); + walletTypeToCryptoCurrency(wallet.type).title.toLowerCase(); - @override String get trackUrl => baseUrl + '/transaction_receipt?transactionId='; String baseUrl; - @override Future requestUrl(String amount, String sourceCurrency) async { final enabledPaymentMethods = 'credit_debit_card%2Capple_pay%2Cgoogle_pay%2Csamsung_pay' @@ -109,7 +162,7 @@ class MoonPayBuyProvider extends BuyProvider { final suffix = '?apiKey=' + _apiKey + '¤cyCode=' + currencyCode + '&enabledPaymentMethods=' + enabledPaymentMethods + - '&walletAddress=' + walletAddress + + '&walletAddress=' + wallet.walletAddresses.address + '&baseCurrencyCode=' + sourceCurrency.toLowerCase() + '&baseCurrencyAmount=' + amount + '&lockAmount=true' + '&showAllCurrencies=false' + '&showWalletAddressForm=false'; @@ -127,7 +180,6 @@ class MoonPayBuyProvider extends BuyProvider { return isTestEnvironment ? originalUrl : urlWithSignature; } - @override Future calculateAmount(String amount, String sourceCurrency) async { final url = _apiUrl + _currenciesSuffix + '/$currencyCode' + _quoteSuffix + '/?apiKey=' + _apiKey + @@ -138,8 +190,8 @@ class MoonPayBuyProvider extends BuyProvider { if (response.statusCode != 200) { throw BuyException( - description: description, - text: 'Quote is not found!'); + title: buyOptionDescription, + content: 'Quote is not found!'); } final responseJSON = json.decode(response.body) as Map; @@ -153,7 +205,6 @@ class MoonPayBuyProvider extends BuyProvider { minAmount: minSourceAmount); } - @override Future findOrderById(String id) async { final url = _apiUrl + _transactionsSuffix + '/$id' + '?apiKey=' + _apiKey; @@ -162,8 +213,8 @@ class MoonPayBuyProvider extends BuyProvider { if (response.statusCode != 200) { throw BuyException( - description: description, - text: 'Transaction $id is not found!'); + title: buyOptionDescription, + content: 'Transaction $id is not found!'); } final responseJSON = json.decode(response.body) as Map; @@ -175,13 +226,13 @@ class MoonPayBuyProvider extends BuyProvider { return Order( id: id, - provider: description, + provider: BuyProviderDescription.moonPay, transferId: id, state: state, createdAt: createdAt, amount: amount.toString(), - receiveAddress: walletAddress, - walletId: walletId + receiveAddress: wallet.walletAddresses.address, + walletId: wallet.id ); } @@ -201,4 +252,14 @@ class MoonPayBuyProvider extends BuyProvider { return isBuyEnable; } + + @override + // TODO: implement sellOptionDescription + String get sellOptionDescription => throw UnimplementedError(); + + @override + Future launchProvider(BuildContext context, bool? isBuyAction) { + // TODO: implement launchProvider + throw UnimplementedError(); + } } \ No newline at end of file diff --git a/lib/buy/onramper/onramper_buy_provider.dart b/lib/buy/onramper/onramper_buy_provider.dart index 872fcebf5..e38d95df3 100644 --- a/lib/buy/onramper/onramper_buy_provider.dart +++ b/lib/buy/onramper/onramper_buy_provider.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/store/settings_store.dart'; @@ -9,20 +10,34 @@ import 'package:cw_core/wallet_base.dart'; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; -class OnRamperBuyProvider { - OnRamperBuyProvider({required SettingsStore settingsStore, required WalletBase wallet}) - : this._settingsStore = settingsStore, - this._wallet = wallet; - - final SettingsStore _settingsStore; - final WalletBase _wallet; +class OnRamperBuyProvider extends BuyProvider { + OnRamperBuyProvider(this._settingsStore, + {required WalletBase wallet, bool isTestEnvironment = false}) + : super(wallet: wallet, isTestEnvironment: isTestEnvironment); static const _baseUrl = 'buy.onramper.com'; + final SettingsStore _settingsStore; + + @override + String get title => 'Onramper'; + + @override + String get buyOptionDescription => S.current.onramper_option_description; + + @override + String get sellOptionDescription => S.current.onramper_option_description; + + @override + String get lightIcon => 'assets/images/onramper_light.png'; + + @override + String get darkIcon => 'assets/images/onramper_dark.png'; + String get _apiKey => secrets.onramperApiKey; String get _normalizeCryptoCurrency { - switch (_wallet.currency) { + switch (wallet.currency) { case CryptoCurrency.ltc: return "LTC_LITECOIN"; case CryptoCurrency.xmr: @@ -32,7 +47,7 @@ class OnRamperBuyProvider { case CryptoCurrency.nano: return "XNO_NANO"; default: - return _wallet.currency.title; + return wallet.currency.title; } } @@ -40,7 +55,7 @@ class OnRamperBuyProvider { return color.value.toRadixString(16).replaceAll(RegExp(r'^ff'), ""); } - Uri requestUrl(BuildContext context) { + Uri requestOnramperUrl(BuildContext context) { String primaryColor, secondaryColor, primaryTextColor, @@ -50,9 +65,10 @@ class OnRamperBuyProvider { primaryColor = getColorStr(Theme.of(context).primaryColor); secondaryColor = getColorStr(Theme.of(context).colorScheme.background); - primaryTextColor = getColorStr(Theme.of(context).extension()!.titleColor); - secondaryTextColor = - getColorStr(Theme.of(context).extension()!.secondaryTextColor); + primaryTextColor = + getColorStr(Theme.of(context).extension()!.titleColor); + secondaryTextColor = getColorStr( + Theme.of(context).extension()!.secondaryTextColor); containerColor = getColorStr(Theme.of(context).colorScheme.background); cardColor = getColorStr(Theme.of(context).cardColor); @@ -60,12 +76,13 @@ class OnRamperBuyProvider { cardColor = getColorStr(Colors.white); } - final networkName = _wallet.currency.fullName?.toUpperCase().replaceAll(" ", ""); + final networkName = + wallet.currency.fullName?.toUpperCase().replaceAll(" ", ""); return Uri.https(_baseUrl, '', { 'apiKey': _apiKey, 'defaultCrypto': _normalizeCryptoCurrency, - 'networkWallets': '${networkName}:${_wallet.walletAddresses.address}', + 'networkWallets': '${networkName}:${wallet.walletAddresses.address}', 'supportSell': "false", 'supportSwap': "false", 'primaryColor': primaryColor, @@ -77,10 +94,11 @@ class OnRamperBuyProvider { }); } - Future launchProvider(BuildContext context) async { - final uri = requestUrl(context); + Future launchProvider(BuildContext context, bool? isBuyAction) async { + final uri = requestOnramperUrl(context); if (DeviceInfo.instance.isMobile) { - Navigator.of(context).pushNamed(Routes.webViewPage, arguments: [S.of(context).buy, uri]); + Navigator.of(context) + .pushNamed(Routes.webViewPage, arguments: [S.of(context).buy, uri]); } else { await launchUrl(uri); } diff --git a/lib/buy/robinhood/robinhood_buy_provider.dart b/lib/buy/robinhood/robinhood_buy_provider.dart index ade0bf99f..ff6c6a8cf 100644 --- a/lib/buy/robinhood/robinhood_buy_provider.dart +++ b/lib/buy/robinhood/robinhood_buy_provider.dart @@ -1,6 +1,10 @@ import 'dart:convert'; import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:cake_wallet/buy/buy_amount.dart'; +import 'package:cake_wallet/buy/buy_provider.dart'; +import 'package:cake_wallet/buy/buy_provider_description.dart'; +import 'package:cake_wallet/buy/order.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; @@ -10,40 +14,48 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:url_launcher/url_launcher.dart'; -class RobinhoodBuyProvider { - RobinhoodBuyProvider({required WalletBase wallet}) : this._wallet = wallet; - - final WalletBase _wallet; +class RobinhoodBuyProvider extends BuyProvider{ + RobinhoodBuyProvider({required WalletBase wallet, bool isTestEnvironment = false}) + : super(wallet: wallet, isTestEnvironment: isTestEnvironment); static const _baseUrl = 'applink.robinhood.com'; static const _cIdBaseUrl = 'exchange-helper.cakewallet.com'; + + @override + String get title => 'Robinhood Connect'; + + @override + String get buyOptionDescription => S.current.robinhood_option_description; + + @override + String get sellOptionDescription => S.current.robinhood_option_description; + + @override + String get lightIcon => 'assets/images/robinhood_light.png'; + + @override + String get darkIcon => 'assets/images/robinhood_dark.png'; + String get _applicationId => secrets.robinhoodApplicationId; String get _apiSecret => secrets.robinhoodCIdApiSecret; - bool get isAvailable => [ - WalletType.bitcoin, - WalletType.bitcoinCash, - WalletType.litecoin, - WalletType.ethereum - ].contains(_wallet.type); - String getSignature(String message) { - switch (_wallet.type) { + switch (wallet.type) { case WalletType.ethereum: - return _wallet.signMessage(message); + return wallet.signMessage(message); case WalletType.litecoin: case WalletType.bitcoin: case WalletType.bitcoinCash: - return _wallet.signMessage(message, address: _wallet.walletAddresses.address); + return wallet.signMessage(message, address: wallet.walletAddresses.address); default: - throw Exception("WalletType is not available for Robinhood ${_wallet.type}"); + throw Exception("WalletType is not available for Robinhood ${wallet.type}"); } } Future getConnectId() async { - final walletAddress = _wallet.walletAddresses.address; + final walletAddress = wallet.walletAddresses.address; final valid_until = (DateTime.now().millisecondsSinceEpoch / 1000).round() + 10; final message = "$_apiSecret:${valid_until}"; @@ -64,22 +76,22 @@ class RobinhoodBuyProvider { } } - Future requestUrl() async { + Future requestProviderUrl() async { final connectId = await getConnectId(); - final networkName = _wallet.currency.fullName?.toUpperCase().replaceAll(" ", "_"); + final networkName = wallet.currency.fullName?.toUpperCase().replaceAll(" ", "_"); return Uri.https(_baseUrl, '/u/connect', { 'applicationId': _applicationId, 'connectId': connectId, - 'walletAddress': _wallet.walletAddresses.address, - 'userIdentifier': _wallet.walletAddresses.address, + 'walletAddress': wallet.walletAddresses.address, + 'userIdentifier': wallet.walletAddresses.address, 'supportedNetworks': networkName }); } - Future launchProvider(BuildContext context) async { + Future launchProvider(BuildContext context, bool? isBuyAction) async { try { - final uri = await requestUrl(); + final uri = await requestProviderUrl(); await launchUrl(uri, mode: LaunchMode.externalApplication); } catch (_) { await showPopUp( @@ -93,4 +105,5 @@ class RobinhoodBuyProvider { }); } } + } diff --git a/lib/buy/wyre/wyre_buy_provider.dart b/lib/buy/wyre/wyre_buy_provider.dart index 652c92f58..80e48fd4f 100644 --- a/lib/buy/wyre/wyre_buy_provider.dart +++ b/lib/buy/wyre/wyre_buy_provider.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'package:cake_wallet/buy/buy_exception.dart'; +import 'package:flutter/src/widgets/framework.dart'; import 'package:http/http.dart'; import 'package:cake_wallet/buy/buy_amount.dart'; import 'package:cake_wallet/buy/buy_provider.dart'; @@ -35,16 +36,20 @@ class WyreBuyProvider extends BuyProvider { String get title => 'Wyre'; @override - BuyProviderDescription get description => BuyProviderDescription.wyre; + String get buyOptionDescription => ''; @override + String get lightIcon => 'assets/images/robinhood_light.png'; + + @override + String get darkIcon => 'assets/images/robinhood_dark.png'; + String get trackUrl => isTestEnvironment ? _trackTestUrl : _trackProductUrl; String baseApiUrl; - @override Future requestUrl(String amount, String sourceCurrency) async { final timestamp = DateTime.now().millisecondsSinceEpoch.toString(); final url = baseApiUrl + _ordersSuffix + _reserveSuffix + @@ -53,8 +58,8 @@ class WyreBuyProvider extends BuyProvider { final body = { 'amount': amount, 'sourceCurrency': sourceCurrency, - 'destCurrency': walletTypeToCryptoCurrency(walletType).title, - 'dest': walletTypeToString(walletType).toLowerCase() + ':' + walletAddress, + 'destCurrency': walletTypeToCryptoCurrency(wallet.type).title, + 'dest': walletTypeToString(wallet.type).toLowerCase() + ':' + wallet.walletAddresses.address, 'referrerAccountId': _accountId, 'lockFields': ['amount', 'sourceCurrency', 'destCurrency', 'dest'] }; @@ -68,8 +73,8 @@ class WyreBuyProvider extends BuyProvider { if (response.statusCode != 200) { throw BuyException( - description: description, - text: 'Url $url is not found!'); + title: buyOptionDescription, + content: 'Url $url is not found!'); } final responseJSON = json.decode(response.body) as Map; @@ -77,14 +82,13 @@ class WyreBuyProvider extends BuyProvider { return urlFromResponse; } - @override Future calculateAmount(String amount, String sourceCurrency) async { final quoteUrl = _baseProductApiUrl + _ordersSuffix + _quoteSuffix; final body = { 'amount': amount, 'sourceCurrency': sourceCurrency, - 'destCurrency': walletTypeToCryptoCurrency(walletType).title, - 'dest': walletTypeToString(walletType).toLowerCase() + ':' + walletAddress, + 'destCurrency': walletTypeToCryptoCurrency(wallet.type).title, + 'dest': walletTypeToString(wallet.type).toLowerCase() + ':' + wallet.walletAddresses.address, 'accountId': _accountId, 'country': _countryCode }; @@ -99,8 +103,8 @@ class WyreBuyProvider extends BuyProvider { if (response.statusCode != 200) { throw BuyException( - description: description, - text: 'Quote is not found!'); + title: buyOptionDescription, + content: 'Quote is not found!'); } final responseJSON = json.decode(response.body) as Map; @@ -111,7 +115,6 @@ class WyreBuyProvider extends BuyProvider { return BuyAmount(sourceAmount: sourceAmount, destAmount: destAmount, achSourceAmount: achAmount); } - @override Future findOrderById(String id) async { final orderUrl = baseApiUrl + _ordersSuffix + '/$id'; final orderUri = Uri.parse(orderUrl); @@ -119,8 +122,8 @@ class WyreBuyProvider extends BuyProvider { if (orderResponse.statusCode != 200) { throw BuyException( - description: description, - text: 'Order $id is not found!'); + title: buyOptionDescription, + content: 'Order $id is not found!'); } final orderResponseJSON = @@ -141,8 +144,8 @@ class WyreBuyProvider extends BuyProvider { if (transferResponse.statusCode != 200) { throw BuyException( - description: description, - text: 'Transfer $transferId is not found!'); + title: buyOptionDescription, + content: 'Transfer $transferId is not found!'); } final transferResponseJSON = @@ -151,15 +154,25 @@ class WyreBuyProvider extends BuyProvider { return Order( id: id, - provider: description, + provider: BuyProviderDescription.wyre, transferId: transferId, from: from, to: to, state: state, createdAt: createdAt, amount: amount.toString(), - receiveAddress: walletAddress, - walletId: walletId + receiveAddress: wallet.walletAddresses.address, + walletId: wallet.id ); } + + @override + Future launchProvider(BuildContext context, bool? isBuyAction) { + // TODO: implement launchProvider + throw UnimplementedError(); + } + + @override + // TODO: implement sellOptionDescription + String get sellOptionDescription => throw UnimplementedError(); } \ No newline at end of file diff --git a/lib/di.dart b/lib/di.dart index 03b7bfec8..1a0dc5788 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/anonpay/anonpay_api.dart'; import 'package:cake_wallet/anonpay/anonpay_info_base.dart'; import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; +import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart'; import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart'; @@ -796,8 +797,12 @@ Future setup({ getIt .registerFactory(() => DFXBuyProvider(wallet: getIt.get().wallet!)); + getIt.registerFactory(() => MoonPaySellProvider( + settingsStore: getIt.get().settingsStore, + wallet: getIt.get().wallet!)); + getIt.registerFactory(() => OnRamperBuyProvider( - settingsStore: getIt.get().settingsStore, + getIt.get().settingsStore, wallet: getIt.get().wallet!, )); @@ -941,7 +946,8 @@ Future setup({ getIt.registerFactory(() => BuyAmountViewModel()); - getIt.registerFactory(() => BuyOptionsPage(getIt.get())); + getIt.registerFactoryParam( + (isBuyOption, _) => BuySellOptionsPage(getIt.get(), isBuyOption)); getIt.registerFactory(() { final wallet = getIt.get().wallet; diff --git a/lib/entities/buy_provider_types.dart b/lib/entities/buy_provider_types.dart index 1faf2b281..1913d1b25 100644 --- a/lib/entities/buy_provider_types.dart +++ b/lib/entities/buy_provider_types.dart @@ -1,57 +1,112 @@ -import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/buy/buy_provider.dart'; +import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart'; +import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; +import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart'; +import 'package:cake_wallet/di.dart'; import 'package:cw_core/wallet_type.dart'; enum BuyProviderType { - AskEachTime, - Robinhood, - Onramper, - DFX; + askEachTime, + robinhood, + dfx, + onramper, +} - @override - String toString() { +extension BuyProviderTypeName on BuyProviderType { + String get name { switch (this) { - case BuyProviderType.AskEachTime: - return S.current.ask_each_time; - case BuyProviderType.Robinhood: - return "Robinhood"; - case BuyProviderType.Onramper: - return "Onramper"; - case BuyProviderType.DFX: - return "DFX"; + case BuyProviderType.askEachTime: + return 'Ask each time'; + case BuyProviderType.robinhood: + return 'Robinhood Connect'; + case BuyProviderType.dfx: + return 'DFX Connect'; + case BuyProviderType.onramper: + return 'Onramper'; + default: + return this.toString().split('.').last; } } - static List getAvailableProviders(WalletType walletType) { + String get id { + switch (this) { + case BuyProviderType.askEachTime: + return 'ask_each_time_provider'; + case BuyProviderType.robinhood: + return 'robinhood_connect_provider'; + case BuyProviderType.dfx: + return 'dfx_connect_provider'; + case BuyProviderType.onramper: + return 'onramper_provider'; + default: + return this.toString().split('.').last.replaceAll('.', '_') + .toLowerCase() + '_provider'; + } + } +} + +class BuyProviderHelper { + static List getAvailableBuyProviderTypes( + WalletType walletType) { switch (walletType) { case WalletType.nano: case WalletType.banano: - return [ - BuyProviderType.AskEachTime, - BuyProviderType.Onramper - ]; + return [BuyProviderType.askEachTime, BuyProviderType.onramper]; case WalletType.monero: return [ - BuyProviderType.AskEachTime, - BuyProviderType.Onramper, - BuyProviderType.DFX + BuyProviderType.askEachTime, + BuyProviderType.onramper, + BuyProviderType.dfx ]; case WalletType.bitcoin: case WalletType.ethereum: return [ - BuyProviderType.AskEachTime, - BuyProviderType.Onramper, - BuyProviderType.DFX, - BuyProviderType.Robinhood + BuyProviderType.askEachTime, + BuyProviderType.onramper, + BuyProviderType.dfx, + BuyProviderType.robinhood ]; case WalletType.litecoin: case WalletType.bitcoinCash: - return [ - BuyProviderType.AskEachTime, - BuyProviderType.Onramper, - BuyProviderType.Robinhood - ]; + return [ + BuyProviderType.askEachTime, + BuyProviderType.onramper, + BuyProviderType.robinhood + ]; default: return []; } } + + static List getAvailableSellProviderTypes( + WalletType walletType) { + switch (walletType) { + case WalletType.nano: + case WalletType.banano: + return [BuyProviderType.askEachTime]; + case WalletType.monero: + return [BuyProviderType.askEachTime, BuyProviderType.dfx]; + case WalletType.bitcoin: + case WalletType.ethereum: + return [BuyProviderType.askEachTime, BuyProviderType.dfx]; + case WalletType.litecoin: + case WalletType.bitcoinCash: + return [BuyProviderType.askEachTime]; + default: + return []; + } + } + + static BuyProvider? getProviderByType(BuyProviderType type) { + switch (type) { + case BuyProviderType.robinhood: + return getIt.get(); + case BuyProviderType.dfx: + return getIt.get(); + case BuyProviderType.onramper: + return getIt.get(); + case BuyProviderType.askEachTime: + return null; + } + } } diff --git a/lib/entities/main_actions.dart b/lib/entities/main_actions.dart index 4c629e768..0e4e05ae0 100644 --- a/lib/entities/main_actions.dart +++ b/lib/entities/main_actions.dart @@ -1,18 +1,9 @@ -import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart'; -import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart'; -import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; -import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart'; -import 'package:cake_wallet/di.dart'; -import 'package:cake_wallet/entities/buy_provider_types.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; -import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; -import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; -import 'package:url_launcher/url_launcher.dart'; class MainActions { final String Function(BuildContext context) name; @@ -46,53 +37,22 @@ class MainActions { canShow: (viewModel) => viewModel.hasBuyAction, onTap: (BuildContext context, DashboardViewModel viewModel) async { if (!viewModel.isEnabledBuyAction) { - await _showErrorDialog(context, S.of(context).unsupported_asset); + await _showErrorDialog( + context, S.of(context).buy, S.of(context).unsupported_asset); return; } final defaultBuyProvider = viewModel.defaultBuyProvider; try { - await _launchProviderByType(context, defaultBuyProvider); + defaultBuyProvider != null + ? await defaultBuyProvider.launchProvider(context, true) + : await Navigator.of(context).pushNamed(Routes.buySellPage, arguments: true); } catch (e) { - await _showErrorDialog(context, e.toString()); + await _showErrorDialog(context, defaultBuyProvider.toString(), e.toString()); } }, ); - static Future _launchProviderByType(BuildContext context, BuyProviderType providerType) async { - switch (providerType) { - case BuyProviderType.AskEachTime: - Navigator.pushNamed(context, Routes.buy); - break; - case BuyProviderType.Onramper: - await getIt.get().launchProvider(context); - break; - case BuyProviderType.Robinhood: - await getIt.get().launchProvider(context); - break; - case BuyProviderType.DFX: - await getIt.get().launchProvider(context); - break; - default: - throw UnsupportedError('Unsupported buy provider type'); - } - } - - - static Future _showErrorDialog(BuildContext context, String errorMessage) async { - await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: S.of(context).buy, - alertContent: errorMessage, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop(), - ); - }, - ); - } - static MainActions receiveAction = MainActions._( name: (context) => S.of(context).receive, image: 'assets/images/received.png', @@ -127,42 +87,35 @@ class MainActions { isEnabled: (viewModel) => viewModel.isEnabledSellAction, canShow: (viewModel) => viewModel.hasSellAction, onTap: (BuildContext context, DashboardViewModel viewModel) async { - final walletType = viewModel.type; + if (!viewModel.isEnabledSellAction) { + await _showErrorDialog( + context, S.of(context).sell, S.of(context).unsupported_asset); + return; + } - switch (walletType) { - case WalletType.bitcoin: - case WalletType.litecoin: - case WalletType.ethereum: - case WalletType.polygon: - case WalletType.bitcoinCash: - if (viewModel.isEnabledSellAction) { - final moonPaySellProvider = MoonPaySellProvider(); - final uri = await moonPaySellProvider.requestUrl( - currency: viewModel.wallet.currency, - refundWalletAddress: viewModel.wallet.walletAddresses.address, - settingsStore: viewModel.settingsStore, - ); - if (DeviceInfo.instance.isMobile) { - Navigator.of(context).pushNamed(Routes.webViewPage, - arguments: [S.of(context).sell, uri]); - } else { - await launchUrl(uri); - } - } - - break; - default: - await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: S.of(context).sell, - alertContent: S.of(context).unsupported_asset, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); - }, - ); + final defaultSellProvider = viewModel.defaultSellProvider; + try { + defaultSellProvider != null + ? await defaultSellProvider.launchProvider(context, false) + : await Navigator.of(context).pushNamed(Routes.buySellPage, arguments: false); + } catch (e) { + await _showErrorDialog(context, defaultSellProvider.toString(), e.toString()); } }, ); -} + + static Future _showErrorDialog( + BuildContext context, String title, String errorMessage) async { + await showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: title, + alertContent: errorMessage, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop(), + ); + }, + ); + } +} \ No newline at end of file diff --git a/lib/router.dart b/lib/router.dart index 11eeb582b..f9ab17d37 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -390,8 +390,10 @@ Route createRoute(RouteSettings settings) { return MaterialPageRoute( builder: (_) => getIt.get(param1: settings.arguments as Order)); - case Routes.buy: - return MaterialPageRoute(builder: (_) => getIt.get()); + case Routes.buySellPage: + final args = settings.arguments as bool; + return MaterialPageRoute( + builder: (_) => getIt.get(param1: args)); case Routes.buyWebView: final args = settings.arguments as List; diff --git a/lib/routes.dart b/lib/routes.dart index 60801297a..59d4acbc9 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -55,7 +55,7 @@ class Routes { static const supportLiveChat = '/support/live_chat'; static const supportOtherLinks = '/support/other'; static const orderDetails = '/order_details'; - static const buy = '/buy'; + static const buySellPage = '/buy_sell_page'; static const buyWebView = '/buy_web_view'; static const unspentCoinsList = '/unspent_coins_list'; static const unspentCoinsDetails = '/unspent_coins_details'; diff --git a/lib/src/screens/buy/buy_options_page.dart b/lib/src/screens/buy/buy_options_page.dart index 62dfc4ec4..6ce8fe9f2 100644 --- a/lib/src/screens/buy/buy_options_page.dart +++ b/lib/src/screens/buy/buy_options_page.dart @@ -1,7 +1,4 @@ -import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart'; -import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; -import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart'; -import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/entities/buy_provider_types.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; @@ -11,19 +8,14 @@ import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:flutter/material.dart'; -class BuyOptionsPage extends BasePage { - BuyOptionsPage(this.dashboardViewModel); +class BuySellOptionsPage extends BasePage { + BuySellOptionsPage(this.dashboardViewModel, this.isBuyAction); final DashboardViewModel dashboardViewModel; - final iconDarkRobinhood = 'assets/images/robinhood_dark.png'; - final iconLightRobinhood = 'assets/images/robinhood_light.png'; - final iconDarkOnramper = 'assets/images/onramper_dark.png'; - final iconLightOnramper = 'assets/images/onramper_light.png'; - final iconDarkDFX = 'assets/images/dfx_dark.png'; - final iconLightDFX = 'assets/images/dfx_light.png'; + final bool isBuyAction; @override - String get title => S.current.buy; + String get title => isBuyAction ? S.current.buy : S.current.sell; @override AppBarStyle get appBarStyle => AppBarStyle.regular; @@ -32,17 +24,9 @@ class BuyOptionsPage extends BasePage { Widget body(BuildContext context) { final isLightMode = Theme.of(context).extension()?.useDarkImage ?? false; - final iconRobinhood = Image.asset( - isLightMode ? iconLightRobinhood : iconDarkRobinhood, - height: 40, - width: 40); - final iconOnramper = Image.asset( - isLightMode ? iconLightOnramper : iconDarkOnramper, - height: 40, - width: 40); - final iconDFX = Image.asset(isLightMode ? iconLightDFX : iconDarkDFX, - height: 40, width: 40); - final availableProviders = dashboardViewModel.availableProviders; + final availableProviders = isBuyAction + ? dashboardViewModel.availableBuyProviders + : dashboardViewModel.availableSellProviders; return Container( child: Center( @@ -50,57 +34,43 @@ class BuyOptionsPage extends BasePage { constraints: BoxConstraints(maxWidth: 330), child: Column( children: [ - if (availableProviders.contains(BuyProviderType.Onramper)) - Padding( + ...availableProviders.map((provider) { + final icon = Image.asset( + isLightMode ? provider.lightIcon : provider.darkIcon, + height: 40, + width: 40, + ); + + return Padding( padding: EdgeInsets.only(top: 24), child: OptionTile( - image: iconOnramper, - title: "Onramper", - description: S.of(context).onramper_option_description, - onPressed: () async => await getIt - .get() - .launchProvider(context), + image: icon, + title: provider.toString(), + description: isBuyAction + ? provider.buyOptionDescription + : provider.sellOptionDescription, + onPressed: () => + provider.launchProvider(context, isBuyAction), ), - ), - if (availableProviders.contains(BuyProviderType.Robinhood)) - Padding( - padding: EdgeInsets.only(top: 24), - child: OptionTile( - image: iconRobinhood, - title: "Robinhood Connect", - description: S.of(context).robinhood_option_description, - onPressed: () async => await getIt - .get() - .launchProvider(context), - ), - ), - if (availableProviders.contains(BuyProviderType.DFX)) - Padding( - padding: EdgeInsets.only(top: 24), - child: OptionTile( - image: iconDFX, - title: "DFX Connect", - description: S.of(context).dfx_option_description, - onPressed: () async => await getIt - .get() - .launchProvider(context), - ), - ), + ); + }).toList(), Spacer(), - Padding( - padding: EdgeInsets.fromLTRB(24, 24, 24, 32), - child: Text( - S.of(context).select_buy_provider_notice, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, - color: Theme.of(context) - .extension()! - .detailsTitlesColor, + Padding( + padding: EdgeInsets.fromLTRB(24, 24, 24, 32), + child: Text( + isBuyAction + ? S.of(context).select_buy_provider_notice + : S.of(context).select_sell_provider_notice, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: Theme.of(context) + .extension()! + .detailsTitlesColor, + ), ), ), - ), ], ), ), diff --git a/lib/src/screens/buy/widgets/buy_list_item.dart b/lib/src/screens/buy/widgets/buy_list_item.dart index fc8fea81d..d8adddfdc 100644 --- a/lib/src/screens/buy/widgets/buy_list_item.dart +++ b/lib/src/screens/buy/widgets/buy_list_item.dart @@ -29,11 +29,10 @@ class BuyListItem extends StatelessWidget { @override Widget build(BuildContext context) { - final isSelected = selectedProvider?.description == provider.description; + final isSelected = selectedProvider?.buyOptionDescription == provider.buyOptionDescription; final iconColor = isSelected ? Colors.white : Colors.black; - final providerIcon = getBuyProviderIcon(provider.description, - iconColor: iconColor)!; + final providerIcon = Image.asset('assets/images/wyre-icon.png', width: 36, height: 36); final backgroundColor = isSelected ? Palette.greyBlueCraiola @@ -76,7 +75,7 @@ class BuyListItem extends StatelessWidget { padding: EdgeInsets.only(right: 10), child: providerIcon), Text( - provider.description.title, + provider.title, style: TextStyle( color: secondaryTextColor, fontSize: 20, diff --git a/lib/src/screens/settings/other_settings_page.dart b/lib/src/screens/settings/other_settings_page.dart index cc92641f3..c58523185 100644 --- a/lib/src/screens/settings/other_settings_page.dart +++ b/lib/src/screens/settings/other_settings_page.dart @@ -43,10 +43,18 @@ class OtherSettingsPage extends BasePage { if(_otherSettingsViewModel.isEnabledBuyAction) SettingsPickerCell( title: S.current.default_buy_provider, - items: _otherSettingsViewModel.availableBuyProviders, + items: _otherSettingsViewModel.availableBuyProvidersTypes, displayItem: _otherSettingsViewModel.getBuyProviderType, selectedItem: _otherSettingsViewModel.buyProviderType, - onItemSelected: _otherSettingsViewModel.onBuyProviderTypeSelected, + onItemSelected: _otherSettingsViewModel.onBuyProviderTypeSelected + ), + if(_otherSettingsViewModel.isEnabledSellAction) + SettingsPickerCell( + title: S.current.default_sell_provider, + items: _otherSettingsViewModel.availableSellProvidersTypes, + displayItem: _otherSettingsViewModel.getSellProviderType, + selectedItem: _otherSettingsViewModel.sellProviderType, + onItemSelected: _otherSettingsViewModel.onSellProviderTypeSelected, ), SettingsCellWithArrow( title: S.current.settings_terms_and_conditions, diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 95254e557..20f310c83 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; +import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/buy_provider_types.dart'; import 'package:cake_wallet/entities/cake_2fa_preset_options.dart'; @@ -147,7 +148,9 @@ abstract class SettingsStoreBase with Store { initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings, currentSyncMode = initialSyncMode, currentSyncAll = initialSyncAll, - priority = ObservableMap() { + priority = ObservableMap(), + defaultBuyProviders = ObservableMap(), + defaultSellProviders = ObservableMap() { //this.nodes = ObservableMap.of(nodes); if (initialMoneroTransactionPriority != null) { @@ -181,9 +184,25 @@ abstract class SettingsStoreBase with Store { initializeTrocadorProviderStates(); WalletType.values.forEach((walletType) { - final key = 'defaultBuyProvider_${walletType.toString()}'; - final providerIndex = sharedPreferences.getInt(key); - defaultBuyProviders[walletType] = providerIndex != null ? BuyProviderType.values[providerIndex] : BuyProviderType.AskEachTime; + final key = 'buyProvider_${walletType.toString()}'; + final providerId = sharedPreferences.getString(key); + if (providerId != null) { + defaultBuyProviders[walletType] = BuyProviderType.values + .firstWhere((provider) => provider.id == providerId, orElse: () => BuyProviderType.askEachTime); + } else { + defaultBuyProviders[walletType] = BuyProviderType.askEachTime; + } + }); + + WalletType.values.forEach((walletType) { + final key = 'sellProvider_${walletType.toString()}'; + final providerId = sharedPreferences.getString(key); + if (providerId != null) { + defaultSellProviders[walletType] = BuyProviderType.values + .firstWhere((provider) => provider.id == providerId, orElse: () => BuyProviderType.askEachTime); + } else { + defaultSellProviders[walletType] = BuyProviderType.askEachTime; + } }); reaction( @@ -196,6 +215,20 @@ abstract class SettingsStoreBase with Store { (bool shouldShowYatPopup) => sharedPreferences.setBool(PreferencesKey.shouldShowYatPopup, shouldShowYatPopup)); + defaultBuyProviders.observe((change) { + final String key = 'buyProvider_${change.key.toString()}'; + if (change.newValue != null) { + sharedPreferences.setString(key, change.newValue!.id); + } + }); + + defaultSellProviders.observe((change) { + final String key = 'sellProvider_${change.key.toString()}'; + if (change.newValue != null) { + sharedPreferences.setString(key, change.newValue!.id); + } + }); + priority.observe((change) { final String? key; switch (change.key) { @@ -251,16 +284,6 @@ abstract class SettingsStoreBase with Store { (bool disableSell) => sharedPreferences.setBool(PreferencesKey.disableSellKey, disableSell)); - reaction( - (_) => defaultBuyProviders.asObservable(), - (ObservableMap providers) { - providers.forEach((walletType, provider) { - final key = 'defaultBuyProvider_${walletType.toString()}'; - sharedPreferences.setInt(key, provider.index); - }); - } - ); - reaction( (_) => walletListOrder, (WalletListOrderType walletListOrder) => @@ -594,7 +617,10 @@ abstract class SettingsStoreBase with Store { ObservableMap trocadorProviderStates = ObservableMap(); @observable - ObservableMap defaultBuyProviders = ObservableMap(); + ObservableMap defaultBuyProviders; + + @observable + ObservableMap defaultSellProviders; @observable SortBalanceBy sortBalanceBy; diff --git a/lib/view_model/buy/buy_view_model.dart b/lib/view_model/buy/buy_view_model.dart index 080caa3ad..c0e22dae2 100644 --- a/lib/view_model/buy/buy_view_model.dart +++ b/lib/view_model/buy/buy_view_model.dart @@ -61,8 +61,7 @@ abstract class BuyViewModelBase with Store { String _url = ''; try { - _url = await selectedProvider - !.requestUrl(doubleAmount.toString(), fiatCurrency.title); + _url = await selectedProvider!.requestUrl(doubleAmount.toString(), fiatCurrency.title); } catch (e) { print(e.toString()); } diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 19b5ad544..f4c66b432 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/balance_display_mode.dart'; @@ -41,6 +42,7 @@ import 'package:cw_core/wallet_type.dart'; import 'package:eth_sig_util/util/utils.dart'; import 'package:flutter/services.dart'; import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/entities/buy_provider_types.dart'; part 'dashboard_view_model.g.dart'; @@ -295,13 +297,31 @@ abstract class DashboardViewModelBase with Store { Map> filterItems; - BuyProviderType get defaultBuyProvider => - settingsStore.defaultBuyProviders[wallet.type] ?? BuyProviderType.AskEachTime; + BuyProvider? get defaultBuyProvider => BuyProviderHelper.getProviderByType( + settingsStore.defaultBuyProviders[wallet.type] ?? BuyProviderType.askEachTime); + + BuyProvider? get defaultSellProvider => BuyProviderHelper.getProviderByType( + settingsStore.defaultSellProviders[wallet.type] ?? BuyProviderType.askEachTime); bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled; - List get availableProviders => - BuyProviderType.getAvailableProviders(wallet.type); + List get availableBuyProviders { + final providerTypes = BuyProviderHelper.getAvailableBuyProviderTypes(wallet.type); + return providerTypes + .map((type) => BuyProviderHelper.getProviderByType(type)) + .where((provider) => provider != null) + .cast() + .toList(); + } + + List get availableSellProviders { + final providerTypes = BuyProviderHelper.getAvailableSellProviderTypes(wallet.type); + return providerTypes + .map((type) => BuyProviderHelper.getProviderByType(type)) + .where((provider) => provider != null) + .cast() + .toList(); + } bool get shouldShowYatPopup => settingsStore.shouldShowYatPopup; @@ -315,16 +335,15 @@ abstract class DashboardViewModelBase with Store { bool hasExchangeAction; @computed - bool get isEnabledBuyAction => !settingsStore.disableBuy && wallet.type != WalletType.haven; + bool get isEnabledBuyAction => + !settingsStore.disableBuy && availableBuyProviders.isNotEmpty; @observable bool hasBuyAction; @computed bool get isEnabledSellAction => - !settingsStore.disableSell && - wallet.type != WalletType.haven && - wallet.type != WalletType.monero; + !settingsStore.disableSell && availableSellProviders.isNotEmpty; @observable bool hasSellAction; diff --git a/lib/view_model/order_details_view_model.dart b/lib/view_model/order_details_view_model.dart index 0047ed7b2..4ac848ecf 100644 --- a/lib/view_model/order_details_view_model.dart +++ b/lib/view_model/order_details_view_model.dart @@ -50,8 +50,10 @@ abstract class OrderDetailsViewModelBase with Store { @action Future _updateOrder() async { try { - if (_provider != null) { - final updatedOrder = await _provider!.findOrderById(order.id); + if (_provider != null && (_provider is MoonPayBuyProvider || _provider is WyreBuyProvider)) { + final updatedOrder = _provider is MoonPayBuyProvider + ? await (_provider as MoonPayBuyProvider).findOrderById(order.id) + : await (_provider as WyreBuyProvider).findOrderById(order.id); updatedOrder.from = order.from; updatedOrder.to = order.to; updatedOrder.receiveAddress = order.receiveAddress; @@ -87,19 +89,26 @@ abstract class OrderDetailsViewModelBase with Store { value: order.provider.title) ); - if (_provider?.trackUrl.isNotEmpty ?? false) { - final buildURL = _provider!.trackUrl + '${order.transferId}'; - items.add( - TrackTradeListItem( - title: 'Track', - value: buildURL, - onTap: () { - try { - launch(buildURL); - } catch (e) {} - } - ) - ); + if (_provider != null && (_provider is MoonPayBuyProvider || _provider is WyreBuyProvider)) { + + final trackUrl = _provider is MoonPayBuyProvider + ? (_provider as MoonPayBuyProvider).trackUrl + : (_provider as WyreBuyProvider).trackUrl; + + if (trackUrl.isNotEmpty ?? false) { + final buildURL = trackUrl + '${order.transferId}'; + items.add( + TrackTradeListItem( + title: 'Track', + value: buildURL, + onTap: () { + try { + launch(buildURL); + } catch (e) {} + } + ) + ); + } } items.add( diff --git a/lib/view_model/settings/other_settings_view_model.dart b/lib/view_model/settings/other_settings_view_model.dart index 4e712dae1..60ed550ef 100644 --- a/lib/view_model/settings/other_settings_view_model.dart +++ b/lib/view_model/settings/other_settings_view_model.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/entities/buy_provider_types.dart'; import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cw_core/balance.dart'; import 'package:cw_core/transaction_history.dart'; @@ -59,12 +60,24 @@ abstract class OtherSettingsViewModelBase with Store { bool get isEnabledBuyAction => !_settingsStore.disableBuy && _wallet.type != WalletType.haven; - List get availableBuyProviders => - BuyProviderType.getAvailableProviders(walletType); + @computed + bool get isEnabledSellAction => + !_settingsStore.disableSell && _wallet.type != WalletType.haven; + + List get availableBuyProvidersTypes { + return BuyProviderHelper.getAvailableBuyProviderTypes(walletType); + } + + List get availableSellProvidersTypes => + BuyProviderHelper.getAvailableSellProviderTypes(walletType); BuyProviderType get buyProviderType => _settingsStore.defaultBuyProviders[walletType] ?? - BuyProviderType.AskEachTime; + BuyProviderType.askEachTime; + + BuyProviderType get sellProviderType => + _settingsStore.defaultSellProviders[walletType] ?? + BuyProviderType.askEachTime; String getDisplayPriority(dynamic priority) { final _priority = priority as TransactionPriority; @@ -81,13 +94,27 @@ abstract class OtherSettingsViewModelBase with Store { String getBuyProviderType(dynamic buyProviderType) { final _buyProviderType = buyProviderType as BuyProviderType; + return _buyProviderType == BuyProviderType.askEachTime + ? S.current.ask_each_time + : _buyProviderType.name; + } - return _buyProviderType.toString(); + String getSellProviderType(dynamic sellProviderType) { + final _sellProviderType = sellProviderType as BuyProviderType; + return _sellProviderType == BuyProviderType.askEachTime + ? S.current.ask_each_time + : _sellProviderType.name; } void onDisplayPrioritySelected(TransactionPriority priority) => _settingsStore.priority[_wallet.type] = priority; - void onBuyProviderTypeSelected(BuyProviderType buyProviderType) => + @action + BuyProviderType onBuyProviderTypeSelected(BuyProviderType buyProviderType) => _settingsStore.defaultBuyProviders[walletType] = buyProviderType; + + @action + BuyProviderType onSellProviderTypeSelected( + BuyProviderType sellProviderType) => + _settingsStore.defaultSellProviders[walletType] = sellProviderType; } diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 251afa409..ba2f7731b 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -754,6 +754,8 @@ "dfx_option_description": "ﺎﺑﻭﺭﻭﺃ ﻲﻓ ﺕﺎﻛﺮﺸﻟﺍﻭ ﺔﺋﺰﺠﺘﻟﺍ ءﻼﻤﻌﻟ .ﻲﻓﺎﺿﺇ KYC ﻥﻭﺪﺑ ﻭﺭﻮﻳ 990 ﻰﻟﺇ ﻞﺼﻳ ﺎﻣ .ﻱﺮﺴﻳﻮﺴﻟﺍ", "polygonscan_history": "ﻥﺎﻜﺴﻧﻮﺠﻴﻟﻮﺑ ﺦﻳﺭﺎﺗ", "wallet_seed_legacy": "بذرة محفظة قديمة", + "default_sell_provider": " ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ", + "select_sell_provider_notice": ".ﻖﻴﺒﻄﺘﻟﺍ ﺕﺍﺩﺍﺪﻋﺇ ﻲﻓ ﻚﺑ ﺹﺎﺨﻟﺍ ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ ﻦﻴﻴﻌﺗ ﻖﻳﺮﻃ ﻦﻋ ﺔﺷﺎﺸﻟﺍ ﻩﺬﻫ ﻲﻄﺨﺗ", "custom_drag": "مخصص (عقد وسحب)", "switchToEVMCompatibleWallet": " (Ethereum، Polygon) ﻯﺮﺧﺃ ﺓﺮﻣ ﺔﻟﻭﺎﺤﻤﻟﺍﻭ EVM ﻊﻣ ﺔﻘﻓﺍﻮﺘﻣ ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻰﺟﺮﻳ" } diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index ae83da481..d53df2d0e 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -750,6 +750,8 @@ "dfx_option_description": "Купете крипто с EUR и CHF. До 990 € без допълнителен KYC. За клиенти на дребно и корпоративни клиенти в Европа", "polygonscan_history": "История на PolygonScan", "wallet_seed_legacy": "Наследено портфейл семе", + "default_sell_provider": "Доставчик за продажба по подразбиране", + "select_sell_provider_notice": "Изберете доставчик на продажба по-горе. Можете да пропуснете този екран, като зададете своя доставчик на продажба по подразбиране в настройките на приложението.", "custom_drag": "Персонализиране (задръжте и плъзнете)", "switchToEVMCompatibleWallet": "Моля, превключете към портфейл, съвместим с EVM, и опитайте отново (Ethereum, Polygon)" } diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index da22f9f6b..4d29f32a0 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -750,6 +750,8 @@ "dfx_option_description": "Nakupujte kryptoměny za EUR a CHF. Až 990 € bez dalších KYC. Pro maloobchodní a firemní zákazníky v Evropě", "polygonscan_history": "Historie PolygonScan", "wallet_seed_legacy": "Starší semeno peněženky", + "default_sell_provider": "Výchozí poskytovatel prodeje", + "select_sell_provider_notice": "Výše vyberte poskytovatele prodeje. Tuto obrazovku můžete přeskočit nastavením výchozího poskytovatele prodeje v nastavení aplikace.", "custom_drag": "Custom (Hold and Drag)", "switchToEVMCompatibleWallet": "Přepněte na peněženku kompatibilní s EVM a zkuste to znovu (Ethereum, Polygon)" } diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 69b7e47d2..e8f03a23b 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -758,6 +758,8 @@ "dfx_option_description": "Krypto mit EUR und CHF kaufen. Bis zu 990€ ohne zusätzliches KYC. Für Privat- und Firmenkunden in Europa", "polygonscan_history": "PolygonScan-Verlauf", "wallet_seed_legacy": "Legacy Wallet Seed", + "default_sell_provider": "Standard-Verkaufsanbieter", + "select_sell_provider_notice": "Wählen Sie oben einen Verkaufsanbieter aus. Sie können diesen Bildschirm überspringen, indem Sie in den App-Einstellungen Ihren Standard-Verkaufsanbieter festlegen.", "custom_drag": "Custom (Hold and Drag)", "switchToEVMCompatibleWallet": "Bitte wechseln Sie zu einem EVM-kompatiblen Wallet und versuchen Sie es erneut (Ethereum, Polygon)" } diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index e1a7fdebc..3f7cbdad2 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -759,6 +759,8 @@ "dfx_option_description": "Buy crypto with EUR & CHF. Up to 990€ without additional KYC. For retail and corporate customers in Europe", "polygonscan_history": "PolygonScan history", "wallet_seed_legacy": "Legacy wallet seed", + "default_sell_provider": "Default Sell Provider", + "select_sell_provider_notice": "Select a sell provider above. You can skip this screen by setting your default sell provider in app settings.", "custom_drag": "Custom (Hold and Drag)", "switchToEVMCompatibleWallet": "Please switch to an EVM compatible wallet and try again (Ethereum, Polygon)" } diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 21de24163..09bbe985e 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -758,6 +758,8 @@ "dfx_option_description": "Compre criptomonedas con EUR y CHF. Hasta 990€ sin KYC adicional. Para clientes minoristas y corporativos en Europa", "polygonscan_history": "Historial de PolygonScan", "wallet_seed_legacy": "Semilla de billetera heredada", + "default_sell_provider": "Proveedor de venta predeterminado", + "select_sell_provider_notice": "Seleccione un proveedor de venta arriba. Puede omitir esta pantalla configurando su proveedor de venta predeterminado en la configuración de la aplicación.", "custom_drag": "Custom (mantenía y arrastre)", "switchToEVMCompatibleWallet": "Cambie a una billetera compatible con EVM e inténtelo nuevamente (Ethereum, Polygon)" } diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 2d3895351..7af139a8f 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -758,6 +758,8 @@ "dfx_option_description": "Achetez des crypto-monnaies avec EUR et CHF. Jusqu'à 990€ sans KYC supplémentaire. Pour les clients particuliers et entreprises en Europe", "polygonscan_history": "Historique de PolygonScan", "wallet_seed_legacy": "Graine de portefeuille hérité", + "default_sell_provider": "Fournisseur de vente par défaut", + "select_sell_provider_notice": "Sélectionnez un fournisseur de vente ci-dessus. Vous pouvez ignorer cet écran en définissant votre fournisseur de vente par défaut dans les paramètres de l'application.", "custom_drag": "Custom (maintenir et traîner)", "switchToEVMCompatibleWallet": "Veuillez passer à un portefeuille compatible EVM et réessayer (Ethereum, Polygon)" } diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 69e118f49..ec820f01f 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -736,6 +736,8 @@ "dfx_option_description": "Sayi crypto tare da EUR & CHF. Har zuwa € 990 ba tare da ƙarin KYC ba. Don 'yan kasuwa da abokan ciniki na kamfanoni a Turai", "polygonscan_history": "PolygonScan tarihin kowane zamani", "wallet_seed_legacy": "Tallarin walat walat", + "default_sell_provider": "Tsohuwar Mai Bayar Siyarwa", + "select_sell_provider_notice": "Zaɓi mai bada siyarwa a sama. Kuna iya tsallake wannan allon ta saita mai bada siyar da ku a cikin saitunan app.", "custom_drag": "Al'ada (riƙe da ja)", "switchToEVMCompatibleWallet": "Da fatan za a canza zuwa walat ɗin EVM mai jituwa kuma a sake gwadawa (Ethereum, Polygon)" } diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 5774203db..da83e8cc7 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -758,6 +758,8 @@ "dfx_option_description": "EUR और CHF के साथ क्रिप्टो खरीदें। अतिरिक्त केवाईसी के बिना 990€ तक। यूरोप में खुदरा और कॉर्पोरेट ग्राहकों के लिए", "polygonscan_history": "पॉलीगॉनस्कैन इतिहास", "wallet_seed_legacy": "विरासत बटुए बीज", + "default_sell_provider": "डिफ़ॉल्ट विक्रय प्रदाता", + "select_sell_provider_notice": "ऊपर एक विक्रय प्रदाता का चयन करें। आप ऐप सेटिंग में अपना डिफ़ॉल्ट विक्रय प्रदाता सेट करके इस स्क्रीन को छोड़ सकते हैं।", "custom_drag": "कस्टम (पकड़ और खींचें)", "switchToEVMCompatibleWallet": "कृपया ईवीएम संगत वॉलेट पर स्विच करें और पुनः प्रयास करें (एथेरियम, पॉलीगॉन)" } diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 5fde4d6de..71b767bdc 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -756,6 +756,8 @@ "dfx_option_description": "Kupujte kripto s EUR i CHF. Do 990 € bez dodatnog KYC-a. Za maloprodajne i poslovne korisnike u Europi", "polygonscan_history": "Povijest PolygonScan", "wallet_seed_legacy": "Sjeme naslijeđenog novčanika", + "default_sell_provider": "Zadani dobavljač prodaje", + "select_sell_provider_notice": "Gore odaberite pružatelja usluga prodaje. Ovaj zaslon možete preskočiti postavljanjem zadanog pružatelja usluga prodaje u postavkama aplikacije.", "custom_drag": "Prilagođeni (držite i povucite)", "switchToEVMCompatibleWallet": "Prijeđite na novčanik kompatibilan s EVM-om i pokušajte ponovno (Ethereum, Polygon)" } diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 208569b31..2398eccd1 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -746,6 +746,8 @@ "dfx_option_description": "Beli kripto dengan EUR & CHF. Hingga 990€ tanpa KYC tambahan. Untuk pelanggan ritel dan korporat di Eropa", "polygonscan_history": "Sejarah PolygonScan", "wallet_seed_legacy": "Biji dompet warisan", + "default_sell_provider": "Penyedia Penjualan Default", + "select_sell_provider_notice": "Pilih penyedia jual di atas. Anda dapat melewati layar ini dengan mengatur penyedia penjualan default Anda di pengaturan aplikasi.", "custom_drag": "Khusus (tahan dan seret)", "switchToEVMCompatibleWallet": "Silakan beralih ke dompet yang kompatibel dengan EVM dan coba lagi (Ethereum, Polygon)" } diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 51beaf576..e8b07c25f 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -758,6 +758,8 @@ "dfx_option_description": "Acquista criptovalute con EUR e CHF. Fino a 990€ senza KYC aggiuntivi. Per clienti al dettaglio e aziendali in Europa", "polygonscan_history": "Cronologia PolygonScan", "wallet_seed_legacy": "Seme di portafoglio legacy", + "default_sell_provider": "Fornitore di vendita predefinito", + "select_sell_provider_notice": "Seleziona un fornitore di vendita sopra. Puoi saltare questa schermata impostando il tuo fornitore di vendita predefinito nelle impostazioni dell'app.", "custom_drag": "Custom (Hold and Drag)", "switchToEVMCompatibleWallet": "Passa a un portafoglio compatibile con EVM e riprova (Ethereum, Polygon)" } diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index ca197adae..94d43cbfb 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -758,6 +758,8 @@ "dfx_option_description": "EUR と CHF で暗号通貨を購入します。追加のKYCなしで最大990ユーロ。ヨーロッパの小売および法人顧客向け", "polygonscan_history": "ポリゴンスキャン履歴", "wallet_seed_legacy": "レガシーウォレットシード", + "default_sell_provider": "デフォルトの販売プロバイダー", + "select_sell_provider_notice": "上記の販売プロバイダーを選択してください。アプリ設定でデフォルトの販売プロバイダーを設定することで、この画面をスキップできます。", "custom_drag": "カスタム(ホールドとドラッグ)", "switchToEVMCompatibleWallet": "EVM 互換のウォレットに切り替えて再試行してください (イーサリアム、ポリゴン)" } diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index c657dad7e..ca727c046 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -756,6 +756,8 @@ "dfx_option_description": "EUR 및 CHF로 암호화폐를 구매하세요. 추가 KYC 없이 최대 990€. 유럽의 소매 및 기업 고객용", "polygonscan_history": "다각형 스캔 기록", "wallet_seed_legacy": "레거시 지갑 시드", + "default_sell_provider": "기본 판매 공급자", + "select_sell_provider_notice": "위에서 판매 공급자를 선택하세요. 앱 설정에서 기본 판매 공급자를 설정하면 이 화면을 건너뛸 수 있습니다.", "custom_drag": "사용자 정의 (홀드 앤 드래그)", "switchToEVMCompatibleWallet": "EVM 호환 지갑으로 전환 후 다시 시도해 주세요. (이더리움, 폴리곤)" } diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 690f59ccc..bd09bf740 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -756,6 +756,8 @@ "dfx_option_description": "EUR & CHF ဖြင့် crypto ကိုဝယ်ပါ။ အပို KYC မပါဘဲ 990€ အထိ။ ဥရောပရှိ လက်လီရောင်းချသူများနှင့် ကော်ပိုရိတ်ဖောက်သည်များအတွက်", "polygonscan_history": "PolygonScan မှတ်တမ်း", "wallet_seed_legacy": "အမွေအနှစ်ပိုက်ဆံအိတ်မျိုးစေ့", + "default_sell_provider": "ပုံသေရောင်းချပေးသူ", + "select_sell_provider_notice": "အထက်ဖော်ပြပါ အရောင်းဝန်ဆောင်မှုပေးသူကို ရွေးပါ။ အက်ပ်ဆက်တင်များတွင် သင်၏မူလရောင်းချပေးသူကို သတ်မှတ်ခြင်းဖြင့် ဤစခရင်ကို ကျော်နိုင်သည်။", "custom_drag": "စိတ်ကြိုက် (Drag)", "switchToEVMCompatibleWallet": "ကျေးဇူးပြု၍ EVM တွဲဖက်သုံးနိုင်သော ပိုက်ဆံအိတ်သို့ ပြောင်းပြီး ထပ်စမ်းကြည့်ပါ (Ethereum၊ Polygon)" } diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 426341bc4..8b8e1875e 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -758,6 +758,8 @@ "dfx_option_description": "Koop crypto met EUR & CHF. Tot 990€ zonder extra KYC. Voor particuliere en zakelijke klanten in Europa", "polygonscan_history": "PolygonScan-geschiedenis", "wallet_seed_legacy": "Legacy portemonnee zaad", + "default_sell_provider": "Standaard verkoopaanbieder", + "select_sell_provider_notice": "Selecteer hierboven een verkoopaanbieder. U kunt dit scherm overslaan door uw standaardverkoopprovider in te stellen in de app-instellingen.", "custom_drag": "Custom (vasthouden en slepen)", "switchToEVMCompatibleWallet": "Schakel over naar een EVM-compatibele portemonnee en probeer het opnieuw (Ethereum, Polygon)" } diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 5b6581bad..03d669904 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -758,6 +758,8 @@ "dfx_option_description": "Kupuj kryptowaluty za EUR i CHF. Do 990 € bez dodatkowego KYC. Dla klientów detalicznych i korporacyjnych w Europie", "polygonscan_history": "Historia PolygonScan", "wallet_seed_legacy": "Dziedziczne ziarno portfela", + "default_sell_provider": "Domyślny dostawca sprzedaży", + "select_sell_provider_notice": "Wybierz dostawcę sprzedaży powyżej. Możesz pominąć ten ekran, ustawiając domyślnego dostawcę sprzedaży w ustawieniach aplikacji.", "custom_drag": "Niestandardowe (trzymaj i przeciągnij)", "switchToEVMCompatibleWallet": "Przejdź na portfel zgodny z EVM i spróbuj ponownie (Ethereum, Polygon)" } diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 5df592837..8cc55ee08 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -757,6 +757,8 @@ "dfx_option_description": "Compre criptografia com EUR e CHF. Até 990€ sem KYC adicional. Para clientes de varejo e corporativos na Europa", "polygonscan_history": "História do PolygonScan", "wallet_seed_legacy": "Semente de carteira herdada", + "default_sell_provider": "Provedor de venda padrão", + "select_sell_provider_notice": "Selecione um fornecedor de venda acima. Você pode pular esta tela definindo seu provedor de venda padrão nas configurações do aplicativo.", "custom_drag": "Personalizado (segure e arraste)", "switchToEVMCompatibleWallet": "Mude para uma carteira compatível com EVM e tente novamente (Ethereum, Polygon)" } diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index e6d149596..bfc6b7b10 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -758,6 +758,8 @@ "dfx_option_description": "Покупайте криптовалюту за EUR и CHF. До 990€ без дополнительного KYC. Для розничных и корпоративных клиентов в Европе", "polygonscan_history": "История PolygonScan", "wallet_seed_legacy": "Наследие семя кошелька", + "default_sell_provider": "Поставщик продаж по умолчанию", + "select_sell_provider_notice": "Выберите поставщика услуг продажи выше. Вы можете пропустить этот экран, установив поставщика услуг продаж по умолчанию в настройках приложения.", "custom_drag": "Пользователь (удерживайте и перетаскивайте)", "switchToEVMCompatibleWallet": "Пожалуйста, переключитесь на кошелек, совместимый с EVM, и повторите попытку (Ethereum, Polygon)." } diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index b445562da..d3142a534 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -756,6 +756,8 @@ "dfx_option_description": "ซื้อ crypto ด้วย EUR และ CHF สูงถึง 990€ โดยไม่มี KYC เพิ่มเติม สำหรับลูกค้ารายย่อยและลูกค้าองค์กรในยุโรป", "polygonscan_history": "ประวัติ PolygonScan", "wallet_seed_legacy": "เมล็ดกระเป๋าเงินมรดก", + "default_sell_provider": "ผู้ให้บริการการขายเริ่มต้น", + "select_sell_provider_notice": "เลือกผู้ให้บริการการขายด้านบน คุณสามารถข้ามหน้าจอนี้ได้โดยการตั้งค่าผู้ให้บริการการขายเริ่มต้นในการตั้งค่าแอป", "custom_drag": "กำหนดเอง (ค้างและลาก)", "switchToEVMCompatibleWallet": "โปรดเปลี่ยนไปใช้กระเป๋าเงินที่รองรับ EVM แล้วลองอีกครั้ง (Ethereum, Polygon)" } diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 5b9af5243..dcae1d6ed 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -752,6 +752,8 @@ "dfx_option_description": "Bumili ng crypto gamit ang EUR at CHF. Hanggang 990€ nang walang karagdagang KYC. Para sa retail at corporate na mga customer sa Europe", "polygonscan_history": "Kasaysayan ng PolygonScan", "wallet_seed_legacy": "Legacy wallet seed", + "default_sell_provider": "Default na Sell Provider", + "select_sell_provider_notice": "Pumili ng provider ng nagbebenta sa itaas. Maaari mong laktawan ang screen na ito sa pamamagitan ng pagtatakda ng iyong default na sell provider sa mga setting ng app.", "custom_drag": "Pasadyang (hawakan at i -drag)", "switchToEVMCompatibleWallet": "Mangyaring lumipat sa isang EVM compatible na wallet at subukang muli (Ethereum, Polygon)" } diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 948415da9..a2722856a 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -756,6 +756,8 @@ "dfx_option_description": "EUR ve CHF ile kripto satın alın. Ek KYC olmadan 990 €'ya kadar. Avrupa'daki perakende ve kurumsal müşteriler için", "polygonscan_history": "PolygonScan geçmişi", "wallet_seed_legacy": "Eski cüzdan tohumu", + "default_sell_provider": "Varsayılan Satış Sağlayıcısı", + "select_sell_provider_notice": "Yukarıdan bir satış sağlayıcısı seçin. Uygulama ayarlarında varsayılan satış sağlayıcınızı ayarlayarak bu ekranı atlayabilirsiniz.", "custom_drag": "Özel (Bekle ve Sürükle)", "switchToEVMCompatibleWallet": "Lütfen EVM uyumlu bir cüzdana geçin ve tekrar deneyin (Ethereum, Polygon)" } diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index f01afeaad..1a37f6550 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -758,6 +758,8 @@ "seed_language_chinese_traditional": "Китайський (традиційний)", "polygonscan_history": "Історія PolygonScan", "wallet_seed_legacy": "Спадець насіння гаманця", + "default_sell_provider": "Постачальник продажу за замовчуванням", + "select_sell_provider_notice": "Виберіть вище постачальника послуг продажу. Ви можете пропустити цей екран, встановивши постачальника послуг продажу за умовчанням у налаштуваннях програми.", "custom_drag": "На замовлення (утримуйте та перетягується)", "switchToEVMCompatibleWallet": "Перейдіть на гаманець, сумісний з EVM, і повторіть спробу (Ethereum, Polygon)" } diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index c8d46bc44..af030e0e7 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -750,6 +750,8 @@ "dfx_option_description": "EUR ﺭﻭﺍ CHF ﯽﻓﺎﺿﺍ ۔ﮟﯾﺪﯾﺮﺧ ﻮﭩﭘﺮﮐ ﮫﺗﺎﺳ ﮯﮐ KYC ﮯﯿﻟ ﮯﮐ ﻦﯿﻓﺭﺎﺻ ﭧﯾﺭﻮﭘﺭﺎﮐ ﺭﻭﺍ ﮦﺩﺭﻮﺧ ﮟ", "polygonscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﻥﻮﮔ ﯽﻟﻮﭘ", "wallet_seed_legacy": "میراثی پرس کا بیج", + "default_sell_provider": " ﮦﺪﻨﻨﮐ ﻢﮨﺍﺮﻓ ﻞﯿﺳ ﭧﻟﺎﻔﯾﮈ", + "select_sell_provider_notice": "۔ﮟﯿﮨ ﮯﺘﮑﺳ ﮌﻮﮭﭼ ﻮﮐ ﻦﯾﺮﮑﺳﺍ ﺱﺍ ﺮﮐ ﮮﺩ ﺐﯿﺗﺮﺗ ﻮﮐ ﮦﺪﻨﻨﮐ ﻢﮨﺍﺮﻓ ﻞﯿﺳ ﭧﻟﺎﻔﯾﮈ ﮯﻨﭘﺍ ﮟﯿﻣ ﺕﺎﺒ", "custom_drag": "کسٹم (ہولڈ اینڈ ڈریگ)", "switchToEVMCompatibleWallet": "(Ethereum, Polygon) ﮟﯾﺮﮐ ﺶﺷﻮﮐ ﮦﺭﺎﺑﻭﺩ ﺭﻭﺍ ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﭧﯿﻟﺍﻭ ﮯﻟﺍﻭ ﮯﻨﮭﮐﺭ ﺖﻘﺑﺎﻄﻣ " } diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 3323429a8..a0dcf33a8 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -752,6 +752,8 @@ "dfx_option_description": "Ra crypto pẹlu EUR & CHF. Titi di 990 € laisi afikun KYC. Fun soobu ati awọn onibara ile-iṣẹ ni Yuroopu", "polygonscan_history": "PolygonScan itan", "wallet_seed_legacy": "Irugbin akole", + "default_sell_provider": "Aiyipada Olupese Tita", + "select_sell_provider_notice": "Yan olupese ti o ta loke. O le foju iboju yii nipa tito olupese iṣẹ tita aiyipada rẹ ni awọn eto app.", "custom_drag": "Aṣa (mu ati fa)", "switchToEVMCompatibleWallet": "Jọwọ yipada si apamọwọ ibaramu EVM ki o tun gbiyanju lẹẹkansi (Ethereum, Polygon)" } diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 148460ffb..b7f216640 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -757,6 +757,8 @@ "dfx_option_description": "用欧元和瑞士法郎购买加密货币。高达 990 欧元,无需额外 KYC。对于欧洲的零售和企业客户", "polygonscan_history": "多边形扫描历史", "wallet_seed_legacy": "旧的钱包种子", + "default_sell_provider": "默认销售提供商", + "select_sell_provider_notice": "选择上面的销售提供商。您可以通过在应用程序设置中设置默认销售提供商来跳过此屏幕。", "custom_drag": "定制(保持和拖动)", "switchToEVMCompatibleWallet": "请切换到 EVM 兼容钱包并重试(以太坊、Polygon)" }