From 76283cd82e223729f69eea5793e61032c6bbee57 Mon Sep 17 00:00:00 2001 From: JoeGruffins <34998433+JoeGruffins@users.noreply.github.com> Date: Fri, 22 Dec 2023 11:59:02 +0000 Subject: [PATCH] multi: Add initial decred screens. (#1165) Use a mock libwallet for now. --- .gitignore | 1 + assets/images/2.0x/decred.png | Bin 0 -> 2574 bytes assets/images/2.0x/decred_menu.png | Bin 0 -> 1871 bytes assets/images/3.0x/decred.png | Bin 0 -> 3825 bytes assets/images/3.0x/decred_menu.png | Bin 0 -> 1423 bytes assets/images/decred.png | Bin 0 -> 1459 bytes assets/images/decred_icon.png | Bin 0 -> 1777 bytes assets/images/decred_menu.png | Bin 0 -> 1482 bytes cw_core/lib/amount_converter.dart | 1 + cw_core/lib/currency_for_wallet_type.dart | 2 + cw_core/lib/node.dart | 8 + cw_core/lib/wallet_type.dart | 14 + cw_decred/.gitignore | 0 cw_decred/.metadata | 10 + cw_decred/CHANGELOG.md | 3 + cw_decred/LICENSE | 1 + cw_decred/README.md | 3 + cw_decred/lib/amount_format.dart | 26 + cw_decred/lib/api/dcrlibwallet.dart | 127 +++ cw_decred/lib/balance.dart | 18 + cw_decred/lib/mnemonic.dart | 28 + .../lib/mnemonic_is_incorrect_exception.dart | 5 + cw_decred/lib/pending_transaction.dart | 35 + cw_decred/lib/transaction_credentials.dart | 11 + cw_decred/lib/transaction_history.dart | 28 + cw_decred/lib/transaction_info.dart | 45 ++ cw_decred/lib/transaction_priority.dart | 54 ++ cw_decred/lib/wallet.dart | 212 +++++ cw_decred/lib/wallet_addresses.dart | 36 + .../lib/wallet_creation_credentials.dart | 44 ++ cw_decred/lib/wallet_service.dart | 99 +++ cw_decred/pubspec.lock | 724 ++++++++++++++++++ cw_decred/pubspec.yaml | 24 + lib/core/wallet_creation_service.dart | 1 + lib/decred/cw_decred.dart | 105 +++ lib/di.dart | 3 + lib/entities/preferences_key.dart | 1 + lib/entities/provider_types.dart | 2 + .../desktop_wallet_selection_dropdown.dart | 3 + .../dashboard/widgets/menu_widget.dart | 6 +- .../screens/wallet_list/wallet_list_page.dart | 2 + lib/store/settings_store.dart | 5 + .../advanced_privacy_settings_view_model.dart | 1 + .../exchange/exchange_view_model.dart | 4 + .../node_create_or_edit_view_model.dart | 1 + lib/view_model/send/send_view_model.dart | 3 + .../transaction_details_view_model.dart | 4 + .../wallet_address_list_view_model.dart | 64 ++ lib/view_model/wallet_new_vm.dart | 3 + lib/view_model/wallet_restore_view_model.dart | 7 + model_generator.sh | 1 + scripts/android/pubspec_gen.sh | 4 +- tool/configure.dart | 87 +++ 53 files changed, 1863 insertions(+), 3 deletions(-) create mode 100644 assets/images/2.0x/decred.png create mode 100644 assets/images/2.0x/decred_menu.png create mode 100644 assets/images/3.0x/decred.png create mode 100644 assets/images/3.0x/decred_menu.png create mode 100644 assets/images/decred.png create mode 100644 assets/images/decred_icon.png create mode 100644 assets/images/decred_menu.png create mode 100644 cw_decred/.gitignore create mode 100644 cw_decred/.metadata create mode 100644 cw_decred/CHANGELOG.md create mode 100644 cw_decred/LICENSE create mode 100644 cw_decred/README.md create mode 100644 cw_decred/lib/amount_format.dart create mode 100644 cw_decred/lib/api/dcrlibwallet.dart create mode 100644 cw_decred/lib/balance.dart create mode 100644 cw_decred/lib/mnemonic.dart create mode 100644 cw_decred/lib/mnemonic_is_incorrect_exception.dart create mode 100644 cw_decred/lib/pending_transaction.dart create mode 100644 cw_decred/lib/transaction_credentials.dart create mode 100644 cw_decred/lib/transaction_history.dart create mode 100644 cw_decred/lib/transaction_info.dart create mode 100644 cw_decred/lib/transaction_priority.dart create mode 100644 cw_decred/lib/wallet.dart create mode 100644 cw_decred/lib/wallet_addresses.dart create mode 100644 cw_decred/lib/wallet_creation_credentials.dart create mode 100644 cw_decred/lib/wallet_service.dart create mode 100644 cw_decred/pubspec.lock create mode 100644 cw_decred/pubspec.yaml create mode 100644 lib/decred/cw_decred.dart diff --git a/.gitignore b/.gitignore index 970241189..194f71ee3 100644 --- a/.gitignore +++ b/.gitignore @@ -137,6 +137,7 @@ lib/polygon/polygon.dart lib/solana/solana.dart lib/tron/tron.dart lib/wownero/wownero.dart +lib/decred/decred.dart ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png diff --git a/assets/images/2.0x/decred.png b/assets/images/2.0x/decred.png new file mode 100644 index 0000000000000000000000000000000000000000..2142dc1025f1c63479a755fbd81cb3e720c107be GIT binary patch literal 2574 zcmV+p3i0)cP)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm0004mX+uL$Nkc;* zaB^>EX>4Tx04R}tkv&MmKpe$iTeYQBL@X%ckfA!+MMWHI6^c+H)C#RSm|XfHG-*gu zTpR`0f`cE6RR<SmT^(EnLGS~_)x}BCMN0f%QfLw5!Ery{-Fw`<1N@B&Q_ZdfplX(p zNhO6`enkkrB7`t{(2qfhnfk0Krr|lh?&0I>U7TlmpZjz4Dg~1PK7lyFbi*QEC!XH4 zbk6(4QC5}|;&bA0gDyz?$aUG}H_j!8{X8>jWHWQbQDULg!Ey()vY`@B5yuo&qkMnP zWrgz=XSGset$XqpMhe>U64z;tAcX}iLWBSrHEf^)3vpUCQcPrMKk4BgcKjl_WO8kQ zkz*cJsE`~#_#gc4)+|m=xJi*D(EVcDAEQ8U7iiXP`}^3on<qfv8MxBg{%Ql5`6RvG z)?!CM-!^b@-PYti;Bp5TdeS9Ba-;xFf3XO>pV2qvfq`2fyz2JW+Q;bwkfpBDH^9Lm zFjk`MHIH|PI(z%~Osl^iI~sD3>NA5u00006VoOIv00000008+zyMF)x010qNS#tmY z3ljhU3ljkVnw%H_000McNliru=LiZ18#!wve<%O|02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00-ttL_t(&-rbsOj8tbC$A8az&d$v4?1f!+fn~*oMQ#F8 zF6E0ADaH!XCXJWIVEW0VCN`FkfURP-Km<ZDYGPv2#72xUB}UN{Ye<{46v~HU-O_Ti zP+$@E2D{t6&-I+g4|8UAcIV86b_>XpoXj~h=Y7xr|2~)Zd7d-EClpn+fo5Q>cPs<u z0W*NGciSj%6SxYT1x|U#H4#a2r$tpoRU1`xo2tI8sv~;)GUAQh=FKH{3Ve&I{#sQB zbn0Zlo3rK91YV)4t*Y9mpPux2�Jc@GCua-zj-pRaZ`xt#4M<TYBf^miKJ=xZo{S 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 literal 0 HcmV?d00001 diff --git a/assets/images/2.0x/decred_menu.png b/assets/images/2.0x/decred_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..4a41efef1199f489b2668e9ce5b9130203ecb460 GIT binary patch literal 1871 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 JPDHLkV1jmSUwr@o literal 0 HcmV?d00001 diff --git a/assets/images/3.0x/decred.png b/assets/images/3.0x/decred.png new file mode 100644 index 0000000000000000000000000000000000000000..eef85bde5691e5e34636d52f0498e8503af5159a GIT binary patch literal 3825 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 literal 0 HcmV?d00001 diff --git a/assets/images/3.0x/decred_menu.png b/assets/images/3.0x/decred_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..e55b3fb5c843a0d48b496d8a90a4de1868af8e2b GIT binary patch literal 1423 zcmV;A1#tR_P)<h;3K|Lk000e1NJLTq004LZ004Lh1^@s6Ib=4{0004mX+uL$Nkc;* zaB^>EX>4Tx04R}tkv&MmKpe$iTeYQBL@X%ckfA!+MMWHI6^c+H)C#RSm|XfHG-*gu zTpR`0f`cE6RR<SmT^(EnLGS~_)x}BCMN0f%QfLw5!Ery{-Fw`<1N@B&Q_ZdfplX(p zNhO6`enkkrB7`t{(2qfhnfk0Krr|lh?&0I>U7TlmpZjz4Dg~1PK7lyFbi*QEC!XH4 zbk6(4QC5}|;&bA0gDyz?$aUG}H_j!8{X8>jWHWQbQDULg!Ey()vY`@B5yuo&qkMnP zWrgz=XSGset$XqpMhe>U64z;tAcX}iLWBSrHEf^)3vpUCQcPrMKk4BgcKjl_WO8kQ zkz*cJsE`~#_#gc4)+|m=xJi*D(EVcDAEQ8U7iiXP`}^3on<qfv8MxBg{%Ql5`6RvG z)?!CM-!^b@-PYti;Bp5TdeS9Ba-;xFf3XO>pV2qvfq`2fyz2JW+Q;bwkfpBDH^9Lm zFjk`MHIH|PI(z%~Osl^iI~sD3>NA5u00006VoOIv00000008+zyMF)x010qNS#tmY z3ljhU3ljkVnw%H_000McNliru=LiZ0J0zTBU1R_N02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00U=9L_t(|+U?!hQ6xbO1kjaY?86g$7$5Ki2jPo|{Q!dL z(UF;zZ2*+&wd&}pBmN0IulM(lj=%eMe+l-w34>~bhe5T$!>HQWVOVYGFswFo7*-oP z466+thSi1+<7y*^VYQ*dxZ22JSZ(Mqt~PQQR~tEutBoAS)kY5EY9ohnwUNWP+Q?yC zZR9YnHgXtO8#!HN+D}wvZ{_6tnO36n<2_32^+=3S6<l@2UaicHl@lBAe~mLLgE$#^ z8oLzukwP1J4*D-KMn^%7jDwsEfn9~#9u&RZUjlmB?6Vc*OrCk!ev2=ef@00&&1{Z| zN^vIlzi>armW%;0XY%xmmQxjB<eel+ghHI!C$IFuC=+y#u50Qx6@zjn{{TE0u#so# zHMWCAX|A_3t!r8u2yZhBGE14i1xEO8%Z6NC?hfSjB|BZ8@TNw{=zVyq!5SF=cyj*9 zgxt1$l(D~3?USqf;Z)38#Q!hdM59#hL^;s<<2)^M&@;KZe7Uuh#K<$_j&!DIH`94? zhMbVe$8SDUN4~k~(|&H~1vDxUFzPQWCpY`)to0zvxWw_70ar9j3b<&aa-Y%RCfZFi z^PJilEZ#+>vsW{7Tjs&eS_2z)xD~q@!J}wD#AA?jr8$F*Ok76!CK?%eqHGYrOj|SA z%9JOqFg&d+qdOZArZou2(`87-3_&dC>;3&><9|a9vQ)QwZod4bJN&DFRCYUV!nW-2 zuL_7gled~<s(J9#>8$Esyjl)n>4IU}D2H;WP0KM#tVaZ@ApajnvSk`mbB*w#%YKW# z+J~zlXIEjE2QSRXqo>U>pXREP$#F_wbi%;vyRv%8j(8@AYG9{j^6ITCUjcdGHUGlL zWKc-ijB?LI_m4ahcQ$yQd@+M3Fj#L0Ww|*fP_m5?8Uguf;Eg7A3mXCXk#dw=5WU@R zPGU1P*QHmyqYRAvGy)r$5BFhUBXi-d&4c~SJ-+WxID0Px8<}S|0dob6pPp%$j~^7q zEde$%E^ZmHkuh;gfsKrZTMlewEZi1gBjez<0UH?uw-wmXyRc)xM&83612*(L><X}v zfp#_+nj3Z<_!Q{v)uPn<g$zpstOAX8_9yH?q-@Zrhq_XnG>eMOa8fVjQNkxI!ao{# d3S8kV?N6GS<`Mcb7Cry~002ovPDHLkV1kl0ii`jN literal 0 HcmV?d00001 diff --git a/assets/images/decred.png b/assets/images/decred.png new file mode 100644 index 0000000000000000000000000000000000000000..271413394cc062efa570a1fba7241f9327ff9ca9 GIT binary patch literal 1459 zcmV;k1x)&hP)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv0004mX+uL$Nkc;* zaB^>EX>4Tx04R}tkv&MmKpe$iTeYQBL@X%ckfA!+MMWHI6^c+H)C#RSm|XfHG-*gu zTpR`0f`cE6RR<SmT^(EnLGS~_)x}BCMN0f%QfLw5!Ery{-Fw`<1N@B&Q_ZdfplX(p zNhO6`enkkrB7`t{(2qfhnfk0Krr|lh?&0I>U7TlmpZjz4Dg~1PK7lyFbi*QEC!XH4 zbk6(4QC5}|;&bA0gDyz?$aUG}H_j!8{X8>jWHWQbQDULg!Ey()vY`@B5yuo&qkMnP zWrgz=XSGset$XqpMhe>U64z;tAcX}iLWBSrHEf^)3vpUCQcPrMKk4BgcKjl_WO8kQ zkz*cJsE`~#_#gc4)+|m=xJi*D(EVcDAEQ8U7iiXP`}^3on<qfv8MxBg{%Ql5`6RvG z)?!CM-!^b@-PYti;Bp5TdeS9Ba-;xFf3XO>pV2qvfq`2fyz2JW+Q;bwkfpBDH^9Lm zFjk`MHIH|PI(z%~Osl^iI~sD3>NA5u00006VoOIv00000008+zyMF)x010qNS#tmY z3ljhU3ljkVnw%H_000McNliru=LiZ19ynoCyZZnD02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00WCjL_t(Y$Gw(MY*bYk#ed&@Z>B^4%#_mU(29UV1S&+W z5gKLD1dtfm8p{H@FfL3qaiejgNtc)yH6aia!;TorqKS#M329h0!~{cGQ2tn3HBbRt zXs1j&Gw;p4pNr0vX|1hj>Phb6z3;wr?z``v^F3h&sOmQ0O<+H;3rLnq6TmQV5jZa* zqdW;zHLa@WR5j4m5tQbp{|T|EYL}{J^vRJaJu54bf268b|2=GpLzWZha;mmyotB4l zkDqkv)VjsPsrx&Tt}?Hb_Z%1siW)zCVUmG6Im9?RHbr^wz(!&bLng>`V&pQn!Ymkv zcuI3LN=MyB4m50|AzD+p-Wi}xM8?Vj=YURteBk)Je}ePB<uIP3GhM~|2b+mS3=a!) zeDc!|TnQh6rwc+U-{VBfE9|IERf_b8$WbXB#T!7RTv<)zn>)i4u-ptX47*t{j->B% zX3IeuqqSw45NP$fySv{34pmAUBUF2a{*gRZ;d-V>b}Gl7t+mueqwH;XidP#pakw!} z%E4f6k~tg=sytuYT&D4Wd!}Tf1q7jT>bq&)>djCH4Qf4{HS~-X_;TPbh0ra`^~G=` zwUd|qIH)i%JC0MWq}%V89J+ws!8yLVT0}gL-EqUwwit;TK}126@rMP{sdWqUJu&R8 zO7LTLhTn@J7Kgzrmv{LU_9vTDP7W4PYj|nB;pFiI^|juL<}9k?+FS-yNJ$hAQ&~>> zl~?y3I3{xj@!)uSbXD@eS-u^=$xvYqjPP1b3q}@u%r6C3Gk~?AZhi%|F>k36>HBqz zE13s~QJgq#g;{=ac`(9ezly_++n26k!jw`GfDH*D!o0|(>z2yzB_bT!yp=l7r!O-_ z|I9RFK>$wZh}H8&dLJ9AYL*HNn^NSK1PpKNFre`DkmKxyJQGun*}M{jO4Rds_qiA7 zT;BvL;Dp_gYCe9potEmva=Uo(FGjn4-*W79p1b+^Ac=U6sPAY?DxV&!BU$eeSj(v! zSLw<80Y*8JsON+2dsyd3D#=5j)s&h=Z~5-iDUUO6$7oLqD1_E97aA_z7d|}uh>2-C z|0b<3(pldG3VkzEd^U2KTrpg9;zeY1QP=_T$8n)t8?}s$Tk;_w4n%2)!|Qvi@jVEv z<@?(|leHlj<(Zl|`<k{a3cE#d{U3Y5vT^<YHJmkXnQPVbWvzc({sd$sgLei6yCMJp N002ovPDHLkV1g_ynvnni literal 0 HcmV?d00001 diff --git a/assets/images/decred_icon.png b/assets/images/decred_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6f181da18c2ad7957f7f23cee21475cbdb7b985d GIT binary patch literal 1777 zcmV<N1`hd&P)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F80004mX+uL$Nkc;* zaB^>EX>4Tx04R}tkv&MmKpe$iTeYQBL@X%ckfA!+MMWHI6^c+H)C#RSm|XfHG-*gu zTpR`0f`cE6RR<SmT^(EnLGS~_)x}BCMN0f%QfLw5!Ery{-Fw`<1N@B&Q_ZdfplX(p zNhO6`enkkrB7`t{(2qfhnfk0Krr|lh?&0I>U7TlmpZjz4Dg~1PK7lyFbi*QEC!XH4 zbk6(4QC5}|;&bA0gDyz?$aUG}H_j!8{X8>jWHWQbQDULg!Ey()vY`@B5yuo&qkMnP zWrgz=XSGset$XqpMhe>U64z;tAcX}iLWBSrHEf^)3vpUCQcPrMKk4BgcKjl_WO8kQ zkz*cJsE`~#_#gc4)+|m=xJi*D(EVcDAEQ8U7iiXP`}^3on<qfv8MxBg{%Ql5`6RvG z)?!CM-!^b@-PYti;Bp5TdeS9Ba-;xFf3XO>pV2qvfq`2fyz2JW+Q;bwkfpBDH^9Lm zFjk`MHIH|PI(z%~Osl^iI~sD3>NA5u00006VoOIv00000008+zyMF)x010qNS#tmY z3ljhU3ljkVnw%H_000McNliru=LiZ19Xh0Ui4*_;02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00hZNL_t(o!_AjXXk1kk$A9O}n@lE?N!lb$$C{)alh&qc zL!xn^6fHs#Z8xGWEVxjxg4K;tG#f$0fTUGK-AG+pq*AI?3U0dS$~KZJ7%S$(beb|H zX&N#~C-ZfWi+OpOnaq@^)%Fhq!@Kw0^MB{#f6f)w2vuza_5izqZXgP*bC>6U31ApF z4g4S?SGgHb)$OW!L{;Z??U;Ap+5T?~hgEe@Rg?P9k#x@p|6Svqs(M~;9?q+3=UR>T zs%l(sKH{p{yOuBiFN_zGDDs39#vC}KO4pxPQe>_5R(%GxG*@FJ|3HD*!W?OvEjmR6 zYMd$>t3p&6U#0K-8Q3Kv2>_>H;T@n$1d<uc!Ea}n&E*iIh|ki0e;rTtHeo~%Ag9Wk zqeEQQG#JhQL=mI3`TaayznMKvTd8+~r3AWMTYVmPw_JvS>g8P{7n8hpa)ud~gz;J4 zE+7Fgk(i}#^y~@=$rBM{!6@zi0Pk*kh-gh?DTyVZTSP7x_rV*5#sPM;1^IYi6OBF} zY6Y7!4E!|D@pCg+RRE&l1_nDG<PPIUEvUNjQ2`akvMKte&Ja({mlCLUZ6dD1O#o$U zbtIO&VK@M;Bp0}vzK){AQwfgFjxv(F?uN$lL~sNBt&e#7T_k|0apkqFacd!+hG>n0 zT7z0ampw5wgUvgFjlo)W)Hica{YD;Z?%<=2-8>pt?|I`?a+=uEwbJ9mz#ijj`zkCX zErTbn@%-0woSU&=jYq=f3<E>+93PtY!cmA?C%{Xay7Hm0f~xS_;+1k{b~|okDMBWz z96piYtMM#i3<4;YNR^Lu0plgwv5`eW0UwX=Y(PrRxHDK!s|gUZOQ3K$y-<!ox8ruT zA`D$fbM*Hd#t4dGTdkr0@me}t1NfanQ(7!06fdclknaE0h&lx=)x$SuGkKQ-VMjUq zOdVSy{xa`NMOD4HI7cj#0V9Yi5r1vDZR?!UC&`rZV+_>hEA+V?P6gwYF_%vAN$eLl z3G-g)3^kYC>$t;&rxfW7EM^4F31EnBC@-mrbecb|&7z_#SC>V(yp-U}nMo$?5}3UE zpA0s!xu&5kSI5-=kAUBSpbzFU2Cz)emH*0+-_NDR1qNc{sH=R-N|;a5yzzUTATPJ{ z;FGf9nlP>ed#I@vdLnuEt2G=O&M`Z0SG{RO*cWZ5zwIu>RXz|;1s#O_^_%!$>n<7t zp-RyjHtq~pgnhjZ%Sk()5Z*hw#ME?dbqb2GcS}3{t?hXdb|w4YirmdB?GLcNDtM!G zIbBwYlPTrkXGzY)S869*rR-{fraBO#Jo|7pTU+wUX;t}ta+LRDW2hT~uunMD(ap|= zNG0P5AiCTQj{q+el~Fv$fzMNnEv%3bV-a6ogj#BpPxsfdt+klMz@;&)+XKQr;ZS=| zB@$nV$cq)*F~HQc<-_ACPL5kIBsU_Vs(d#&%D~j9OG2sh34`rD>|7ry+l~t-qCw#G zQkvNu{5qcH+=U#Yam!K`5bI@pD4@K$H$<et=aD!S8{_nB+}l&-6Ao;?kG7Dfd51)# zuk1+XPAnB>M$ig+SXyPJO-cmvvsCd&cFTv-+iNVpEiNXv#ntTpyqwCdzMlUDA>lyU TIo>wV00000NkvXXu0mjfCAc+& literal 0 HcmV?d00001 diff --git a/assets/images/decred_menu.png b/assets/images/decred_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..5c67923c57c9b9f2fc269a3099a56cf105d008f0 GIT binary patch literal 1482 zcmV;*1vUDKP)<h;3K|Lk000e1NJLTq001Ze001Zm1^@s6jQ+T70004mX+uL$Nkc;* zaB^>EX>4Tx04R}tkv&MmKpe$iTeYQBL@X%ckfA!+MMWHI6^c+H)C#RSm|XfHG-*gu zTpR`0f`cE6RR<SmT^(EnLGS~_)x}BCMN0f%QfLw5!Ery{-Fw`<1N@B&Q_ZdfplX(p zNhO6`enkkrB7`t{(2qfhnfk0Krr|lh?&0I>U7TlmpZjz4Dg~1PK7lyFbi*QEC!XH4 zbk6(4QC5}|;&bA0gDyz?$aUG}H_j!8{X8>jWHWQbQDULg!Ey()vY`@B5yuo&qkMnP zWrgz=XSGset$XqpMhe>U64z;tAcX}iLWBSrHEf^)3vpUCQcPrMKk4BgcKjl_WO8kQ zkz*cJsE`~#_#gc4)+|m=xJi*D(EVcDAEQ8U7iiXP`}^3on<qfv8MxBg{%Ql5`6RvG z)?!CM-!^b@-PYti;Bp5TdeS9Ba-;xFf3XO>pV2qvfq`2fyz2JW+Q;bwkfpBDH^9Lm zFjk`MHIH|PI(z%~Osl^iI~sD3>NA5u00006VoOIv00000008+zyMF)x010qNS#tmY z3ljhU3ljkVnw%H_000McNliru=LiZ1A_P6izJmY&02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00W{)L_t(o!_}G3OB+EH$3MFma+F?+AmYJ;w^D)<Y=w~P zQt1y{idb(wGzGm$pv*-)6e_sw*^0CxrB(J;ieL$$;=P9+f(XHXp~#lBhrZBtnVFq6 zMC^lHlG&Z_zW06K_nnPL&UmgO|2GT+C13_914D-8ZJ-J40!JR4tgE1tXFo3|urja$ zOaO(P&lUrRK;46;D^MqZH5E+>urlD&TXZC=^-u+ko&eTVR0LLmXBWtrOGaM<%b^O+ zas#ZXXax8IjMCp|0o70i$Ibu++&(bE<-{>i(Sh`?05~g1Ps6$IQdSc$3YtcVh5V){ z<|y}gl9TC;Rs~!P+yDkOpE>zG87~ZL2Z3wAJ>Vhm!Sdk9-mBUSij?dpy4A>wnu;C) zn~AkCFdwR5+d;BMfem2F(%qG=1o>SN7M)8<$q2A7oIDo-WImC}0<bB-tr4oA1yq0~ zO(nYbS`kct0m_6ZYZmtlWE`qsO>|HfWx<3eBkaJE@{5%F3GlHyIYJf0hH@8#R=T|# z9!z_ZogWU9q6JKcDrh@^>?AUK3LF7%bfZnUe_&9CF<)GX3%CW@42N?2A}vLs$*fRC zSN=ob#K6UpFP6y`+oDa4(BeQeAaxsVr1>o6KQdf1;~SbI32xpt%yWV)0(Ugu@0yxr zz+yI!fdw7Gh1@0Jy5_oHKm^=1+;<XK(^xQF3=P;4qRU)OMYE#z!x!|1-!$JD?=xUg z^F2eyZS~*{&vf^-^?`43y{9Q~x@nUa+(KGQ6LiUyK~oo&EDyA6;Zj>Dqcf!wLYJNa zX2qva1@H4*AlX~7anP5ib3H>GS?=@JWe%snZDx$vwl60vT{&;2G_C{GGlba5EQsaW z2eQ^SeS=8)z<rGnH^i_W8~Bz9vZkU%aqUwMiQe_4Y`UyGHQ1~jU<|kgJdn?0;HM+V zJ=09rQ7X%B?ErHoyOhezZQyg_{9$z2jt8@b2h;xgKW^ztHVf251GDDvSD>0`I%6h9 zZYn|*lWf)3RnYO}VA3=+3&2ZYOHO=QARA)kdj;7POx8n_&OC&|p%a_+s;Q_9yh{jh zR=KwluIk*ODSa+}1uQ!1VHVLRQ%)%sm!o^IAz2Tw7OG&$G)b0$Ame>ZA;Ux3uDk2t z+?S1JLF`r4Lf(GA`lzNY-j#oO<QuxN+hMIs0M{~svpQM1U|j5Z+y`K>+<V*v?*D%U koQx~|5?urI(*B0{1E!mzip#o~umAu607*qoM6N<$g6@i%1ONa4 literal 0 HcmV?d00001 diff --git a/cw_core/lib/amount_converter.dart b/cw_core/lib/amount_converter.dart index 1c5456b07..de32fcc29 100644 --- a/cw_core/lib/amount_converter.dart +++ b/cw_core/lib/amount_converter.dart @@ -27,6 +27,7 @@ class AmountConverter { case CryptoCurrency.btc: case CryptoCurrency.bch: case CryptoCurrency.ltc: + case CryptoCurrency.dcr: return _bitcoinAmountToString(amount); case CryptoCurrency.xhv: case CryptoCurrency.xag: diff --git a/cw_core/lib/currency_for_wallet_type.dart b/cw_core/lib/currency_for_wallet_type.dart index af6037a3b..d80c87efc 100644 --- a/cw_core/lib/currency_for_wallet_type.dart +++ b/cw_core/lib/currency_for_wallet_type.dart @@ -30,6 +30,8 @@ CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) { return CryptoCurrency.trx; case WalletType.wownero: return CryptoCurrency.wow; + case WalletType.decred: + return CryptoCurrency.dcr; case WalletType.none: throw Exception( 'Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType'); diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index aa7d27254..9bf6f5b97 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -101,6 +101,8 @@ class Node extends HiveObject with Keyable { case WalletType.tron: return Uri.parse( "http${isSSL ? "s" : ""}://$uriRaw${path!.startsWith("/") ? path : "/$path"}"); + case WalletType.decred: + return Uri.http(uriRaw, ''); case WalletType.none: throw Exception('Unexpected type ${type.toString()} for Node uri'); } @@ -160,6 +162,8 @@ class Node extends HiveObject with Keyable { case WalletType.solana: case WalletType.tron: return requestElectrumServer(); + case WalletType.decred: + return requestDecredNode(); case WalletType.none: return false; } @@ -284,3 +288,7 @@ class Node extends HiveObject with Keyable { } } } + + Future<bool> requestDecredNode() async { + return true; +} diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart index e3957b4e7..2b5769f97 100644 --- a/cw_core/lib/wallet_type.dart +++ b/cw_core/lib/wallet_type.dart @@ -16,6 +16,7 @@ const walletTypes = [ WalletType.polygon, WalletType.solana, WalletType.tron, + WalletType.decred, ]; @HiveType(typeId: WALLET_TYPE_TYPE_ID) @@ -58,6 +59,9 @@ enum WalletType { @HiveField(12) wownero, + + @HiveField(13) + decred } int serializeToInt(WalletType type) { @@ -86,6 +90,8 @@ int serializeToInt(WalletType type) { return 10; case WalletType.wownero: return 11; + case WalletType.decred: + return 12; case WalletType.none: return -1; } @@ -117,6 +123,8 @@ WalletType deserializeFromInt(int raw) { return WalletType.tron; case 11: return WalletType.wownero; + case 12: + return WalletType.decred; default: throw Exception('Unexpected token: $raw for WalletType deserializeFromInt'); } @@ -148,6 +156,8 @@ String walletTypeToString(WalletType type) { return 'Tron'; case WalletType.wownero: return 'Wownero'; + case WalletType.decred: + return 'Decred'; case WalletType.none: return ''; } @@ -179,6 +189,8 @@ String walletTypeToDisplayName(WalletType type) { return 'Tron (TRX)'; case WalletType.wownero: return 'Wownero (WOW)'; + case WalletType.decred: + return 'Decred (DCR)'; case WalletType.none: return ''; } @@ -213,6 +225,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal return CryptoCurrency.trx; case WalletType.wownero: return CryptoCurrency.wow; + case WalletType.decred: + return CryptoCurrency.dcr; case WalletType.none: throw Exception( 'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency'); diff --git a/cw_decred/.gitignore b/cw_decred/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/cw_decred/.metadata b/cw_decred/.metadata new file mode 100644 index 000000000..4ce247fd6 --- /dev/null +++ b/cw_decred/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: b1395592de68cc8ac4522094ae59956dd21a91db + channel: stable + +project_type: package diff --git a/cw_decred/CHANGELOG.md b/cw_decred/CHANGELOG.md new file mode 100644 index 000000000..ac071598e --- /dev/null +++ b/cw_decred/CHANGELOG.md @@ -0,0 +1,3 @@ +## [0.0.1] - TODO: Add release date. + +* TODO: Describe initial release. diff --git a/cw_decred/LICENSE b/cw_decred/LICENSE new file mode 100644 index 000000000..ba75c69f7 --- /dev/null +++ b/cw_decred/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/cw_decred/README.md b/cw_decred/README.md new file mode 100644 index 000000000..d24bc80a4 --- /dev/null +++ b/cw_decred/README.md @@ -0,0 +1,3 @@ +# cw_decred + +TODO: Fill this out. diff --git a/cw_decred/lib/amount_format.dart b/cw_decred/lib/amount_format.dart new file mode 100644 index 000000000..421cec1b8 --- /dev/null +++ b/cw_decred/lib/amount_format.dart @@ -0,0 +1,26 @@ +import 'package:intl/intl.dart'; +import 'package:cw_core/crypto_amount_format.dart'; + +const decredAmountLength = 8; +const decredAmountDivider = 100000000; +final decredAmountFormat = NumberFormat() + ..maximumFractionDigits = decredAmountLength + ..minimumFractionDigits = 1; + +String decredAmountToString({required int amount}) => decredAmountFormat + .format(cryptoAmountToDouble(amount: amount, divider: decredAmountDivider)); + +double decredAmountToDouble({required int amount}) => + cryptoAmountToDouble(amount: amount, divider: decredAmountDivider); + +int stringDoubleToDecredAmount(String amount) { + int result = 0; + + try { + result = (double.parse(amount) * decredAmountDivider).round(); + } catch (e) { + result = 0; + } + + return result; +} diff --git a/cw_decred/lib/api/dcrlibwallet.dart b/cw_decred/lib/api/dcrlibwallet.dart new file mode 100644 index 000000000..3ac7b40af --- /dev/null +++ b/cw_decred/lib/api/dcrlibwallet.dart @@ -0,0 +1,127 @@ +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/sync_status.dart'; +import 'package:cw_core/unspent_transaction_output.dart'; +import 'package:cw_decred/balance.dart'; +import 'package:cw_decred/pending_transaction.dart'; +import 'package:cw_decred/transaction_info.dart'; +import 'package:cw_core/transaction_direction.dart'; +import 'dart:typed_data'; +import 'dart:io'; + +// Will it work if none of these are async functions? +class SPVWallet { + SPVWallet(); + + SPVWallet create(Uint8List seed, String password, WalletInfo walletInfo) { + return SPVWallet(); + } + + SPVWallet load(String password, String name, WalletInfo walletInfo) { + return SPVWallet(); + } + + DecredBalance balance() { + return DecredBalance( + confirmed: 777, + unconfirmed: 111, + ); + } + + int feeRate(int priority) { + return 1000; + } + + int calculateEstimatedFeeWithFeeRate(int feeRate, int amount) { + // Ideally we create a tx with wallet going to this amount and just return + // the fee we get back. + return 123000; + } + + void close() {} + + DecredPendingTransaction createTransaction(Object credentials) { + return DecredPendingTransaction( + spv: this, + txid: + "3cbf3eb9523fd04e96dbaf98cdbd21779222cc8855ece8700494662ae7578e02", + amount: 12345678, + fee: 1234, + rawHex: "baadbeef"); + } + + void rescan(int height) { + sleep(Duration(seconds: 10)); + } + + void startSync() { + sleep(Duration(seconds: 5)); + } + + SyncStatus syncStatus() { + return SyncedSyncStatus(); + } + + int height() { + return 400; + } + + Map<String, DecredTransactionInfo> transactions() { + final txInfo = DecredTransactionInfo( + id: "3cbf3eb9523fd04e96dbaf98cdbd21779222cc8855ece8700494662ae7578e02", + amount: 1234567, + fee: 123, + direction: TransactionDirection.outgoing, + isPending: true, + date: DateTime.now(), + height: 0, + confirmations: 0, + to: "DsT4qJPPaYEuQRimfgvSKxKH3paysn1x3Nt", + ); + return { + "3cbf3eb9523fd04e96dbaf98cdbd21779222cc8855ece8700494662ae7578e02": txInfo + }; + } + + String newAddress() { + // external + return "DsT4qJPPaYEuQRimfgvSKxKH3paysn1x3Nt"; + } + + List<String> addresses() { + return [ + "DsT4qJPPaYEuQRimfgvSKxKH3paysn1x3Nt", + "DsVZGfGpd7WVffBZ5wbFZEHLV3FHNmXs9Az" + ]; + } + + List<Unspent> unspents() { + return [ + Unspent( + "DsT4qJPPaYEuQRimfgvSKxKH3paysn1x3Nt", + "3cbf3eb9523fd04e96dbaf98cdbd21779222cc8855ece8700494662ae7578e02", + 1234567, + 0, + null) + ]; + } + + void changePassword(String newPW) {} + + void sendRawTransaction(String rawHex) {} + + String signMessage(String message, String? address) { + return "abababababababab"; + } +} + +Uint8List mnemonicToSeedBytes(String mnemonic) { + return Uint8List(32); +} + +String generateMnemonic() { + return "maid upper strategy dove theory dream material cruel season best napkin ethics biology top episode rough hotel flight video target organ six disagree verify maid upper strategy dove theory dream material cruel season best napkin ethics biology top episode rough hotel flight video target organ six disagree verify"; +} + +bool validateMnemonic(String mnemonic) { + return true; +} diff --git a/cw_decred/lib/balance.dart b/cw_decred/lib/balance.dart new file mode 100644 index 000000000..97a435f21 --- /dev/null +++ b/cw_decred/lib/balance.dart @@ -0,0 +1,18 @@ +import 'package:cw_decred/amount_format.dart'; +import 'package:cw_core/balance.dart'; + +class DecredBalance extends Balance { + const DecredBalance({required this.confirmed, required this.unconfirmed}) + : super(confirmed, unconfirmed); + + final int confirmed; + final int unconfirmed; + + @override + String get formattedAvailableBalance => + decredAmountToString(amount: confirmed); + + @override + String get formattedAdditionalBalance => + decredAmountToString(amount: unconfirmed); +} diff --git a/cw_decred/lib/mnemonic.dart b/cw_decred/lib/mnemonic.dart new file mode 100644 index 000000000..dd5607e57 --- /dev/null +++ b/cw_decred/lib/mnemonic.dart @@ -0,0 +1,28 @@ +List<String> wordList() { + return [ + "maid", + "upper", + "strategy", + "dove", + "theory", + "dream", + "material", + "cruel", + "season", + "best", + "napkin", + "ethics", + "biology", + "top", + "episode", + "rough", + "hotel", + "flight", + "video", + "target", + "organ", + "six", + "disagree", + "verify" + ]; +} diff --git a/cw_decred/lib/mnemonic_is_incorrect_exception.dart b/cw_decred/lib/mnemonic_is_incorrect_exception.dart new file mode 100644 index 000000000..a89ae2201 --- /dev/null +++ b/cw_decred/lib/mnemonic_is_incorrect_exception.dart @@ -0,0 +1,5 @@ +class DecredMnemonicIsIncorrectException implements Exception { + @override + String toString() => + 'Decred mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.'; +} diff --git a/cw_decred/lib/pending_transaction.dart b/cw_decred/lib/pending_transaction.dart new file mode 100644 index 000000000..10af5f04c --- /dev/null +++ b/cw_decred/lib/pending_transaction.dart @@ -0,0 +1,35 @@ +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_decred/amount_format.dart'; +import 'package:cw_decred/api/dcrlibwallet.dart'; + +class DecredPendingTransaction with PendingTransaction { + DecredPendingTransaction( + {required this.spv, + required this.txid, + required this.amount, + required this.fee, + required this.rawHex}); + + final SPVWallet spv; + final int amount; + final int fee; + final String txid; + final String rawHex; + + @override + String get id => txid; + + @override + String get amountFormatted => decredAmountToString(amount: amount); + + @override + String get feeFormatted => decredAmountToString(amount: fee); + + @override + String get hex => rawHex; + + @override + Future<void> commit() async { + this.spv.sendRawTransaction(this.rawHex); + } +} diff --git a/cw_decred/lib/transaction_credentials.dart b/cw_decred/lib/transaction_credentials.dart new file mode 100644 index 000000000..fa59e6633 --- /dev/null +++ b/cw_decred/lib/transaction_credentials.dart @@ -0,0 +1,11 @@ +import 'package:cw_decred/transaction_priority.dart'; +import 'package:cw_core/output_info.dart'; + +class DecredTransactionCredentials { + DecredTransactionCredentials(this.outputs, + {required this.priority, this.feeRate}); + + final List<OutputInfo> outputs; + final DecredTransactionPriority? priority; + final int? feeRate; +} diff --git a/cw_decred/lib/transaction_history.dart b/cw_decred/lib/transaction_history.dart new file mode 100644 index 000000000..d2abafbf4 --- /dev/null +++ b/cw_decred/lib/transaction_history.dart @@ -0,0 +1,28 @@ +import 'package:mobx/mobx.dart'; +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/transaction_history.dart'; + +// NOTE: Methods currently not used. +class DecredTransactionHistory extends TransactionHistoryBase<TransactionInfo> { + DecredTransactionHistory() { + transactions = ObservableMap<String, TransactionInfo>(); + } + + Future<void> init() async {} + + @override + void addOne(TransactionInfo transaction) => + transactions[transaction.id] = transaction; + + @override + void addMany(Map<String, TransactionInfo> transactions) => + this.transactions.addAll(transactions); + + @override + Future<void> save() async {} + + Future<void> changePassword(String password) async {} + + void _update(TransactionInfo transaction) => + transactions[transaction.id] = transaction; +} diff --git a/cw_decred/lib/transaction_info.dart b/cw_decred/lib/transaction_info.dart new file mode 100644 index 000000000..01f0cccd7 --- /dev/null +++ b/cw_decred/lib/transaction_info.dart @@ -0,0 +1,45 @@ +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/format_amount.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cw_decred/amount_format.dart'; + +class DecredTransactionInfo extends TransactionInfo { + DecredTransactionInfo({ + required String id, + required int amount, + required int fee, + required TransactionDirection direction, + required bool isPending, + required DateTime date, + required int height, + required int confirmations, + required String to, + }) { + this.id = id; + this.amount = amount; + this.fee = fee; + this.height = height; + this.direction = direction; + this.date = date; + this.isPending = isPending; + this.confirmations = confirmations; + this.to = to; + } + + String? _fiatAmount; + + @override + String amountFormatted() => + '${formatAmount(decredAmountToString(amount: amount))} ${walletTypeToCryptoCurrency(WalletType.decred).title}'; + + @override + String? feeFormatted() => + '${formatAmount(decredAmountToString(amount: amount))} ${walletTypeToCryptoCurrency(WalletType.decred).title}'; + + @override + String fiatAmount() => _fiatAmount ?? ''; + + @override + void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); +} diff --git a/cw_decred/lib/transaction_priority.dart b/cw_decred/lib/transaction_priority.dart new file mode 100644 index 000000000..f9e601539 --- /dev/null +++ b/cw_decred/lib/transaction_priority.dart @@ -0,0 +1,54 @@ +import 'package:cw_core/transaction_priority.dart'; + +class DecredTransactionPriority extends TransactionPriority { + const DecredTransactionPriority({required String title, required int raw}) + : super(title: title, raw: raw); + + static const List<DecredTransactionPriority> all = [fast, medium, slow]; + static const DecredTransactionPriority slow = + DecredTransactionPriority(title: 'Slow', raw: 0); + static const DecredTransactionPriority medium = + DecredTransactionPriority(title: 'Medium', raw: 1); + static const DecredTransactionPriority fast = + DecredTransactionPriority(title: 'Fast', raw: 2); + + static DecredTransactionPriority deserialize({required int raw}) { + switch (raw) { + case 0: + return slow; + case 1: + return medium; + case 2: + return fast; + default: + throw Exception( + 'Unexpected token: $raw for DecredTransactionPriority deserialize'); + } + } + + String get units => 'atom'; + + @override + String toString() { + var label = ''; + + switch (this) { + case DecredTransactionPriority.slow: + label = + 'Slow ~24hrs'; // '${S.current.transaction_priority_slow} ~24hrs'; + break; + case DecredTransactionPriority.medium: + label = 'Medium'; // S.current.transaction_priority_medium; + break; + case DecredTransactionPriority.fast: + label = 'Fast'; // S.current.transaction_priority_fast; + break; + default: + break; + } + + return label; + } + + String labelWithRate(int rate) => '${toString()} ($rate ${units}/byte)'; +} diff --git a/cw_decred/lib/wallet.dart b/cw_decred/lib/wallet.dart new file mode 100644 index 000000000..7d82c9cd3 --- /dev/null +++ b/cw_decred/lib/wallet.dart @@ -0,0 +1,212 @@ +import 'package:cw_decred/transaction_history.dart'; +import 'package:cw_decred/wallet_addresses.dart'; +import 'package:cw_decred/transaction_priority.dart'; +import 'package:cw_decred/api/dcrlibwallet.dart'; +import 'package:cw_decred/balance.dart'; +import 'package:cw_decred/transaction_info.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:mobx/mobx.dart'; +import 'dart:io'; +import 'package:flutter/foundation.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/sync_status.dart'; +import 'package:cw_core/node.dart'; +import 'package:cw_core/unspent_transaction_output.dart'; + +part 'wallet.g.dart'; + +class DecredWallet = DecredWalletBase with _$DecredWallet; + +abstract class DecredWalletBase extends WalletBase<DecredBalance, + DecredTransactionHistory, DecredTransactionInfo> with Store { + DecredWalletBase(SPVWallet spv, WalletInfo walletInfo) + : this.spv = spv, + balance = ObservableMap<CryptoCurrency, DecredBalance>.of( + {CryptoCurrency.dcr: spv.balance()}), + syncStatus = NotConnectedSyncStatus(), + super(walletInfo) { + transactionHistory = DecredTransactionHistory(); + walletAddresses = DecredWalletAddresses(walletInfo, spv); + } + + final SPVWallet spv; + + static Future<DecredWallet> create( + {required String mnemonic, + required String password, + required WalletInfo walletInfo}) async { + final seed = mnemonicToSeedBytes(mnemonic); + final spv = SPVWallet().create(seed, password, walletInfo); + final wallet = DecredWallet(spv, walletInfo); + return wallet; + } + + static Future<DecredWallet> open( + {required String password, + required String name, + required WalletInfo walletInfo}) async { + final spv = SPVWallet().load(name, password, walletInfo); + final wallet = DecredWallet(spv, walletInfo); + return wallet; + } + + // TODO: Set up a way to change the balance and sync status when dcrlibwallet + // changes. Long polling probably? + @override + @observable + late ObservableMap<CryptoCurrency, DecredBalance> balance; + + @override + @observable + SyncStatus syncStatus; + + // @override + // set syncStatus(SyncStatus status); + + @override + String? get seed { + // throw UnimplementedError(); + return ""; + } + + // @override + // String? get privateKey => null; + + @override + Object get keys { + // throw UnimplementedError(); + return {}; + } + + @override + late DecredWalletAddresses walletAddresses; + + // @override + // set isEnabledAutoGenerateSubaddress(bool value) {} + + // @override + // bool get isEnabledAutoGenerateSubaddress => false; + + @override + Future<void> connectToNode({required Node node}) async { + //throw UnimplementedError(); + } + + @action + @override + Future<void> startSync() async { + try { + this.spv.startSync(); + syncStatus = this.spv.syncStatus(); + } catch (e, stacktrace) { + print(stacktrace); + print(e.toString()); + syncStatus = FailedSyncStatus(); + } + } + + @override + Future<PendingTransaction> createTransaction(Object credentials) async { + return this.spv.createTransaction(credentials); + } + + int feeRate(TransactionPriority priority) { + try { + return this.spv.feeRate(priority.raw); + } catch (_) { + return 0; + } + } + + @override + int calculateEstimatedFee(TransactionPriority priority, int? amount) { + if (priority is DecredTransactionPriority) { + return this.spv.calculateEstimatedFeeWithFeeRate( + this.spv.feeRate(priority.raw), amount ?? 0); + } + + return 0; + } + + @override + Future<Map<String, DecredTransactionInfo>> fetchTransactions() async { + return this.spv.transactions(); + } + + @override + Future<void> save() async {} + + @override + Future<void> rescan({required int height}) async { + return spv.rescan(height); + } + + @override + void close() { + this.spv.close(); + } + + @override + Future<void> changePassword(String password) async { + return this.spv.changePassword(password); + } + + @override + String get password { + // throw UnimplementedError(); + return ""; + } + + @override + Future<void>? updateBalance() async { + balance[CryptoCurrency.dcr] = this.spv.balance(); + } + + @override + void setExceptionHandler(void Function(FlutterErrorDetails) onError) => + onError; + + Future<void> renameWalletFiles(String newWalletName) async { + final currentWalletPath = + await pathForWallet(name: walletInfo.name, type: type); + final currentWalletFile = File(currentWalletPath); + + final currentDirPath = + await pathForWalletDir(name: walletInfo.name, type: type); + + // TODO: Stop the wallet, wait, and restart after. + + // Copies current wallet files into new wallet name's dir and files + if (currentWalletFile.existsSync()) { + final newWalletPath = + await pathForWallet(name: newWalletName, type: type); + await currentWalletFile.copy(newWalletPath); + } + + // Delete old name's dir and files + await Directory(currentDirPath).delete(recursive: true); + } + + @override + String signMessage(String message, {String? address = null}) { + return this.spv.signMessage(message, address); + } + + List<Unspent> unspents() { + return this.spv.unspents(); + } + + @override + Future<bool> verifyMessage(String message, String signature, {String? address = null}) { + return true; + } + + @override + String get password { + return ""; + } +} diff --git a/cw_decred/lib/wallet_addresses.dart b/cw_decred/lib/wallet_addresses.dart new file mode 100644 index 000000000..09f26951e --- /dev/null +++ b/cw_decred/lib/wallet_addresses.dart @@ -0,0 +1,36 @@ +import 'package:cw_core/wallet_addresses.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_decred/api/dcrlibwallet.dart'; + +class DecredWalletAddresses extends WalletAddresses { + DecredWalletAddresses(WalletInfo walletInfo, SPVWallet spv) + : this.spv = spv, + super(walletInfo); + + final SPVWallet spv; + + @override + String get address { + return this.spv.newAddress(); + } + + String generateNewAddress() { + return this.spv.newAddress(); + } + + List<String> addresses() { + return this.spv.addresses(); + } + + @override + set address(String addr) {} + + @override + Future<void> init() async {} + + @override + Future<void> updateAddressesInBox() async {} + + @override + Future<void> saveAddressesInBox() async {} +} diff --git a/cw_decred/lib/wallet_creation_credentials.dart b/cw_decred/lib/wallet_creation_credentials.dart new file mode 100644 index 000000000..ef63bbc37 --- /dev/null +++ b/cw_decred/lib/wallet_creation_credentials.dart @@ -0,0 +1,44 @@ +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/hardware/hardware_account_data.dart'; + +class DecredNewWalletCredentials extends WalletCredentials { + DecredNewWalletCredentials({required String name, WalletInfo? walletInfo}) + : super(name: name, walletInfo: walletInfo); +} + +class DecredRestoreWalletFromSeedCredentials extends WalletCredentials { + DecredRestoreWalletFromSeedCredentials( + {required String name, + required String password, + required this.mnemonic, + WalletInfo? walletInfo}) + : super(name: name, password: password, walletInfo: walletInfo); + + final String mnemonic; +} + +class DecredRestoreWalletFromWIFCredentials extends WalletCredentials { + DecredRestoreWalletFromWIFCredentials( + {required String name, + required String password, + required this.wif, + WalletInfo? walletInfo}) + : t = throw UnimplementedError(), // TODO: Maybe can be used to create watching only wallets? + super(name: name, password: password, walletInfo: walletInfo); + + final String wif; + final void t; +} + +class DecredRestoreWalletFromHardwareCredentials extends WalletCredentials { + DecredRestoreWalletFromHardwareCredentials( + {required String name, + required this.hwAccountData, + WalletInfo? walletInfo}) + : t = throw UnimplementedError(), + super(name: name, walletInfo: walletInfo); + + final HardwareAccountData hwAccountData; + final void t; +} diff --git a/cw_decred/lib/wallet_service.dart b/cw_decred/lib/wallet_service.dart new file mode 100644 index 000000000..45995db41 --- /dev/null +++ b/cw_decred/lib/wallet_service.dart @@ -0,0 +1,99 @@ +import 'dart:io'; +import 'package:cw_decred/mnemonic_is_incorrect_exception.dart'; +import 'package:cw_decred/wallet_creation_credentials.dart'; +import 'package:cw_decred/wallet.dart'; +import 'package:cw_decred/api/dcrlibwallet.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:hive/hive.dart'; +import 'package:collection/collection.dart'; + +class DecredWalletService extends WalletService< + DecredNewWalletCredentials, + DecredRestoreWalletFromSeedCredentials, + DecredRestoreWalletFromPubkeyCredentials, + DecredRestoreWalletFromHardwareCredentials> { + DecredWalletService(this.walletInfoSource); + + final Box<WalletInfo> walletInfoSource; + + @override + WalletType getType() => WalletType.decred; + + @override + Future<DecredWallet> create(DecredNewWalletCredentials credentials, + {bool? isTestnet}) async { + return await DecredWalletBase.create( + mnemonic: generateMnemonic(), + password: credentials.password!, + walletInfo: credentials.walletInfo!); + } + + @override + Future<bool> isWalletExit(String name) async => + File(await pathForWallet(name: name, type: getType())).existsSync(); + + @override + Future<DecredWallet> openWallet(String name, String password) async { + final walletInfo = walletInfoSource.values.firstWhereOrNull( + (info) => info.id == WalletBase.idFor(name, getType()))!; + final wallet = await DecredWalletBase.open( + password: password, name: name, walletInfo: walletInfo); + return wallet; + } + + @override + Future<void> remove(String wallet) async { + File(await pathForWalletDir(name: wallet, type: getType())) + .delete(recursive: true); + final walletInfo = walletInfoSource.values.firstWhereOrNull( + (info) => info.id == WalletBase.idFor(wallet, getType()))!; + await walletInfoSource.delete(walletInfo.key); + } + + @override + Future<void> rename( + String currentName, String password, String newName) async { + final currentWalletInfo = walletInfoSource.values.firstWhereOrNull( + (info) => info.id == WalletBase.idFor(currentName, getType()))!; + final currentWallet = await DecredWalletBase.open( + password: password, name: currentName, walletInfo: currentWalletInfo); + + await currentWallet.renameWalletFiles(newName); + + final newWalletInfo = currentWalletInfo; + newWalletInfo.id = WalletBase.idFor(newName, getType()); + newWalletInfo.name = newName; + + await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + } + + @override + Future<DecredWallet> restoreFromSeed( + DecredRestoreWalletFromSeedCredentials credentials, + {bool? isTestnet}) async { + if (!validateMnemonic(credentials.mnemonic)) { + throw DecredMnemonicIsIncorrectException(); + } + + final wallet = await DecredWalletBase.create( + password: credentials.password!, + mnemonic: credentials.mnemonic, + walletInfo: credentials.walletInfo!); + return wallet; + } + + @override + Future<DecredWallet> restoreFromKeys( + DecredRestoreWalletFromWIFCredentials credentials, + {bool? isTestnet}) async => + throw UnimplementedError(); + + @override + Future<DecredWallet> restoreFromHardwareWallet( + DecredRestoreWalletFromHardwareCredentials credentials) async => + throw UnimplementedError(); +} diff --git a/cw_decred/pubspec.lock b/cw_decred/pubspec.lock new file mode 100644 index 000000000..63794dab9 --- /dev/null +++ b/cw_decred/pubspec.lock @@ -0,0 +1,724 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8" + url: "https://pub.dev" + source: hosted + version: "47.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80" + url: "https://pub.dev" + source: hosted + version: "4.7.0" + args: + dependency: transitive + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + asn1lib: + dependency: transitive + description: + name: asn1lib + sha256: "21afe4333076c02877d14f4a89df111e658a6d466cbfc802eb705eb91bd5adfd" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + build_resolvers: + dependency: "direct dev" + description: + name: build_resolvers + sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6" + url: "https://pub.dev" + source: hosted + version: "2.0.10" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "10c6bcdbf9d049a0b666702cf1cee4ddfdc38f02a19d35ae392863b47519848b" + url: "https://pub.dev" + source: hosted + version: "2.4.6" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41" + url: "https://pub.dev" + source: hosted + version: "7.2.10" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "723b4021e903217dfc445ec4cf5b42e27975aece1fc4ebbc1ca6329c2d9fb54e" + url: "https://pub.dev" + source: hosted + version: "8.7.0" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "1be9be30396d7e4c0db42c35ea6ccd7cc6a1e19916b5dc64d6ac216b5544d677" + url: "https://pub.dev" + source: hosted + version: "4.7.0" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + cw_core: + dependency: "direct main" + description: + path: "../cw_core" + relative: true + source: path + version: "0.0.1" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" + url: "https://pub.dev" + source: hosted + version: "2.2.4" + encrypt: + dependency: transitive + description: + name: encrypt + sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2" + url: "https://pub.dev" + source: hosted + version: "5.0.3" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + file: + dependency: transitive + description: + name: file + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" + source: hosted + version: "6.1.4" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_mobx: + dependency: transitive + description: + name: flutter_mobx + sha256: "2ba0aa5a42811eaaeff2e35626689cf2b8a3869907d0e8889c914f2c95d8fd76" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + hive: + dependency: transitive + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + hive_generator: + dependency: "direct dev" + description: + name: hive_generator + sha256: "81fd20125cb2ce8fd23623d7744ffbaf653aae93706c9bd3bf7019ea0ace3938" + url: "https://pub.dev" + source: hosted + version: "1.1.3" + http: + dependency: transitive + description: + name: http + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + intl: + dependency: transitive + description: + name: intl + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + url: "https://pub.dev" + source: hosted + version: "0.18.1" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + url: "https://pub.dev" + source: hosted + version: "0.8.0" + meta: + dependency: transitive + description: + name: meta + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + mime: + dependency: transitive + description: + name: mime + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" + source: hosted + version: "1.0.4" + mobx: + dependency: transitive + description: + name: mobx + sha256: "42ae7277ec5c36fa5ce02aa14551065babce3c38a35947330144ff47bc775c75" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + mobx_codegen: + dependency: "direct dev" + description: + name: mobx_codegen + sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c + url: "https://pub.dev" + source: hosted + version: "2.3.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + platform: + dependency: transitive + description: + name: platform + sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + url: "https://pub.dev" + source: hosted + version: "2.1.6" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + url: "https://pub.dev" + source: hosted + version: "3.7.3" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + provider: + dependency: transitive + description: + name: provider + sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f + url: "https://pub.dev" + source: hosted + version: "6.0.5" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d" + url: "https://pub.dev" + source: hosted + version: "1.2.6" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" + source: hosted + version: "0.6.1" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + unorm_dart: + dependency: transitive + description: + name: unorm_dart + sha256: "23d8bf65605401a6a32cff99435fed66ef3dab3ddcad3454059165df46496a3b" + url: "https://pub.dev" + source: hosted + version: "0.3.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + url: "https://pub.dev" + source: hosted + version: "13.0.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.dev" + source: hosted + version: "2.4.0" + win32: + dependency: transitive + description: + name: win32 + sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" + url: "https://pub.dev" + source: hosted + version: "5.0.9" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.2.0-0 <4.0.0" + flutter: ">=3.7.0" diff --git a/cw_decred/pubspec.yaml b/cw_decred/pubspec.yaml new file mode 100644 index 000000000..9c0155c34 --- /dev/null +++ b/cw_decred/pubspec.yaml @@ -0,0 +1,24 @@ +name: cw_decred +description: A new Flutter package project. +version: 0.0.1 +publish_to: none +author: Cake Wallet +homepage: https://cakewallet.com + +environment: + sdk: ">=2.17.5 <3.0.0" + flutter: ">=1.20.0" + +dependencies: + flutter: + sdk: flutter + cw_core: + path: ../cw_core + +dev_dependencies: + flutter_test: + sdk: flutter + build_runner: ^2.1.11 + build_resolvers: ^2.0.9 + mobx_codegen: ^2.0.7 + hive_generator: ^1.1.3 diff --git a/lib/core/wallet_creation_service.dart b/lib/core/wallet_creation_service.dart index 3ee630b33..328b52b0e 100644 --- a/lib/core/wallet_creation_service.dart +++ b/lib/core/wallet_creation_service.dart @@ -89,6 +89,7 @@ class WalletCreationService { case WalletType.haven: case WalletType.nano: case WalletType.banano: + case WalletType.decred: return false; } } diff --git a/lib/decred/cw_decred.dart b/lib/decred/cw_decred.dart new file mode 100644 index 000000000..9fa2bb3d1 --- /dev/null +++ b/lib/decred/cw_decred.dart @@ -0,0 +1,105 @@ +part of 'decred.dart'; + +class CWDecred extends Decred { + @override + TransactionPriority getMediumTransactionPriority() => + DecredTransactionPriority.medium; + + @override + WalletCredentials createDecredRestoreWalletFromSeedCredentials( + {required String name, + required String mnemonic, + required String password}) => + DecredRestoreWalletFromSeedCredentials( + name: name, mnemonic: mnemonic, password: password); + + @override + WalletCredentials createDecredNewWalletCredentials( + {required String name, WalletInfo? walletInfo}) => + DecredNewWalletCredentials(name: name, walletInfo: walletInfo); + + @override + List<String> getWordList() => wordList(); + + @override + List<TransactionPriority> getTransactionPriorities() => + DecredTransactionPriority.all; + + @override + TransactionPriority deserializeDecredTransactionPriority(int raw) => + DecredTransactionPriority.deserialize(raw: raw); + + @override + int getFeeRate(Object wallet, TransactionPriority priority) { + final decredWallet = wallet as DecredWallet; + return decredWallet.feeRate(priority); + } + + @override + Future<void> generateNewAddress(Object wallet) async { + final decredWallet = wallet as DecredWallet; + await decredWallet.walletAddresses.generateNewAddress(); + } + + @override + Object createDecredTransactionCredentials( + List<Output> outputs, TransactionPriority priority) => + DecredTransactionCredentials( + outputs + .map((out) => OutputInfo( + fiatAmount: out.fiatAmount, + cryptoAmount: out.cryptoAmount, + address: out.address, + note: out.note, + sendAll: out.sendAll, + extractedAddress: out.extractedAddress, + isParsedAddress: out.isParsedAddress, + formattedCryptoAmount: out.formattedCryptoAmount)) + .toList(), + priority: + priority != null ? priority as DecredTransactionPriority : null); + + @override + List<String> getAddresses(Object wallet) { + final decredWallet = wallet as DecredWallet; + return decredWallet.walletAddresses.addresses(); + } + + @override + String getAddress(Object wallet) { + final decredWallet = wallet as DecredWallet; + return decredWallet.walletAddresses.address; + } + + @override + String formatterDecredAmountToString({required int amount}) => + decredAmountToString(amount: amount); + + @override + double formatterDecredAmountToDouble({required int amount}) => + decredAmountToDouble(amount: amount); + + @override + int formatterStringDoubleToDecredAmount(String amount) => + stringDoubleToDecredAmount(amount); + + @override + List<Unspent> getUnspents(Object wallet) { + final decredWallet = wallet as DecredWallet; + return decredWallet.unspents(); + } + + void updateUnspents(Object wallet) async {} + + WalletService createDecredWalletService(Box<WalletInfo> walletInfoSource) { + return DecredWalletService(walletInfoSource); + } + + @override + TransactionPriority getDecredTransactionPriorityMedium() => + DecredTransactionPriority.medium; + + @override + TransactionPriority getDecredTransactionPrioritySlow() => + DecredTransactionPriority.slow; +} diff --git a/lib/di.dart b/lib/di.dart index 358f72a77..bb3b3952a 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -62,6 +62,7 @@ import 'package:cake_wallet/haven/haven.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/polygon/polygon.dart'; +import 'package:cake_wallet/decred/decred.dart'; import 'package:cake_wallet/reactions/on_authentication_state_change.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/solana/solana.dart'; @@ -1093,6 +1094,8 @@ Future<void> setup({ return tron!.createTronWalletService(_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput); case WalletType.wownero: return wownero!.createWowneroWalletService(_walletInfoSource, _unspentCoinsInfoSource); + case WalletType.decred: + return decred!.createDecredWalletService(_walletInfoSource); case WalletType.none: throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService'); } diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index 58a540278..b637db19b 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -9,6 +9,7 @@ class PreferencesKey { static const currentPolygonNodeIdKey = 'current_node_id_matic'; static const currentNanoNodeIdKey = 'current_node_id_nano'; static const currentNanoPowNodeIdKey = 'current_node_id_nano_pow'; + static const currentDecredNodeIdKey = 'current_node_id_decred'; static const currentBananoNodeIdKey = 'current_node_id_banano'; static const currentBananoPowNodeIdKey = 'current_node_id_banano_pow'; static const currentFiatCurrencyKey = 'current_fiat_currency'; diff --git a/lib/entities/provider_types.dart b/lib/entities/provider_types.dart index 42ec74c12..41abc209d 100644 --- a/lib/entities/provider_types.dart +++ b/lib/entities/provider_types.dart @@ -76,6 +76,7 @@ class ProvidersHelper { ]; case WalletType.none: case WalletType.haven: + case WalletType.decred: return []; } } @@ -109,6 +110,7 @@ class ProvidersHelper { case WalletType.none: case WalletType.haven: case WalletType.wownero: + case WalletType.decred: return []; } } diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart index e5f38010d..6442234ff 100644 --- a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart @@ -46,6 +46,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD final solanaIcon = Image.asset('assets/images/sol_icon.png', height: 24, width: 24); final tronIcon = Image.asset('assets/images/trx_icon.png', height: 24, width: 24); final wowneroIcon = Image.asset('assets/images/wownero_icon.png', height: 24, width: 24); + final decredIcon = Image.asset('assets/images/decred_icon.png', height: 24, width: 24); final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24); Image _newWalletImage(BuildContext context) => Image.asset( @@ -178,6 +179,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD return tronIcon; case WalletType.wownero: return wowneroIcon; + case WalletType.decred: + return decredIcon; default: return nonWalletTypeIcon; } diff --git a/lib/src/screens/dashboard/widgets/menu_widget.dart b/lib/src/screens/dashboard/widgets/menu_widget.dart index b49a08584..0b0c28c61 100644 --- a/lib/src/screens/dashboard/widgets/menu_widget.dart +++ b/lib/src/screens/dashboard/widgets/menu_widget.dart @@ -37,7 +37,8 @@ class MenuWidgetState extends State<MenuWidget> { this.polygonIcon = Image.asset('assets/images/matic_icon.png'), this.solanaIcon = Image.asset('assets/images/sol_icon.png'), this.tronIcon = Image.asset('assets/images/trx_icon.png'), - this.wowneroIcon = Image.asset('assets/images/wownero_icon.png'); + this.wowneroIcon = Image.asset('assets/images/wownero_icon.png'), + this.decredIcon = Image.asset('assets/images/decred_menu.png'); final largeScreen = 731; @@ -62,6 +63,7 @@ class MenuWidgetState extends State<MenuWidget> { Image solanaIcon; Image tronIcon; Image wowneroIcon; + Image decredIcon; @override void initState() { @@ -245,6 +247,8 @@ class MenuWidgetState extends State<MenuWidget> { return tronIcon; case WalletType.wownero: return wowneroIcon; + case WalletType.decred: + return decredIcon; default: throw Exception('No icon for ${type.toString()}'); } diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index 9bf924f61..5340a238b 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -122,6 +122,7 @@ class WalletListBodyState extends State<WalletListBody> { final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24); final tBitcoinIcon = Image.asset('assets/images/tbtc.png', height: 24, width: 24); final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24); + final decredIcon = Image.asset('assets/images/decred_icon.png', height: 24, width: 24); final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24); final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24); final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24); @@ -131,6 +132,7 @@ class WalletListBodyState extends State<WalletListBody> { final solanaIcon = Image.asset('assets/images/sol_icon.png', height: 24, width: 24); final tronIcon = Image.asset('assets/images/trx_icon.png', height: 24, width: 24); final wowneroIcon = Image.asset('assets/images/wownero_icon.png', height: 24, width: 24); + final decredIcon = Image.asset('assets/images/decred_icon.png', height: 24, width: 24); final scrollController = ScrollController(); final double tileHeight = 60; Flushbar<void>? _progressBar; diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index aa7df4ba9..99f0154da 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -816,6 +816,11 @@ abstract class SettingsStoreBase with Store { Node getCurrentNode(WalletType walletType) { final node = nodes[walletType]; + // TODO: Implement connecting to a user's preferred node. + if (walletType == WalletType.decred) { + return Node(); + } + if (node == null) { throw Exception('No node found for wallet type: ${walletType.toString()}'); } diff --git a/lib/view_model/advanced_privacy_settings_view_model.dart b/lib/view_model/advanced_privacy_settings_view_model.dart index 85b9dbead..b8bb625e9 100644 --- a/lib/view_model/advanced_privacy_settings_view_model.dart +++ b/lib/view_model/advanced_privacy_settings_view_model.dart @@ -54,6 +54,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { case WalletType.wownero: case WalletType.none: case WalletType.haven: + case WalletType.decred: return false; } } diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index ee8a88b6b..79b695136 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -739,6 +739,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with depositCurrency = CryptoCurrency.wow; receiveCurrency = CryptoCurrency.xmr; break; + case WalletType.decred: + depositCurrency = CryptoCurrency.dcr; + receiveCurrency = CryptoCurrency.xmr; + break; case WalletType.none: break; } diff --git a/lib/view_model/node_list/node_create_or_edit_view_model.dart b/lib/view_model/node_list/node_create_or_edit_view_model.dart index 8b3c70c5e..19ef23f0a 100644 --- a/lib/view_model/node_list/node_create_or_edit_view_model.dart +++ b/lib/view_model/node_list/node_create_or_edit_view_model.dart @@ -85,6 +85,7 @@ abstract class NodeCreateOrEditViewModelBase with Store { case WalletType.litecoin: case WalletType.bitcoinCash: case WalletType.bitcoin: + case WalletType.decred: return false; } } diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index f8599513c..b6ce2a656 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/exchange/provider/exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart'; import 'package:cake_wallet/nano/nano.dart'; +import 'package:cake_wallet/decred/decred.dart'; import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/wallet_contact.dart'; @@ -569,6 +570,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor .createSolanaTransactionCredentials(outputs, currency: selectedCryptoCurrency); case WalletType.tron: return tron!.createTronTransactionCredentials(outputs, currency: selectedCryptoCurrency); + case WalletType.decred: + return decred!.createDecredTransactionCredentials(outputs, priority!); default: throw Exception('Unexpected wallet type: ${wallet.type}'); } diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index 9ec542361..4911cc7ed 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -181,6 +181,8 @@ abstract class TransactionDetailsViewModelBase with Store { return 'https://tronscan.org/#/transaction/${txId}'; case WalletType.wownero: return 'https://explore.wownero.com/tx/${txId}'; + case WalletType.decred: + return 'https://dcrdata.decred.org/tx/${txId}'; case WalletType.none: return ''; } @@ -211,6 +213,8 @@ abstract class TransactionDetailsViewModelBase with Store { return S.current.view_transaction_on + 'tronscan.org'; case WalletType.wownero: return S.current.view_transaction_on + 'Wownero.com'; + case WalletType.decred: + return S.current.view_transaction_on + 'dcrdata.decred.org'; case WalletType.none: return ''; } diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index 3e399266a..3ad1aaacf 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -205,6 +205,22 @@ class WowneroURI extends PaymentURI { } } +class DecredURI extends PaymentURI { + DecredURI({required String amount, required String address}) + : super(amount: amount, address: address); + + @override + String toString() { + var base = 'decred:' + address; + + if (amount.isNotEmpty) { + base += '?amount=${amount.replaceAll(',', '.')}'; + } + + return base; + } +} + abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store { WalletAddressListViewModelBase({ @@ -298,9 +314,57 @@ abstract class WalletAddressListViewModelBase return TronURI(amount: amount, address: address.address); case WalletType.wownero: return WowneroURI(amount: amount, address: address.address); + case WalletType.decred: + return DecredURI(amount: amount, address: address.address); case WalletType.none: throw Exception('Unexpected type: ${type.toString()}'); } + + if (wallet.type == WalletType.haven) { + return HavenURI(amount: amount, address: address.address); + } + + if (wallet.type == WalletType.bitcoin) { + return BitcoinURI(amount: amount, address: address.address); + } + + if (wallet.type == WalletType.litecoin) { + return LitecoinURI(amount: amount, address: address.address); + } + + if (wallet.type == WalletType.ethereum) { + return EthereumURI(amount: amount, address: address.address); + } + + if (wallet.type == WalletType.bitcoinCash) { + return BitcoinCashURI(amount: amount, address: address.address); + } + + if (wallet.type == WalletType.nano) { + return NanoURI(amount: amount, address: address.address); + } + + if (wallet.type == WalletType.polygon) { + return PolygonURI(amount: amount, address: address.address); + } + + if (wallet.type == WalletType.solana) { + return SolanaURI(amount: amount, address: address.address); + } + + if (wallet.type == WalletType.tron) { + return TronURI(amount: amount, address: address.address); + } + + if (wallet.type == WalletType.wownero) { + return WowneroURI(amount: amount, address: address.address); + } + + if (wallet.type == WalletType.decred) { + return DecredURI(amount: amount, address: address.address); + } + + throw Exception('Unexpected type: ${type.toString()}'); } @computed diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index be30811d9..82ab634ef 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -13,6 +13,7 @@ import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/view_model/seed_settings_view_model.dart'; import 'package:cake_wallet/view_model/wallet_creation_vm.dart'; import 'package:cake_wallet/wownero/wownero.dart'; +import 'package:cake_wallet/decred/decred.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; @@ -156,6 +157,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { language: options!.first as String, isPolyseed: options.last as bool, ); + case WalletType.decred: + return decred!.createDecredNewWalletCredentials(name: name); case WalletType.none: throw Exception('Unexpected type: ${type.toString()}'); } diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index d37b69f74..2274b34f5 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -11,6 +11,7 @@ import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/tron/tron.dart'; +import 'package:cake_wallet/decred/decred.dart'; import 'package:cake_wallet/view_model/restore/restore_mode.dart'; import 'package:cake_wallet/view_model/seed_settings_view_model.dart'; import 'package:cake_wallet/view_model/wallet_creation_vm.dart'; @@ -162,6 +163,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { password: password, height: height, ); + case WalletType.decred: + return decred!.createDecredRestoreWalletFromSeedCredentials( + name: name, + mnemonic: seed, + password: password, + ); case WalletType.none: break; } diff --git a/model_generator.sh b/model_generator.sh index 730817c24..fd7cdc3c4 100755 --- a/model_generator.sh +++ b/model_generator.sh @@ -11,6 +11,7 @@ cd cw_bitcoin_cash; flutter pub get; dart run build_runner build --delete-confli cd cw_solana; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. cd cw_tron; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. cd cw_wownero; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. +cd cw_decred; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. cd cw_polygon; flutter pub get; cd .. cd cw_ethereum; flutter pub get; cd .. cd cw_mweb && flutter pub get && cd .. diff --git a/scripts/android/pubspec_gen.sh b/scripts/android/pubspec_gen.sh index febc4f9e9..2a8f93d91 100755 --- a/scripts/android/pubspec_gen.sh +++ b/scripts/android/pubspec_gen.sh @@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in CONFIG_ARGS="--monero" ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero" + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --decred" if [ "$CW_WITH_HAVEN" = true ];then CONFIG_ARGS="$CONFIG_ARGS --haven" fi @@ -26,4 +26,4 @@ flutter pub get dart run tool/generate_pubspec.dart flutter pub get dart run tool/configure.dart $CONFIG_ARGS -cd scripts/android \ No newline at end of file +cd scripts/android diff --git a/tool/configure.dart b/tool/configure.dart index 6abd73d9e..6aba6bfef 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -10,6 +10,7 @@ const polygonOutputPath = 'lib/polygon/polygon.dart'; const solanaOutputPath = 'lib/solana/solana.dart'; const tronOutputPath = 'lib/tron/tron.dart'; const wowneroOutputPath = 'lib/wownero/wownero.dart'; +const decredOutputPath = 'lib/decred/decred.dart'; const walletTypesPath = 'lib/wallet_types.g.dart'; const secureStoragePath = 'lib/core/secure_storage.dart'; const pubspecDefaultPath = 'pubspec_default.yaml'; @@ -28,6 +29,7 @@ Future<void> main(List<String> args) async { final hasSolana = args.contains('${prefix}solana'); final hasTron = args.contains('${prefix}tron'); final hasWownero = args.contains('${prefix}wownero'); + final hasDecred = args.contains('${prefix}decred'); final excludeFlutterSecureStorage = args.contains('${prefix}excludeFlutterSecureStorage'); await generateBitcoin(hasBitcoin); @@ -41,6 +43,7 @@ Future<void> main(List<String> args) async { await generateTron(hasTron); await generateWownero(hasWownero); // await generateBanano(hasEthereum); + await generateDecred(hasDecred); await generatePubspec( hasMonero: hasMonero, @@ -55,6 +58,7 @@ Future<void> main(List<String> args) async { hasSolana: hasSolana, hasTron: hasTron, hasWownero: hasWownero, + hasDecred: hasDecred, ); await generateWalletTypes( hasMonero: hasMonero, @@ -68,6 +72,7 @@ Future<void> main(List<String> args) async { hasSolana: hasSolana, hasTron: hasTron, hasWownero: hasWownero, + hasDecred: hasDecred, ); await injectSecureStorage(!excludeFlutterSecureStorage); } @@ -1398,6 +1403,74 @@ abstract class Tron { await outputFile.writeAsString(output); } +Future<void> generateDecred(bool hasImplementation) async { + final outputFile = File(decredOutputPath); + const decredCommonHeaders = """ +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/output_info.dart'; +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/unspent_transaction_output.dart'; +import 'package:cake_wallet/view_model/send/output.dart'; +import 'package:hive/hive.dart';"""; + const decredCWHeaders = """ +import 'package:cw_decred/mnemonic.dart'; +import 'package:cw_decred/transaction_priority.dart'; +import 'package:cw_decred/wallet.dart'; +import 'package:cw_decred/wallet_service.dart'; +import 'package:cw_decred/wallet_creation_credentials.dart'; +import 'package:cw_decred/amount_format.dart'; +import 'package:cw_decred/transaction_credentials.dart'; +"""; + const decredCwPart = "part 'cw_decred.dart';"; + const decredContent = """ + +abstract class Decred { + TransactionPriority getMediumTransactionPriority(); + + WalletCredentials createDecredRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password}); + WalletCredentials createDecredNewWalletCredentials({required String name, WalletInfo? walletInfo}); + List<String> getWordList(); + List<TransactionPriority> getTransactionPriorities(); + TransactionPriority deserializeDecredTransactionPriority(int raw); + int getFeeRate(Object wallet, TransactionPriority priority); + Future<void> generateNewAddress(Object wallet); + Object createDecredTransactionCredentials(List<Output> outputs, TransactionPriority priority); + + List<String> getAddresses(Object wallet); + String getAddress(Object wallet); + + String formatterDecredAmountToString({required int amount}); + double formatterDecredAmountToDouble({required int amount}); + int formatterStringDoubleToDecredAmount(String amount); + + List<Unspent> getUnspents(Object wallet); + void updateUnspents(Object wallet); + WalletService createDecredWalletService(Box<WalletInfo> walletInfoSource); + TransactionPriority getDecredTransactionPriorityMedium(); + TransactionPriority getDecredTransactionPrioritySlow(); +} +"""; + + const decredEmptyDefinition = 'Decred? decred;\n'; + const decredCWDefinition = 'Decred? decred = CWDecred();\n'; + + final output = '$decredCommonHeaders\n' + + (hasImplementation ? '$decredCWHeaders\n' : '\n') + + (hasImplementation ? '$decredCwPart\n\n' : '\n') + + (hasImplementation ? decredCWDefinition : decredEmptyDefinition) + + '\n' + + decredContent; + + if (outputFile.existsSync()) { + await outputFile.delete(); + } + + await outputFile.writeAsString(output); +} + Future<void> generatePubspec({ required bool hasMonero, required bool hasBitcoin, @@ -1411,6 +1484,7 @@ Future<void> generatePubspec({ required bool hasSolana, required bool hasTron, required bool hasWownero, + required bool hasDecred, }) async { const cwCore = """ cw_core: @@ -1475,6 +1549,10 @@ Future<void> generatePubspec({ cw_wownero: path: ./cw_wownero """; + const cwDecred = """ + cw_decred: + path: ./cw_decred + """; final inputFile = File(pubspecOutputPath); final inputText = await inputFile.readAsString(); final inputLines = inputText.split('\n'); @@ -1524,6 +1602,10 @@ Future<void> generatePubspec({ output += '\n$cwSharedExternal\n$cwHaven'; } + if (hasDecred) { + output += '\n$cwDecred'; + } + if (hasFlutterSecureStorage) { output += '\n$flutterSecureStorage\n'; } @@ -1560,6 +1642,7 @@ Future<void> generateWalletTypes({ required bool hasSolana, required bool hasTron, required bool hasWownero, + required bool hasDecred, }) async { final walletTypesFile = File(walletTypesPath); @@ -1619,6 +1702,10 @@ Future<void> generateWalletTypes({ outputContent += '\tWalletType.haven,\n'; } + if (hasDecred) { + outputContent += '\tWalletType.decred,\n'; + } + outputContent += '];\n'; await walletTypesFile.writeAsString(outputContent); }