From 06a835f413175f768e5c5c8e5069578e325b9a9f Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Mon, 16 Jun 2025 10:10:20 +0200 Subject: [PATCH 1/3] feat: add support for Gnosis blockchain integration - Introduced Gnosis wallet and client implementations. - Implemented Gnosis transaction history, transaction info, and wallet service. - Added default Gnosis RPC node and ERC20 token support. - Updated node management and preference handling for Gnosis. - Extended wallet type and currency handling to include Gnosis. --- assets/gnosis_node_list.yml | 4 + assets/images/gno.png | Bin 0 -> 3493 bytes assets/images/gnosis_icon.png | Bin 0 -> 3493 bytes assets/images/xdai.png | Bin 0 -> 10541 bytes cw_core/lib/crypto_currency.dart | 1 + cw_core/lib/currency_for_wallet_type.dart | 4 + cw_core/lib/erc20_token.dart | 1 + cw_core/lib/node.dart | 2 + cw_core/lib/wallet_type.dart | 18 +- cw_evm/lib/evm_chain_wallet.dart | 4 +- cw_gnosis/.gitignore | 30 ++++ cw_gnosis/LICENSE | 1 + cw_gnosis/analysis_options.yaml | 4 + cw_gnosis/devtools_options.yaml | 3 + .../lib/default_gnosis_erc20_tokens.dart | 54 ++++++ cw_gnosis/lib/gnosis_client.dart | 92 ++++++++++ cw_gnosis/lib/gnosis_mnemonics_exception.dart | 5 + cw_gnosis/lib/gnosis_transaction_history.dart | 20 +++ cw_gnosis/lib/gnosis_transaction_info.dart | 39 +++++ cw_gnosis/lib/gnosis_wallet.dart | 163 ++++++++++++++++++ cw_gnosis/lib/gnosis_wallet_service.dart | 162 +++++++++++++++++ cw_gnosis/pubspec.yaml | 74 ++++++++ lib/core/seed_validator.dart | 3 + lib/core/wallet_creation_service.dart | 1 + lib/di.dart | 4 + lib/entities/default_settings_migration.dart | 21 +++ lib/entities/node_list.dart | 3 + lib/entities/preferences_key.dart | 3 + lib/entities/priority_for_wallet_type.dart | 3 + lib/reactions/bip39_wallet_utils.dart | 1 + lib/reactions/wallet_connect.dart | 9 +- .../dashboard/widgets/menu_widget.dart | 4 + lib/store/settings_store.dart | 49 ++++++ .../advanced_privacy_settings_view_model.dart | 3 +- .../dashboard/dashboard_view_model.dart | 1 + .../dashboard/home_settings_view_model.dart | 4 + .../dashboard/transaction_list_item.dart | 8 + .../exchange/exchange_view_model.dart | 4 + .../node_create_or_edit_view_model.dart | 1 + lib/view_model/send/fees_view_model.dart | 3 + lib/view_model/send/output.dart | 9 + .../transaction_details_view_model.dart | 57 ++++++ .../wallet_address_list_view_model.dart | 2 + lib/view_model/wallet_keys_view_model.dart | 1 + lib/view_model/wallet_new_vm.dart | 8 + lib/view_model/wallet_restore_view_model.dart | 10 ++ 46 files changed, 887 insertions(+), 6 deletions(-) create mode 100644 assets/gnosis_node_list.yml create mode 100644 assets/images/gno.png create mode 100644 assets/images/gnosis_icon.png create mode 100644 assets/images/xdai.png create mode 100644 cw_gnosis/.gitignore create mode 100644 cw_gnosis/LICENSE create mode 100644 cw_gnosis/analysis_options.yaml create mode 100644 cw_gnosis/devtools_options.yaml create mode 100644 cw_gnosis/lib/default_gnosis_erc20_tokens.dart create mode 100644 cw_gnosis/lib/gnosis_client.dart create mode 100644 cw_gnosis/lib/gnosis_mnemonics_exception.dart create mode 100644 cw_gnosis/lib/gnosis_transaction_history.dart create mode 100644 cw_gnosis/lib/gnosis_transaction_info.dart create mode 100644 cw_gnosis/lib/gnosis_wallet.dart create mode 100644 cw_gnosis/lib/gnosis_wallet_service.dart create mode 100644 cw_gnosis/pubspec.yaml diff --git a/assets/gnosis_node_list.yml b/assets/gnosis_node_list.yml new file mode 100644 index 000000000..3eb74075e --- /dev/null +++ b/assets/gnosis_node_list.yml @@ -0,0 +1,4 @@ +- + uri: gnosis-rpc.publicnode.com + useSSL: true + isDefault: true diff --git a/assets/images/gno.png b/assets/images/gno.png new file mode 100644 index 0000000000000000000000000000000000000000..dd01c318785284c53051b8f7dbca856dbc67c64a GIT binary patch literal 3493 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 T8%AXt00000NkvXXu0mjff2w3j literal 0 HcmV?d00001 diff --git a/assets/images/gnosis_icon.png b/assets/images/gnosis_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..dd01c318785284c53051b8f7dbca856dbc67c64a GIT binary patch literal 3493 zcmV;W4O;SvP)Px?Ur9tkRCr$PoojC8HWG!UjQ7>1Z5~XpteukHCZ?8Ku{4}ip_A%VHW#0ey$L?>>X3p_h2K<1;fiX>A z=5bH2!(d{9+c1uQ`zjaEo&_!w>-7lX1DJ98br?2kQm?}^SRcdftU#Rxpb(gG7+$A( zkl-ziu1+mhq>noFbXaJdEXFLpDAN&PIut-J z|9<>y^7TX|f8#X%ysVjggCC4)y%l?e}Rl211{S9}l=MBoTpmhdPHvql- z^7CJN3A$*-r-`H3J9>S1bj( z^#n}{k|HooHyIp3sRj!gR>>Y z+r$E-1`t&RD4r;L%O&>sca!o|;KU?{o?xU<-QIuy^D9}(s6Z8fuzV*wqSJ#DRV7Vm ztE5K70aE8Xl?-7tAx*1Mt5F5p6$MBMPgN;4^}KOqDv%08R16^1pc^hrjeT!cohl6| z2vHG$6!2WCLm(%%Z6S!v0qSjlW)jd*MLpTr6d>`yu8_sXQykKamBB#^F*a{xkH4}g?hyX7aD$)`jQ-Cgr3K5d2wlzHF06`F? zo*lcw8H+$JJpM#)0UMizuyFKHVtwUAEMQjlZM^#&_5}v${$c}k34TE&<|BI{S9yG8 z4CV3lGmb=Y0dl7Y;3fD2BKVQ##deGVmvK{{wN`;=K3NO<7y>A=!wENVDUl6~72%mX z3{GMxK(2`kA{#cyTB^e{_XzSY2@r9A1!@FZou$kO&IHm62Q9FN2gnB)Hp{{Z+_$6w zq94<^ASwiBBjs6*hPqdl1&DEX_kze6q8L1WFDKWImGZ3C>G>Cd<^iJB$2YYM?1IP$ zq8^kd97;}wk^p)2`IvrX-YvTzQbM$X2S|4~D<+?d8gD}80ooj`B_))Ry7bno?EylP=gdb# zEIt@=c8FTQ^T;f_o#-Y6>Wi$daSBt0$RF;St7eb)fZU&9f< zF_g-E9^nym^4TCN2~Y5{1$Wjf`;J6WKN4YV4Uk6Oh}>rf!;?WIa+=ge#C76{8QE0a z0wi92!Riyn38{h5SOeCNRwpf22cA`8N^aze`m`7z^tB$K<%QpXY{uaPoh+U;VEs}! zsOv;tA=DKmi2a zH2t(EO**ZHvb?KY)MxC$lS!0u&v893Wt&u&*s2!W41V zH>ET*xuOK?$5&evZu&8~7RYhU-LOwDHjziKKxI2QcNcWBc(#J|o8%B+TjH^-J6Q=J zol2DK7bSai6X&b&{2gKdc6*#?l3>BS-(vjXV=%D(ASY>XBQo`4@I>;+(tS>)UGzp`(l0=cy04j>-RurQq77I71|ZQYjL!DIy}b_OWFyP>!IXAPQ2!C(p-#rA zmhg)YA^-tP(0CE#`4gSWRmq7qjZEYGWS<27(ts1LBGpH_!5g$|2@tu|wje%gv&N3< zvQNOtn3wIHctP-~3J|i|0}{73IT1&gI{>~t#J3n%YWWphkt~7RNy#^sxAo6vWT(VN?8Lm4#TTq?XUyQjXS@l|2s!fM9Dn^ zo(HV96NdH0YhLaNS+Af1&F%9c^6Di#TFsb=G4tflSZ!8+0(inUmCj^=V?IGQ3A@Gf zKr4T&5-y=ry@6?%ZgUy0IP>qyA4rLm@T@>`w|IUUUWaM=LOdGcp>fx8$Et)iZPC>e zi6!n;ZAex6c7E_*U_frWn$w=#wo>KMH<(cEh483Xn%l;z0puDMDiLL%QvZ)aWT-r) z`fV5{=`Jqz2oDw-=FrjhSve6s0$vHpt7RT9rewh5AI=-czb#(0#T(&gY3&1oNW1s_ zK9iHFNP~|u4b*67P!I@i*xaTD@+)Ba7ar>jyBuwQII|G1qvA#UU`5@L=6TC8KuUme4d>eTlD|ISUy8oVkKst zy~lfeb9sYCmBh190THz9kE~wYpQJg`J**C+-!C%?RZNTmo9SOL#cUfYKF)g{{^w)x zdp!4W_b{OQBesV^Hz|Nf1kb)F^Tff&=}NYNCg^+a4Q1FpG3HYivF5eH zt~nQ0pgYa0j41OCAV`HuC$lL_Y1-9X8|{yIg%$Wn^%)yW?0NS%KqVjVs9>gZG=Vv_ z+E^1i>E!_GZ2Z*>ebR_04Hr&KxdVhLGv)!hlIafcYup9mF_bJoeW?(hWYn4Qcz+rrplY5vs!ju> zP@y`G6xioEd`}yOvIFoC90^cgDn#2vQX&MsTj(VNQ(i-F)REsF?7VF5g1fX)w5jp@#CZiS-U&?QQu!*LuvlwFB@;7!J=Ys)e_ z2UR^rkcM8sZ=v^vMccCo`3OC-d z%KOhiNxvypgX=>9goHNnQoN?M>9)Gdai}gILV==+6kDb7MgCC@meYA947zi&S4sVl>S<^){dO(4;jMH0X7Ee0gUVKnV>nX0#RW3QzPy} zz4xDK`ojK*?S3&egR*>e`pmGz?0aejqJY{Z=MJ#&$HBIIH9GwBS=EGVFIWEmXwWq# T8%AXt00000NkvXXu0mjff2w3j literal 0 HcmV?d00001 diff --git a/assets/images/xdai.png b/assets/images/xdai.png new file mode 100644 index 0000000000000000000000000000000000000000..10b2efaa094fa778ca9890b549e7691cbf2e9339 GIT binary patch literal 10541 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 WUUJ2<1`W(VQs_L=*C with Serializable implemen static const zano = CryptoCurrency(title: 'ZANO', tag: 'ZANO', fullName: 'Zano', raw: 96, name: 'zano', iconPath: 'assets/images/zano_icon.png', decimals: 12); static const flip = CryptoCurrency(title: 'FLIP', tag: 'ETH', fullName: 'Chainflip', raw: 97, name: 'flip', iconPath: 'assets/images/flip_icon.png', decimals: 18); static const deuro = CryptoCurrency(title: 'DEURO', tag: 'ETH', fullName: 'Decentralized Euro', raw: 98, name: 'deuro', iconPath: 'assets/images/deuro_icon.png', decimals: 18); + static const xdai = CryptoCurrency(title: 'XDAI', tag: 'XDAI', fullName: 'xDAI', raw: 99, name: 'xDAI', iconPath: 'assets/images/xdai.png', decimals: 18); static final Map _rawCurrencyMap = [...all, ...havenCurrencies].fold>({}, (acc, item) { diff --git a/cw_core/lib/currency_for_wallet_type.dart b/cw_core/lib/currency_for_wallet_type.dart index 0f913cb79..0231e2441 100644 --- a/cw_core/lib/currency_for_wallet_type.dart +++ b/cw_core/lib/currency_for_wallet_type.dart @@ -34,6 +34,8 @@ CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) { return CryptoCurrency.zano; case WalletType.decred: return CryptoCurrency.dcr; + case WalletType.gnosis: + return CryptoCurrency.xdai; case WalletType.none: throw Exception( 'Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType'); @@ -60,6 +62,8 @@ WalletType? walletTypeForCurrency(CryptoCurrency currency) { return WalletType.banano; case CryptoCurrency.maticpoly: return WalletType.polygon; + case CryptoCurrency.xdai: + return WalletType.gnosis; case CryptoCurrency.sol: return WalletType.solana; case CryptoCurrency.trx: diff --git a/cw_core/lib/erc20_token.dart b/cw_core/lib/erc20_token.dart index fd76d28fc..d8ffdb4c5 100644 --- a/cw_core/lib/erc20_token.dart +++ b/cw_core/lib/erc20_token.dart @@ -70,6 +70,7 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin { static const boxName = 'Erc20Tokens'; static const ethereumBoxName = 'EthereumErc20Tokens'; static const polygonBoxName = 'PolygonErc20Tokens'; + static const gnosisBoxName = 'GnosisErc20Tokens'; @override bool operator ==(other) => diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index 38fcde9e1..385ba4332 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -100,6 +100,7 @@ class Node extends HiveObject with Keyable { case WalletType.banano: case WalletType.ethereum: case WalletType.polygon: + case WalletType.gnosis: case WalletType.solana: case WalletType.tron: case WalletType.zano: @@ -163,6 +164,7 @@ class Node extends HiveObject with Keyable { case WalletType.bitcoinCash: case WalletType.ethereum: case WalletType.polygon: + case WalletType.gnosis: case WalletType.solana: case WalletType.tron: return requestElectrumServer(); diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart index 40a2b31d5..0114339ba 100644 --- a/cw_core/lib/wallet_type.dart +++ b/cw_core/lib/wallet_type.dart @@ -10,6 +10,7 @@ const walletTypes = [ WalletType.litecoin, WalletType.haven, WalletType.ethereum, + WalletType.gnosis, WalletType.bitcoinCash, WalletType.nano, WalletType.banano, @@ -65,7 +66,10 @@ enum WalletType { zano, @HiveField(14) - decred + decred, + + @HiveField(15) + gnosis } int serializeToInt(WalletType type) { @@ -98,6 +102,8 @@ int serializeToInt(WalletType type) { return 12; case WalletType.decred: return 13; + case WalletType.gnosis: + return 14; case WalletType.none: return -1; } @@ -133,6 +139,8 @@ WalletType deserializeFromInt(int raw) { return WalletType.zano; case 13: return WalletType.decred; + case 14: + return WalletType.gnosis; default: throw Exception( 'Unexpected token: $raw for WalletType deserializeFromInt'); @@ -169,6 +177,8 @@ String walletTypeToString(WalletType type) { return 'Zano'; case WalletType.decred: return 'Decred'; + case WalletType.gnosis: + return 'Gnosis'; case WalletType.none: return ''; } @@ -204,6 +214,8 @@ String walletTypeToDisplayName(WalletType type) { return 'Zano (ZANO)'; case WalletType.decred: return 'Decred (DCR)'; + case WalletType.gnosis: + return 'Gnosis (xDAI)'; case WalletType.none: return ''; } @@ -242,6 +254,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal return CryptoCurrency.zano; case WalletType.decred: return CryptoCurrency.dcr; + case WalletType.gnosis: + return CryptoCurrency.xdai; case WalletType.none: throw Exception( 'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency'); @@ -278,6 +292,8 @@ WalletType? cryptoCurrencyToWalletType(CryptoCurrency type) { return WalletType.zano; case CryptoCurrency.dcr: return WalletType.decred; + case CryptoCurrency.xdai: + return WalletType.gnosis; default: return null; } diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index a1b253dd8..d0e8eab9d 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -621,7 +621,9 @@ abstract class EVMChainWalletBase } else { balance.remove(token); } - } catch (_) {} + } catch (e) { + print(e); + } } } diff --git a/cw_gnosis/.gitignore b/cw_gnosis/.gitignore new file mode 100644 index 000000000..96486fd93 --- /dev/null +++ b/cw_gnosis/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/cw_gnosis/LICENSE b/cw_gnosis/LICENSE new file mode 100644 index 000000000..ba75c69f7 --- /dev/null +++ b/cw_gnosis/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/cw_gnosis/analysis_options.yaml b/cw_gnosis/analysis_options.yaml new file mode 100644 index 000000000..a5744c1cf --- /dev/null +++ b/cw_gnosis/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/cw_gnosis/devtools_options.yaml b/cw_gnosis/devtools_options.yaml new file mode 100644 index 000000000..fa0b357c4 --- /dev/null +++ b/cw_gnosis/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/cw_gnosis/lib/default_gnosis_erc20_tokens.dart b/cw_gnosis/lib/default_gnosis_erc20_tokens.dart new file mode 100644 index 000000000..d98def594 --- /dev/null +++ b/cw_gnosis/lib/default_gnosis_erc20_tokens.dart @@ -0,0 +1,54 @@ +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/erc20_token.dart'; + +class DefaultGnosisErc20Tokens { + final List _defaultTokens = [ + Erc20Token( + name: "Wrapped Ether", + symbol: "WETH", + contractAddress: "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1", + decimal: 18, + enabled: false, + ), + Erc20Token( + name: "Tether USD on xDai", + symbol: "USDT", + contractAddress: "0x4ECaBa5870353805a9F068101A40E0f32ed605C6", + decimal: 6, + enabled: true, + ), + Erc20Token( + name: "USD Coin", + symbol: "USDC.e", + contractAddress: "0x2a22f9c3b484c3629090FeED35F17Ff8F88f76F0", + decimal: 6, + enabled: true, + ), + Erc20Token( + name: "Gnosis", + symbol: "GNO", + contractAddress: "0x9C58BAcC331c9aa871AFD802DB6379a98e80CEdb", + decimal: 18, + enabled: true, + ), + Erc20Token( + name: "Decentralized Euro", + symbol: "DEURO", + contractAddress: "0xac90f343820D8299Ac72a06A7674491b07d45f03", + decimal: 18, + enabled: true, + ), + ]; + + List get initialGnosisErc20Tokens => _defaultTokens.map((token) { + String? iconPath; + try { + iconPath = CryptoCurrency.all + .firstWhere((element) => + element.title.toUpperCase() == token.symbol.split(".").first.toUpperCase()) + .iconPath; + } catch (_) {} + + return Erc20Token.copyWith(token, iconPath, 'XDAI'); + }).toList(); +} diff --git a/cw_gnosis/lib/gnosis_client.dart b/cw_gnosis/lib/gnosis_client.dart new file mode 100644 index 000000000..444abb02a --- /dev/null +++ b/cw_gnosis/lib/gnosis_client.dart @@ -0,0 +1,92 @@ +import 'dart:convert'; + +import 'package:cw_evm/evm_chain_client.dart'; +import 'package:cw_evm/.secrets.g.dart' as secrets; +import 'package:cw_evm/evm_chain_transaction_model.dart'; +import 'package:flutter/foundation.dart'; +import 'package:web3dart/web3dart.dart'; + +class GnosisClient extends EVMChainClient { + @override + Transaction createTransaction({ + required EthereumAddress from, + required EthereumAddress to, + required EtherAmount amount, + EtherAmount? maxPriorityFeePerGas, + Uint8List? data, + int? maxGas, + EtherAmount? gasPrice, + EtherAmount? maxFeePerGas, + }) { + return Transaction( + from: from, + to: to, + value: amount, + // data: data, + maxGas: maxGas, + // gasPrice: gasPrice, + // maxFeePerGas: maxFeePerGas, + // maxPriorityFeePerGas: maxPriorityFeePerGas, + ); + } + + @override + Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) => signedTransaction; + + @override + int get chainId => 137; + + @override + Future> fetchTransactions(String address, + {String? contractAddress}) async { + try { + final response = await httpClient.get(Uri.https("api.gnosisscan.io", "/v2/api", { + "chainid": "$chainId", + "module": "account", + "action": contractAddress != null ? "tokentx" : "txlist", + if (contractAddress != null) "contractaddress": contractAddress, + "address": address, + "apikey": secrets.etherScanApiKey, + })); + + final jsonResponse = json.decode(response.body) as Map; + + if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) { + return (jsonResponse['result'] as List) + .map( + (e) => EVMChainTransactionModel.fromJson(e as Map, 'XDAI'), + ) + .toList(); + } + + return []; + } catch (e) { + return []; + } + } + + @override + Future> fetchInternalTransactions(String address) async { + try { + final response = await httpClient.get(Uri.https("api.gnosisscan.io", "/v2/api", { + "chainid": "$chainId", + "module": "account", + "action": "txlistinternal", + "address": address, + "apikey": secrets.etherScanApiKey, + })); + + final jsonResponse = json.decode(response.body) as Map; + + if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) { + return (jsonResponse['result'] as List) + .map((e) => EVMChainTransactionModel.fromJson(e as Map, 'XDAI')) + .toList(); + } + + return []; + } catch (_) { + return []; + } + } +} diff --git a/cw_gnosis/lib/gnosis_mnemonics_exception.dart b/cw_gnosis/lib/gnosis_mnemonics_exception.dart new file mode 100644 index 000000000..d84afd100 --- /dev/null +++ b/cw_gnosis/lib/gnosis_mnemonics_exception.dart @@ -0,0 +1,5 @@ +class GnosisMnemonicIsIncorrectException implements Exception { + @override + String toString() => + 'Polygon mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.'; +} diff --git a/cw_gnosis/lib/gnosis_transaction_history.dart b/cw_gnosis/lib/gnosis_transaction_history.dart new file mode 100644 index 000000000..ed4b42d3c --- /dev/null +++ b/cw_gnosis/lib/gnosis_transaction_history.dart @@ -0,0 +1,20 @@ +import 'dart:core'; + +import 'package:cw_evm/evm_chain_transaction_history.dart'; +import 'package:cw_evm/evm_chain_transaction_info.dart'; +import 'package:cw_gnosis/gnosis_transaction_info.dart'; + +class GnosisTransactionHistory extends EVMChainTransactionHistory { + GnosisTransactionHistory({ + required super.walletInfo, + required super.password, + required super.encryptionFileUtils, + }); + + @override + String getTransactionHistoryFileName() => 'gnosis_transactions.json'; + + @override + EVMChainTransactionInfo getTransactionInfo(Map val) => + GnosisTransactionInfo.fromJson(val); +} diff --git a/cw_gnosis/lib/gnosis_transaction_info.dart b/cw_gnosis/lib/gnosis_transaction_info.dart new file mode 100644 index 000000000..1c5574582 --- /dev/null +++ b/cw_gnosis/lib/gnosis_transaction_info.dart @@ -0,0 +1,39 @@ +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_evm/evm_chain_transaction_info.dart'; + +class GnosisTransactionInfo extends EVMChainTransactionInfo { + GnosisTransactionInfo({ + required super.id, + required super.height, + required super.ethAmount, + required super.ethFee, + required super.tokenSymbol, + required super.direction, + required super.isPending, + required super.date, + required super.confirmations, + required super.to, + required super.from, + super.exponent, + }); + + factory GnosisTransactionInfo.fromJson(Map data) { + return GnosisTransactionInfo( + id: data['id'] as String, + height: data['height'] as int, + ethAmount: BigInt.parse(data['amount']), + exponent: data['exponent'] as int, + ethFee: BigInt.parse(data['fee']), + direction: parseTransactionDirectionFromInt(data['direction'] as int), + date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int), + isPending: data['isPending'] as bool, + confirmations: data['confirmations'] as int, + tokenSymbol: data['tokenSymbol'] as String, + to: data['to'], + from: data['from'], + ); + } + + @override + String get feeCurrency => 'xDAI'; +} diff --git a/cw_gnosis/lib/gnosis_wallet.dart b/cw_gnosis/lib/gnosis_wallet.dart new file mode 100644 index 000000000..ca379a93b --- /dev/null +++ b/cw_gnosis/lib/gnosis_wallet.dart @@ -0,0 +1,163 @@ +import 'dart:convert'; + +import 'package:cw_core/cake_hive.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/encryption_file_utils.dart'; +import 'package:cw_core/erc20_token.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; +import 'package:cw_evm/evm_chain_transaction_history.dart'; +import 'package:cw_evm/evm_chain_transaction_info.dart'; +import 'package:cw_evm/evm_chain_transaction_model.dart'; +import 'package:cw_evm/evm_chain_wallet.dart'; +import 'package:cw_evm/evm_erc20_balance.dart'; +import 'package:cw_gnosis/default_gnosis_erc20_tokens.dart'; +import 'package:cw_gnosis/gnosis_client.dart'; +import 'package:cw_gnosis/gnosis_transaction_history.dart'; +import 'package:cw_gnosis/gnosis_transaction_info.dart'; + +class GnosisWallet extends EVMChainWallet { + GnosisWallet({ + required super.walletInfo, + required super.password, + super.mnemonic, + super.initialBalance, + super.privateKey, + required super.client, + required super.encryptionFileUtils, + super.passphrase, + }) : super(nativeCurrency: CryptoCurrency.xdai); + + @override + Future initErc20TokensBox() async { + final boxName = "${walletInfo.name.replaceAll(" ", "_")}_ ${Erc20Token.gnosisBoxName}"; + if (await CakeHive.boxExists(boxName)) { + evmChainErc20TokensBox = await CakeHive.openBox(boxName); + } else { + evmChainErc20TokensBox = await CakeHive.openBox(boxName.replaceAll(" ", "")); + } + } + + @override + void addInitialTokens([bool isMigration = false]) { + final initialErc20Tokens = DefaultGnosisErc20Tokens().initialGnosisErc20Tokens; + + for (final token in initialErc20Tokens) { + if (!evmChainErc20TokensBox.containsKey(token.contractAddress)) { + if (isMigration) token.enabled = false; + evmChainErc20TokensBox.put(token.contractAddress, token); + } + } + } + + @override + List get getDefaultTokenContractAddresses => + DefaultGnosisErc20Tokens().initialGnosisErc20Tokens.map((e) => e.contractAddress).toList(); + + @override + Future checkIfScanProviderIsEnabled() async { + bool isPolygonScanEnabled = (await sharedPrefs.future).getBool("use_gnosisscan") ?? true; + return isPolygonScanEnabled; + } + + @override + String getTransactionHistoryFileName() => 'gnosis_transactions.json'; + + @override + Erc20Token createNewErc20TokenObject(Erc20Token token, String? iconPath) { + return Erc20Token( + name: token.name, + symbol: token.symbol, + contractAddress: token.contractAddress, + decimal: token.decimal, + enabled: token.enabled, + tag: token.tag ?? "XDAI", + iconPath: iconPath, + isPotentialScam: token.isPotentialScam, + ); + } + + @override + EVMChainTransactionInfo getTransactionInfo( + EVMChainTransactionModel transactionModel, String address) { + final model = GnosisTransactionInfo( + id: transactionModel.hash, + height: transactionModel.blockNumber, + ethAmount: transactionModel.amount, + direction: transactionModel.from == address + ? TransactionDirection.outgoing + : TransactionDirection.incoming, + isPending: false, + date: transactionModel.date, + confirmations: transactionModel.confirmations, + ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice, + exponent: transactionModel.tokenDecimal ?? 18, + tokenSymbol: transactionModel.tokenSymbol ?? "XDAI", + to: transactionModel.to, + from: transactionModel.from, + ); + return model; + } + + @override + EVMChainTransactionHistory setUpTransactionHistory( + WalletInfo walletInfo, String password, EncryptionFileUtils encryptionFileUtils) { + return GnosisTransactionHistory( + walletInfo: walletInfo, + password: password, + encryptionFileUtils: encryptionFileUtils, + ); + } + + static Future open({ + required String name, + required String password, + required WalletInfo walletInfo, + required EncryptionFileUtils encryptionFileUtils, + }) async { + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type); + final path = await pathForWallet(name: name, type: walletInfo.type); + + Map? data; + try { + final jsonSource = await encryptionFileUtils.read(path: path, password: password); + + data = json.decode(jsonSource) as Map; + } catch (e) { + if (!hasKeysFile) rethrow; + } + + final balance = EVMChainERC20Balance.fromJSON(data?['balance'] as String?) ?? + EVMChainERC20Balance(BigInt.zero); + + final WalletKeysData keysData; + // Migrate wallet from the old scheme to then new .keys file scheme + if (!hasKeysFile) { + final mnemonic = data!['mnemonic'] as String?; + final privateKey = data['private_key'] as String?; + final passphrase = data['passphrase'] as String?; + + keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey, passphrase: passphrase); + } else { + keysData = await WalletKeysFile.readKeysFile( + name, + walletInfo.type, + password, + encryptionFileUtils, + ); + } + + return GnosisWallet( + walletInfo: walletInfo, + password: password, + mnemonic: keysData.mnemonic, + privateKey: keysData.privateKey, + passphrase: keysData.passphrase, + initialBalance: balance, + client: GnosisClient(), + encryptionFileUtils: encryptionFileUtils, + ); + } +} diff --git a/cw_gnosis/lib/gnosis_wallet_service.dart b/cw_gnosis/lib/gnosis_wallet_service.dart new file mode 100644 index 000000000..33757fd7d --- /dev/null +++ b/cw_gnosis/lib/gnosis_wallet_service.dart @@ -0,0 +1,162 @@ +import 'package:bip39/bip39.dart' as bip39; +import 'package:cw_core/encryption_file_utils.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart'; +import 'package:cw_evm/evm_chain_wallet_service.dart'; +import 'package:cw_gnosis/gnosis_mnemonics_exception.dart'; +import 'package:cw_gnosis/gnosis_wallet.dart'; +import 'package:cw_gnosis/gnosis_client.dart'; + +class GnosisWalletService extends EVMChainWalletService { + GnosisWalletService( + super.walletInfoSource, super.isDirect, { + required this.client, + }); + + late GnosisClient client; + + @override + WalletType getType() => WalletType.gnosis; + + @override + Future create(EVMChainNewWalletCredentials credentials, {bool? isTestnet}) async { + final strength = credentials.seedPhraseLength == 24 ? 256 : 128; + + final mnemonic = credentials.mnemonic ?? bip39.generateMnemonic(strength: strength); + + final wallet = GnosisWallet( + walletInfo: credentials.walletInfo!, + mnemonic: mnemonic, + password: credentials.password!, + passphrase: credentials.passphrase, + client: client, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + ); + + await wallet.init(); + wallet.addInitialTokens(); + await wallet.save(); + return wallet; + } + + @override + Future openWallet(String name, String password) async { + final walletInfo = + walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType())); + + try { + final wallet = await GnosisWallet.open( + name: name, + password: password, + walletInfo: walletInfo, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + ); + + await wallet.init(); + wallet.addInitialTokens(true); + await wallet.save(); + saveBackup(name); + return wallet; + } catch (_) { + await restoreWalletFilesFromBackup(name); + + final wallet = await GnosisWallet.open( + name: name, + password: password, + walletInfo: walletInfo, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + ); + + await wallet.init(); + await wallet.save(); + return wallet; + } + } + + @override + Future restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials, + {bool? isTestnet}) async { + final wallet = GnosisWallet( + password: credentials.password!, + privateKey: credentials.privateKey, + walletInfo: credentials.walletInfo!, + client: client, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + ); + + await wallet.init(); + wallet.addInitialTokens(); + await wallet.save(); + return wallet; + } + + @override + Future restoreFromHardwareWallet( + EVMChainRestoreWalletFromHardware credentials) async { + credentials.walletInfo!.derivationInfo = DerivationInfo( + derivationType: DerivationType.bip39, + derivationPath: "m/44'/60'/${credentials.hwAccountData.accountIndex}'/0/0" + ); + credentials.walletInfo!.hardwareWalletType = credentials.hardwareWalletType; + credentials.walletInfo!.address = credentials.hwAccountData.address; + + final wallet = GnosisWallet( + walletInfo: credentials.walletInfo!, + password: credentials.password!, + client: client, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + ); + + await wallet.init(); + wallet.addInitialTokens(); + await wallet.save(); + + return wallet; + } + + @override + Future restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials, + {bool? isTestnet}) async { + if (!bip39.validateMnemonic(credentials.mnemonic)) { + throw GnosisMnemonicIsIncorrectException(); + } + + final wallet = GnosisWallet( + password: credentials.password!, + mnemonic: credentials.mnemonic, + walletInfo: credentials.walletInfo!, + passphrase: credentials.passphrase, + client: client, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + ); + + await wallet.init(); + wallet.addInitialTokens(); + await wallet.save(); + + return wallet; + } + + @override + Future rename(String currentName, String password, String newName) async { + final currentWalletInfo = walletInfoSource.values + .firstWhere((info) => info.id == WalletBase.idFor(currentName, getType())); + final currentWallet = await GnosisWallet.open( + password: password, + name: currentName, + walletInfo: currentWalletInfo, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + ); + + await currentWallet.renameWalletFiles(newName); + await saveBackup(newName); + + final newWalletInfo = currentWalletInfo; + newWalletInfo.id = WalletBase.idFor(newName, getType()); + newWalletInfo.name = newName; + + await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + } +} diff --git a/cw_gnosis/pubspec.yaml b/cw_gnosis/pubspec.yaml new file mode 100644 index 000000000..c15119ba6 --- /dev/null +++ b/cw_gnosis/pubspec.yaml @@ -0,0 +1,74 @@ +name: cw_gnosis +description: A new Flutter package project. +version: 0.0.1 +publish_to: none +homepage: https://cakewallet.com + +environment: + sdk: '>=3.0.6 <4.0.0' + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + cw_core: + path: ../cw_core + cw_ethereum: + path: ../cw_ethereum + cw_evm: + path: ../cw_evm + web3dart: ^2.7.1 + hive: ^2.2.3 + bip39: ^1.0.6 + collection: ^1.17.1 + +dependency_overrides: + web3dart: + git: + url: https://github.com/cake-tech/web3dart.git + ref: cake + watcher: ^1.1.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + build_runner: ^2.4.15 + + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/lib/core/seed_validator.dart b/lib/core/seed_validator.dart index 344b5391d..69404474b 100644 --- a/lib/core/seed_validator.dart +++ b/lib/core/seed_validator.dart @@ -2,6 +2,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/core/validator.dart'; import 'package:cake_wallet/entities/mnemonic_item.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/gnosis/gnosis.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/polygon/polygon.dart'; @@ -40,6 +41,8 @@ class SeedValidator extends Validator { return nano!.getNanoWordList(language); case WalletType.polygon: return polygon!.getPolygonWordList(language); + case WalletType.gnosis: + return gnosis!.getGnosisWordList(language); case WalletType.solana: return solana!.getSolanaWordList(language); case WalletType.tron: diff --git a/lib/core/wallet_creation_service.dart b/lib/core/wallet_creation_service.dart index b44e56a98..764c526a5 100644 --- a/lib/core/wallet_creation_service.dart +++ b/lib/core/wallet_creation_service.dart @@ -80,6 +80,7 @@ class WalletCreationService { case WalletType.bitcoinCash: case WalletType.ethereum: case WalletType.polygon: + case WalletType.gnosis: case WalletType.solana: case WalletType.tron: return true; diff --git a/lib/di.dart b/lib/di.dart index 1d925150f..f0b0537d9 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -30,6 +30,7 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/hardware_wallet/require_hardware_wallet_connection.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart'; +import 'package:cake_wallet/gnosis/gnosis.dart'; import 'package:cake_wallet/haven/cw_haven.dart'; import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart'; import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart'; @@ -1140,6 +1141,9 @@ Future setup({ case WalletType.polygon: return polygon!.createPolygonWalletService( _walletInfoSource, SettingsStoreBase.walletPasswordDirectInput); + case WalletType.gnosis: + return gnosis!.createGnosisWalletService( + _walletInfoSource, SettingsStoreBase.walletPasswordDirectInput); case WalletType.solana: return solana!.createSolanaWalletService( _walletInfoSource, SettingsStoreBase.walletPasswordDirectInput); diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 45234d5ec..763b84599 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -36,6 +36,7 @@ const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002'; const havenDefaultNodeUri = 'nodes.havenprotocol.org:443'; const ethereumDefaultNodeUri = 'ethereum-rpc.publicnode.com'; const polygonDefaultNodeUri = 'polygon-bor-rpc.publicnode.com'; +const gnosisDefaultNodeUri = 'gnosis-rpc.publicnode.com'; const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002'; const nanoDefaultNodeUri = 'nano.nownodes.io'; const nanoDefaultPowNodeUri = 'rpc.nano.to'; @@ -511,6 +512,15 @@ Future defaultSettingsMigration( enabled: true, ); break; + case 50: + await addWalletNodeList(nodes: nodes, type: WalletType.gnosis); + await _changeDefaultNode( + nodes: nodes, + sharedPreferences: sharedPreferences, + type: WalletType.gnosis, + currentNodePreferenceKey: PreferencesKey.currentGnosisNodeIdKey, + ); + break; default: break; } @@ -607,6 +617,8 @@ String _getDefaultNodeUri(WalletType type) { return cakeWalletBitcoinCashDefaultNodeUri; case WalletType.polygon: return polygonDefaultNodeUri; + case WalletType.gnosis: + return gnosisDefaultNodeUri; case WalletType.solana: return solanaDefaultNodeUri; case WalletType.tron: @@ -1042,6 +1054,7 @@ Future checkCurrentNodes( final currentHavenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey); final currentEthereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); final currentPolygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey); + final currentGnosisNodeId = sharedPreferences.getInt(PreferencesKey.currentGnosisNodeIdKey); final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey); final currentDecredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey); @@ -1063,6 +1076,8 @@ Future checkCurrentNodes( nodeSource.values.firstWhereOrNull((node) => node.key == currentEthereumNodeId); final currentPolygonNodeServer = nodeSource.values.firstWhereOrNull((node) => node.key == currentPolygonNodeId); + final currentGnosisNodeServer = + nodeSource.values.firstWhereOrNull((node) => node.key == currentGnosisNodeId); final currentNanoNodeServer = nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId); final currentDecredNodeServer = @@ -1146,6 +1161,12 @@ Future checkCurrentNodes( await sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, node.key as int); } + if (currentGnosisNodeServer == null) { + final node = Node(uri: gnosisDefaultNodeUri, type: WalletType.gnosis); + await nodeSource.add(node); + await sharedPreferences.setInt(PreferencesKey.currentGnosisNodeIdKey, node.key as int); + } + if (currentSolanaNodeServer == null) { final node = Node(uri: solanaDefaultNodeUri, type: WalletType.solana); await nodeSource.add(node); diff --git a/lib/entities/node_list.dart b/lib/entities/node_list.dart index bb489e715..4d6966f65 100644 --- a/lib/entities/node_list.dart +++ b/lib/entities/node_list.dart @@ -31,6 +31,9 @@ Future> loadDefaultNodes(WalletType type) async { case WalletType.polygon: path = 'assets/polygon_node_list.yml'; break; + case WalletType.gnosis: + path = 'assets/gnosis_node_list.yml'; + break; case WalletType.solana: path = 'assets/solana_node_list.yml'; break; diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index 9e384f462..e2c2b3882 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -8,6 +8,7 @@ class PreferencesKey { static const currentZanoNodeIdKey = 'current_node_id_zano'; static const currentEthereumNodeIdKey = 'current_node_id_eth'; static const currentPolygonNodeIdKey = 'current_node_id_matic'; + static const currentGnosisNodeIdKey = 'current_node_id_gnosis'; static const currentNanoNodeIdKey = 'current_node_id_nano'; static const currentNanoPowNodeIdKey = 'current_node_id_nano_pow'; static const currentDecredNodeIdKey = 'current_node_id_decred'; @@ -48,6 +49,7 @@ class PreferencesKey { static const litecoinTransactionPriority = 'current_fee_priority_litecoin'; static const ethereumTransactionPriority = 'current_fee_priority_ethereum'; static const polygonTransactionPriority = 'current_fee_priority_polygon'; + static const gnosisTransactionPriority = 'current_fee_priority_gnosis'; static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash'; static const zanoTransactionPriority = 'current_fee_priority_zano'; static const wowneroTransactionPriority = 'current_fee_priority_wownero'; @@ -72,6 +74,7 @@ class PreferencesKey { static const pinNativeTokenAtTop = 'pin_native_token_at_top'; static const useEtherscan = 'use_etherscan'; static const usePolygonScan = 'use_polygonscan'; + static const useGnosisScan = 'use_gnosisscan'; static const useTronGrid = 'use_trongrid'; static const useMempoolFeeAPI = 'use_mempool_fee_api'; static const defaultNanoRep = 'default_nano_representative'; diff --git a/lib/entities/priority_for_wallet_type.dart b/lib/entities/priority_for_wallet_type.dart index 5307250d5..a4b8ffbe6 100644 --- a/lib/entities/priority_for_wallet_type.dart +++ b/lib/entities/priority_for_wallet_type.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/gnosis/gnosis.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/wownero/wownero.dart'; @@ -25,6 +26,8 @@ List priorityForWalletType(WalletType type) { return bitcoinCash!.getTransactionPriorities(); case WalletType.polygon: return polygon!.getTransactionPriorities(); + case WalletType.gnosis: + return gnosis!.getTransactionPriorities(); // no such thing for nano/banano/solana/tron: case WalletType.nano: case WalletType.banano: diff --git a/lib/reactions/bip39_wallet_utils.dart b/lib/reactions/bip39_wallet_utils.dart index a46adb6b1..adb126818 100644 --- a/lib/reactions/bip39_wallet_utils.dart +++ b/lib/reactions/bip39_wallet_utils.dart @@ -4,6 +4,7 @@ bool isBIP39Wallet(WalletType walletType) { switch (walletType) { case WalletType.ethereum: case WalletType.polygon: + case WalletType.gnosis: case WalletType.solana: case WalletType.tron: case WalletType.bitcoin: diff --git a/lib/reactions/wallet_connect.dart b/lib/reactions/wallet_connect.dart index 37a0b92ec..b80fb4237 100644 --- a/lib/reactions/wallet_connect.dart +++ b/lib/reactions/wallet_connect.dart @@ -6,8 +6,9 @@ import 'package:cw_core/wallet_type.dart'; bool isEVMCompatibleChain(WalletType walletType) { switch (walletType) { - case WalletType.polygon: case WalletType.ethereum: + case WalletType.polygon: + case WalletType.gnosis: return true; default: return false; @@ -16,8 +17,9 @@ bool isEVMCompatibleChain(WalletType walletType) { bool isNFTACtivatedChain(WalletType walletType) { switch (walletType) { - case WalletType.polygon: case WalletType.ethereum: + case WalletType.polygon: + case WalletType.gnosis: case WalletType.solana: return true; default: @@ -27,8 +29,9 @@ bool isNFTACtivatedChain(WalletType walletType) { bool isWalletConnectCompatibleChain(WalletType walletType) { switch (walletType) { - case WalletType.polygon: case WalletType.ethereum: + case WalletType.polygon: + case WalletType.gnosis: case WalletType.solana: return true; default: diff --git a/lib/src/screens/dashboard/widgets/menu_widget.dart b/lib/src/screens/dashboard/widgets/menu_widget.dart index b394e96ad..cc940d6ee 100644 --- a/lib/src/screens/dashboard/widgets/menu_widget.dart +++ b/lib/src/screens/dashboard/widgets/menu_widget.dart @@ -33,6 +33,7 @@ class MenuWidgetState extends State { this.bananoIcon = Image.asset('assets/images/nano_icon.png'), this.bitcoinCashIcon = Image.asset('assets/images/bch_icon.png'), this.polygonIcon = Image.asset('assets/images/matic_icon.png'), + this.gnosisIcon = Image.asset('assets/images/gnosis_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'), @@ -59,6 +60,7 @@ class MenuWidgetState extends State { Image nanoIcon; Image bananoIcon; Image polygonIcon; + Image gnosisIcon; Image solanaIcon; Image tronIcon; Image wowneroIcon; @@ -245,6 +247,8 @@ class MenuWidgetState extends State { return bananoIcon; case WalletType.polygon: return polygonIcon; + case WalletType.gnosis: + return gnosisIcon; case WalletType.solana: return solanaIcon; case WalletType.tron: diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index c88590475..96e8d4741 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -26,6 +26,7 @@ import 'package:cake_wallet/entities/seed_type.dart'; import 'package:cake_wallet/entities/sort_balance_types.dart'; import 'package:cake_wallet/entities/wallet_list_order_types.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/gnosis/gnosis.dart'; import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cake_wallet/zano/zano.dart'; import 'package:cw_core/transaction_priority.dart'; @@ -105,6 +106,7 @@ abstract class SettingsStoreBase with Store { required this.pinNativeTokenAtTop, required this.useEtherscan, required this.usePolygonScan, + required this.useGnosisScan, required this.useTronGrid, required this.useMempoolFeeAPI, required this.defaultNanoRep, @@ -134,6 +136,7 @@ abstract class SettingsStoreBase with Store { TransactionPriority? initialLitecoinTransactionPriority, TransactionPriority? initialEthereumTransactionPriority, TransactionPriority? initialPolygonTransactionPriority, + TransactionPriority? initialGnosisTransactionPriority, TransactionPriority? initialBitcoinCashTransactionPriority, TransactionPriority? initialZanoTransactionPriority, TransactionPriority? initialDecredTransactionPriority, @@ -215,6 +218,10 @@ abstract class SettingsStoreBase with Store { priority[WalletType.polygon] = initialPolygonTransactionPriority; } + if (initialGnosisTransactionPriority != null) { + priority[WalletType.gnosis] = initialGnosisTransactionPriority; + } + if (initialBitcoinCashTransactionPriority != null) { priority[WalletType.bitcoinCash] = initialBitcoinCashTransactionPriority; } @@ -277,6 +284,9 @@ abstract class SettingsStoreBase with Store { case WalletType.polygon: key = PreferencesKey.polygonTransactionPriority; break; + case WalletType.gnosis: + key = PreferencesKey.gnosisTransactionPriority; + break; case WalletType.zano: key = PreferencesKey.zanoTransactionPriority; break; @@ -429,6 +439,11 @@ abstract class SettingsStoreBase with Store { (bool usePolygonScan) => _sharedPreferences.setBool(PreferencesKey.usePolygonScan, usePolygonScan)); + reaction( + (_) => useGnosisScan, + (bool useGnosisScan) => + _sharedPreferences.setBool(PreferencesKey.useGnosisScan, useGnosisScan)); + reaction((_) => useTronGrid, (bool useTronGrid) => _sharedPreferences.setBool(PreferencesKey.useTronGrid, useTronGrid)); @@ -773,6 +788,9 @@ abstract class SettingsStoreBase with Store { @observable bool usePolygonScan; + @observable + bool useGnosisScan; + @observable bool useTronGrid; @@ -908,6 +926,7 @@ abstract class SettingsStoreBase with Store { TransactionPriority? litecoinTransactionPriority; TransactionPriority? ethereumTransactionPriority; TransactionPriority? polygonTransactionPriority; + TransactionPriority? gnosisTransactionPriority; TransactionPriority? bitcoinCashTransactionPriority; TransactionPriority? wowneroTransactionPriority; TransactionPriority? zanoTransactionPriority; @@ -929,6 +948,10 @@ abstract class SettingsStoreBase with Store { polygonTransactionPriority = polygon?.deserializePolygonTransactionPriority( sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority)!); } + if (sharedPreferences.getInt(PreferencesKey.gnosisTransactionPriority) != null) { + gnosisTransactionPriority = gnosis?.deserializeGnosisTransactionPriority( + sharedPreferences.getInt(PreferencesKey.gnosisTransactionPriority)!); + } if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) { bitcoinCashTransactionPriority = bitcoinCash?.deserializeBitcoinCashTransactionPriority( sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!); @@ -955,6 +978,7 @@ abstract class SettingsStoreBase with Store { wowneroTransactionPriority ??= wownero?.getDefaultTransactionPriority(); decredTransactionPriority ??= decred?.getDecredTransactionPriorityMedium(); polygonTransactionPriority ??= polygon?.getDefaultTransactionPriority(); + gnosisTransactionPriority ??= gnosis?.getDefaultTransactionPriority(); zanoTransactionPriority ??= zano?.getDefaultTransactionPriority(); final currentBalanceDisplayMode = BalanceDisplayMode.deserialize( @@ -999,6 +1023,7 @@ abstract class SettingsStoreBase with Store { : defaultSeedPhraseLength; final useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true; final usePolygonScan = sharedPreferences.getBool(PreferencesKey.usePolygonScan) ?? true; + final useGnosisScan = sharedPreferences.getBool(PreferencesKey.useGnosisScan) ?? true; final useTronGrid = sharedPreferences.getBool(PreferencesKey.useTronGrid) ?? true; final useMempoolFeeAPI = sharedPreferences.getBool(PreferencesKey.useMempoolFeeAPI) ?? true; final defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? ""; @@ -1043,6 +1068,7 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey); final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey); + final gnosisNodeId = sharedPreferences.getInt(PreferencesKey.currentGnosisNodeIdKey); final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey); final solanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey); @@ -1062,6 +1088,8 @@ abstract class SettingsStoreBase with Store { nodeSource.values.firstWhereOrNull((e) => e.uriRaw == ethereumDefaultNodeUri); final polygonNode = nodeSource.get(polygonNodeId) ?? nodeSource.values.firstWhereOrNull((e) => e.uriRaw == polygonDefaultNodeUri); + final gnosisNode = nodeSource.get(gnosisNodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == gnosisDefaultNodeUri); final bitcoinCashElectrumServer = nodeSource.get(bitcoinCashElectrumServerId) ?? nodeSource.values.firstWhereOrNull((e) => e.uriRaw == cakeWalletBitcoinCashDefaultNodeUri); final nanoNode = nodeSource.get(nanoNodeId) ?? @@ -1132,6 +1160,10 @@ abstract class SettingsStoreBase with Store { nodes[WalletType.polygon] = polygonNode; } + if (gnosisNode != null) { + nodes[WalletType.gnosis] = gnosisNode; + } + if (bitcoinCashElectrumServer != null) { nodes[WalletType.bitcoinCash] = bitcoinCashElectrumServer; } @@ -1304,6 +1336,7 @@ abstract class SettingsStoreBase with Store { pinNativeTokenAtTop: pinNativeTokenAtTop, useEtherscan: useEtherscan, usePolygonScan: usePolygonScan, + useGnosisScan: useGnosisScan, useTronGrid: useTronGrid, useMempoolFeeAPI: useMempoolFeeAPI, defaultNanoRep: defaultNanoRep, @@ -1349,6 +1382,7 @@ abstract class SettingsStoreBase with Store { shouldRequireTOTP2FAForAllSecurityAndBackupSettings, initialEthereumTransactionPriority: ethereumTransactionPriority, initialPolygonTransactionPriority: polygonTransactionPriority, + initialGnosisTransactionPriority: gnosisTransactionPriority, initialSyncMode: savedSyncMode, initialSyncAll: savedSyncAll, shouldShowYatPopup: shouldShowYatPopup, @@ -1398,6 +1432,11 @@ abstract class SettingsStoreBase with Store { priority[WalletType.polygon] = polygon!.deserializePolygonTransactionPriority( sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority)!); } + if (gnosis != null && + sharedPreferences.getInt(PreferencesKey.gnosisTransactionPriority) != null) { + priority[WalletType.gnosis] = gnosis!.deserializeGnosisTransactionPriority( + sharedPreferences.getInt(PreferencesKey.gnosisTransactionPriority)!); + } if (bitcoinCash != null && sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) { priority[WalletType.bitcoinCash] = bitcoinCash!.deserializeBitcoinCashTransactionPriority( @@ -1484,6 +1523,7 @@ abstract class SettingsStoreBase with Store { pinNativeTokenAtTop = sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true; useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true; usePolygonScan = sharedPreferences.getBool(PreferencesKey.usePolygonScan) ?? true; + useGnosisScan = sharedPreferences.getBool(PreferencesKey.usePolygonScan) ?? true; useTronGrid = sharedPreferences.getBool(PreferencesKey.useTronGrid) ?? true; useMempoolFeeAPI = sharedPreferences.getBool(PreferencesKey.useMempoolFeeAPI) ?? true; defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? ""; @@ -1516,6 +1556,7 @@ abstract class SettingsStoreBase with Store { final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey); final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey); + final gnosisNodeId = sharedPreferences.getInt(PreferencesKey.currentGnosisNodeIdKey); final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); final solanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey); final tronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey); @@ -1528,6 +1569,7 @@ abstract class SettingsStoreBase with Store { final havenNode = nodeSource.get(havenNodeId); final ethereumNode = nodeSource.get(ethereumNodeId); final polygonNode = nodeSource.get(polygonNodeId); + final gnosisNode = nodeSource.get(gnosisNodeId); final bitcoinCashNode = nodeSource.get(bitcoinCashElectrumServerId); final nanoNode = nodeSource.get(nanoNodeId); final solanaNode = nodeSource.get(solanaNodeId); @@ -1560,6 +1602,10 @@ abstract class SettingsStoreBase with Store { nodes[WalletType.polygon] = polygonNode; } + if (gnosisNode != null) { + nodes[WalletType.gnosis] = gnosisNode; + } + if (bitcoinCashNode != null) { nodes[WalletType.bitcoinCash] = bitcoinCashNode; } @@ -1716,6 +1762,9 @@ abstract class SettingsStoreBase with Store { case WalletType.polygon: await _sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, node.key as int); break; + case WalletType.gnosis: + await _sharedPreferences.setInt(PreferencesKey.currentGnosisNodeIdKey, node.key as int); + break; case WalletType.solana: await _sharedPreferences.setInt(PreferencesKey.currentSolanaNodeIdKey, node.key as int); break; diff --git a/lib/view_model/advanced_privacy_settings_view_model.dart b/lib/view_model/advanced_privacy_settings_view_model.dart index 803744590..6299dfcfd 100644 --- a/lib/view_model/advanced_privacy_settings_view_model.dart +++ b/lib/view_model/advanced_privacy_settings_view_model.dart @@ -35,9 +35,10 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { // convert to switch case so that it give a syntax error when adding a new wallet type // thus we don't forget about it switch (type) { - case WalletType.ethereum: case WalletType.bitcoinCash: + case WalletType.ethereum: case WalletType.polygon: + case WalletType.gnosis: case WalletType.solana: case WalletType.tron: return true; diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 30bd1c8b3..e191e959e 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -850,6 +850,7 @@ abstract class DashboardViewModelBase with Store { case WalletType.bitcoinCash: case WalletType.ethereum: case WalletType.polygon: + case WalletType.gnosis: case WalletType.solana: case WalletType.nano: case WalletType.banano: diff --git a/lib/view_model/dashboard/home_settings_view_model.dart b/lib/view_model/dashboard/home_settings_view_model.dart index 4794746c4..81649bcf5 100644 --- a/lib/view_model/dashboard/home_settings_view_model.dart +++ b/lib/view_model/dashboard/home_settings_view_model.dart @@ -7,6 +7,7 @@ import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/erc20_token_info_moralis.dart'; import 'package:cake_wallet/entities/sort_balance_types.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/gnosis/gnosis.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/solana/solana.dart'; @@ -200,6 +201,9 @@ abstract class HomeSettingsViewModelBase with Store { case WalletType.polygon: defaultTokenAddresses = polygon!.getDefaultTokenContractAddresses(); break; + case WalletType.gnosis: + defaultTokenAddresses = gnosis!.getDefaultTokenContractAddresses(); + break; case WalletType.solana: defaultTokenAddresses = solana!.getDefaultTokenContractAddresses(); break; diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart index 808ccabea..e2c1197e1 100644 --- a/lib/view_model/dashboard/transaction_list_item.dart +++ b/lib/view_model/dashboard/transaction_list_item.dart @@ -3,6 +3,7 @@ import 'package:cake_wallet/entities/balance_display_mode.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/gnosis/gnosis.dart'; import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart'; @@ -185,6 +186,13 @@ class TransactionListItem extends ActionListItem with Keyable { cryptoAmount: polygon!.formatterPolygonAmountToDouble(transaction: transaction), price: price); break; + case WalletType.gnosis: + final asset = gnosis!.assetOfTransaction(balanceViewModel.wallet, transaction); + final price = balanceViewModel.fiatConvertationStore.prices[asset]; + amount = calculateFiatAmountRaw( + cryptoAmount: gnosis!.formatterGnosisAmountToDouble(transaction: transaction), + price: price); + break; case WalletType.nano: amount = calculateFiatAmountRaw( cryptoAmount: double.parse(nanoUtil!.getRawAsUsableString( diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 58b5e5756..714984151 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -779,6 +779,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with depositCurrency = CryptoCurrency.dcr; receiveCurrency = CryptoCurrency.xmr; break; + case WalletType.gnosis: + depositCurrency = CryptoCurrency.xdai; + 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 7e4e73915..fd5e8db51 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 @@ -73,6 +73,7 @@ abstract class NodeCreateOrEditViewModelBase with Store { switch (_walletType) { case WalletType.ethereum: case WalletType.polygon: + case WalletType.gnosis: case WalletType.solana: case WalletType.banano: case WalletType.nano: diff --git a/lib/view_model/send/fees_view_model.dart b/lib/view_model/send/fees_view_model.dart index f6dd0f201..8eecdbb2e 100644 --- a/lib/view_model/send/fees_view_model.dart +++ b/lib/view_model/send/fees_view_model.dart @@ -3,6 +3,7 @@ import 'package:cake_wallet/decred/decred.dart'; import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/gnosis/gnosis.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/store/app_store.dart'; @@ -89,6 +90,8 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor return transactionPriority == bitcoinCash!.getBitcoinCashTransactionPrioritySlow(); case WalletType.polygon: return transactionPriority == polygon!.getPolygonTransactionPrioritySlow(); + case WalletType.gnosis: + return transactionPriority == gnosis!.getGnosisTransactionPrioritySlow(); case WalletType.decred: return transactionPriority == decred!.getDecredTransactionPrioritySlow(); case WalletType.none: diff --git a/lib/view_model/send/output.dart b/lib/view_model/send/output.dart index 6c3588404..c34332797 100644 --- a/lib/view_model/send/output.dart +++ b/lib/view_model/send/output.dart @@ -4,6 +4,7 @@ import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/entities/parsed_address.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/gnosis/gnosis.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/solana/solana.dart'; @@ -110,6 +111,9 @@ abstract class OutputBase with Store { case WalletType.polygon: _amount = polygon!.formatterPolygonParseAmount(_cryptoAmount); break; + case WalletType.gnosis: + _amount = gnosis!.formatterGnosisParseAmount(_cryptoAmount); + break; case WalletType.wownero: _amount = wownero!.formatterWowneroParseAmount(amount: _cryptoAmount); break; @@ -186,6 +190,10 @@ abstract class OutputBase with Store { return polygon!.formatterPolygonAmountToDouble(amount: BigInt.from(fee)); } + if (_wallet.type == WalletType.gnosis) { + return gnosis!.formatterGnosisAmountToDouble(amount: BigInt.from(fee)); + } + if (_wallet.type == WalletType.zano) { return zano!.formatterIntAmountToDouble(amount: fee, currency: cryptoCurrencyHandler(), forFee: true); } @@ -296,6 +304,7 @@ abstract class OutputBase with Store { case WalletType.monero: case WalletType.ethereum: case WalletType.polygon: + case WalletType.gnosis: case WalletType.solana: case WalletType.tron: case WalletType.haven: diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index 067ca73f9..982d1535c 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -73,6 +73,9 @@ abstract class TransactionDetailsViewModelBase with Store { case WalletType.polygon: _addPolygonListItems(tx, dateFormat); break; + case WalletType.gnosis: + _addGnosisListItems(tx, dateFormat); + break; case WalletType.solana: _addSolanaListItems(tx, dateFormat); break; @@ -183,6 +186,8 @@ abstract class TransactionDetailsViewModelBase with Store { return 'https://nanexplorer.com/banano/block/${txId}'; case WalletType.polygon: return 'https://polygonscan.com/tx/${txId}'; + case WalletType.gnosis: + return 'https://gnosisscan.io/tx/${txId}'; case WalletType.solana: return 'https://solscan.io/tx/${txId}'; case WalletType.tron: @@ -217,6 +222,8 @@ abstract class TransactionDetailsViewModelBase with Store { return S.current.view_transaction_on + 'nanexplorer.com'; case WalletType.polygon: return S.current.view_transaction_on + 'polygonscan.com'; + case WalletType.gnosis: + return S.current.view_transaction_on + 'gnosisscan.io'; case WalletType.solana: return S.current.view_transaction_on + 'solscan.io'; case WalletType.tron: @@ -516,6 +523,56 @@ abstract class TransactionDetailsViewModelBase with Store { items.addAll(_items); } + void _addGnosisListItems(TransactionInfo tx, DateFormat dateFormat) { + final _items = [ + StandartListItem( + title: S.current.transaction_details_transaction_id, + value: tx.txHash, + key: ValueKey('standard_list_item_transaction_details_id_key'), + ), + StandartListItem( + title: S.current.transaction_details_date, + value: dateFormat.format(tx.date), + key: ValueKey('standard_list_item_transaction_details_date_key'), + ), + StandartListItem( + title: S.current.confirmations, + value: tx.confirmations.toString(), + key: ValueKey('standard_list_item_transaction_confirmations_key'), + ), + StandartListItem( + title: S.current.transaction_details_height, + value: '${tx.height}', + key: ValueKey('standard_list_item_transaction_details_height_key'), + ), + StandartListItem( + title: S.current.transaction_details_amount, + value: tx.amountFormatted(), + key: ValueKey('standard_list_item_transaction_details_amount_key'), + ), + if (tx.feeFormatted()?.isNotEmpty ?? false) + StandartListItem( + title: S.current.transaction_details_fee, + value: tx.feeFormatted()!, + key: ValueKey('standard_list_item_transaction_details_fee_key'), + ), + if (showRecipientAddress && tx.to != null && tx.direction == TransactionDirection.outgoing) + StandartListItem( + title: S.current.transaction_details_recipient_address, + value: tx.to!, + key: ValueKey('standard_list_item_transaction_details_recipient_address_key'), + ), + if (tx.direction == TransactionDirection.incoming && tx.from != null) + StandartListItem( + title: S.current.transaction_details_source_address, + value: tx.from!, + key: ValueKey('standard_list_item_transaction_details_source_address_key'), + ), + ]; + + items.addAll(_items); + } + void _addSolanaListItems(TransactionInfo tx, DateFormat dateFormat) { final _items = [ StandartListItem( 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 910f081ee..7918931f3 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 @@ -340,6 +340,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo return NanoURI(amount: amount, address: address.address); case WalletType.polygon: return PolygonURI(amount: amount, address: address.address); + case WalletType.gnosis: + return EthereumURI(amount: amount, address: address.address); // ToDo: ? case WalletType.solana: return SolanaURI(amount: amount, address: address.address); case WalletType.tron: diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index da5d04f59..1249a9921 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -124,6 +124,7 @@ abstract class WalletKeysViewModelBase with Store { break; case WalletType.ethereum: case WalletType.polygon: + case WalletType.gnosis: case WalletType.solana: case WalletType.tron: items.addAll([ diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index 86d1be65f..6c7f5aa40 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/core/new_wallet_arguments.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/gnosis/gnosis.dart'; import 'package:cake_wallet/zano/zano.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/solana/solana.dart'; @@ -111,6 +112,13 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { mnemonic: newWalletArguments!.mnemonic, passphrase: passphrase, ); + case WalletType.gnosis: + return gnosis!.createGnosisNewWalletCredentials( + name: name, + password: walletPassword, + mnemonic: newWalletArguments!.mnemonic, + passphrase: passphrase, + ); case WalletType.solana: return solana!.createSolanaNewWalletCredentials( name: name, diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index 6e00ba4cc..1a9d60e91 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -4,6 +4,7 @@ import 'package:cake_wallet/core/generate_wallet_password.dart'; import 'package:cake_wallet/core/wallet_creation_service.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/gnosis/gnosis.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/polygon/polygon.dart'; @@ -38,6 +39,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { type == WalletType.monero || type == WalletType.haven || type == WalletType.wownero, hasRestoreFromPrivateKey = type == WalletType.ethereum || type == WalletType.polygon || + type == WalletType.gnosis || type == WalletType.nano || type == WalletType.banano || type == WalletType.solana || @@ -59,6 +61,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { case WalletType.haven: case WalletType.ethereum: case WalletType.polygon: + case WalletType.gnosis: case WalletType.decred: availableModes = [WalletRestoreMode.seed, WalletRestoreMode.keys]; break; @@ -150,6 +153,13 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { password: password, passphrase: passphrase, ); + case WalletType.gnosis: + return gnosis!.createGnosisRestoreWalletFromSeedCredentials( + name: name, + mnemonic: seed, + password: password, + passphrase: passphrase, + ); case WalletType.solana: return solana!.createSolanaRestoreWalletFromSeedCredentials( name: name, From 4dd82f5911da007dc9c2c04aecb424e81c350b3d Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Mon, 16 Jun 2025 12:29:09 +0200 Subject: [PATCH 2/3] feat: implement Gnosis wallet and currency integration - Added `CWGnosis` class with support for Gnosis blockchain functionalities. - Extended ERC20 token support with `xdai` and `gno` tokens. - Introduced Gnosis-specific methods for wallet creation, transaction handling, and token management. - Updated EVM chain support with Gnosis-specific implementations. --- cw_core/lib/crypto_currency.dart | 5 +- lib/gnosis/cw_gnosis.dart | 214 +++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 lib/gnosis/cw_gnosis.dart diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index 81f55651b..f521b974f 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -111,7 +111,9 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen CryptoCurrency.zano, CryptoCurrency.ton, CryptoCurrency.flip, - CryptoCurrency.deuro + CryptoCurrency.deuro, + CryptoCurrency.xdai, + CryptoCurrency.gno, ]; static const havenCurrencies = [ @@ -234,6 +236,7 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen static const flip = CryptoCurrency(title: 'FLIP', tag: 'ETH', fullName: 'Chainflip', raw: 97, name: 'flip', iconPath: 'assets/images/flip_icon.png', decimals: 18); static const deuro = CryptoCurrency(title: 'DEURO', tag: 'ETH', fullName: 'Decentralized Euro', raw: 98, name: 'deuro', iconPath: 'assets/images/deuro_icon.png', decimals: 18); static const xdai = CryptoCurrency(title: 'XDAI', tag: 'XDAI', fullName: 'xDAI', raw: 99, name: 'xDAI', iconPath: 'assets/images/xdai.png', decimals: 18); + static const gno = CryptoCurrency(title: 'GNO', tag: 'XDAI', fullName: 'Gnosis', raw: 99, name: 'gnoxdai', iconPath: 'assets/images/gno.png', decimals: 18); static final Map _rawCurrencyMap = [...all, ...havenCurrencies].fold>({}, (acc, item) { diff --git a/lib/gnosis/cw_gnosis.dart b/lib/gnosis/cw_gnosis.dart new file mode 100644 index 000000000..560648776 --- /dev/null +++ b/lib/gnosis/cw_gnosis.dart @@ -0,0 +1,214 @@ +part of 'gnosis.dart'; + +class CWGnosis extends Gnosis { + @override + List getGnosisWordList(String language) => EVMChainMnemonics.englishWordlist; + + WalletService createGnosisWalletService(Box walletInfoSource, bool isDirect) => + GnosisWalletService(walletInfoSource, isDirect, client: GnosisClient()); + + @override + WalletCredentials createGnosisNewWalletCredentials({ + required String name, + String? mnemonic, + WalletInfo? walletInfo, + String? password, + String? passphrase, + }) => + EVMChainNewWalletCredentials( + name: name, + walletInfo: walletInfo, + password: password, + mnemonic: mnemonic, + passphrase: passphrase, + ); + + @override + WalletCredentials createGnosisRestoreWalletFromSeedCredentials({ + required String name, + required String mnemonic, + required String password, + String? passphrase, + }) => + EVMChainRestoreWalletFromSeedCredentials( + name: name, + password: password, + mnemonic: mnemonic, + passphrase: passphrase, + ); + + @override + WalletCredentials createGnosisRestoreWalletFromPrivateKey({ + required String name, + required String privateKey, + required String password, + }) => + EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey); + + @override + WalletCredentials createGnosisHardwareWalletCredentials({ + required String name, + required HardwareAccountData hwAccountData, + WalletInfo? walletInfo, + }) => + EVMChainRestoreWalletFromHardware( + name: name, hwAccountData: hwAccountData, walletInfo: walletInfo); + + @override + String getAddress(WalletBase wallet) => (wallet as GnosisWallet).walletAddresses.address; + + @override + String getPrivateKey(WalletBase wallet) { + final privateKeyHolder = (wallet as GnosisWallet).evmChainPrivateKey; + if (privateKeyHolder is EthPrivateKey) return bytesToHex(privateKeyHolder.privateKey); + return ""; + } + + @override + String getPublicKey(WalletBase wallet) { + final privateKeyInUnitInt = (wallet as GnosisWallet).evmChainPrivateKey; + final publicKey = privateKeyInUnitInt.address.hex; + return publicKey; + } + + @override + TransactionPriority getDefaultTransactionPriority() => EVMChainTransactionPriority.medium; + + @override + TransactionPriority getGnosisTransactionPrioritySlow() => EVMChainTransactionPriority.slow; + + @override + List getTransactionPriorities() => EVMChainTransactionPriority.all; + + @override + TransactionPriority deserializeGnosisTransactionPriority(int raw) => + EVMChainTransactionPriority.deserialize(raw: raw); + + Object createGnosisTransactionCredentials( + List outputs, { + required TransactionPriority priority, + required CryptoCurrency currency, + int? feeRate, + }) => + EVMChainTransactionCredentials( + 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 as EVMChainTransactionPriority, + currency: currency, + feeRate: feeRate, + ); + + Object createGnosisTransactionCredentialsRaw( + List outputs, { + TransactionPriority? priority, + required CryptoCurrency currency, + required int feeRate, + }) => + EVMChainTransactionCredentials( + outputs, + priority: priority as EVMChainTransactionPriority?, + currency: currency, + feeRate: feeRate, + ); + + @override + int formatterGnosisParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount); + + @override + double formatterGnosisAmountToDouble( + {TransactionInfo? transaction, BigInt? amount, int exponent = 18}) { + assert(transaction != null || amount != null); + + if (transaction != null) { + transaction as EVMChainTransactionInfo; + return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent); + } else { + return (amount!) / BigInt.from(10).pow(exponent); + } + } + + @override + List getERC20Currencies(WalletBase wallet) { + final polygonWallet = wallet as GnosisWallet; + return polygonWallet.erc20Currencies; + } + + @override + Future addErc20Token(WalletBase wallet, CryptoCurrency token) async => + await (wallet as GnosisWallet).addErc20Token(token as Erc20Token); + + @override + Future deleteErc20Token(WalletBase wallet, CryptoCurrency token) async => + await (wallet as GnosisWallet).deleteErc20Token(token as Erc20Token); + + @override + Future removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) async => + await (wallet as GnosisWallet).removeTokenTransactionsInHistory(token as Erc20Token); + + @override + Future getErc20Token(WalletBase wallet, String contractAddress) async { + final polygonWallet = wallet as GnosisWallet; + return await polygonWallet.getErc20Token(contractAddress, 'polygon'); + } + + @override + CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) { + transaction as EVMChainTransactionInfo; + if (transaction.tokenSymbol == CryptoCurrency.maticpoly.title || + transaction.tokenSymbol == "MATIC") { + return CryptoCurrency.maticpoly; + } + + wallet as GnosisWallet; + + return wallet.erc20Currencies.firstWhere( + (element) => transaction.tokenSymbol.toLowerCase() == element.symbol.toLowerCase(), + ); + } + + @override + void updateGnosisScanUsageState(WalletBase wallet, bool isEnabled) { + (wallet as GnosisWallet).updateScanProviderUsageState(isEnabled); + } + + @override + Web3Client? getWeb3Client(WalletBase wallet) { + return (wallet as GnosisWallet).getWeb3Client(); + } + + String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress; + + @override + void setLedgerConnection( + WalletBase wallet, ledger.LedgerConnection connection) { + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials) + .setLedgerConnection( + connection, wallet.walletInfo.derivationInfo?.derivationPath); + } + + @override + Future> getHardwareWalletAccounts(LedgerViewModel ledgerVM, + {int index = 0, int limit = 5}) async { + final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.connection); + try { + return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit); + } catch (err) { + printV(err); + throw err; + } + } + + @override + List getDefaultTokenContractAddresses() { + return DefaultGnosisErc20Tokens().initialGnosisErc20Tokens.map((e) => e.contractAddress).toList(); + } +} From 77b33fa94231ae71c9bf2ce1e2e9c558bcd1dad0 Mon Sep 17 00:00:00 2001 From: Konstantin Ullrich Date: Tue, 17 Jun 2025 17:16:24 +0200 Subject: [PATCH 3/3] feat: add gnosis.dart generation --- .gitignore | 1 + cakewallet.bat | 2 +- scripts/android/pubspec_gen.sh | 2 +- scripts/ios/app_config.sh | 2 +- scripts/linux/app_config.sh | 2 +- scripts/macos/app_config.sh | 2 +- tool/configure.dart | 126 +++++++++++++++++++++++++++++++++ 7 files changed, 132 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 84a7ecdcd..7777bc3cc 100644 --- a/.gitignore +++ b/.gitignore @@ -135,6 +135,7 @@ lib/ethereum/ethereum.dart lib/bitcoin_cash/bitcoin_cash.dart lib/nano/nano.dart lib/polygon/polygon.dart +lib/gnosis/gnosis.dart lib/solana/solana.dart lib/tron/tron.dart lib/wownero/wownero.dart diff --git a/cakewallet.bat b/cakewallet.bat index 1904c5710..64c6f52be 100644 --- a/cakewallet.bat +++ b/cakewallet.bat @@ -1,5 +1,5 @@ @echo off -set cw_win_app_config=--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron +set cw_win_app_config=--monero --bitcoin --ethereum --polygon --gnosis --nano --bitcoinCash --solana --tron set cw_root=%cd% set cw_archive_name=Cake Wallet.zip set cw_archive_path=%cw_root%\%cw_archive_name% diff --git a/scripts/android/pubspec_gen.sh b/scripts/android/pubspec_gen.sh index b980f877d..f86afa095 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 --zano --decred" + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --gnosis --nano --bitcoinCash --solana --tron --wownero --zano --decred" ;; esac diff --git a/scripts/ios/app_config.sh b/scripts/ios/app_config.sh index b642d67e4..0eb3e2199 100755 --- a/scripts/ios/app_config.sh +++ b/scripts/ios/app_config.sh @@ -31,7 +31,7 @@ case $APP_IOS_TYPE in ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred" + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --gnosis --nano --bitcoinCash --solana --tron --wownero --zano --decred" ;; esac diff --git a/scripts/linux/app_config.sh b/scripts/linux/app_config.sh index 5d9d8597b..dff88f74b 100755 --- a/scripts/linux/app_config.sh +++ b/scripts/linux/app_config.sh @@ -13,7 +13,7 @@ CONFIG_ARGS="" case $APP_LINUX_TYPE in $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --excludeFlutterSecureStorage";; + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --gnosis --nano --bitcoinCash --solana --tron --wownero --excludeFlutterSecureStorage";; esac cp -rf pubspec_description.yaml pubspec.yaml diff --git a/scripts/macos/app_config.sh b/scripts/macos/app_config.sh index 641a7b46b..3b8696db2 100755 --- a/scripts/macos/app_config.sh +++ b/scripts/macos/app_config.sh @@ -36,7 +36,7 @@ case $APP_MACOS_TYPE in $MONERO_COM) CONFIG_ARGS="--monero";; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero";; + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --gnosis --nano --bitcoinCash --solana --tron --wownero";; esac cp -rf pubspec_description.yaml pubspec.yaml diff --git a/tool/configure.dart b/tool/configure.dart index 612958f4a..dec1132c5 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -6,6 +6,7 @@ const ethereumOutputPath = 'lib/ethereum/ethereum.dart'; const bitcoinCashOutputPath = 'lib/bitcoin_cash/bitcoin_cash.dart'; const nanoOutputPath = 'lib/nano/nano.dart'; const polygonOutputPath = 'lib/polygon/polygon.dart'; +const gnosisOutputPath = 'lib/gnosis/gnosis.dart'; const solanaOutputPath = 'lib/solana/solana.dart'; const tronOutputPath = 'lib/tron/tron.dart'; const wowneroOutputPath = 'lib/wownero/wownero.dart'; @@ -25,6 +26,7 @@ Future main(List args) async { final hasNano = args.contains('${prefix}nano'); final hasBanano = args.contains('${prefix}banano'); final hasPolygon = args.contains('${prefix}polygon'); + final hasGnosis = args.contains('${prefix}gnosis'); final hasSolana = args.contains('${prefix}solana'); final hasTron = args.contains('${prefix}tron'); final hasWownero = args.contains('${prefix}wownero'); @@ -38,6 +40,7 @@ Future main(List args) async { await generateBitcoinCash(hasBitcoinCash); await generateNano(hasNano); await generatePolygon(hasPolygon); + await generateGnosis(hasGnosis); await generateSolana(hasSolana); await generateTron(hasTron); await generateWownero(hasWownero); @@ -54,6 +57,7 @@ Future main(List args) async { hasBitcoinCash: hasBitcoinCash, hasFlutterSecureStorage: !excludeFlutterSecureStorage, hasPolygon: hasPolygon, + hasGnosis: hasGnosis, hasSolana: hasSolana, hasTron: hasTron, hasWownero: hasWownero, @@ -68,6 +72,7 @@ Future main(List args) async { hasBanano: hasBanano, hasBitcoinCash: hasBitcoinCash, hasPolygon: hasPolygon, + hasGnosis: hasGnosis, hasSolana: hasSolana, hasTron: hasTron, hasWownero: hasWownero, @@ -875,6 +880,113 @@ abstract class Polygon { await outputFile.writeAsString(output); } +Future generateGnosis(bool hasImplementation) async { + final outputFile = File(gnosisOutputPath); + const gnosisCommonHeaders = """ +import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; +import 'package:cake_wallet/view_model/send/output.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/erc20_token.dart'; +import 'package:cw_core/hardware/hardware_account_data.dart'; +import 'package:cw_core/output_info.dart'; +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:hive/hive.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; +import 'package:web3dart/web3dart.dart'; + +"""; + const gnosisCWHeaders = """ +import 'package:cw_evm/evm_chain_formatter.dart'; +import 'package:cw_evm/evm_chain_mnemonics.dart'; +import 'package:cw_evm/evm_chain_transaction_credentials.dart'; +import 'package:cw_evm/evm_chain_transaction_info.dart'; +import 'package:cw_evm/evm_chain_transaction_priority.dart'; +import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart'; +import 'package:cw_evm/evm_chain_hardware_wallet_service.dart'; +import 'package:cw_evm/evm_ledger_credentials.dart'; +import 'package:cw_evm/evm_chain_wallet.dart'; + +import 'package:cw_gnosis/gnosis_client.dart'; +import 'package:cw_gnosis/gnosis_wallet.dart'; +import 'package:cw_gnosis/gnosis_wallet_service.dart'; +import 'package:cw_gnosis/default_gnosis_erc20_tokens.dart'; + +import 'package:eth_sig_util/util/utils.dart'; + +"""; + const gnosisCwPart = "part 'cw_gnosis.dart';"; + const gnosisContent = """ +abstract class Gnosis { + List getGnosisWordList(String language); + WalletService createGnosisWalletService(Box walletInfoSource, bool isDirect); + WalletCredentials createGnosisNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? mnemonic, String? passphrase}); + WalletCredentials createGnosisRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password, String? passphrase}); + WalletCredentials createGnosisRestoreWalletFromPrivateKey({required String name, required String privateKey, required String password}); + WalletCredentials createGnosisHardwareWalletCredentials({required String name, required HardwareAccountData hwAccountData, WalletInfo? walletInfo}); + String getAddress(WalletBase wallet); + String getPrivateKey(WalletBase wallet); + String getPublicKey(WalletBase wallet); + TransactionPriority getDefaultTransactionPriority(); + TransactionPriority getGnosisTransactionPrioritySlow(); + List getTransactionPriorities(); + TransactionPriority deserializeGnosisTransactionPriority(int raw); + + Object createGnosisTransactionCredentials( + List outputs, { + required TransactionPriority priority, + required CryptoCurrency currency, + int? feeRate, + }); + + Object createGnosisTransactionCredentialsRaw( + List outputs, { + TransactionPriority? priority, + required CryptoCurrency currency, + required int feeRate, + }); + + int formatterGnosisParseAmount(String amount); + double formatterGnosisAmountToDouble({TransactionInfo? transaction, BigInt? amount, int exponent = 18}); + List getERC20Currencies(WalletBase wallet); + Future addErc20Token(WalletBase wallet, CryptoCurrency token); + Future deleteErc20Token(WalletBase wallet, CryptoCurrency token); + Future removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token); + Future getErc20Token(WalletBase wallet, String contractAddress); + + CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction); + void updateGnosisScanUsageState(WalletBase wallet, bool isEnabled); + Web3Client? getWeb3Client(WalletBase wallet); + String getTokenAddress(CryptoCurrency asset); + + void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection); + Future> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); + List getDefaultTokenContractAddresses(); +} + """; + + const gnosisEmptyDefinition = 'Gnosis? gnosis;\n'; + const gnosisCWDefinition = 'Gnosis? gnosis = CWGnosis();\n'; + + final output = '$gnosisCommonHeaders\n' + + (hasImplementation ? '$gnosisCWHeaders\n' : '\n') + + (hasImplementation ? '$gnosisCwPart\n\n' : '\n') + + (hasImplementation ? gnosisCWDefinition : gnosisEmptyDefinition) + + '\n' + + gnosisContent; + + if (outputFile.existsSync()) { + await outputFile.delete(); + } + + await outputFile.writeAsString(output); +} + Future generateBitcoinCash(bool hasImplementation) async { final outputFile = File(bitcoinCashOutputPath); const bitcoinCashCommonHeaders = """ @@ -1400,6 +1512,7 @@ Future generatePubspec({ required bool hasBitcoinCash, required bool hasFlutterSecureStorage, required bool hasPolygon, + required bool hasGnosis, required bool hasSolana, required bool hasTron, required bool hasWownero, @@ -1445,6 +1558,10 @@ Future generatePubspec({ cw_polygon: path: ./cw_polygon """; + const cwGnosis = """ + cw_gnosis: + path: ./cw_gnosis + """; const cwSolana = """ cw_solana: path: ./cw_solana @@ -1506,6 +1623,10 @@ Future generatePubspec({ output += '\n$cwPolygon'; } + if (hasGnosis) { + output += '\n$cwGnosis'; + } + if (hasSolana) { output += '\n$cwSolana'; } @@ -1554,6 +1675,7 @@ Future generateWalletTypes({ required bool hasBanano, required bool hasBitcoinCash, required bool hasPolygon, + required bool hasGnosis, required bool hasSolana, required bool hasTron, required bool hasWownero, @@ -1594,6 +1716,10 @@ Future generateWalletTypes({ outputContent += '\tWalletType.polygon,\n'; } + if (hasGnosis) { + outputContent += '\tWalletType.gnosis,\n'; + } + if (hasSolana) { outputContent += '\tWalletType.solana,\n'; }