index.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. import 'dart:async';
  2. import 'package:flutter/material.dart';
  3. import 'package:get_storage/get_storage.dart';
  4. import 'package:polkawallet_sdk/api/types/balanceData.dart';
  5. import 'package:polkawallet_sdk/api/types/networkParams.dart';
  6. import 'package:polkawallet_sdk/api/types/networkStateData.dart';
  7. import 'package:polkawallet_sdk/plugin/homeNavItem.dart';
  8. import 'package:polkawallet_sdk/plugin/store/balances.dart';
  9. import 'package:polkawallet_sdk/polkawallet_sdk.dart';
  10. import 'package:polkawallet_sdk/service/webViewRunner.dart';
  11. import 'package:polkawallet_sdk/storage/keyring.dart';
  12. import 'package:polkawallet_sdk/storage/keyringEVM.dart';
  13. import 'package:polkawallet_sdk/storage/types/keyPairData.dart';
  14. import 'package:polkawallet_sdk/utils/app.dart';
  15. const String sdk_cache_key = 'polka_wallet_sdk_cache';
  16. const String net_state_cache_key = 'network_state';
  17. const String net_const_cache_key = 'network_const';
  18. const String balance_cache_key = 'balances';
  19. abstract class PolkawalletPlugin implements PolkawalletPluginBase {
  20. /// A plugin has a [WalletSDK] instance for connecting to it's node.
  21. final WalletSDK sdk = WalletSDK();
  22. /// Plugin should retrieve [balances] from sdk
  23. /// for display in Assets page of Polkawallet App.
  24. final balances = BalancesStore();
  25. /// Plugin should provide a list of defaultTokens
  26. /// for users of Polkawallet App.
  27. List<String> get defaultTokens => [];
  28. /// Plugin should provide a list of noneNativeToken
  29. /// for users of Polkawallet App.
  30. List<TokenBalanceData> get noneNativeTokensAll => [];
  31. final recoveryEnabled = false;
  32. final appUtils = AppUtils();
  33. /// The App will display this widget in assets page
  34. Widget? getAggregatedAssetsWidget(
  35. {String priceCurrency = 'USD',
  36. bool hideBalance = false,
  37. double rate = 1.0,
  38. @required Function? onSwitchBack,
  39. @required Function? onSwitchHideBalance}) =>
  40. null;
  41. /// The App will display this widget in native token detail page
  42. /// @param[transferType] = 0 | 1 | 2, (0 = all, 1 = in, 2 = out)
  43. Widget? getNativeTokenTransfers(
  44. {required String address, int transferType = 0}) =>
  45. null;
  46. /// Plugin should retrieve [networkState] & [networkConst] while start
  47. NetworkStateData get networkState {
  48. try {
  49. return NetworkStateData.fromJson(Map<String, dynamic>.from(
  50. _cache.read(_getNetworkCacheKey(net_state_cache_key)) ?? {}));
  51. } catch (err) {
  52. print(err);
  53. }
  54. return NetworkStateData();
  55. }
  56. Map get networkConst =>
  57. _cache.read(_getNetworkCacheKey(net_const_cache_key)) ?? {};
  58. GetStorage get _cache => GetStorage(sdk_cache_key);
  59. String _getNetworkCacheKey(String key) => '${key}_${basic.name}';
  60. String _getBalanceCacheKey(String? pubKey) =>
  61. '${balance_cache_key}_${basic.name}_$pubKey';
  62. Future<void> updateNetworkState() async {
  63. final state = await sdk.api.service.setting.queryNetwork();
  64. if (state != null) {
  65. _cache.write(_getNetworkCacheKey(net_const_cache_key), state["const"]);
  66. _cache.write(_getNetworkCacheKey(net_state_cache_key), state["props"]);
  67. }
  68. }
  69. void _updateBalances(KeyPairData acc, BalanceData data,
  70. {bool isFromCache = false}) {
  71. if (acc.address == data.accountId || isFromCache) {
  72. data.isFromCache = isFromCache;
  73. balances.setBalance(data);
  74. if (!isFromCache) {
  75. _cache.write(_getBalanceCacheKey(acc.pubKey), data.toJson());
  76. }
  77. }
  78. }
  79. /// This method will be called while user request to query balance.
  80. Future<void> updateBalances(KeyPairData acc) async {
  81. if (acc.pubKey == acc.address) {
  82. //eth
  83. final data =
  84. await sdk.api.eth.account.getNativeTokenBalance(acc.address ?? '');
  85. _updateBalances(
  86. acc,
  87. BalanceData()
  88. ..accountId = acc.address
  89. ..freeBalance = data
  90. ..availableBalance = data
  91. ..lockedBalance = '0'
  92. ..reservedBalance = '0');
  93. } else {
  94. final data = await (sdk.api.account.queryBalance(acc.address));
  95. if (data == null) {
  96. print(
  97. '\n\n\n\nERROR PolkawalletPlugin.updateBalances sdk.api.account.queryBalance returned null\n\n\n\n');
  98. throw Exception(
  99. '(sdk.api.account.queryBalance(acc.address) returned null');
  100. } else {
  101. _updateBalances(acc, data);
  102. }
  103. }
  104. }
  105. void loadBalances(KeyPairData acc) {
  106. // do not load balance data from cache if we have no decimals data.
  107. if (networkState.tokenDecimals == null) return;
  108. _updateBalances(
  109. acc,
  110. BalanceData.fromJson(Map<String, dynamic>.from(
  111. _cache.read(_getBalanceCacheKey(acc.pubKey)) ??
  112. {'accountId': acc.address})),
  113. isFromCache: true);
  114. }
  115. /// This method will be called while App switched to a plugin.
  116. /// In this method, the plugin will init [WalletSDK] and start
  117. /// a webView for running `polkadot-js/api`.
  118. Future<void> beforeStart(
  119. Keyring keyring, {
  120. KeyringEVM? keyringEVM,
  121. WebViewRunner? webView,
  122. String? jsCode,
  123. Function? socketDisconnectedAction,
  124. bool isEVM = false,
  125. }) async {
  126. await sdk.init(keyring,
  127. keyringEVM: keyringEVM,
  128. webView: webView,
  129. jsCode: jsCode ?? (await loadJSCode()),
  130. socketDisconnectedAction: socketDisconnectedAction,
  131. isEVM: isEVM);
  132. await (isEVM ? onWillStartEVM(keyringEVM!) : onWillStart(keyring));
  133. }
  134. /// This method will be called while App switched to a plugin.
  135. /// In this method, the plugin will:
  136. /// 1. connect to nodes.
  137. /// 2. retrieve network const & state.
  138. /// 3. subscribe balances & set balancesStore.
  139. Future<NetworkParams?> start(Keyring keyring,
  140. {List<NetworkParams>? nodes,
  141. KeyringEVM? keyringEVM,
  142. NetworkParams? nodeEVM}) async {
  143. if (nodeEVM == null) {
  144. final res = await sdk.api.connectNode(keyring, nodes ?? nodeList);
  145. if (res == null) return null;
  146. keyring.setSS58(res.ss58);
  147. await updateNetworkState();
  148. if (keyring.current.address != null) {
  149. sdk.api.account.subscribeBalance(keyring.current.address,
  150. (BalanceData data) {
  151. _updateBalances(keyring.current, data);
  152. });
  153. }
  154. onStarted(keyring);
  155. return res;
  156. }
  157. final evmRes = await sdk.api.connectEVM(nodeEVM);
  158. if (evmRes == null) return null;
  159. if (keyringEVM?.current.address != null) {
  160. final data = await sdk.api.eth.account
  161. .getNativeTokenBalance(keyringEVM?.current.address ?? '');
  162. _updateBalances(
  163. keyringEVM!.current.toKeyPairData(),
  164. BalanceData()
  165. ..accountId = keyringEVM.current.address
  166. ..freeBalance = data
  167. ..availableBalance = data
  168. ..lockedBalance = '0'
  169. ..reservedBalance = '0');
  170. }
  171. onStartedEVM(keyringEVM!);
  172. return evmRes;
  173. }
  174. /// This method will be called while App user changes account.
  175. Future<void> changeAccount(KeyPairData account) async {
  176. onAccountChanged(account);
  177. if (account.pubKey == account.address) {
  178. //eth
  179. final data = await sdk.api.eth.account
  180. .getNativeTokenBalance(account.address ?? '');
  181. _updateBalances(
  182. account,
  183. BalanceData()
  184. ..accountId = account.address
  185. ..freeBalance = data
  186. ..availableBalance = data
  187. ..lockedBalance = '0'
  188. ..reservedBalance = '0');
  189. } else {
  190. sdk.api.account.unsubscribeBalance();
  191. loadBalances(account);
  192. sdk.api.account.subscribeBalance(account.address, (BalanceData data) {
  193. _updateBalances(account, data);
  194. });
  195. }
  196. }
  197. /// This method will be called before plugin start
  198. Future<void> onWillStart(Keyring keyring) async {
  199. if (keyring.current.address != null) {
  200. loadBalances(keyring.current);
  201. }
  202. }
  203. Future<void> onWillStartEVM(KeyringEVM keyring) async {
  204. if (keyring.current.address != null) {
  205. loadBalances(keyring.current.toKeyPairData());
  206. }
  207. }
  208. /// This method will be called after plugin started
  209. Future<void> onStarted(Keyring keyring) async => null;
  210. /// This method will be called after plugin started
  211. Future<void> onStartedEVM(KeyringEVM keyringEvm) async => null;
  212. /// This method will be called while App user changes account.
  213. /// In this method, the plugin should do:
  214. /// 1. update balance subscription to update balancesStore.
  215. /// 2. update other user state of plugin if needed.
  216. Future<void> onAccountChanged(KeyPairData account) async => null;
  217. /// we don't really need this method, calling webView.launch
  218. /// more than once will cause some exception.
  219. /// We just pass a [webViewParam] instance to the sdk.init function,
  220. /// so the sdk knows how to deal with the webView.
  221. Future<void> dispose() async {
  222. // do nothing
  223. }
  224. }
  225. abstract class PolkawalletPluginBase {
  226. /// A plugin's basic info, including: name, primaryColor and icons.
  227. final basic = PluginBasicData(
  228. name: 'kusama', primaryColor: Colors.black as MaterialColor?);
  229. /// Plugin should define a list of node to connect
  230. /// for users of Polkawallet App.
  231. List<NetworkParams> get nodeList => [];
  232. /// Plugin should provide [tokenIcons]
  233. /// for display in Assets page of Polkawallet App.
  234. final Map<String, Widget> tokenIcons = {};
  235. /// The [getNavItems] method returns a list of [HomeNavItem] which defines
  236. /// the [Widget] to be used in home page of polkawallet App.
  237. List<HomeNavItem> getNavItems(BuildContext context, Keyring keyring) => [];
  238. /// App will add plugin's pages with custom [routes].
  239. Map<String, WidgetBuilder> getRoutes(Keyring keyring) =>
  240. Map<String, WidgetBuilder>();
  241. /// App will inject plugin's [jsCode] into webview to connect.
  242. Future<String>? loadJSCode() => null;
  243. }
  244. class PluginBasicData {
  245. PluginBasicData({
  246. this.name,
  247. this.genesisHash,
  248. this.ss58,
  249. this.primaryColor,
  250. this.gradientColor,
  251. this.backgroundImage,
  252. this.icon,
  253. this.iconDisabled,
  254. this.jsCodeVersion,
  255. this.isTestNet = true,
  256. this.isXCMSupport = false,
  257. this.parachainId,
  258. });
  259. final String? name;
  260. final String? genesisHash;
  261. final int? ss58;
  262. final MaterialColor? primaryColor;
  263. final Color? gradientColor;
  264. /// The image will be displayed in network-select page
  265. final AssetImage? backgroundImage;
  266. /// The icons will be displayed in network-select page
  267. /// in Polkawallet App.
  268. final Widget? icon;
  269. final Widget? iconDisabled;
  270. /// JavaScript code version of your plugin.
  271. ///
  272. /// Polkawallet App will perform hot-update for the js code
  273. /// of your plugin with it.
  274. final int? jsCodeVersion;
  275. /// Your plugin is connected to a para-chain testNet by default.
  276. final bool isTestNet;
  277. /// Whether this para-chain receives assets from relay-chain.
  278. /// should set [parachainId] if [isXCMSupport] enabled.
  279. final bool isXCMSupport;
  280. final String? parachainId;
  281. }