diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index f29702b8c9..6cdbbf8d59 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -9,6 +9,10 @@ -dontwarn com.google.android.play.core.splitcompat.SplitCompatApplication -dontwarn com.google.android.gms.common.annotation.NoNullnessRewrite +## Starknet JNI + rustls-platform-verifier +-keep, includedescriptorclasses class com.cakewallet.cake_wallet.StarknetRust { *; } +-keep, includedescriptorclasses class org.rustls.platformverifier.** { *; } + # start reown -dontwarn com.github.luben.zstd.BufferPool -dontwarn com.github.luben.zstd.ZstdInputStream diff --git a/android/app/src/main/java/com/cakewallet/cake_wallet/Application.java b/android/app/src/main/java/com/cakewallet/cake_wallet/Application.java index d3eb70ba6a..6aff98c957 100644 --- a/android/app/src/main/java/com/cakewallet/cake_wallet/Application.java +++ b/android/app/src/main/java/com/cakewallet/cake_wallet/Application.java @@ -3,4 +3,9 @@ import io.flutter.app.FlutterApplication; public class Application extends FlutterApplication { + @Override + public void onCreate() { + super.onCreate(); + StarknetRust.ensureInitialized(this); + } } diff --git a/android/app/src/main/java/com/cakewallet/cake_wallet/StarknetRust.java b/android/app/src/main/java/com/cakewallet/cake_wallet/StarknetRust.java new file mode 100644 index 0000000000..853ae513d0 --- /dev/null +++ b/android/app/src/main/java/com/cakewallet/cake_wallet/StarknetRust.java @@ -0,0 +1,21 @@ +package com.cakewallet.cake_wallet; + +import android.content.Context; + +public final class StarknetRust { + private static boolean sInitialized = false; + + static { + System.loadLibrary("cw_starknet_rust"); + } + + private StarknetRust() {} + + public static synchronized void ensureInitialized(Context context) { + if (sInitialized) return; + nativeInit(context.getApplicationContext()); + sInitialized = true; + } + + private static native void nativeInit(Context context); +} diff --git a/assets/images/crypto/starknet.webp b/assets/images/crypto/starknet.webp new file mode 100644 index 0000000000..3fad6cc632 Binary files /dev/null and b/assets/images/crypto/starknet.webp differ diff --git a/assets/new-ui/balance_card_icons/starknet.svg b/assets/new-ui/balance_card_icons/starknet.svg new file mode 100644 index 0000000000..1fa6a3e27d --- /dev/null +++ b/assets/new-ui/balance_card_icons/starknet.svg @@ -0,0 +1,5 @@ + + + diff --git a/assets/starknet_node_list.yml b/assets/starknet_node_list.yml new file mode 100644 index 0000000000..3aa8a8dfd0 --- /dev/null +++ b/assets/starknet_node_list.yml @@ -0,0 +1,17 @@ +- + uri: api.cartridge.gg/x/starknet/mainnet + useSSL: true + is_default: true + isEnabledForAutoSwitching: true +- + uri: rpc.starknet.lava.build + useSSL: true + isEnabledForAutoSwitching: true +- + uri: starknet-rpc.publicnode.com + useSSL: true + isEnabledForAutoSwitching: true +- + uri: starknet.drpc.org + useSSL: true + isEnabledForAutoSwitching: true diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index f69748b4bc..47c3f5c112 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -4,7 +4,9 @@ import 'package:collection/collection.dart'; import 'package:cw_core/format_fixed.dart'; import 'package:cw_core/parse_fixed.dart'; -class CryptoCurrency extends EnumerableItem with Serializable implements Currency { +class CryptoCurrency extends EnumerableItem + with Serializable + implements Currency { const CryptoCurrency({ String title = '', int raw = -1, @@ -128,6 +130,7 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen CryptoCurrency.arbEth, CryptoCurrency.usdcArb, CryptoCurrency.usdtArb, + CryptoCurrency.strk, ]; static const havenCurrencies = [ @@ -169,19 +172,32 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen static const xrp = CryptoCurrency(title: 'XRP', fullName: 'Ripple', raw: 15, name: 'xrp', iconPath: 'assets/images/xrp_icon.png', decimals: 6); static const xhv = CryptoCurrency(title: 'XHV', fullName: 'Haven Protocol', raw: 16, name: 'xhv', iconPath: 'assets/images/xhv_logo.png', decimals: 12); - static const xag = CryptoCurrency(title: 'XAG', tag: 'XHV', raw: 17, name: 'xag', decimals: 12); - static const xau = CryptoCurrency(title: 'XAU', tag: 'XHV', raw: 18, name: 'xau', decimals: 12); - static const xaud = CryptoCurrency(title: 'XAUD', tag: 'XHV', raw: 19, name: 'xaud', decimals: 12); - static const xbtc = CryptoCurrency(title: 'XBTC', tag: 'XHV', raw: 20, name: 'xbtc', decimals: 12); - static const xcad = CryptoCurrency(title: 'XCAD', tag: 'XHV', raw: 21, name: 'xcad', decimals: 12); - static const xchf = CryptoCurrency(title: 'XCHF', tag: 'XHV', raw: 22, name: 'xchf', decimals: 12); - static const xcny = CryptoCurrency(title: 'XCNY', tag: 'XHV', raw: 23, name: 'xcny', decimals: 12); - static const xeur = CryptoCurrency(title: 'XEUR', tag: 'XHV', raw: 24, name: 'xeur', decimals: 12); - static const xgbp = CryptoCurrency(title: 'XGBP', tag: 'XHV', raw: 25, name: 'xgbp', decimals: 12); - static const xjpy = CryptoCurrency(title: 'XJPY', tag: 'XHV', raw: 26, name: 'xjpy', decimals: 12); - static const xnok = CryptoCurrency(title: 'XNOK', tag: 'XHV', raw: 27, name: 'xnok', decimals: 12); - static const xnzd = CryptoCurrency(title: 'XNZD', tag: 'XHV', raw: 28, name: 'xnzd', decimals: 12); - static const xusd = CryptoCurrency(title: 'XUSD', tag: 'XHV', raw: 29, name: 'xusd', decimals: 12); + static const xag = CryptoCurrency( + title: 'XAG', tag: 'XHV', raw: 17, name: 'xag', decimals: 12); + static const xau = CryptoCurrency( + title: 'XAU', tag: 'XHV', raw: 18, name: 'xau', decimals: 12); + static const xaud = CryptoCurrency( + title: 'XAUD', tag: 'XHV', raw: 19, name: 'xaud', decimals: 12); + static const xbtc = CryptoCurrency( + title: 'XBTC', tag: 'XHV', raw: 20, name: 'xbtc', decimals: 12); + static const xcad = CryptoCurrency( + title: 'XCAD', tag: 'XHV', raw: 21, name: 'xcad', decimals: 12); + static const xchf = CryptoCurrency( + title: 'XCHF', tag: 'XHV', raw: 22, name: 'xchf', decimals: 12); + static const xcny = CryptoCurrency( + title: 'XCNY', tag: 'XHV', raw: 23, name: 'xcny', decimals: 12); + static const xeur = CryptoCurrency( + title: 'XEUR', tag: 'XHV', raw: 24, name: 'xeur', decimals: 12); + static const xgbp = CryptoCurrency( + title: 'XGBP', tag: 'XHV', raw: 25, name: 'xgbp', decimals: 12); + static const xjpy = CryptoCurrency( + title: 'XJPY', tag: 'XHV', raw: 26, name: 'xjpy', decimals: 12); + static const xnok = CryptoCurrency( + title: 'XNOK', tag: 'XHV', raw: 27, name: 'xnok', decimals: 12); + static const xnzd = CryptoCurrency( + title: 'XNZD', tag: 'XHV', raw: 28, name: 'xnzd', decimals: 12); + static const xusd = CryptoCurrency( + title: 'XUSD', tag: 'XHV', raw: 29, name: 'xusd', decimals: 12); static const ape = CryptoCurrency(title: 'APE', tag: 'ETH', fullName: 'ApeCoin', raw: 30, name: 'ape', iconPath: 'assets/images/ape_icon.png', decimals: 18); static const avaxc = CryptoCurrency(title: 'AVAX', tag: 'AVAXC', fullName: 'Avalanche', raw: 31, name: 'avaxc', iconPath: 'assets/images/avaxc_icon.png',iconSvgPath: "assets/new-ui/crypto_full_icons/avax.svg", decimals: 9); @@ -265,21 +281,31 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen static const usdcArb = CryptoCurrency(title: 'USDC', tag: 'ARB', fullName: 'USDC Coin', raw: 108, name: 'usdcarb', iconPath: 'assets/images/crypto/usdc.webp',iconSvgPath: "assets/new-ui/crypto_full_icons/usdc.svg", decimals: 6); static const usdtArb = CryptoCurrency(title: 'USDT', tag: 'ARB', fullName: 'USDT Tether', raw: 109, name: 'usdtarb', iconPath: 'assets/images/crypto/tether.webp',iconSvgPath: "assets/new-ui/crypto_full_icons/usdt.svg", decimals: 6); static const ltcmweb = CryptoCurrency(title: 'LTC', fullName: 'Litecoin MWeb', raw: 110, name: 'ltcmweb', iconPath: 'assets/images/crypto/litecoin.webp',iconSvgPath: "assets/new-ui/crypto_full_icons/litecoin.svg", decimals: 8); + static const strk = CryptoCurrency(title: 'STRK', fullName: 'Starknet', raw: 111, name: 'strk', iconPath: 'assets/images/crypto/starknet.webp', decimals: 18, flatIconPath: "assets/new-ui/balance_card_icons/starknet.svg"); - static final Map _rawCurrencyMap = - [...all, ...havenCurrencies, ...zcashCurrencies].fold>({}, (acc, item) { + static final Map _rawCurrencyMap = [ + ...all, + ...havenCurrencies, + ...zcashCurrencies + ].fold>({}, (acc, item) { acc.addAll({item.raw: item}); return acc; }); - static final Map _nameCurrencyMap = - [...all, ...havenCurrencies, ...zcashCurrencies].fold>({}, (acc, item) { + static final Map _nameCurrencyMap = [ + ...all, + ...havenCurrencies, + ...zcashCurrencies + ].fold>({}, (acc, item) { acc.addAll({item.name: item}); return acc; }); - static final Map _fullNameCurrencyMap = - [...all, ...havenCurrencies, ...zcashCurrencies].fold>({}, (acc, item) { + static final Map _fullNameCurrencyMap = [ + ...all, + ...havenCurrencies, + ...zcashCurrencies + ].fold>({}, (acc, item) { if(item.fullName != null){ acc.addAll({item.fullName!.toLowerCase(): item}); } @@ -321,10 +347,9 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen return _rawCurrencyMap[raw]; } - // TODO: refactor this - static CryptoCurrency fromString(String name, {CryptoCurrency? walletCurrency}) { - + static CryptoCurrency fromString(String name, + {CryptoCurrency? walletCurrency}) { final schemeMatch = _schemeCurrencyMap[name.toLowerCase()]; if (schemeMatch != null) return schemeMatch; @@ -339,7 +364,8 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen // search by fullName if not found by title: try { - return CryptoCurrency.all.firstWhere((element) => element.fullName?.toLowerCase() == name); + return CryptoCurrency.all + .firstWhere((element) => element.fullName?.toLowerCase() == name); } catch (_) {} if (CryptoCurrency._nameCurrencyMap[name.toLowerCase()] == null) { @@ -351,11 +377,14 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen } static CryptoCurrency fromFullName(String name) { - if (CryptoCurrency._fullNameCurrencyMap[name.split("(").first.trim().toLowerCase()] == null) { + if (CryptoCurrency + ._fullNameCurrencyMap[name.split("(").first.trim().toLowerCase()] == + null) { final s = 'Unexpected token: $name for CryptoCurrency fromFullName'; throw ArgumentError.value(name, 'Fullname', s); } - return CryptoCurrency._fullNameCurrencyMap[name.split("(").first.trim().toLowerCase()]!; + return CryptoCurrency + ._fullNameCurrencyMap[name.split("(").first.trim().toLowerCase()]!; } static CryptoCurrency? safeParseCurrencyFromString( @@ -377,12 +406,12 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen return null; } - // Try for native currency with same title and tag if (tag == null || tag.isEmpty) { final match = CryptoCurrency.all.firstWhereOrNull( (e) => - e.title.toUpperCase() == raw.toUpperCase() && (e.tag == raw.toUpperCase()), + e.title.toUpperCase() == raw.toUpperCase() && + (e.tag == raw.toUpperCase()), ); if (match != null) return match; @@ -393,7 +422,8 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen } catch (_) {} // try cleaned (keep only A–Z/0–9) - final cleaned = raw.trim().toUpperCase().replaceAll(RegExp(r'[^A-Z0-9]'), ''); + final cleaned = + raw.trim().toUpperCase().replaceAll(RegExp(r'[^A-Z0-9]'), ''); try { return CryptoCurrency.fromString(cleaned, walletCurrency: walletCurrency); } catch (_) {} @@ -427,11 +457,14 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen @override String toString() => title; - bool titleAndTagEqual(CryptoCurrency other) => title == other.title && tag == other.tag; + bool titleAndTagEqual(CryptoCurrency other) => + title == other.title && tag == other.tag; /// Format the raw amount into its decimal representation eg. turn Sats into Bitcoin - String formatAmount(BigInt amount, {int? fractionalDigits, bool trimZeros = true}) => - formatFixed(amount, decimals, fractionalDigits: fractionalDigits, trimZeros: trimZeros); + String formatAmount(BigInt amount, + {int? fractionalDigits, bool trimZeros = true}) => + formatFixed(amount, decimals, + fractionalDigits: fractionalDigits, trimZeros: trimZeros); /// Parse the [value] and turn it into the smallest denomination eg. turn Bitcoin into Sats BigInt parseAmount(String value) => parseFixed(value, decimals); diff --git a/cw_core/lib/currency_for_wallet_type.dart b/cw_core/lib/currency_for_wallet_type.dart index f6a8849546..9319df7ce9 100644 --- a/cw_core/lib/currency_for_wallet_type.dart +++ b/cw_core/lib/currency_for_wallet_type.dart @@ -48,6 +48,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal return CryptoCurrency.doge; case WalletType.zcash: return CryptoCurrency.zec; + case WalletType.starknet: + return CryptoCurrency.strk; case WalletType.none: throw Exception( 'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency'); diff --git a/cw_core/lib/hardware/hardware_account_data.dart b/cw_core/lib/hardware/hardware_account_data.dart index a9562f8beb..0049a0ae1e 100644 --- a/cw_core/lib/hardware/hardware_account_data.dart +++ b/cw_core/lib/hardware/hardware_account_data.dart @@ -5,6 +5,7 @@ class HardwareAccountData { required this.address, required this.accountIndex, required this.derivationPath, + this.publicKey, this.xpub, this.masterFingerprint, }); @@ -12,6 +13,7 @@ class HardwareAccountData { final String address; final int accountIndex; final String derivationPath; + final String? publicKey; // Bitcoin Specific final Uint8List? masterFingerprint; diff --git a/cw_core/lib/hive_type_ids.dart b/cw_core/lib/hive_type_ids.dart index 72139ca75b..157649833f 100644 --- a/cw_core/lib/hive_type_ids.dart +++ b/cw_core/lib/hive_type_ids.dart @@ -21,4 +21,5 @@ const HARDWARE_WALLET_TYPE_TYPE_ID = 19; const MWEB_UTXO_TYPE_ID = 20; const HAVEN_SEED_STORE_TYPE_ID = 21; const ZANO_ASSET_TYPE_ID = 22; -const PAYJOIN_SESSION_TYPE_ID = 23; \ No newline at end of file +const PAYJOIN_SESSION_TYPE_ID = 23; +const STARKNET_TOKEN_TYPE_ID = 24; diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index 6ed3964a21..4a6439459a 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -62,7 +62,8 @@ class Node extends HiveObject with Keyable { useSSL = map['useSSL'] as bool?, trusted = map['trusted'] as bool? ?? false, socksProxyAddress = map['socksProxyPort'] as String?, - isEnabledForAutoSwitching = map['isEnabledForAutoSwitching'] as bool? ?? false; + isEnabledForAutoSwitching = + map['isEnabledForAutoSwitching'] as bool? ?? false; static const typeId = NODE_TYPE_ID; static const boxName = 'Nodes'; @@ -108,7 +109,8 @@ class Node extends HiveObject with Keyable { bool get isSSL => useSSL ?? false; - bool get useSocksProxy => socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty; + bool get useSocksProxy => + socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty; Uri get uri { switch (type) { @@ -133,6 +135,7 @@ class Node extends HiveObject with Keyable { case WalletType.tron: case WalletType.zano: case WalletType.decred: + case WalletType.starknet: return Uri.parse( "http${isSSL ? "s" : ""}://$uriRaw${path!.startsWith("/") || path!.isEmpty ? path : "/$path"}"); case WalletType.none: @@ -206,6 +209,8 @@ class Node extends HiveObject with Keyable { return requestZanoNode(); case WalletType.decred: return requestDecredNode(); + case WalletType.starknet: + return requestStarknetNode(); case WalletType.none: return false; } @@ -214,9 +219,33 @@ class Node extends HiveObject with Keyable { } } + Future requestStarknetNode() async { + final body = { + 'jsonrpc': '2.0', + 'id': '0', + 'method': 'starknet_blockNumber', + 'params': [], + }; + + try { + final response = await ProxyWrapper().post( + clearnetUri: uri, + headers: {'Content-Type': 'application/json'}, + body: json.encode(body), + ); + + final resBody = json.decode(response.body) as Map; + return resBody['result'] != null; + } catch (e) { + printV("error: $e"); + return false; + } + } + Future requestZanoNode() async { final path = '/json_rpc'; - final rpcUri = isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path); + final rpcUri = + isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path); final body = {'jsonrpc': '2.0', 'id': '0', 'method': "getinfo"}; try { @@ -228,7 +257,6 @@ class Node extends HiveObject with Keyable { body: jsonBody, ); - final resBody = json.decode(response.body) as Map; return resBody['result']['height'] != null; @@ -244,7 +272,8 @@ class Node extends HiveObject with Keyable { } final path = '/json_rpc'; - final rpcUri = isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path); + final rpcUri = + isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path); final body = {'jsonrpc': '2.0', 'id': '0', 'method': methodName}; try { @@ -270,14 +299,16 @@ class Node extends HiveObject with Keyable { final responseString = await response.body; - if ((responseString.contains("400 Bad Request") // Some other generic error + if ((responseString + .contains("400 Bad Request") // Some other generic error || - responseString.contains("plain HTTP request was sent to HTTPS port") // Cloudflare + responseString.contains( + "plain HTTP request was sent to HTTPS port") // Cloudflare || response.headers["location"] != null // Generic reverse proxy || - responseString - .contains("301 Moved Permanently") // Poorly configured generic reverse proxy + responseString.contains( + "301 Moved Permanently") // Poorly configured generic reverse proxy ) && !(useSSL ?? false)) { final oldUseSSL = useSSL; @@ -310,7 +341,8 @@ class Node extends HiveObject with Keyable { String? proxy = socksProxyAddress; if ((proxy?.isEmpty ?? true) && CakeTor.instance!.enabled) { - proxy = "${InternetAddress.loopbackIPv4.address}:${CakeTor.instance!.port}"; + proxy = + "${InternetAddress.loopbackIPv4.address}:${CakeTor.instance!.port}"; } printV("proxy: $proxy"); if (proxy == null) { @@ -319,7 +351,8 @@ class Node extends HiveObject with Keyable { final proxyAddress = proxy.split(':')[0]; final proxyPort = int.parse(proxy.split(':')[1]); try { - final socket = await Socket.connect(proxyAddress, proxyPort, timeout: Duration(seconds: 5)); + final socket = await Socket.connect(proxyAddress, proxyPort, + timeout: Duration(seconds: 5)); socket.destroy(); return true; } catch (_) { @@ -333,8 +366,8 @@ class Node extends HiveObject with Keyable { Future requestElectrumServer() async { try { final ProxySocket socket; - socket = await ProxyWrapper().getSocksSocket(useSSL ?? false, uri.host, uri.port); - + socket = await ProxyWrapper() + .getSocksSocket(useSSL ?? false, uri.host, uri.port); socket.destroy(); return true; @@ -347,11 +380,15 @@ class Node extends HiveObject with Keyable { try { final response = await ProxyWrapper().post( clearnetUri: uri, - headers: {"Content-Type": "application/json", "nano-app": "cake-wallet"}, + headers: { + "Content-Type": "application/json", + "nano-app": "cake-wallet" + }, body: jsonEncode( { "action": "account_balance", - "account": "nano_38713x95zyjsqzx6nm1dsom1jmm668owkeb9913ax6nfgj15az3nu8xkx579", + "account": + "nano_38713x95zyjsqzx6nm1dsom1jmm668owkeb9913ax6nfgj15az3nu8xkx579", }, ), ); @@ -372,8 +409,11 @@ class Node extends HiveObject with Keyable { Future requestEthereumServer() async { try { - final req = await ProxyWrapper().getHttpClient() - .getUrl(uri,) + final req = await ProxyWrapper() + .getHttpClient() + .getUrl( + uri, + ) .timeout(Duration(seconds: 15)); final response = await req.close(); @@ -391,7 +431,8 @@ class Node extends HiveObject with Keyable { return true; } try { - final socket = await Socket.connect(uri.host, uri.port, timeout: Duration(seconds: 5)); + final socket = await Socket.connect(uri.host, uri.port, + timeout: Duration(seconds: 5)); socket.destroy(); return true; } catch (_) { @@ -470,7 +511,8 @@ class DigestAuth { } /// Helper to format the nonce count. - String _formatNonceCount(int count) => count.toRadixString(16).padLeft(8, '0'); + String _formatNonceCount(int count) => + count.toRadixString(16).padLeft(8, '0'); /// Compute the MD5 hash of a string. String md5Hash(String input) { @@ -486,7 +528,8 @@ class DaemonRpc { DaemonRpc(this.rpcUrl, {required this.username, required this.password}); /// Perform a JSON-RPC call with Digest Authentication. - Future> call(String method, Map params) async { + Future> call( + String method, Map params) async { final client = ProxyWrapper().getHttpIOClient(); final DigestAuth digestAuth = DigestAuth(username, password); diff --git a/cw_core/lib/payment_uris.dart b/cw_core/lib/payment_uris.dart index 6ad43a875f..ac0ae8d739 100644 --- a/cw_core/lib/payment_uris.dart +++ b/cw_core/lib/payment_uris.dart @@ -52,7 +52,8 @@ class BitcoinURI extends PaymentURI { qp['pj'] = pjUri; } - return Uri(scheme: 'bitcoin', path: address, queryParameters: qp).toString(); + return Uri(scheme: 'bitcoin', path: address, queryParameters: qp) + .toString(); } } @@ -86,6 +87,21 @@ class EthereumURI extends PaymentURI { } } +class StarknetURI extends PaymentURI { + StarknetURI({required super.amount, required super.address}); + + @override + String toString() { + var base = 'starknet:$address'; + + if (amount.isNotEmpty) { + base += '?amount=${amount.replaceAll(',', '.')}'; + } + + return base; + } +} + class BaseURI extends PaymentURI { BaseURI({required super.amount, required super.address}); @@ -361,8 +377,11 @@ class ERC681URI extends PaymentURI { final (isContract, targetAddress) = _getTargetAddress(uri.path); final chainId = _getChainID(uri.path); - final address = isContract ? uri.queryParameters["address"] ?? '' : targetAddress; - final amountParam = isContract ? uri.queryParameters["uint256"] : uri.queryParameters["value"]; + final address = + isContract ? uri.queryParameters["address"] ?? '' : targetAddress; + final amountParam = isContract + ? uri.queryParameters["uint256"] + : uri.queryParameters["value"]; var formatedAmount = ""; @@ -389,8 +408,9 @@ class ERC681URI extends PaymentURI { } static (bool, String) _getTargetAddress(String path) { - final targetAddress = - RegExp(r'^(0x)?[0-9a-f]{40}', caseSensitive: false).firstMatch(path)!.group(0)!; + final targetAddress = RegExp(r'^(0x)?[0-9a-f]{40}', caseSensitive: false) + .firstMatch(path)! + .group(0)!; return (path.contains("/"), targetAddress); } @@ -483,4 +503,3 @@ class LightningPaymentRequest extends PaymentURI { @override String toString() => bolt11Invoice ?? "lightning:$lnURL"; } - diff --git a/cw_core/lib/starknet_token.dart b/cw_core/lib/starknet_token.dart new file mode 100644 index 0000000000..3e440ef48e --- /dev/null +++ b/cw_core/lib/starknet_token.dart @@ -0,0 +1,90 @@ +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/hive_type_ids.dart'; +import 'package:hive/hive.dart'; + +part 'starknet_token.g.dart'; + +@HiveType(typeId: StarknetToken.typeId) +class StarknetToken extends CryptoCurrency with HiveObjectMixin { + @HiveField(0) + final String name; + + @HiveField(1) + final String symbol; + + @HiveField(2) + final String contractAddress; + + @HiveField(3) + final int decimal; + + @HiveField(4, defaultValue: true) + bool _enabled; + + @HiveField(5) + final String? iconPath; + + @HiveField(6) + final String? tag; + + @HiveField(7, defaultValue: false) + final bool isPotentialScam; + + bool get enabled => _enabled; + + set enabled(bool value) => _enabled = value; + + StarknetToken({ + required this.name, + required this.symbol, + required this.contractAddress, + required this.decimal, + bool enabled = true, + this.iconPath, + this.tag = 'STRK', + this.isPotentialScam = false, + }) : _enabled = enabled, + super( + name: symbol.toLowerCase(), + title: symbol.toUpperCase(), + fullName: name, + tag: tag, + iconPath: iconPath, + decimals: decimal, + isPotentialScam: isPotentialScam, + ); + + StarknetToken.copyWith( + StarknetToken other, { + String? icon, + String? tag, + bool? enabled, + }) : name = other.name, + symbol = other.symbol, + contractAddress = other.contractAddress, + decimal = other.decimal, + _enabled = enabled ?? other.enabled, + iconPath = icon ?? other.iconPath, + tag = tag ?? other.tag, + isPotentialScam = other.isPotentialScam, + super( + name: other.name, + title: other.symbol.toUpperCase(), + fullName: other.name, + tag: tag ?? other.tag, + iconPath: icon ?? other.iconPath, + decimals: other.decimal, + isPotentialScam: other.isPotentialScam, + ); + + static const typeId = STARKNET_TOKEN_TYPE_ID; + static const boxName = 'StarknetTokens'; + + @override + bool operator ==(Object other) => + (other is StarknetToken && other.contractAddress == contractAddress) || + (other is CryptoCurrency && other.title == title && other.tag == tag); + + @override + int get hashCode => contractAddress.hashCode; +} diff --git a/cw_core/lib/wallet_keys_file.dart b/cw_core/lib/wallet_keys_file.dart index ee25713e4c..8913da404c 100644 --- a/cw_core/lib/wallet_keys_file.dart +++ b/cw_core/lib/wallet_keys_file.dart @@ -97,20 +97,37 @@ class WalletKeysData { final String? mnemonic; final String? altMnemonic; final String? passphrase; + final String? publicKey; + final String? derivationPath; final String? xPub; final String? scanSecret; final String? spendPubkey; - - WalletKeysData({this.privateKey, this.mnemonic, this.altMnemonic, this.passphrase, this.xPub, this.scanSecret, this.spendPubkey}); + final String? accountClassHashHex; + + WalletKeysData({ + this.privateKey, + this.mnemonic, + this.altMnemonic, + this.passphrase, + this.publicKey, + this.derivationPath, + this.xPub, + this.scanSecret, + this.spendPubkey, + this.accountClassHashHex, + }); String toJSON() => jsonEncode({ "privateKey": privateKey, "mnemonic": mnemonic, if (altMnemonic != null) "altMnemonic": altMnemonic, if (passphrase != null) "passphrase": passphrase, + if (publicKey != null) "publicKey": publicKey, + if (derivationPath != null) "derivationPath": derivationPath, if (xPub != null) "xPub": xPub, if (scanSecret != null) "scanSecret": scanSecret, if (spendPubkey != null) "spendPubkey": spendPubkey, + if (accountClassHashHex != null) "accountClassHashHex": accountClassHashHex, }); static WalletKeysData fromJSON(Map json) => WalletKeysData( @@ -118,8 +135,11 @@ class WalletKeysData { mnemonic: json["mnemonic"] as String?, altMnemonic: json["altMnemonic"] as String?, passphrase: json["passphrase"] as String?, + publicKey: json["publicKey"] as String?, + derivationPath: json["derivationPath"] as String?, xPub: json["xPub"] as String?, scanSecret: json["scanSecret"] as String?, spendPubkey: json["spendPubkey"] as String?, + accountClassHashHex: json["accountClassHashHex"] as String?, ); } diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart index 10a472d604..24163ba411 100644 --- a/cw_core/lib/wallet_type.dart +++ b/cw_core/lib/wallet_type.dart @@ -23,6 +23,7 @@ const walletTypes = [ WalletType.arbitrum, WalletType.zcash, WalletType.bsc, + WalletType.starknet, ]; const electrumWalletTypes = [ @@ -101,6 +102,9 @@ enum WalletType { @HiveField(19) bsc, + + @HiveField(20) + starknet, } int serializeToInt(WalletType type) { @@ -143,6 +147,8 @@ int serializeToInt(WalletType type) { return 17; case WalletType.bsc: return 18; + case WalletType.starknet: + return 19; case WalletType.none: return -1; } @@ -188,6 +194,8 @@ WalletType deserializeFromInt(int raw) { return WalletType.zcash; case 18: return WalletType.bsc; + case 19: + return WalletType.starknet; default: throw Exception('Unexpected token: $raw for WalletType deserializeFromInt'); } @@ -233,6 +241,8 @@ String walletTypeToString(WalletType type) { return 'Zcash'; case WalletType.bsc: return 'BNB Smart Chain'; + case WalletType.starknet: + return 'Starknet'; case WalletType.none: return ''; } @@ -278,6 +288,8 @@ String walletTypeToDisplayName(WalletType type) { return 'Zcash (ZEC)'; case WalletType.bsc: return 'BNB Smart Chain (BNB)'; + case WalletType.starknet: + return 'Starknet (STRK)'; case WalletType.none: return ''; } @@ -325,6 +337,8 @@ WalletType? _cryptoCurrencyToWalletType(CryptoCurrency type) { return WalletType.dogecoin; case CryptoCurrency.zec: return WalletType.zcash; + case CryptoCurrency.strk: + return WalletType.starknet; default: return null; } diff --git a/cw_starknet/.gitignore b/cw_starknet/.gitignore new file mode 100644 index 0000000000..567609b123 --- /dev/null +++ b/cw_starknet/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/cw_starknet/android/.gitignore b/cw_starknet/android/.gitignore new file mode 100644 index 0000000000..161bdcdaf8 --- /dev/null +++ b/cw_starknet/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/cw_starknet/android/build.gradle b/cw_starknet/android/build.gradle new file mode 100644 index 0000000000..469f9d2ddc --- /dev/null +++ b/cw_starknet/android/build.gradle @@ -0,0 +1,77 @@ +// The Android Gradle Plugin builds the native code with the Android NDK. + +group 'com.flutter_rust_bridge.cw_starknet' +version '1.0' + +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + // The Android Gradle Plugin knows how to build native code with the NDK. + classpath 'com.android.tools.build:gradle:7.3.0' + } +} + +import groovy.json.JsonSlurper + +rootProject.allprojects { + repositories { + google() + mavenCentral() + maven { + url = findRustlsPlatformVerifierRepo() + metadataSources.artifact() + } + } +} + +apply plugin: 'com.android.library' + +android { + if (project.android.hasProperty("namespace")) { + namespace 'com.flutter_rust_bridge.cw_starknet' + } + + // Bumping the plugin compileSdkVersion requires all clients of this plugin + // to bump the version in their app. + compileSdkVersion 33 + + // Use the NDK version + // declared in /android/app/build.gradle file of the Flutter project. + // Replace it with a version number if this plugin requires a specfic NDK version. + // (e.g. ndkVersion "23.1.7779620") + ndkVersion android.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdkVersion 19 + } +} + +dependencies { + implementation "rustls:rustls-platform-verifier:latest.release" +} + +apply from: "../cargokit/gradle/plugin.gradle" +cargokit { + manifestDir = "../rust" + libname = "cw_starknet_rust" +} + +String findRustlsPlatformVerifierRepo() { + def dependencyText = providers.exec { + it.workingDir = new File("$projectDir/../rust") + it.commandLine("cargo", "metadata", "--format-version", "1", "--filter-platform", "aarch64-linux-android") + }.standardOutput.asText.get() + + def dependencyJson = new JsonSlurper().parseText(dependencyText) + def manifestPath = file(dependencyJson.packages.find { it.name == "rustls-platform-verifier-android" }.manifest_path) + return new File(manifestPath.parentFile, "maven").path +} diff --git a/cw_starknet/android/settings.gradle b/cw_starknet/android/settings.gradle new file mode 100644 index 0000000000..d25d418f95 --- /dev/null +++ b/cw_starknet/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'cw_starknet' diff --git a/cw_starknet/android/src/main/AndroidManifest.xml b/cw_starknet/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..50d1dcbc70 --- /dev/null +++ b/cw_starknet/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/cw_starknet/cargokit/.gitignore b/cw_starknet/cargokit/.gitignore new file mode 100644 index 0000000000..cf7bb868c0 --- /dev/null +++ b/cw_starknet/cargokit/.gitignore @@ -0,0 +1,4 @@ +target +.dart_tool +*.iml +!pubspec.lock diff --git a/cw_starknet/cargokit/LICENSE b/cw_starknet/cargokit/LICENSE new file mode 100644 index 0000000000..d33a5fea52 --- /dev/null +++ b/cw_starknet/cargokit/LICENSE @@ -0,0 +1,42 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +Copyright 2022 Matej Knopp + +================================================================================ + +MIT LICENSE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +================================================================================ + +APACHE LICENSE, VERSION 2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/cw_starknet/cargokit/README b/cw_starknet/cargokit/README new file mode 100644 index 0000000000..398474dbc8 --- /dev/null +++ b/cw_starknet/cargokit/README @@ -0,0 +1,11 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +Experimental repository to provide glue for seamlessly integrating cargo build +with flutter plugins and packages. + +See https://matejknopp.com/post/flutter_plugin_in_rust_with_no_prebuilt_binaries/ +for a tutorial on how to use Cargokit. + +Example plugin available at https://github.com/irondash/hello_rust_ffi_plugin. + diff --git a/cw_starknet/cargokit/build_pod.sh b/cw_starknet/cargokit/build_pod.sh new file mode 100755 index 0000000000..ed0e0d987d --- /dev/null +++ b/cw_starknet/cargokit/build_pod.sh @@ -0,0 +1,58 @@ +#!/bin/sh +set -e + +BASEDIR=$(dirname "$0") + +# Workaround for https://github.com/dart-lang/pub/issues/4010 +BASEDIR=$(cd "$BASEDIR" ; pwd -P) + +# Remove XCode SDK from path. Otherwise this breaks tool compilation when building iOS project +NEW_PATH=`echo $PATH | tr ":" "\n" | grep -v "Contents/Developer/" | tr "\n" ":"` + +export PATH=${NEW_PATH%?} # remove trailing : + +env + +# Platform name (macosx, iphoneos, iphonesimulator) +export CARGOKIT_DARWIN_PLATFORM_NAME=$PLATFORM_NAME + +# Arctive architectures (arm64, armv7, x86_64), space separated. +export CARGOKIT_DARWIN_ARCHS=$ARCHS + +# Current build configuration (Debug, Release) +export CARGOKIT_CONFIGURATION=$CONFIGURATION + +# Path to directory containing Cargo.toml. +export CARGOKIT_MANIFEST_DIR=$PODS_TARGET_SRCROOT/$1 + +# Temporary directory for build artifacts. +export CARGOKIT_TARGET_TEMP_DIR=$TARGET_TEMP_DIR + +# Output directory for final artifacts. +export CARGOKIT_OUTPUT_DIR=$PODS_CONFIGURATION_BUILD_DIR/$PRODUCT_NAME + +# Directory to store built tool artifacts. +export CARGOKIT_TOOL_TEMP_DIR=$TARGET_TEMP_DIR/build_tool + +# Directory inside root project. Not necessarily the top level directory of root project. +export CARGOKIT_ROOT_PROJECT_DIR=$SRCROOT + +FLUTTER_EXPORT_BUILD_ENVIRONMENT=( + "$PODS_ROOT/../Flutter/ephemeral/flutter_export_environment.sh" # macOS + "$PODS_ROOT/../Flutter/flutter_export_environment.sh" # iOS +) + +for path in "${FLUTTER_EXPORT_BUILD_ENVIRONMENT[@]}" +do + if [[ -f "$path" ]]; then + source "$path" + fi +done + +sh "$BASEDIR/run_build_tool.sh" build-pod "$@" + +# Make a symlink from built framework to phony file, which will be used as input to +# build script. This should force rebuild (podspec currently doesn't support alwaysOutOfDate +# attribute on custom build phase) +ln -fs "$OBJROOT/XCBuildData/build.db" "${BUILT_PRODUCTS_DIR}/cargokit_phony" +ln -fs "${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}" "${BUILT_PRODUCTS_DIR}/cargokit_phony_out" diff --git a/cw_starknet/cargokit/build_tool/README.md b/cw_starknet/cargokit/build_tool/README.md new file mode 100644 index 0000000000..a878c27964 --- /dev/null +++ b/cw_starknet/cargokit/build_tool/README.md @@ -0,0 +1,5 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +A sample command-line application with an entrypoint in `bin/`, library code +in `lib/`, and example unit test in `test/`. diff --git a/cw_starknet/cargokit/build_tool/analysis_options.yaml b/cw_starknet/cargokit/build_tool/analysis_options.yaml new file mode 100644 index 0000000000..0e16a8b092 --- /dev/null +++ b/cw_starknet/cargokit/build_tool/analysis_options.yaml @@ -0,0 +1,34 @@ +# This is copied from Cargokit (which is the official way to use it currently) +# Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +linter: + rules: + - prefer_relative_imports + - directives_ordering + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/cw_starknet/cargokit/build_tool/bin/build_tool.dart b/cw_starknet/cargokit/build_tool/bin/build_tool.dart new file mode 100644 index 0000000000..268eb524dc --- /dev/null +++ b/cw_starknet/cargokit/build_tool/bin/build_tool.dart @@ -0,0 +1,8 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'package:build_tool/build_tool.dart' as build_tool; + +void main(List arguments) { + build_tool.runMain(arguments); +} diff --git a/cw_starknet/cargokit/build_tool/lib/build_tool.dart b/cw_starknet/cargokit/build_tool/lib/build_tool.dart new file mode 100644 index 0000000000..7c1bb750a4 --- /dev/null +++ b/cw_starknet/cargokit/build_tool/lib/build_tool.dart @@ -0,0 +1,8 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'src/build_tool.dart' as build_tool; + +Future runMain(List args) async { + return build_tool.runMain(args); +} diff --git a/cw_starknet/cargokit/build_tool/lib/src/android_environment.dart b/cw_starknet/cargokit/build_tool/lib/src/android_environment.dart new file mode 100644 index 0000000000..15fc9eedac --- /dev/null +++ b/cw_starknet/cargokit/build_tool/lib/src/android_environment.dart @@ -0,0 +1,195 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; +import 'dart:isolate'; +import 'dart:math' as math; + +import 'package:collection/collection.dart'; +import 'package:path/path.dart' as path; +import 'package:version/version.dart'; + +import 'target.dart'; +import 'util.dart'; + +class AndroidEnvironment { + AndroidEnvironment({ + required this.sdkPath, + required this.ndkVersion, + required this.minSdkVersion, + required this.targetTempDir, + required this.target, + }); + + static void clangLinkerWrapper(List args) { + final clang = Platform.environment['_CARGOKIT_NDK_LINK_CLANG']; + if (clang == null) { + throw Exception( + "cargo-ndk rustc linker: didn't find _CARGOKIT_NDK_LINK_CLANG env var"); + } + final target = Platform.environment['_CARGOKIT_NDK_LINK_TARGET']; + if (target == null) { + throw Exception( + "cargo-ndk rustc linker: didn't find _CARGOKIT_NDK_LINK_TARGET env var"); + } + + runCommand(clang, [ + target, + ...args, + ]); + } + + /// Full path to Android SDK. + final String sdkPath; + + /// Full version of Android NDK. + final String ndkVersion; + + /// Minimum supported SDK version. + final int minSdkVersion; + + /// Target directory for build artifacts. + final String targetTempDir; + + /// Target being built. + final Target target; + + bool ndkIsInstalled() { + final ndkPath = path.join(sdkPath, 'ndk', ndkVersion); + final ndkPackageXml = File(path.join(ndkPath, 'package.xml')); + return ndkPackageXml.existsSync(); + } + + void installNdk({ + required String javaHome, + }) { + final sdkManagerExtension = Platform.isWindows ? '.bat' : ''; + final sdkManager = path.join( + sdkPath, + 'cmdline-tools', + 'latest', + 'bin', + 'sdkmanager$sdkManagerExtension', + ); + + log.info('Installing NDK $ndkVersion'); + runCommand(sdkManager, [ + '--install', + 'ndk;$ndkVersion', + ], environment: { + 'JAVA_HOME': javaHome, + }); + } + + Future> buildEnvironment() async { + final hostArch = Platform.isMacOS + ? "darwin-x86_64" + : (Platform.isLinux ? "linux-x86_64" : "windows-x86_64"); + + final ndkPath = path.join(sdkPath, 'ndk', ndkVersion); + final toolchainPath = path.join( + ndkPath, + 'toolchains', + 'llvm', + 'prebuilt', + hostArch, + 'bin', + ); + + final minSdkVersion = + math.max(target.androidMinSdkVersion!, this.minSdkVersion); + + final exe = Platform.isWindows ? '.exe' : ''; + + final arKey = 'AR_${target.rust}'; + final arValue = ['${target.rust}-ar', 'llvm-ar', 'llvm-ar.exe'] + .map((e) => path.join(toolchainPath, e)) + .firstWhereOrNull((element) => File(element).existsSync()); + if (arValue == null) { + throw Exception('Failed to find ar for $target in $toolchainPath'); + } + + final targetArg = '--target=${target.rust}$minSdkVersion'; + + final ccKey = 'CC_${target.rust}'; + final ccValue = path.join(toolchainPath, 'clang$exe'); + final cfFlagsKey = 'CFLAGS_${target.rust}'; + final cFlagsValue = targetArg; + + final cxxKey = 'CXX_${target.rust}'; + final cxxValue = path.join(toolchainPath, 'clang++$exe'); + final cxxFlagsKey = 'CXXFLAGS_${target.rust}'; + final cxxFlagsValue = targetArg; + + final linkerKey = + 'cargo_target_${target.rust.replaceAll('-', '_')}_linker'.toUpperCase(); + + final ranlibKey = 'RANLIB_${target.rust}'; + final ranlibValue = path.join(toolchainPath, 'llvm-ranlib$exe'); + + final ndkVersionParsed = Version.parse(ndkVersion); + final rustFlagsKey = 'CARGO_ENCODED_RUSTFLAGS'; + final rustFlagsValue = _libGccWorkaround(targetTempDir, ndkVersionParsed); + + final runRustTool = + Platform.isWindows ? 'run_build_tool.cmd' : 'run_build_tool.sh'; + + final packagePath = (await Isolate.resolvePackageUri( + Uri.parse('package:build_tool/buildtool.dart')))! + .toFilePath(); + final selfPath = path.canonicalize(path.join( + packagePath, + '..', + '..', + '..', + runRustTool, + )); + + // Make sure that run_build_tool is working properly even initially launched directly + // through dart run. + final toolTempDir = + Platform.environment['CARGOKIT_TOOL_TEMP_DIR'] ?? targetTempDir; + + return { + arKey: arValue, + ccKey: ccValue, + cfFlagsKey: cFlagsValue, + cxxKey: cxxValue, + cxxFlagsKey: cxxFlagsValue, + ranlibKey: ranlibValue, + rustFlagsKey: rustFlagsValue, + linkerKey: selfPath, + // Recognized by main() so we know when we're acting as a wrapper + '_CARGOKIT_NDK_LINK_TARGET': targetArg, + '_CARGOKIT_NDK_LINK_CLANG': ccValue, + 'CARGOKIT_TOOL_TEMP_DIR': toolTempDir, + }; + } + + // Workaround for libgcc missing in NDK23, inspired by cargo-ndk + String _libGccWorkaround(String buildDir, Version ndkVersion) { + final workaroundDir = path.join( + buildDir, + 'cargokit', + 'libgcc_workaround', + '${ndkVersion.major}', + ); + Directory(workaroundDir).createSync(recursive: true); + if (ndkVersion.major >= 23) { + File(path.join(workaroundDir, 'libgcc.a')) + .writeAsStringSync('INPUT(-lunwind)'); + } else { + // Other way around, untested, forward libgcc.a from libunwind once Rust + // gets updated for NDK23+. + File(path.join(workaroundDir, 'libunwind.a')) + .writeAsStringSync('INPUT(-lgcc)'); + } + + var rustFlags = Platform.environment['CARGO_ENCODED_RUSTFLAGS'] ?? ''; + if (rustFlags.isNotEmpty) { + rustFlags = '$rustFlags\x1f'; + } + rustFlags = '$rustFlags-L\x1f$workaroundDir'; + return rustFlags; + } +} diff --git a/cw_starknet/cargokit/build_tool/lib/src/artifacts_provider.dart b/cw_starknet/cargokit/build_tool/lib/src/artifacts_provider.dart new file mode 100644 index 0000000000..e608cece73 --- /dev/null +++ b/cw_starknet/cargokit/build_tool/lib/src/artifacts_provider.dart @@ -0,0 +1,266 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:ed25519_edwards/ed25519_edwards.dart'; +import 'package:http/http.dart'; +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as path; + +import 'builder.dart'; +import 'crate_hash.dart'; +import 'options.dart'; +import 'precompile_binaries.dart'; +import 'rustup.dart'; +import 'target.dart'; + +class Artifact { + /// File system location of the artifact. + final String path; + + /// Actual file name that the artifact should have in destination folder. + final String finalFileName; + + AritifactType get type { + if (finalFileName.endsWith('.dll') || + finalFileName.endsWith('.dll.lib') || + finalFileName.endsWith('.pdb') || + finalFileName.endsWith('.so') || + finalFileName.endsWith('.dylib')) { + return AritifactType.dylib; + } else if (finalFileName.endsWith('.lib') || finalFileName.endsWith('.a')) { + return AritifactType.staticlib; + } else { + throw Exception('Unknown artifact type for $finalFileName'); + } + } + + Artifact({ + required this.path, + required this.finalFileName, + }); +} + +final _log = Logger('artifacts_provider'); + +class ArtifactProvider { + ArtifactProvider({ + required this.environment, + required this.userOptions, + }); + + final BuildEnvironment environment; + final CargokitUserOptions userOptions; + + Future>> getArtifacts(List targets) async { + final result = await _getPrecompiledArtifacts(targets); + + final pendingTargets = List.of(targets); + pendingTargets.removeWhere((element) => result.containsKey(element)); + + if (pendingTargets.isEmpty) { + return result; + } + + final rustup = Rustup(); + for (final target in targets) { + final builder = RustBuilder(target: target, environment: environment); + builder.prepare(rustup); + _log.info('Building ${environment.crateInfo.packageName} for $target'); + final targetDir = await builder.build(); + // For local build accept both static and dynamic libraries. + final artifactNames = { + ...getArtifactNames( + target: target, + libraryName: environment.crateInfo.packageName, + aritifactType: AritifactType.dylib, + remote: false, + ), + ...getArtifactNames( + target: target, + libraryName: environment.crateInfo.packageName, + aritifactType: AritifactType.staticlib, + remote: false, + ) + }; + final artifacts = artifactNames + .map((artifactName) => Artifact( + path: path.join(targetDir, artifactName), + finalFileName: artifactName, + )) + .where((element) => File(element.path).existsSync()) + .toList(); + result[target] = artifacts; + } + return result; + } + + Future>> _getPrecompiledArtifacts( + List targets) async { + if (userOptions.usePrecompiledBinaries == false) { + _log.info('Precompiled binaries are disabled'); + return {}; + } + if (environment.crateOptions.precompiledBinaries == null) { + _log.fine('Precompiled binaries not enabled for this crate'); + return {}; + } + + final start = Stopwatch()..start(); + final crateHash = CrateHash.compute(environment.manifestDir, + tempStorage: environment.targetTempDir); + _log.fine( + 'Computed crate hash $crateHash in ${start.elapsedMilliseconds}ms'); + + final downloadedArtifactsDir = + path.join(environment.targetTempDir, 'precompiled', crateHash); + Directory(downloadedArtifactsDir).createSync(recursive: true); + + final res = >{}; + + for (final target in targets) { + final requiredArtifacts = getArtifactNames( + target: target, + libraryName: environment.crateInfo.packageName, + remote: true, + ); + final artifactsForTarget = []; + + for (final artifact in requiredArtifacts) { + final fileName = PrecompileBinaries.fileName(target, artifact); + final downloadedPath = path.join(downloadedArtifactsDir, fileName); + if (!File(downloadedPath).existsSync()) { + final signatureFileName = + PrecompileBinaries.signatureFileName(target, artifact); + await _tryDownloadArtifacts( + crateHash: crateHash, + fileName: fileName, + signatureFileName: signatureFileName, + finalPath: downloadedPath, + ); + } + if (File(downloadedPath).existsSync()) { + artifactsForTarget.add(Artifact( + path: downloadedPath, + finalFileName: artifact, + )); + } else { + break; + } + } + + // Only provide complete set of artifacts. + if (artifactsForTarget.length == requiredArtifacts.length) { + _log.fine('Found precompiled artifacts for $target'); + res[target] = artifactsForTarget; + } + } + + return res; + } + + static Future _get(Uri url, {Map? headers}) async { + int attempt = 0; + const maxAttempts = 10; + while (true) { + try { + return await get(url, headers: headers); + } on SocketException catch (e) { + // Try to detect reset by peer error and retry. + if (attempt++ < maxAttempts && + (e.osError?.errorCode == 54 || e.osError?.errorCode == 10054)) { + _log.severe( + 'Failed to download $url: $e, attempt $attempt of $maxAttempts, will retry...'); + await Future.delayed(Duration(seconds: 1)); + continue; + } else { + rethrow; + } + } + } + } + + Future _tryDownloadArtifacts({ + required String crateHash, + required String fileName, + required String signatureFileName, + required String finalPath, + }) async { + final precompiledBinaries = environment.crateOptions.precompiledBinaries!; + final prefix = precompiledBinaries.uriPrefix; + final url = Uri.parse('$prefix$crateHash/$fileName'); + final signatureUrl = Uri.parse('$prefix$crateHash/$signatureFileName'); + _log.fine('Downloading signature from $signatureUrl'); + final signature = await _get(signatureUrl); + if (signature.statusCode == 404) { + _log.warning( + 'Precompiled binaries not available for crate hash $crateHash ($fileName)'); + return; + } + if (signature.statusCode != 200) { + _log.severe( + 'Failed to download signature $signatureUrl: status ${signature.statusCode}'); + return; + } + _log.fine('Downloading binary from $url'); + final res = await _get(url); + if (res.statusCode != 200) { + _log.severe('Failed to download binary $url: status ${res.statusCode}'); + return; + } + if (verify( + precompiledBinaries.publicKey, res.bodyBytes, signature.bodyBytes)) { + File(finalPath).writeAsBytesSync(res.bodyBytes); + } else { + _log.shout('Signature verification failed! Ignoring binary.'); + } + } +} + +enum AritifactType { + staticlib, + dylib, +} + +AritifactType artifactTypeForTarget(Target target) { + if (target.darwinPlatform != null) { + return AritifactType.staticlib; + } else { + return AritifactType.dylib; + } +} + +List getArtifactNames({ + required Target target, + required String libraryName, + required bool remote, + AritifactType? aritifactType, +}) { + aritifactType ??= artifactTypeForTarget(target); + if (target.darwinArch != null) { + if (aritifactType == AritifactType.staticlib) { + return ['lib$libraryName.a']; + } else { + return ['lib$libraryName.dylib']; + } + } else if (target.rust.contains('-windows-')) { + if (aritifactType == AritifactType.staticlib) { + return ['$libraryName.lib']; + } else { + return [ + '$libraryName.dll', + '$libraryName.dll.lib', + if (!remote) '$libraryName.pdb' + ]; + } + } else if (target.rust.contains('-linux-')) { + if (aritifactType == AritifactType.staticlib) { + return ['lib$libraryName.a']; + } else { + return ['lib$libraryName.so']; + } + } else { + throw Exception("Unsupported target: ${target.rust}"); + } +} diff --git a/cw_starknet/cargokit/build_tool/lib/src/build_cmake.dart b/cw_starknet/cargokit/build_tool/lib/src/build_cmake.dart new file mode 100644 index 0000000000..6f3b2a4ec1 --- /dev/null +++ b/cw_starknet/cargokit/build_tool/lib/src/build_cmake.dart @@ -0,0 +1,40 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:path/path.dart' as path; + +import 'artifacts_provider.dart'; +import 'builder.dart'; +import 'environment.dart'; +import 'options.dart'; +import 'target.dart'; + +class BuildCMake { + final CargokitUserOptions userOptions; + + BuildCMake({required this.userOptions}); + + Future build() async { + final targetPlatform = Environment.targetPlatform; + final target = Target.forFlutterName(Environment.targetPlatform); + if (target == null) { + throw Exception("Unknown target platform: $targetPlatform"); + } + + final environment = BuildEnvironment.fromEnvironment(isAndroid: false); + final provider = + ArtifactProvider(environment: environment, userOptions: userOptions); + final artifacts = await provider.getArtifacts([target]); + + final libs = artifacts[target]!; + + for (final lib in libs) { + if (lib.type == AritifactType.dylib) { + File(lib.path) + .copySync(path.join(Environment.outputDir, lib.finalFileName)); + } + } + } +} diff --git a/cw_starknet/cargokit/build_tool/lib/src/build_gradle.dart b/cw_starknet/cargokit/build_tool/lib/src/build_gradle.dart new file mode 100644 index 0000000000..7e61fcbb7c --- /dev/null +++ b/cw_starknet/cargokit/build_tool/lib/src/build_gradle.dart @@ -0,0 +1,49 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as path; + +import 'artifacts_provider.dart'; +import 'builder.dart'; +import 'environment.dart'; +import 'options.dart'; +import 'target.dart'; + +final log = Logger('build_gradle'); + +class BuildGradle { + BuildGradle({required this.userOptions}); + + final CargokitUserOptions userOptions; + + Future build() async { + final targets = Environment.targetPlatforms.map((arch) { + final target = Target.forFlutterName(arch); + if (target == null) { + throw Exception( + "Unknown darwin target or platform: $arch, ${Environment.darwinPlatformName}"); + } + return target; + }).toList(); + + final environment = BuildEnvironment.fromEnvironment(isAndroid: true); + final provider = + ArtifactProvider(environment: environment, userOptions: userOptions); + final artifacts = await provider.getArtifacts(targets); + + for (final target in targets) { + final libs = artifacts[target]!; + final outputDir = path.join(Environment.outputDir, target.android!); + Directory(outputDir).createSync(recursive: true); + + for (final lib in libs) { + if (lib.type == AritifactType.dylib) { + File(lib.path).copySync(path.join(outputDir, lib.finalFileName)); + } + } + } + } +} diff --git a/cw_starknet/cargokit/build_tool/lib/src/build_pod.dart b/cw_starknet/cargokit/build_tool/lib/src/build_pod.dart new file mode 100644 index 0000000000..8a9c0db5de --- /dev/null +++ b/cw_starknet/cargokit/build_tool/lib/src/build_pod.dart @@ -0,0 +1,89 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:path/path.dart' as path; + +import 'artifacts_provider.dart'; +import 'builder.dart'; +import 'environment.dart'; +import 'options.dart'; +import 'target.dart'; +import 'util.dart'; + +class BuildPod { + BuildPod({required this.userOptions}); + + final CargokitUserOptions userOptions; + + Future build() async { + final targets = Environment.darwinArchs.map((arch) { + final target = Target.forDarwin( + platformName: Environment.darwinPlatformName, darwinAarch: arch); + if (target == null) { + throw Exception( + "Unknown darwin target or platform: $arch, ${Environment.darwinPlatformName}"); + } + return target; + }).toList(); + + final environment = BuildEnvironment.fromEnvironment(isAndroid: false); + final provider = + ArtifactProvider(environment: environment, userOptions: userOptions); + final artifacts = await provider.getArtifacts(targets); + + void performLipo(String targetFile, Iterable sourceFiles) { + runCommand("lipo", [ + '-create', + ...sourceFiles, + '-output', + targetFile, + ]); + } + + final outputDir = Environment.outputDir; + + Directory(outputDir).createSync(recursive: true); + + final staticLibs = artifacts.values + .expand((element) => element) + .where((element) => element.type == AritifactType.staticlib) + .toList(); + final dynamicLibs = artifacts.values + .expand((element) => element) + .where((element) => element.type == AritifactType.dylib) + .toList(); + + final libName = environment.crateInfo.packageName; + + // If there is static lib, use it and link it with pod + if (staticLibs.isNotEmpty) { + final finalTargetFile = path.join(outputDir, "lib$libName.a"); + performLipo(finalTargetFile, staticLibs.map((e) => e.path)); + } else { + // Otherwise try to replace bundle dylib with our dylib + final bundlePaths = [ + '$libName.framework/Versions/A/$libName', + '$libName.framework/$libName', + ]; + + for (final bundlePath in bundlePaths) { + final targetFile = path.join(outputDir, bundlePath); + if (File(targetFile).existsSync()) { + performLipo(targetFile, dynamicLibs.map((e) => e.path)); + + // Replace absolute id with @rpath one so that it works properly + // when moved to Frameworks. + runCommand("install_name_tool", [ + '-id', + '@rpath/$bundlePath', + targetFile, + ]); + return; + } + } + throw Exception('Unable to find bundle for dynamic library'); + } + } +} diff --git a/cw_starknet/cargokit/build_tool/lib/src/build_tool.dart b/cw_starknet/cargokit/build_tool/lib/src/build_tool.dart new file mode 100644 index 0000000000..70dfe0eb1a --- /dev/null +++ b/cw_starknet/cargokit/build_tool/lib/src/build_tool.dart @@ -0,0 +1,276 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:args/command_runner.dart'; +import 'package:ed25519_edwards/ed25519_edwards.dart'; +import 'package:github/github.dart'; +import 'package:hex/hex.dart'; +import 'package:logging/logging.dart'; + +import 'android_environment.dart'; +import 'build_cmake.dart'; +import 'build_gradle.dart'; +import 'build_pod.dart'; +import 'logging.dart'; +import 'options.dart'; +import 'precompile_binaries.dart'; +import 'target.dart'; +import 'util.dart'; +import 'verify_binaries.dart'; + +final log = Logger('build_tool'); + +abstract class BuildCommand extends Command { + Future runBuildCommand(CargokitUserOptions options); + + @override + Future run() async { + final options = CargokitUserOptions.load(); + + if (options.verboseLogging || + Platform.environment['CARGOKIT_VERBOSE'] == '1') { + enableVerboseLogging(); + } + + await runBuildCommand(options); + } +} + +class BuildPodCommand extends BuildCommand { + @override + final name = 'build-pod'; + + @override + final description = 'Build cocoa pod library'; + + @override + Future runBuildCommand(CargokitUserOptions options) async { + final build = BuildPod(userOptions: options); + await build.build(); + } +} + +class BuildGradleCommand extends BuildCommand { + @override + final name = 'build-gradle'; + + @override + final description = 'Build android library'; + + @override + Future runBuildCommand(CargokitUserOptions options) async { + final build = BuildGradle(userOptions: options); + await build.build(); + } +} + +class BuildCMakeCommand extends BuildCommand { + @override + final name = 'build-cmake'; + + @override + final description = 'Build CMake library'; + + @override + Future runBuildCommand(CargokitUserOptions options) async { + final build = BuildCMake(userOptions: options); + await build.build(); + } +} + +class GenKeyCommand extends Command { + @override + final name = 'gen-key'; + + @override + final description = 'Generate key pair for signing precompiled binaries'; + + @override + void run() { + final kp = generateKey(); + final private = HEX.encode(kp.privateKey.bytes); + final public = HEX.encode(kp.publicKey.bytes); + print("Private Key: $private"); + print("Public Key: $public"); + } +} + +class PrecompileBinariesCommand extends Command { + PrecompileBinariesCommand() { + argParser + ..addOption( + 'repository', + mandatory: true, + help: 'Github repository slug in format owner/name', + ) + ..addOption( + 'manifest-dir', + mandatory: true, + help: 'Directory containing Cargo.toml', + ) + ..addMultiOption('target', + help: 'Rust target triple of artifact to build.\n' + 'Can be specified multiple times or omitted in which case\n' + 'all targets for current platform will be built.') + ..addOption( + 'android-sdk-location', + help: 'Location of Android SDK (if available)', + ) + ..addOption( + 'android-ndk-version', + help: 'Android NDK version (if available)', + ) + ..addOption( + 'android-min-sdk-version', + help: 'Android minimum rquired version (if available)', + ) + ..addOption( + 'temp-dir', + help: 'Directory to store temporary build artifacts', + ) + ..addOption( + 'glibc-version', + help: 'GLIBC version to use for linux builds', + ) + ..addFlag( + "verbose", + abbr: "v", + defaultsTo: false, + help: "Enable verbose logging", + ); + } + + @override + final name = 'precompile-binaries'; + + @override + final description = 'Prebuild and upload binaries\n' + 'Private key must be passed through PRIVATE_KEY environment variable. ' + 'Use gen_key through generate priave key.\n' + 'Github token must be passed as GITHUB_TOKEN environment variable.\n'; + + @override + Future run() async { + final verbose = argResults!['verbose'] as bool; + if (verbose) { + enableVerboseLogging(); + } + + final privateKeyString = Platform.environment['PRIVATE_KEY']; + if (privateKeyString == null) { + throw ArgumentError('Missing PRIVATE_KEY environment variable'); + } + final githubToken = Platform.environment['GITHUB_TOKEN']; + if (githubToken == null) { + throw ArgumentError('Missing GITHUB_TOKEN environment variable'); + } + final privateKey = HEX.decode(privateKeyString); + if (privateKey.length != 64) { + throw ArgumentError('Private key must be 64 bytes long'); + } + final manifestDir = argResults!['manifest-dir'] as String; + if (!Directory(manifestDir).existsSync()) { + throw ArgumentError('Manifest directory does not exist: $manifestDir'); + } + String? androidMinSdkVersionString = + argResults!['android-min-sdk-version'] as String?; + int? androidMinSdkVersion; + if (androidMinSdkVersionString != null) { + androidMinSdkVersion = int.tryParse(androidMinSdkVersionString); + if (androidMinSdkVersion == null) { + throw ArgumentError( + 'Invalid android-min-sdk-version: $androidMinSdkVersionString'); + } + } + final targetStrigns = argResults!['target'] as List; + final targets = targetStrigns.map((target) { + final res = Target.forRustTriple(target); + if (res == null) { + throw ArgumentError('Invalid target: $target'); + } + return res; + }).toList(growable: false); + final precompileBinaries = PrecompileBinaries( + privateKey: PrivateKey(privateKey), + githubToken: githubToken, + manifestDir: manifestDir, + repositorySlug: RepositorySlug.full(argResults!['repository'] as String), + targets: targets, + androidSdkLocation: argResults!['android-sdk-location'] as String?, + androidNdkVersion: argResults!['android-ndk-version'] as String?, + androidMinSdkVersion: androidMinSdkVersion, + tempDir: argResults!['temp-dir'] as String?, + glibcVersion: argResults!['glibc-version'] as String?, + ); + + await precompileBinaries.run(); + } +} + +class VerifyBinariesCommand extends Command { + VerifyBinariesCommand() { + argParser.addOption( + 'manifest-dir', + mandatory: true, + help: 'Directory containing Cargo.toml', + ); + } + + @override + final name = "verify-binaries"; + + @override + final description = 'Verifies published binaries\n' + 'Checks whether there is a binary published for each targets\n' + 'and checks the signature.'; + + @override + Future run() async { + final manifestDir = argResults!['manifest-dir'] as String; + final verifyBinaries = VerifyBinaries( + manifestDir: manifestDir, + ); + await verifyBinaries.run(); + } +} + +Future runMain(List args) async { + try { + // Init logging before options are loaded + initLogging(); + + if (Platform.environment['_CARGOKIT_NDK_LINK_TARGET'] != null) { + return AndroidEnvironment.clangLinkerWrapper(args); + } + + final runner = CommandRunner('build_tool', 'Cargokit built_tool') + ..addCommand(BuildPodCommand()) + ..addCommand(BuildGradleCommand()) + ..addCommand(BuildCMakeCommand()) + ..addCommand(GenKeyCommand()) + ..addCommand(PrecompileBinariesCommand()) + ..addCommand(VerifyBinariesCommand()); + + await runner.run(args); + } on ArgumentError catch (e) { + stderr.writeln(e.toString()); + exit(1); + } catch (e, s) { + log.severe(kDoubleSeparator); + log.severe('Cargokit BuildTool failed with error:'); + log.severe(kSeparator); + log.severe(e); + // This tells user to install Rust, there's no need to pollute the log with + // stack trace. + if (e is! RustupNotFoundException) { + log.severe(kSeparator); + log.severe(s); + log.severe(kSeparator); + log.severe('BuildTool arguments: $args'); + } + log.severe(kDoubleSeparator); + exit(1); + } +} diff --git a/cw_starknet/cargokit/build_tool/lib/src/builder.dart b/cw_starknet/cargokit/build_tool/lib/src/builder.dart new file mode 100644 index 0000000000..cd5269f174 --- /dev/null +++ b/cw_starknet/cargokit/build_tool/lib/src/builder.dart @@ -0,0 +1,209 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'package:collection/collection.dart'; +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as path; + +import 'android_environment.dart'; +import 'cargo.dart'; +import 'environment.dart'; +import 'options.dart'; +import 'rustup.dart'; +import 'target.dart'; +import 'util.dart'; + +final _log = Logger('builder'); + +enum BuildConfiguration { + debug, + release, + profile, +} + +extension on BuildConfiguration { + bool get isDebug => this == BuildConfiguration.debug; + String get rustName => switch (this) { + BuildConfiguration.debug => 'debug', + BuildConfiguration.release => 'release', + BuildConfiguration.profile => 'release', + }; +} + +class BuildException implements Exception { + final String message; + + BuildException(this.message); + + @override + String toString() { + return 'BuildException: $message'; + } +} + +class BuildEnvironment { + final BuildConfiguration configuration; + final CargokitCrateOptions crateOptions; + final String targetTempDir; + final String manifestDir; + final CrateInfo crateInfo; + + final bool isAndroid; + final String? androidSdkPath; + final String? androidNdkVersion; + final int? androidMinSdkVersion; + final String? javaHome; + + final String? glibcVersion; + + BuildEnvironment({ + required this.configuration, + required this.crateOptions, + required this.targetTempDir, + required this.manifestDir, + required this.crateInfo, + required this.isAndroid, + this.androidSdkPath, + this.androidNdkVersion, + this.androidMinSdkVersion, + this.javaHome, + this.glibcVersion, + }); + + static BuildConfiguration parseBuildConfiguration(String value) { + // XCode configuration adds the flavor to configuration name. + final firstSegment = value.split('-').first; + final buildConfiguration = BuildConfiguration.values.firstWhereOrNull( + (e) => e.name == firstSegment, + ); + if (buildConfiguration == null) { + _log.warning('Unknown build configuraiton $value, will assume release'); + return BuildConfiguration.release; + } + return buildConfiguration; + } + + static BuildEnvironment fromEnvironment({ + required bool isAndroid, + }) { + final buildConfiguration = + parseBuildConfiguration(Environment.configuration); + final manifestDir = Environment.manifestDir; + final crateOptions = CargokitCrateOptions.load( + manifestDir: manifestDir, + ); + final crateInfo = CrateInfo.load(manifestDir); + return BuildEnvironment( + configuration: buildConfiguration, + crateOptions: crateOptions, + targetTempDir: Environment.targetTempDir, + manifestDir: manifestDir, + crateInfo: crateInfo, + isAndroid: isAndroid, + androidSdkPath: isAndroid ? Environment.sdkPath : null, + androidNdkVersion: isAndroid ? Environment.ndkVersion : null, + androidMinSdkVersion: + isAndroid ? int.parse(Environment.minSdkVersion) : null, + javaHome: isAndroid ? Environment.javaHome : null, + ); + } +} + +class RustBuilder { + final Target target; + final BuildEnvironment environment; + + RustBuilder({ + required this.target, + required this.environment, + }); + + void prepare( + Rustup rustup, + ) { + final toolchain = _toolchain; + if (rustup.installedTargets(toolchain) == null) { + rustup.installToolchain(toolchain); + } + if (toolchain == 'nightly') { + rustup.installRustSrcForNightly(); + } + if (!rustup.installedTargets(toolchain)!.contains(target.rust)) { + rustup.installTarget(target.rust, toolchain: toolchain); + } + if (environment.glibcVersion != null) { + rustup.installZigBuild(toolchain); + } + } + + CargoBuildOptions? get _buildOptions => + environment.crateOptions.cargo[environment.configuration]; + + String get _toolchain => _buildOptions?.toolchain.name ?? 'stable'; + + /// Returns the path of directory containing build artifacts. + Future build() async { + final extraArgs = _buildOptions?.flags ?? []; + final manifestPath = path.join(environment.manifestDir, 'Cargo.toml'); + runCommand( + 'rustup', + [ + 'run', + _toolchain, + 'cargo', + (target.android == null && environment.glibcVersion != null) + ? 'zigbuild' + : 'build', + ...extraArgs, + '--manifest-path', + manifestPath, + '-p', + environment.crateInfo.packageName, + if (!environment.configuration.isDebug) '--release', + '--target', + target.rust + + ((target.android == null && environment.glibcVersion != null) + ? '.${environment.glibcVersion!}' + : ""), + '--target-dir', + environment.targetTempDir, + ], + environment: await _buildEnvironment(), + ); + return path.join( + environment.targetTempDir, + target.rust, + environment.configuration.rustName, + ); + } + + Future> _buildEnvironment() async { + if (target.android == null) { + return {}; + } else { + final sdkPath = environment.androidSdkPath; + final ndkVersion = environment.androidNdkVersion; + final minSdkVersion = environment.androidMinSdkVersion; + if (sdkPath == null) { + throw BuildException('androidSdkPath is not set'); + } + if (ndkVersion == null) { + throw BuildException('androidNdkVersion is not set'); + } + if (minSdkVersion == null) { + throw BuildException('androidMinSdkVersion is not set'); + } + final env = AndroidEnvironment( + sdkPath: sdkPath, + ndkVersion: ndkVersion, + minSdkVersion: minSdkVersion, + targetTempDir: environment.targetTempDir, + target: target, + ); + if (!env.ndkIsInstalled() && environment.javaHome != null) { + env.installNdk(javaHome: environment.javaHome!); + } + return env.buildEnvironment(); + } + } +} diff --git a/cw_starknet/cargokit/build_tool/lib/src/cargo.dart b/cw_starknet/cargokit/build_tool/lib/src/cargo.dart new file mode 100644 index 0000000000..0d8958ff2e --- /dev/null +++ b/cw_starknet/cargokit/build_tool/lib/src/cargo.dart @@ -0,0 +1,48 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:toml/toml.dart'; + +class ManifestException { + ManifestException(this.message, {required this.fileName}); + + final String? fileName; + final String message; + + @override + String toString() { + if (fileName != null) { + return 'Failed to parse package manifest at $fileName: $message'; + } else { + return 'Failed to parse package manifest: $message'; + } + } +} + +class CrateInfo { + CrateInfo({required this.packageName}); + + final String packageName; + + static CrateInfo parseManifest(String manifest, {final String? fileName}) { + final toml = TomlDocument.parse(manifest); + final package = toml.toMap()['package']; + if (package == null) { + throw ManifestException('Missing package section', fileName: fileName); + } + final name = package['name']; + if (name == null) { + throw ManifestException('Missing package name', fileName: fileName); + } + return CrateInfo(packageName: name); + } + + static CrateInfo load(String manifestDir) { + final manifestFile = File(path.join(manifestDir, 'Cargo.toml')); + final manifest = manifestFile.readAsStringSync(); + return parseManifest(manifest, fileName: manifestFile.path); + } +} diff --git a/cw_starknet/cargokit/build_tool/lib/src/crate_hash.dart b/cw_starknet/cargokit/build_tool/lib/src/crate_hash.dart new file mode 100644 index 0000000000..0c4d88d16b --- /dev/null +++ b/cw_starknet/cargokit/build_tool/lib/src/crate_hash.dart @@ -0,0 +1,124 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:collection/collection.dart'; +import 'package:convert/convert.dart'; +import 'package:crypto/crypto.dart'; +import 'package:path/path.dart' as path; + +class CrateHash { + /// Computes a hash uniquely identifying crate content. This takes into account + /// content all all .rs files inside the src directory, as well as Cargo.toml, + /// Cargo.lock, build.rs and cargokit.yaml. + /// + /// If [tempStorage] is provided, computed hash is stored in a file in that directory + /// and reused on subsequent calls if the crate content hasn't changed. + static String compute(String manifestDir, {String? tempStorage}) { + return CrateHash._( + manifestDir: manifestDir, + tempStorage: tempStorage, + )._compute(); + } + + CrateHash._({ + required this.manifestDir, + required this.tempStorage, + }); + + String _compute() { + final files = getFiles(); + final tempStorage = this.tempStorage; + if (tempStorage != null) { + final quickHash = _computeQuickHash(files); + final quickHashFolder = Directory(path.join(tempStorage, 'crate_hash')); + quickHashFolder.createSync(recursive: true); + final quickHashFile = File(path.join(quickHashFolder.path, quickHash)); + if (quickHashFile.existsSync()) { + return quickHashFile.readAsStringSync(); + } + final hash = _computeHash(files); + quickHashFile.writeAsStringSync(hash); + return hash; + } else { + return _computeHash(files); + } + } + + /// Computes a quick hash based on files stat (without reading contents). This + /// is used to cache the real hash, which is slower to compute since it involves + /// reading every single file. + String _computeQuickHash(List files) { + final output = AccumulatorSink(); + final input = sha256.startChunkedConversion(output); + + final data = ByteData(8); + for (final file in files) { + input.add(utf8.encode(file.path)); + final stat = file.statSync(); + data.setUint64(0, stat.size); + input.add(data.buffer.asUint8List()); + data.setUint64(0, stat.modified.millisecondsSinceEpoch); + input.add(data.buffer.asUint8List()); + } + + input.close(); + return base64Url.encode(output.events.single.bytes); + } + + String _computeHash(List files) { + final output = AccumulatorSink(); + final input = sha256.startChunkedConversion(output); + + void addTextFile(File file) { + // text Files are hashed by lines in case we're dealing with github checkout + // that auto-converts line endings. + final splitter = LineSplitter(); + if (file.existsSync()) { + final data = file.readAsStringSync(); + final lines = splitter.convert(data); + for (final line in lines) { + input.add(utf8.encode(line)); + } + } + } + + for (final file in files) { + addTextFile(file); + } + + input.close(); + final res = output.events.single; + + // Truncate to 128bits. + final hash = res.bytes.sublist(0, 16); + return hex.encode(hash); + } + + List getFiles() { + final src = Directory(path.join(manifestDir, 'src')); + final files = src + .listSync(recursive: true, followLinks: false) + .whereType() + .toList(); + files.sortBy((element) => element.path); + void addFile(String relative) { + final file = File(path.join(manifestDir, relative)); + if (file.existsSync()) { + files.add(file); + } + } + + addFile('Cargo.toml'); + addFile('Cargo.lock'); + addFile('build.rs'); + addFile('cargokit.yaml'); + return files; + } + + final String manifestDir; + final String? tempStorage; +} diff --git a/cw_starknet/cargokit/build_tool/lib/src/environment.dart b/cw_starknet/cargokit/build_tool/lib/src/environment.dart new file mode 100644 index 0000000000..996483a180 --- /dev/null +++ b/cw_starknet/cargokit/build_tool/lib/src/environment.dart @@ -0,0 +1,68 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +extension on String { + String resolveSymlink() => File(this).resolveSymbolicLinksSync(); +} + +class Environment { + /// Current build configuration (debug or release). + static String get configuration => + _getEnv("CARGOKIT_CONFIGURATION").toLowerCase(); + + static bool get isDebug => configuration == 'debug'; + static bool get isRelease => configuration == 'release'; + + /// Temporary directory where Rust build artifacts are placed. + static String get targetTempDir => _getEnv("CARGOKIT_TARGET_TEMP_DIR"); + + /// Final output directory where the build artifacts are placed. + static String get outputDir => _getEnvPath('CARGOKIT_OUTPUT_DIR'); + + /// Path to the crate manifest (containing Cargo.toml). + static String get manifestDir => _getEnvPath('CARGOKIT_MANIFEST_DIR'); + + /// Directory inside root project. Not necessarily root folder. Symlinks are + /// not resolved on purpose. + static String get rootProjectDir => _getEnv('CARGOKIT_ROOT_PROJECT_DIR'); + + // Pod + + /// Platform name (macosx, iphoneos, iphonesimulator). + static String get darwinPlatformName => + _getEnv("CARGOKIT_DARWIN_PLATFORM_NAME"); + + /// List of architectures to build for (arm64, armv7, x86_64). + static List get darwinArchs => + _getEnv("CARGOKIT_DARWIN_ARCHS").split(' '); + + // Gradle + static String get minSdkVersion => _getEnv("CARGOKIT_MIN_SDK_VERSION"); + static String get ndkVersion => _getEnv("CARGOKIT_NDK_VERSION"); + static String get sdkPath => _getEnvPath("CARGOKIT_SDK_DIR"); + static String get javaHome => _getEnvPath("CARGOKIT_JAVA_HOME"); + static List get targetPlatforms => + _getEnv("CARGOKIT_TARGET_PLATFORMS").split(','); + + // CMAKE + static String get targetPlatform => _getEnv("CARGOKIT_TARGET_PLATFORM"); + + static String _getEnv(String key) { + final res = Platform.environment[key]; + if (res == null) { + throw Exception("Missing environment variable $key"); + } + return res; + } + + static String _getEnvPath(String key) { + final res = _getEnv(key); + if (Directory(res).existsSync()) { + return res.resolveSymlink(); + } else { + return res; + } + } +} diff --git a/cw_starknet/cargokit/build_tool/lib/src/logging.dart b/cw_starknet/cargokit/build_tool/lib/src/logging.dart new file mode 100644 index 0000000000..5edd4fd184 --- /dev/null +++ b/cw_starknet/cargokit/build_tool/lib/src/logging.dart @@ -0,0 +1,52 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:logging/logging.dart'; + +const String kSeparator = "--"; +const String kDoubleSeparator = "=="; + +bool _lastMessageWasSeparator = false; + +void _log(LogRecord rec) { + final prefix = '${rec.level.name}: '; + final out = rec.level == Level.SEVERE ? stderr : stdout; + if (rec.message == kSeparator) { + if (!_lastMessageWasSeparator) { + out.write(prefix); + out.writeln('-' * 80); + _lastMessageWasSeparator = true; + } + return; + } else if (rec.message == kDoubleSeparator) { + out.write(prefix); + out.writeln('=' * 80); + _lastMessageWasSeparator = true; + return; + } + out.write(prefix); + out.writeln(rec.message); + _lastMessageWasSeparator = false; +} + +void initLogging() { + Logger.root.level = Level.INFO; + Logger.root.onRecord.listen((LogRecord rec) { + final lines = rec.message.split('\n'); + for (final line in lines) { + if (line.isNotEmpty || lines.length == 1 || line != lines.last) { + _log(LogRecord( + rec.level, + line, + rec.loggerName, + )); + } + } + }); +} + +void enableVerboseLogging() { + Logger.root.level = Level.ALL; +} diff --git a/cw_starknet/cargokit/build_tool/lib/src/options.dart b/cw_starknet/cargokit/build_tool/lib/src/options.dart new file mode 100644 index 0000000000..22aef1d371 --- /dev/null +++ b/cw_starknet/cargokit/build_tool/lib/src/options.dart @@ -0,0 +1,309 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:ed25519_edwards/ed25519_edwards.dart'; +import 'package:hex/hex.dart'; +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as path; +import 'package:source_span/source_span.dart'; +import 'package:yaml/yaml.dart'; + +import 'builder.dart'; +import 'environment.dart'; +import 'rustup.dart'; + +final _log = Logger('options'); + +/// A class for exceptions that have source span information attached. +class SourceSpanException implements Exception { + // This is a getter so that subclasses can override it. + /// A message describing the exception. + String get message => _message; + final String _message; + + // This is a getter so that subclasses can override it. + /// The span associated with this exception. + /// + /// This may be `null` if the source location can't be determined. + SourceSpan? get span => _span; + final SourceSpan? _span; + + SourceSpanException(this._message, this._span); + + /// Returns a string representation of `this`. + /// + /// [color] may either be a [String], a [bool], or `null`. If it's a string, + /// it indicates an ANSI terminal color escape that should be used to + /// highlight the span's text. If it's `true`, it indicates that the text + /// should be highlighted using the default color. If it's `false` or `null`, + /// it indicates that the text shouldn't be highlighted. + @override + String toString({Object? color}) { + if (span == null) return message; + return 'Error on ${span!.message(message, color: color)}'; + } +} + +enum Toolchain { + stable, + beta, + nightly, +} + +class CargoBuildOptions { + final Toolchain toolchain; + final List flags; + + CargoBuildOptions({ + required this.toolchain, + required this.flags, + }); + + static Toolchain _toolchainFromNode(YamlNode node) { + if (node case YamlScalar(value: String name)) { + final toolchain = + Toolchain.values.firstWhereOrNull((element) => element.name == name); + if (toolchain != null) { + return toolchain; + } + } + throw SourceSpanException( + 'Unknown toolchain. Must be one of ${Toolchain.values.map((e) => e.name)}.', + node.span); + } + + static CargoBuildOptions parse(YamlNode node) { + if (node is! YamlMap) { + throw SourceSpanException('Cargo options must be a map', node.span); + } + Toolchain toolchain = Toolchain.stable; + List flags = []; + for (final MapEntry(:key, :value) in node.nodes.entries) { + if (key case YamlScalar(value: 'toolchain')) { + toolchain = _toolchainFromNode(value); + } else if (key case YamlScalar(value: 'extra_flags')) { + if (value case YamlList(nodes: List list)) { + if (list.every((element) { + if (element case YamlScalar(value: String _)) { + return true; + } + return false; + })) { + flags = list.map((e) => e.value as String).toList(); + continue; + } + } + throw SourceSpanException( + 'Extra flags must be a list of strings', value.span); + } else { + throw SourceSpanException( + 'Unknown cargo option type. Must be "toolchain" or "extra_flags".', + key.span); + } + } + return CargoBuildOptions(toolchain: toolchain, flags: flags); + } +} + +extension on YamlMap { + /// Map that extracts keys so that we can do map case check on them. + Map get valueMap => + nodes.map((key, value) => MapEntry(key.value, value)); +} + +class PrecompiledBinaries { + final String uriPrefix; + final PublicKey publicKey; + + PrecompiledBinaries({ + required this.uriPrefix, + required this.publicKey, + }); + + static PublicKey _publicKeyFromHex(String key, SourceSpan? span) { + final bytes = HEX.decode(key); + if (bytes.length != 32) { + throw SourceSpanException( + 'Invalid public key. Must be 32 bytes long.', span); + } + return PublicKey(bytes); + } + + static PrecompiledBinaries parse(YamlNode node) { + if (node case YamlMap(valueMap: Map map)) { + if (map + case { + 'url_prefix': YamlNode urlPrefixNode, + 'public_key': YamlNode publicKeyNode, + }) { + final urlPrefix = switch (urlPrefixNode) { + YamlScalar(value: String urlPrefix) => urlPrefix, + _ => throw SourceSpanException( + 'Invalid URL prefix value.', urlPrefixNode.span), + }; + final publicKey = switch (publicKeyNode) { + YamlScalar(value: String publicKey) => + _publicKeyFromHex(publicKey, publicKeyNode.span), + _ => throw SourceSpanException( + 'Invalid public key value.', publicKeyNode.span), + }; + return PrecompiledBinaries( + uriPrefix: urlPrefix, + publicKey: publicKey, + ); + } + } + throw SourceSpanException( + 'Invalid precompiled binaries value. ' + 'Expected Map with "url_prefix" and "public_key".', + node.span); + } +} + +/// Cargokit options specified for Rust crate. +class CargokitCrateOptions { + CargokitCrateOptions({ + this.cargo = const {}, + this.precompiledBinaries, + }); + + final Map cargo; + final PrecompiledBinaries? precompiledBinaries; + + static CargokitCrateOptions parse(YamlNode node) { + if (node is! YamlMap) { + throw SourceSpanException('Cargokit options must be a map', node.span); + } + final options = {}; + PrecompiledBinaries? precompiledBinaries; + + for (final entry in node.nodes.entries) { + if (entry + case MapEntry( + key: YamlScalar(value: 'cargo'), + value: YamlNode node, + )) { + if (node is! YamlMap) { + throw SourceSpanException('Cargo options must be a map', node.span); + } + for (final MapEntry(:YamlNode key, :value) in node.nodes.entries) { + if (key case YamlScalar(value: String name)) { + final configuration = BuildConfiguration.values + .firstWhereOrNull((element) => element.name == name); + if (configuration != null) { + options[configuration] = CargoBuildOptions.parse(value); + continue; + } + } + throw SourceSpanException( + 'Unknown build configuration. Must be one of ${BuildConfiguration.values.map((e) => e.name)}.', + key.span); + } + } else if (entry.key case YamlScalar(value: 'precompiled_binaries')) { + precompiledBinaries = PrecompiledBinaries.parse(entry.value); + } else { + throw SourceSpanException( + 'Unknown cargokit option type. Must be "cargo" or "precompiled_binaries".', + entry.key.span); + } + } + return CargokitCrateOptions( + cargo: options, + precompiledBinaries: precompiledBinaries, + ); + } + + static CargokitCrateOptions load({ + required String manifestDir, + }) { + final uri = Uri.file(path.join(manifestDir, "cargokit.yaml")); + final file = File.fromUri(uri); + if (file.existsSync()) { + final contents = loadYamlNode(file.readAsStringSync(), sourceUrl: uri); + return parse(contents); + } else { + return CargokitCrateOptions(); + } + } +} + +class CargokitUserOptions { + // When Rustup is installed always build locally unless user opts into + // using precompiled binaries. + static bool defaultUsePrecompiledBinaries() { + return Rustup.executablePath() == null; + } + + CargokitUserOptions({ + required this.usePrecompiledBinaries, + required this.verboseLogging, + }); + + CargokitUserOptions._() + : usePrecompiledBinaries = defaultUsePrecompiledBinaries(), + verboseLogging = false; + + static CargokitUserOptions parse(YamlNode node) { + if (node is! YamlMap) { + throw SourceSpanException('Cargokit options must be a map', node.span); + } + bool usePrecompiledBinaries = defaultUsePrecompiledBinaries(); + bool verboseLogging = false; + + for (final entry in node.nodes.entries) { + if (entry.key case YamlScalar(value: 'use_precompiled_binaries')) { + if (entry.value case YamlScalar(value: bool value)) { + usePrecompiledBinaries = value; + continue; + } + throw SourceSpanException( + 'Invalid value for "use_precompiled_binaries". Must be a boolean.', + entry.value.span); + } else if (entry.key case YamlScalar(value: 'verbose_logging')) { + if (entry.value case YamlScalar(value: bool value)) { + verboseLogging = value; + continue; + } + throw SourceSpanException( + 'Invalid value for "verbose_logging". Must be a boolean.', + entry.value.span); + } else { + throw SourceSpanException( + 'Unknown cargokit option type. Must be "use_precompiled_binaries" or "verbose_logging".', + entry.key.span); + } + } + return CargokitUserOptions( + usePrecompiledBinaries: usePrecompiledBinaries, + verboseLogging: verboseLogging, + ); + } + + static CargokitUserOptions load() { + String fileName = "cargokit_options.yaml"; + var userProjectDir = Directory(Environment.rootProjectDir); + + while (userProjectDir.parent.path != userProjectDir.path) { + final configFile = File(path.join(userProjectDir.path, fileName)); + if (configFile.existsSync()) { + final contents = loadYamlNode( + configFile.readAsStringSync(), + sourceUrl: configFile.uri, + ); + final res = parse(contents); + if (res.verboseLogging) { + _log.info('Found user options file at ${configFile.path}'); + } + return res; + } + userProjectDir = userProjectDir.parent; + } + return CargokitUserOptions._(); + } + + final bool usePrecompiledBinaries; + final bool verboseLogging; +} diff --git a/cw_starknet/cargokit/build_tool/lib/src/precompile_binaries.dart b/cw_starknet/cargokit/build_tool/lib/src/precompile_binaries.dart new file mode 100644 index 0000000000..019859c33a --- /dev/null +++ b/cw_starknet/cargokit/build_tool/lib/src/precompile_binaries.dart @@ -0,0 +1,205 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:ed25519_edwards/ed25519_edwards.dart'; +import 'package:github/github.dart'; +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as path; + +import 'artifacts_provider.dart'; +import 'builder.dart'; +import 'cargo.dart'; +import 'crate_hash.dart'; +import 'options.dart'; +import 'rustup.dart'; +import 'target.dart'; + +final _log = Logger('precompile_binaries'); + +class PrecompileBinaries { + PrecompileBinaries({ + required this.privateKey, + required this.githubToken, + required this.repositorySlug, + required this.manifestDir, + required this.targets, + this.androidSdkLocation, + this.androidNdkVersion, + this.androidMinSdkVersion, + this.tempDir, + this.glibcVersion, + }); + + final PrivateKey privateKey; + final String githubToken; + final RepositorySlug repositorySlug; + final String manifestDir; + final List targets; + final String? androidSdkLocation; + final String? androidNdkVersion; + final int? androidMinSdkVersion; + final String? tempDir; + final String? glibcVersion; + + static String fileName(Target target, String name) { + return '${target.rust}_$name'; + } + + static String signatureFileName(Target target, String name) { + return '${target.rust}_$name.sig'; + } + + Future run() async { + final crateInfo = CrateInfo.load(manifestDir); + + final targets = List.of(this.targets); + if (targets.isEmpty) { + targets.addAll([ + ...Target.buildableTargets(), + if (androidSdkLocation != null) ...Target.androidTargets(), + ]); + } + + _log.info('Precompiling binaries for $targets'); + + final hash = CrateHash.compute(manifestDir); + _log.info('Computed crate hash: $hash'); + + final String tagName = 'precompiled_$hash'; + + final github = GitHub(auth: Authentication.withToken(githubToken)); + final repo = github.repositories; + final release = await _getOrCreateRelease( + repo: repo, + tagName: tagName, + packageName: crateInfo.packageName, + hash: hash, + ); + + final tempDir = this.tempDir != null + ? Directory(this.tempDir!) + : Directory.systemTemp.createTempSync('precompiled_'); + + tempDir.createSync(recursive: true); + + final crateOptions = CargokitCrateOptions.load( + manifestDir: manifestDir, + ); + + final buildEnvironment = BuildEnvironment( + configuration: BuildConfiguration.release, + crateOptions: crateOptions, + targetTempDir: tempDir.path, + manifestDir: manifestDir, + crateInfo: crateInfo, + isAndroid: androidSdkLocation != null, + androidSdkPath: androidSdkLocation, + androidNdkVersion: androidNdkVersion, + androidMinSdkVersion: androidMinSdkVersion, + glibcVersion: glibcVersion, + ); + + final rustup = Rustup(); + + for (final target in targets) { + final artifactNames = getArtifactNames( + target: target, + libraryName: crateInfo.packageName, + remote: true, + ); + + if (artifactNames.every((name) { + final fileName = PrecompileBinaries.fileName(target, name); + return (release.assets ?? []).any((e) => e.name == fileName); + })) { + _log.info("All artifacts for $target already exist - skipping"); + continue; + } + + _log.info('Building for $target'); + + final builder = + RustBuilder(target: target, environment: buildEnvironment); + builder.prepare(rustup); + final res = await builder.build(); + + final assets = []; + for (final name in artifactNames) { + final file = File(path.join(res, name)); + if (!file.existsSync()) { + throw Exception('Missing artifact: ${file.path}'); + } + + final data = file.readAsBytesSync(); + final create = CreateReleaseAsset( + name: PrecompileBinaries.fileName(target, name), + contentType: "application/octet-stream", + assetData: data, + ); + final signature = sign(privateKey, data); + final signatureCreate = CreateReleaseAsset( + name: signatureFileName(target, name), + contentType: "application/octet-stream", + assetData: signature, + ); + bool verified = verify(public(privateKey), data, signature); + if (!verified) { + throw Exception('Signature verification failed'); + } + assets.add(create); + assets.add(signatureCreate); + } + _log.info('Uploading assets: ${assets.map((e) => e.name)}'); + for (final asset in assets) { + // This seems to be failing on CI so do it one by one + int retryCount = 0; + while (true) { + try { + await repo.uploadReleaseAssets(release, [asset]); + break; + } on Exception catch (e) { + if (retryCount == 10) { + rethrow; + } + ++retryCount; + _log.shout( + 'Upload failed (attempt $retryCount, will retry): ${e.toString()}'); + await Future.delayed(Duration(seconds: 2)); + } + } + } + } + + _log.info('Cleaning up'); + tempDir.deleteSync(recursive: true); + } + + Future _getOrCreateRelease({ + required RepositoriesService repo, + required String tagName, + required String packageName, + required String hash, + }) async { + Release release; + try { + _log.info('Fetching release $tagName'); + release = await repo.getReleaseByTagName(repositorySlug, tagName); + } on ReleaseNotFound { + _log.info('Release not found - creating release $tagName'); + release = await repo.createRelease( + repositorySlug, + CreateRelease.from( + tagName: tagName, + name: 'Precompiled binaries ${hash.substring(0, 8)}', + targetCommitish: null, + isDraft: false, + isPrerelease: false, + body: 'Precompiled binaries for crate $packageName, ' + 'crate hash $hash.', + )); + } + return release; + } +} diff --git a/cw_starknet/cargokit/build_tool/lib/src/rustup.dart b/cw_starknet/cargokit/build_tool/lib/src/rustup.dart new file mode 100644 index 0000000000..e46722be49 --- /dev/null +++ b/cw_starknet/cargokit/build_tool/lib/src/rustup.dart @@ -0,0 +1,149 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:path/path.dart' as path; + +import 'util.dart'; + +class _Toolchain { + _Toolchain( + this.name, + this.targets, + ); + + final String name; + final List targets; +} + +class Rustup { + List? installedTargets(String toolchain) { + final targets = _installedTargets(toolchain); + return targets != null ? List.unmodifiable(targets) : null; + } + + void installToolchain(String toolchain) { + log.info("Installing Rust toolchain: $toolchain"); + runCommand("rustup", ['toolchain', 'install', toolchain]); + _installedToolchains + .add(_Toolchain(toolchain, _getInstalledTargets(toolchain))); + } + + void installTarget( + String target, { + required String toolchain, + }) { + log.info("Installing Rust target: $target"); + runCommand("rustup", ['target', 'add', '--toolchain', toolchain, target]); + _installedTargets(toolchain)?.add(target); + } + + bool _didInstallZigBuild = false; + + void installZigBuild(String toolchain) { + if (_didInstallZigBuild) { + return; + } + + log.info("Installing Zig build"); + runCommand("rustup", [ + 'run', + toolchain, + 'cargo', + 'install', + '--locked', + 'cargo-zigbuild', + ]); + _didInstallZigBuild = true; + } + + final List<_Toolchain> _installedToolchains; + + Rustup() : _installedToolchains = _getInstalledToolchains(); + + List? _installedTargets(String toolchain) => _installedToolchains + .firstWhereOrNull( + (e) => e.name == toolchain || e.name.startsWith('$toolchain-')) + ?.targets; + + static List<_Toolchain> _getInstalledToolchains() { + String extractToolchainName(String line) { + // ignore (default) after toolchain name + final parts = line.split(' '); + return parts[0]; + } + + final res = runCommand("rustup", ['toolchain', 'list']); + + // To list all non-custom toolchains, we need to filter out lines that + // don't start with "stable", "beta", or "nightly". + Pattern nonCustom = RegExp(r"^(stable|beta|nightly)"); + final lines = res.stdout + .toString() + .split('\n') + .where((e) => e.isNotEmpty && e.startsWith(nonCustom)) + .map(extractToolchainName) + .toList(growable: true); + + return lines + .map( + (name) => _Toolchain( + name, + _getInstalledTargets(name), + ), + ) + .toList(growable: true); + } + + static List _getInstalledTargets(String toolchain) { + final res = runCommand("rustup", [ + 'target', + 'list', + '--toolchain', + toolchain, + '--installed', + ]); + final lines = res.stdout + .toString() + .split('\n') + .where((e) => e.isNotEmpty) + .toList(growable: true); + return lines; + } + + bool _didInstallRustSrcForNightly = false; + + void installRustSrcForNightly() { + if (_didInstallRustSrcForNightly) { + return; + } + // Useful for -Z build-std + runCommand( + "rustup", + ['component', 'add', 'rust-src', '--toolchain', 'nightly'], + ); + _didInstallRustSrcForNightly = true; + } + + static String? executablePath() { + final envPath = Platform.environment['PATH']; + final envPathSeparator = Platform.isWindows ? ';' : ':'; + final home = Platform.isWindows + ? Platform.environment['USERPROFILE'] + : Platform.environment['HOME']; + final paths = [ + if (home != null) path.join(home, '.cargo', 'bin'), + if (envPath != null) ...envPath.split(envPathSeparator), + ]; + for (final p in paths) { + final rustup = Platform.isWindows ? 'rustup.exe' : 'rustup'; + final rustupPath = path.join(p, rustup); + if (File(rustupPath).existsSync()) { + return rustupPath; + } + } + return null; + } +} diff --git a/cw_starknet/cargokit/build_tool/lib/src/target.dart b/cw_starknet/cargokit/build_tool/lib/src/target.dart new file mode 100644 index 0000000000..624504edd9 --- /dev/null +++ b/cw_starknet/cargokit/build_tool/lib/src/target.dart @@ -0,0 +1,147 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:collection/collection.dart'; + +import 'util.dart'; + +class Target { + Target({ + required this.rust, + this.flutter, + this.android, + this.androidMinSdkVersion, + this.darwinPlatform, + this.darwinArch, + }); + + static final all = [ + Target( + rust: 'armv7-linux-androideabi', + flutter: 'android-arm', + android: 'armeabi-v7a', + androidMinSdkVersion: 16, + ), + Target( + rust: 'aarch64-linux-android', + flutter: 'android-arm64', + android: 'arm64-v8a', + androidMinSdkVersion: 21, + ), + Target( + rust: 'i686-linux-android', + flutter: 'android-x86', + android: 'x86', + androidMinSdkVersion: 16, + ), + Target( + rust: 'x86_64-linux-android', + flutter: 'android-x64', + android: 'x86_64', + androidMinSdkVersion: 21, + ), + Target( + rust: 'x86_64-pc-windows-msvc', + flutter: 'windows-x64', + ), + Target( + rust: 'aarch64-pc-windows-msvc', + flutter: 'windows-arm64', + ), + Target( + rust: 'x86_64-unknown-linux-gnu', + flutter: 'linux-x64', + ), + Target( + rust: 'aarch64-unknown-linux-gnu', + flutter: 'linux-arm64', + ), + Target(rust: 'riscv64gc-unknown-linux-gnu', flutter: 'linux-riscv64'), + Target( + rust: 'x86_64-apple-darwin', + darwinPlatform: 'macosx', + darwinArch: 'x86_64', + ), + Target( + rust: 'aarch64-apple-darwin', + darwinPlatform: 'macosx', + darwinArch: 'arm64', + ), + Target( + rust: 'aarch64-apple-ios', + darwinPlatform: 'iphoneos', + darwinArch: 'arm64', + ), + Target( + rust: 'aarch64-apple-ios-sim', + darwinPlatform: 'iphonesimulator', + darwinArch: 'arm64', + ), + Target( + rust: 'x86_64-apple-ios', + darwinPlatform: 'iphonesimulator', + darwinArch: 'x86_64', + ), + ]; + + static Target? forFlutterName(String flutterName) { + return all.firstWhereOrNull((element) => element.flutter == flutterName); + } + + static Target? forDarwin({ + required String platformName, + required String darwinAarch, + }) { + return all.firstWhereOrNull((element) => // + element.darwinPlatform == platformName && + element.darwinArch == darwinAarch); + } + + static Target? forRustTriple(String triple) { + return all.firstWhereOrNull((element) => element.rust == triple); + } + + static List androidTargets() { + return all + .where((element) => element.android != null) + .toList(growable: false); + } + + /// Returns buildable targets on current host platform ignoring Android targets. + static List buildableTargets() { + if (Platform.isLinux) { + // Right now we don't support cross-compiling on Linux. So we just return + // the host target. + final arch = (runCommand('arch', []).stdout as String).trim(); + if (arch == 'aarch64') { + return [Target.forRustTriple('aarch64-unknown-linux-gnu')!]; + } else if (arch == 'riscv64') { + return [Target.forRustTriple('riscv64gc-unknown-linux-gnu')!]; + } else { + return [Target.forRustTriple('x86_64-unknown-linux-gnu')!]; + } + } + return all.where((target) { + if (Platform.isWindows) { + return target.rust.contains('-windows-'); + } else if (Platform.isMacOS) { + return target.darwinPlatform != null; + } + return false; + }).toList(growable: false); + } + + @override + String toString() { + return rust; + } + + final String? flutter; + final String rust; + final String? android; + final int? androidMinSdkVersion; + final String? darwinPlatform; + final String? darwinArch; +} diff --git a/cw_starknet/cargokit/build_tool/lib/src/util.dart b/cw_starknet/cargokit/build_tool/lib/src/util.dart new file mode 100644 index 0000000000..8bb6a8724f --- /dev/null +++ b/cw_starknet/cargokit/build_tool/lib/src/util.dart @@ -0,0 +1,172 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:convert'; +import 'dart:io'; + +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as path; + +import 'logging.dart'; +import 'rustup.dart'; + +final log = Logger("process"); + +class CommandFailedException implements Exception { + final String executable; + final List arguments; + final ProcessResult result; + + CommandFailedException({ + required this.executable, + required this.arguments, + required this.result, + }); + + @override + String toString() { + final stdout = result.stdout.toString().trim(); + final stderr = result.stderr.toString().trim(); + return [ + "External Command: $executable ${arguments.map((e) => '"$e"').join(' ')}", + "Returned Exit Code: ${result.exitCode}", + kSeparator, + "STDOUT:", + if (stdout.isNotEmpty) stdout, + kSeparator, + "STDERR:", + if (stderr.isNotEmpty) stderr, + ].join('\n'); + } +} + +class TestRunCommandArgs { + final String executable; + final List arguments; + final String? workingDirectory; + final Map? environment; + final bool includeParentEnvironment; + final bool runInShell; + final Encoding? stdoutEncoding; + final Encoding? stderrEncoding; + + TestRunCommandArgs({ + required this.executable, + required this.arguments, + this.workingDirectory, + this.environment, + this.includeParentEnvironment = true, + this.runInShell = false, + this.stdoutEncoding, + this.stderrEncoding, + }); +} + +class TestRunCommandResult { + TestRunCommandResult({ + this.pid = 1, + this.exitCode = 0, + this.stdout = '', + this.stderr = '', + }); + + final int pid; + final int exitCode; + final String stdout; + final String stderr; +} + +TestRunCommandResult Function(TestRunCommandArgs args)? testRunCommandOverride; + +ProcessResult runCommand( + String executable, + List arguments, { + String? workingDirectory, + Map? environment, + bool includeParentEnvironment = true, + bool runInShell = false, + Encoding? stdoutEncoding = systemEncoding, + Encoding? stderrEncoding = systemEncoding, +}) { + if (testRunCommandOverride != null) { + final result = testRunCommandOverride!(TestRunCommandArgs( + executable: executable, + arguments: arguments, + workingDirectory: workingDirectory, + environment: environment, + includeParentEnvironment: includeParentEnvironment, + runInShell: runInShell, + stdoutEncoding: stdoutEncoding, + stderrEncoding: stderrEncoding, + )); + return ProcessResult( + result.pid, + result.exitCode, + result.stdout, + result.stderr, + ); + } + log.finer('Running command $executable ${arguments.join(' ')}'); + final res = Process.runSync( + _resolveExecutable(executable), + arguments, + workingDirectory: workingDirectory, + environment: environment, + includeParentEnvironment: includeParentEnvironment, + runInShell: runInShell, + stderrEncoding: stderrEncoding, + stdoutEncoding: stdoutEncoding, + ); + if (res.exitCode != 0) { + throw CommandFailedException( + executable: executable, + arguments: arguments, + result: res, + ); + } else { + return res; + } +} + +class RustupNotFoundException implements Exception { + @override + String toString() { + return [ + ' ', + 'rustup not found in PATH.', + ' ', + 'Maybe you need to install Rust? It only takes a minute:', + ' ', + if (Platform.isWindows) 'https://www.rust-lang.org/tools/install', + if (hasHomebrewRustInPath()) ...[ + '\$ brew unlink rust # Unlink homebrew Rust from PATH', + ], + if (!Platform.isWindows) + "\$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh", + ' ', + ].join('\n'); + } + + static bool hasHomebrewRustInPath() { + if (!Platform.isMacOS) { + return false; + } + final envPath = Platform.environment['PATH'] ?? ''; + final paths = envPath.split(':'); + return paths.any((p) { + return p.contains('homebrew') && File(path.join(p, 'rustc')).existsSync(); + }); + } +} + +String _resolveExecutable(String executable) { + if (executable == 'rustup') { + final resolved = Rustup.executablePath(); + if (resolved != null) { + return resolved; + } + throw RustupNotFoundException(); + } else { + return executable; + } +} diff --git a/cw_starknet/cargokit/build_tool/lib/src/verify_binaries.dart b/cw_starknet/cargokit/build_tool/lib/src/verify_binaries.dart new file mode 100644 index 0000000000..2366b57bfd --- /dev/null +++ b/cw_starknet/cargokit/build_tool/lib/src/verify_binaries.dart @@ -0,0 +1,84 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:ed25519_edwards/ed25519_edwards.dart'; +import 'package:http/http.dart'; + +import 'artifacts_provider.dart'; +import 'cargo.dart'; +import 'crate_hash.dart'; +import 'options.dart'; +import 'precompile_binaries.dart'; +import 'target.dart'; + +class VerifyBinaries { + VerifyBinaries({ + required this.manifestDir, + }); + + final String manifestDir; + + Future run() async { + final crateInfo = CrateInfo.load(manifestDir); + + final config = CargokitCrateOptions.load(manifestDir: manifestDir); + final precompiledBinaries = config.precompiledBinaries; + if (precompiledBinaries == null) { + stdout.writeln('Crate does not support precompiled binaries.'); + } else { + final crateHash = CrateHash.compute(manifestDir); + stdout.writeln('Crate hash: $crateHash'); + + for (final target in Target.all) { + final message = 'Checking ${target.rust}...'; + stdout.write(message.padRight(40)); + stdout.flush(); + + final artifacts = getArtifactNames( + target: target, + libraryName: crateInfo.packageName, + remote: true, + ); + + final prefix = precompiledBinaries.uriPrefix; + + bool ok = true; + + for (final artifact in artifacts) { + final fileName = PrecompileBinaries.fileName(target, artifact); + final signatureFileName = + PrecompileBinaries.signatureFileName(target, artifact); + + final url = Uri.parse('$prefix$crateHash/$fileName'); + final signatureUrl = + Uri.parse('$prefix$crateHash/$signatureFileName'); + + final signature = await get(signatureUrl); + if (signature.statusCode != 200) { + stdout.writeln('MISSING'); + ok = false; + break; + } + final asset = await get(url); + if (asset.statusCode != 200) { + stdout.writeln('MISSING'); + ok = false; + break; + } + + if (!verify(precompiledBinaries.publicKey, asset.bodyBytes, + signature.bodyBytes)) { + stdout.writeln('INVALID SIGNATURE'); + ok = false; + } + } + + if (ok) { + stdout.writeln('OK'); + } + } + } + } +} diff --git a/cw_starknet/cargokit/build_tool/pubspec.lock b/cw_starknet/cargokit/build_tool/pubspec.lock new file mode 100644 index 0000000000..343bdd3694 --- /dev/null +++ b/cw_starknet/cargokit/build_tool/pubspec.lock @@ -0,0 +1,453 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 + url: "https://pub.dev" + source: hosted + version: "64.0.0" + adaptive_number: + dependency: transitive + description: + name: adaptive_number + sha256: "3a567544e9b5c9c803006f51140ad544aedc79604fd4f3f2c1380003f97c1d77" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" + url: "https://pub.dev" + source: hosted + version: "6.2.0" + args: + dependency: "direct main" + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + collection: + dependency: "direct main" + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + convert: + dependency: "direct main" + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + coverage: + dependency: transitive + description: + name: coverage + sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" + url: "https://pub.dev" + source: hosted + version: "1.6.3" + crypto: + dependency: "direct main" + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + ed25519_edwards: + dependency: "direct main" + description: + name: ed25519_edwards + sha256: "6ce0112d131327ec6d42beede1e5dfd526069b18ad45dcf654f15074ad9276cd" + url: "https://pub.dev" + source: hosted + version: "0.3.1" + file: + dependency: transitive + description: + name: file + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" + source: hosted + version: "6.1.4" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + github: + dependency: "direct main" + description: + name: github + sha256: "9966bc13bf612342e916b0a343e95e5f046c88f602a14476440e9b75d2295411" + url: "https://pub.dev" + source: hosted + version: "9.17.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + hex: + dependency: "direct main" + description: + name: hex + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + http: + dependency: "direct main" + description: + name: http + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" + lints: + dependency: "direct dev" + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + logging: + dependency: "direct main" + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + meta: + dependency: transitive + description: + name: meta + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + mime: + dependency: transitive + description: + name: mime + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" + source: hosted + version: "1.0.4" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: "direct main" + description: + name: path + sha256: "2ad4cddff7f5cc0e2d13069f2a3f7a73ca18f66abd6f5ecf215219cdb3638edb" + url: "https://pub.dev" + source: hosted + version: "1.8.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + url: "https://pub.dev" + source: hosted + version: "5.4.0" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e + url: "https://pub.dev" + source: hosted + version: "1.1.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" + source: hosted + version: "0.10.12" + source_span: + dependency: "direct main" + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test: + dependency: "direct dev" + description: + name: test + sha256: "9b0dd8e36af4a5b1569029949d50a52cb2a2a2fdaa20cebb96e6603b9ae241f9" + url: "https://pub.dev" + source: hosted + version: "1.24.6" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" + source: hosted + version: "0.6.1" + test_core: + dependency: transitive + description: + name: test_core + sha256: "4bef837e56375537055fdbbbf6dd458b1859881f4c7e6da936158f77d61ab265" + url: "https://pub.dev" + source: hosted + version: "0.5.6" + toml: + dependency: "direct main" + description: + name: toml + sha256: "157c5dca5160fced243f3ce984117f729c788bb5e475504f3dbcda881accee44" + url: "https://pub.dev" + source: hosted + version: "0.14.0" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + version: + dependency: "direct main" + description: + name: version + sha256: "2307e23a45b43f96469eeab946208ed63293e8afca9c28cd8b5241ff31c55f55" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0fae432c85c4ea880b33b497d32824b97795b04cdaa74d270219572a1f50268d" + url: "https://pub.dev" + source: hosted + version: "11.9.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.dev" + source: hosted + version: "2.4.0" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + yaml: + dependency: "direct main" + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.0.0 <4.0.0" diff --git a/cw_starknet/cargokit/build_tool/pubspec.yaml b/cw_starknet/cargokit/build_tool/pubspec.yaml new file mode 100644 index 0000000000..18c61e3386 --- /dev/null +++ b/cw_starknet/cargokit/build_tool/pubspec.yaml @@ -0,0 +1,33 @@ +# This is copied from Cargokit (which is the official way to use it currently) +# Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +name: build_tool +description: Cargokit build_tool. Facilitates the build of Rust crate during Flutter application build. +publish_to: none +version: 1.0.0 + +environment: + sdk: ">=3.0.0 <4.0.0" + +# Add regular dependencies here. +dependencies: + # these are pinned on purpose because the bundle_tool_runner doesn't have + # pubspec.lock. See run_build_tool.sh + logging: 1.2.0 + path: 1.8.0 + version: 3.0.0 + collection: 1.18.0 + ed25519_edwards: 0.3.1 + hex: 0.2.0 + yaml: 3.1.2 + source_span: 1.10.0 + github: 9.17.0 + args: 2.4.2 + crypto: 3.0.3 + convert: 3.1.1 + http: 1.1.0 + toml: 0.14.0 + +dev_dependencies: + lints: ^2.1.0 + test: ^1.24.0 diff --git a/cw_starknet/cargokit/cmake/cargokit.cmake b/cw_starknet/cargokit/cmake/cargokit.cmake new file mode 100644 index 0000000000..ddd05df9b4 --- /dev/null +++ b/cw_starknet/cargokit/cmake/cargokit.cmake @@ -0,0 +1,99 @@ +SET(cargokit_cmake_root "${CMAKE_CURRENT_LIST_DIR}/..") + +# Workaround for https://github.com/dart-lang/pub/issues/4010 +get_filename_component(cargokit_cmake_root "${cargokit_cmake_root}" REALPATH) + +if(WIN32) + # REALPATH does not properly resolve symlinks on windows :-/ + execute_process(COMMAND powershell -ExecutionPolicy Bypass -File "${CMAKE_CURRENT_LIST_DIR}/resolve_symlinks.ps1" "${cargokit_cmake_root}" OUTPUT_VARIABLE cargokit_cmake_root OUTPUT_STRIP_TRAILING_WHITESPACE) +endif() + +# Arguments +# - target: CMAKE target to which rust library is linked +# - manifest_dir: relative path from current folder to directory containing cargo manifest +# - lib_name: cargo package name +# - any_symbol_name: name of any exported symbol from the library. +# used on windows to force linking with library. +function(apply_cargokit target manifest_dir lib_name any_symbol_name) + + set(CARGOKIT_LIB_NAME "${lib_name}") + set(CARGOKIT_LIB_FULL_NAME "${CMAKE_SHARED_MODULE_PREFIX}${CARGOKIT_LIB_NAME}${CMAKE_SHARED_MODULE_SUFFIX}") + if (CMAKE_CONFIGURATION_TYPES) + set(CARGOKIT_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/$") + set(OUTPUT_LIB "${CMAKE_CURRENT_BINARY_DIR}/$/${CARGOKIT_LIB_FULL_NAME}") + else() + set(CARGOKIT_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}") + set(OUTPUT_LIB "${CMAKE_CURRENT_BINARY_DIR}/${CARGOKIT_LIB_FULL_NAME}") + endif() + set(CARGOKIT_TEMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/cargokit_build") + + if (FLUTTER_TARGET_PLATFORM) + set(CARGOKIT_TARGET_PLATFORM "${FLUTTER_TARGET_PLATFORM}") + else() + set(CARGOKIT_TARGET_PLATFORM "windows-x64") + endif() + + set(CARGOKIT_ENV + "CARGOKIT_CMAKE=${CMAKE_COMMAND}" + "CARGOKIT_CONFIGURATION=$" + "CARGOKIT_MANIFEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/${manifest_dir}" + "CARGOKIT_TARGET_TEMP_DIR=${CARGOKIT_TEMP_DIR}" + "CARGOKIT_OUTPUT_DIR=${CARGOKIT_OUTPUT_DIR}" + "CARGOKIT_TARGET_PLATFORM=${CARGOKIT_TARGET_PLATFORM}" + "CARGOKIT_TOOL_TEMP_DIR=${CARGOKIT_TEMP_DIR}/tool" + "CARGOKIT_ROOT_PROJECT_DIR=${CMAKE_SOURCE_DIR}" + ) + + if (WIN32) + set(SCRIPT_EXTENSION ".cmd") + set(IMPORT_LIB_EXTENSION ".lib") + else() + set(SCRIPT_EXTENSION ".sh") + set(IMPORT_LIB_EXTENSION "") + execute_process(COMMAND chmod +x "${cargokit_cmake_root}/run_build_tool${SCRIPT_EXTENSION}") + endif() + + # Using generators in custom command is only supported in CMake 3.20+ + if (CMAKE_CONFIGURATION_TYPES AND ${CMAKE_VERSION} VERSION_LESS "3.20.0") + foreach(CONFIG IN LISTS CMAKE_CONFIGURATION_TYPES) + add_custom_command( + OUTPUT + "${CMAKE_CURRENT_BINARY_DIR}/${CONFIG}/${CARGOKIT_LIB_FULL_NAME}" + "${CMAKE_CURRENT_BINARY_DIR}/_phony_" + COMMAND ${CMAKE_COMMAND} -E env ${CARGOKIT_ENV} + "${cargokit_cmake_root}/run_build_tool${SCRIPT_EXTENSION}" build-cmake + VERBATIM + ) + endforeach() + else() + add_custom_command( + OUTPUT + ${OUTPUT_LIB} + "${CMAKE_CURRENT_BINARY_DIR}/_phony_" + COMMAND ${CMAKE_COMMAND} -E env ${CARGOKIT_ENV} + "${cargokit_cmake_root}/run_build_tool${SCRIPT_EXTENSION}" build-cmake + VERBATIM + ) + endif() + + + set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/_phony_" PROPERTIES SYMBOLIC TRUE) + + if (TARGET ${target}) + # If we have actual cmake target provided create target and make existing + # target depend on it + add_custom_target("${target}_cargokit" DEPENDS ${OUTPUT_LIB}) + add_dependencies("${target}" "${target}_cargokit") + target_link_libraries("${target}" PRIVATE "${OUTPUT_LIB}${IMPORT_LIB_EXTENSION}") + if(WIN32) + target_link_options(${target} PRIVATE "/INCLUDE:${any_symbol_name}") + endif() + else() + # Otherwise (FFI) just use ALL to force building always + add_custom_target("${target}_cargokit" ALL DEPENDS ${OUTPUT_LIB}) + endif() + + # Allow adding the output library to plugin bundled libraries + set("${target}_cargokit_lib" ${OUTPUT_LIB} PARENT_SCOPE) + +endfunction() diff --git a/cw_starknet/cargokit/cmake/resolve_symlinks.ps1 b/cw_starknet/cargokit/cmake/resolve_symlinks.ps1 new file mode 100644 index 0000000000..2ac593a1ff --- /dev/null +++ b/cw_starknet/cargokit/cmake/resolve_symlinks.ps1 @@ -0,0 +1,34 @@ +function Resolve-Symlinks { + [CmdletBinding()] + [OutputType([string])] + param( + [Parameter(Position = 0, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] + [string] $Path + ) + + [string] $separator = '/' + [string[]] $parts = $Path.Split($separator) + + [string] $realPath = '' + foreach ($part in $parts) { + if ($realPath -and !$realPath.EndsWith($separator)) { + $realPath += $separator + } + + $realPath += $part.Replace('\', '/') + + # The slash is important when using Get-Item on Drive letters in pwsh. + if (-not($realPath.Contains($separator)) -and $realPath.EndsWith(':')) { + $realPath += '/' + } + + $item = Get-Item $realPath + if ($item.LinkTarget) { + $realPath = $item.LinkTarget.Replace('\', '/') + } + } + $realPath +} + +$path = Resolve-Symlinks -Path $args[0] +Write-Host $path diff --git a/cw_starknet/cargokit/gradle/plugin.gradle b/cw_starknet/cargokit/gradle/plugin.gradle new file mode 100644 index 0000000000..68ff64991f --- /dev/null +++ b/cw_starknet/cargokit/gradle/plugin.gradle @@ -0,0 +1,184 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import java.nio.file.Paths +import org.apache.tools.ant.taskdefs.condition.Os + +CargoKitPlugin.file = buildscript.sourceFile + +apply plugin: CargoKitPlugin + +class CargoKitExtension { + String manifestDir; // Relative path to folder containing Cargo.toml + String libname; // Library name within Cargo.toml. Must be a cdylib +} + +abstract class CargoKitBuildTask extends DefaultTask { + + @Input + String buildMode + + @Input + String buildDir + + @Input + String outputDir + + @Input + String ndkVersion + + @Input + String sdkDirectory + + @Input + int compileSdkVersion; + + @Input + int minSdkVersion; + + @Input + String pluginFile + + @Input + List targetPlatforms + + @TaskAction + def build() { + if (project.cargokit.manifestDir == null) { + throw new GradleException("Property 'manifestDir' must be set on cargokit extension"); + } + + if (project.cargokit.libname == null) { + throw new GradleException("Property 'libname' must be set on cargokit extension"); + } + + def executableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "run_build_tool.cmd" : "run_build_tool.sh" + def path = Paths.get(new File(pluginFile).parent, "..", executableName); + + def manifestDir = Paths.get(project.buildscript.sourceFile.parent, project.cargokit.manifestDir) + + def rootProjectDir = project.rootProject.projectDir + + if (!Os.isFamily(Os.FAMILY_WINDOWS)) { + project.exec { + commandLine 'chmod', '+x', path + } + } + + project.exec { + executable path + args "build-gradle" + environment "CARGOKIT_ROOT_PROJECT_DIR", rootProjectDir + environment "CARGOKIT_TOOL_TEMP_DIR", "${buildDir}/build_tool" + environment "CARGOKIT_MANIFEST_DIR", manifestDir + environment "CARGOKIT_CONFIGURATION", buildMode + environment "CARGOKIT_TARGET_TEMP_DIR", buildDir + environment "CARGOKIT_OUTPUT_DIR", outputDir + environment "CARGOKIT_NDK_VERSION", ndkVersion + environment "CARGOKIT_SDK_DIR", sdkDirectory + environment "CARGOKIT_COMPILE_SDK_VERSION", compileSdkVersion + environment "CARGOKIT_MIN_SDK_VERSION", minSdkVersion + environment "CARGOKIT_TARGET_PLATFORMS", targetPlatforms.join(",") + environment "CARGOKIT_JAVA_HOME", System.properties['java.home'] + } + } +} + +class CargoKitPlugin implements Plugin { + + static String file; + + private Plugin findFlutterPlugin(Project rootProject) { + _findFlutterPlugin(rootProject.childProjects) + } + + private Plugin _findFlutterPlugin(Map projects) { + for (project in projects) { + for (plugin in project.value.getPlugins()) { + if (plugin.class.name == "com.flutter.gradle.FlutterPlugin" || plugin.class.name == "FlutterPlugin") { + return plugin; + } + } + def plugin = _findFlutterPlugin(project.value.childProjects); + if (plugin != null) { + return plugin; + } + } + return null; + } + + @Override + void apply(Project project) { + def plugin = findFlutterPlugin(project.rootProject); + + project.extensions.create("cargokit", CargoKitExtension) + + if (plugin == null) { + print("Flutter plugin not found, CargoKit plugin will not be applied.") + return; + } + + def cargoBuildDir = "${project.buildDir}/build" + + // Determine if the project is an application or library + def isApplication = plugin.project.plugins.hasPlugin('com.android.application') + def variants = isApplication ? plugin.project.android.applicationVariants : plugin.project.android.libraryVariants + + variants.all { variant -> + + final buildType = variant.buildType.name + + def cargoOutputDir = "${project.buildDir}/jniLibs/${buildType}"; + def jniLibs = project.android.sourceSets.maybeCreate(buildType).jniLibs; + jniLibs.srcDir(new File(cargoOutputDir)) + + def List platforms + try { + platforms = com.flutter.gradle.FlutterPluginUtils.getTargetPlatforms(project).collect() + } catch (Exception ignored) { + platforms = plugin.getTargetPlatforms().collect() + } + + // Same thing addFlutterDependencies does in flutter.gradle + if (buildType == "debug") { + platforms.add("android-x86") + platforms.add("android-x64") + } + + // The task name depends on plugin properties, which are not available + // at this point + project.getGradle().afterProject { + def taskName = "cargokitCargoBuild${project.cargokit.libname.capitalize()}${buildType.capitalize()}"; + + if (project.tasks.findByName(taskName)) { + return + } + + if (plugin.project.android.ndkVersion == null) { + throw new GradleException("Please set 'android.ndkVersion' in 'app/build.gradle'.") + } + + def task = project.tasks.create(taskName, CargoKitBuildTask.class) { + buildMode = variant.buildType.name + buildDir = cargoBuildDir + outputDir = cargoOutputDir + ndkVersion = plugin.project.android.ndkVersion + sdkDirectory = plugin.project.android.sdkDirectory + minSdkVersion = plugin.project.android.defaultConfig.minSdkVersion.apiLevel as int + compileSdkVersion = plugin.project.android.compileSdkVersion.substring(8) as int + targetPlatforms = platforms + pluginFile = CargoKitPlugin.file + } + def onTask = { newTask -> + if (newTask.name == "merge${buildType.capitalize()}NativeLibs") { + newTask.dependsOn task + // Fix gradle 7.4.2 not picking up JNI library changes + newTask.outputs.upToDateWhen { false } + } + } + project.tasks.each onTask + project.tasks.whenTaskAdded onTask + } + } + } +} diff --git a/cw_starknet/cargokit/run_build_tool.cmd b/cw_starknet/cargokit/run_build_tool.cmd new file mode 100755 index 0000000000..c45d0aa8b5 --- /dev/null +++ b/cw_starknet/cargokit/run_build_tool.cmd @@ -0,0 +1,91 @@ +@echo off +setlocal + +setlocal ENABLEDELAYEDEXPANSION + +SET BASEDIR=%~dp0 + +if not exist "%CARGOKIT_TOOL_TEMP_DIR%" ( + mkdir "%CARGOKIT_TOOL_TEMP_DIR%" +) +cd /D "%CARGOKIT_TOOL_TEMP_DIR%" + +SET BUILD_TOOL_PKG_DIR=%BASEDIR%build_tool +SET DART=%FLUTTER_ROOT%\bin\cache\dart-sdk\bin\dart + +set BUILD_TOOL_PKG_DIR_POSIX=%BUILD_TOOL_PKG_DIR:\=/% + +( + echo name: build_tool_runner + echo version: 1.0.0 + echo publish_to: none + echo. + echo environment: + echo sdk: '^>=3.0.0 ^<4.0.0' + echo. + echo dependencies: + echo build_tool: + echo path: %BUILD_TOOL_PKG_DIR_POSIX% +) >pubspec.yaml + +if not exist bin ( + mkdir bin +) + +( + echo import 'package:build_tool/build_tool.dart' as build_tool; + echo void main^(List^ args^) ^{ + echo build_tool.runMain^(args^); + echo ^} +) >bin\build_tool_runner.dart + +SET PRECOMPILED=bin\build_tool_runner.dill + +REM To detect changes in package we compare output of DIR /s (recursive) +set PREV_PACKAGE_INFO=.dart_tool\package_info.prev +set CUR_PACKAGE_INFO=.dart_tool\package_info.cur + +DIR "%BUILD_TOOL_PKG_DIR%" /s > "%CUR_PACKAGE_INFO%_orig" + +REM Last line in dir output is free space on harddrive. That is bound to +REM change between invocation so we need to remove it +( + Set "Line=" + For /F "UseBackQ Delims=" %%A In ("%CUR_PACKAGE_INFO%_orig") Do ( + SetLocal EnableDelayedExpansion + If Defined Line Echo !Line! + EndLocal + Set "Line=%%A") +) >"%CUR_PACKAGE_INFO%" +DEL "%CUR_PACKAGE_INFO%_orig" + +REM Compare current directory listing with previous +FC /B "%CUR_PACKAGE_INFO%" "%PREV_PACKAGE_INFO%" > nul 2>&1 + +If %ERRORLEVEL% neq 0 ( + REM Changed - copy current to previous and remove precompiled kernel + if exist "%PREV_PACKAGE_INFO%" ( + DEL "%PREV_PACKAGE_INFO%" + ) + MOVE /Y "%CUR_PACKAGE_INFO%" "%PREV_PACKAGE_INFO%" + if exist "%PRECOMPILED%" ( + DEL "%PRECOMPILED%" + ) +) + +REM There is no CUR_PACKAGE_INFO it was renamed in previous step to %PREV_PACKAGE_INFO% +REM which means we need to do pub get and precompile +if not exist "%PRECOMPILED%" ( + echo Running pub get in "%cd%" + "%DART%" pub get --no-precompile + "%DART%" compile kernel bin/build_tool_runner.dart +) + +"%DART%" "%PRECOMPILED%" %* + +REM 253 means invalid snapshot version. +If %ERRORLEVEL% equ 253 ( + "%DART%" pub get --no-precompile + "%DART%" compile kernel bin/build_tool_runner.dart + "%DART%" "%PRECOMPILED%" %* +) diff --git a/cw_starknet/cargokit/run_build_tool.sh b/cw_starknet/cargokit/run_build_tool.sh new file mode 100755 index 0000000000..24b0ed89da --- /dev/null +++ b/cw_starknet/cargokit/run_build_tool.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash + +set -e + +BASEDIR=$(dirname "$0") + +mkdir -p "$CARGOKIT_TOOL_TEMP_DIR" + +cd "$CARGOKIT_TOOL_TEMP_DIR" + +# Write a very simple bin package in temp folder that depends on build_tool package +# from Cargokit. This is done to ensure that we don't pollute Cargokit folder +# with .dart_tool contents. + +BUILD_TOOL_PKG_DIR="$BASEDIR/build_tool" + +if [[ -z $FLUTTER_ROOT ]]; then # not defined + DART=dart +else + DART="$FLUTTER_ROOT/bin/cache/dart-sdk/bin/dart" +fi + +cat << EOF > "pubspec.yaml" +name: build_tool_runner +version: 1.0.0 +publish_to: none + +environment: + sdk: '>=3.0.0 <4.0.0' + +dependencies: + build_tool: + path: "$BUILD_TOOL_PKG_DIR" +EOF + +mkdir -p "bin" + +cat << EOF > "bin/build_tool_runner.dart" +import 'package:build_tool/build_tool.dart' as build_tool; +void main(List args) { + build_tool.runMain(args); +} +EOF + +# Create alias for `shasum` if it does not exist and `sha1sum` exists +if ! [ -x "$(command -v shasum)" ] && [ -x "$(command -v sha1sum)" ]; then + shopt -s expand_aliases + alias shasum="sha1sum" +fi + +# Dart run will not cache any package that has a path dependency, which +# is the case for our build_tool_runner. So instead we precompile the package +# ourselves. +# To invalidate the cached kernel we use the hash of ls -LR of the build_tool +# package directory. This should be good enough, as the build_tool package +# itself is not meant to have any path dependencies. + +if [[ "$OSTYPE" == "darwin"* ]]; then + PACKAGE_HASH=$(ls -lTR "$BUILD_TOOL_PKG_DIR" | shasum) +else + PACKAGE_HASH=$(ls -lR --full-time "$BUILD_TOOL_PKG_DIR" | shasum) +fi + +PACKAGE_HASH_FILE=".package_hash" + +if [ -f "$PACKAGE_HASH_FILE" ]; then + EXISTING_HASH=$(cat "$PACKAGE_HASH_FILE") + if [ "$PACKAGE_HASH" != "$EXISTING_HASH" ]; then + rm "$PACKAGE_HASH_FILE" + fi +fi + +# Run pub get if needed. +if [ ! -f "$PACKAGE_HASH_FILE" ]; then + "$DART" pub get --no-precompile + "$DART" compile kernel bin/build_tool_runner.dart + echo "$PACKAGE_HASH" > "$PACKAGE_HASH_FILE" +fi + +# Rebuild the tool if it was deleted by Android Studio +if [ ! -f "bin/build_tool_runner.dill" ]; then + "$DART" compile kernel bin/build_tool_runner.dart +fi + +set +e + +"$DART" bin/build_tool_runner.dill "$@" + +exit_code=$? + +# 253 means invalid snapshot version. +if [ $exit_code == 253 ]; then + "$DART" pub get --no-precompile + "$DART" compile kernel bin/build_tool_runner.dart + "$DART" bin/build_tool_runner.dill "$@" + exit_code=$? +fi + +exit $exit_code diff --git a/cw_starknet/example/lib/main.dart b/cw_starknet/example/lib/main.dart new file mode 100644 index 0000000000..62fd0ee9ff --- /dev/null +++ b/cw_starknet/example/lib/main.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:cw_starknet/cw_starknet.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await ensureStarknetRustInitialized(); + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar(title: const Text('cw_starknet')), + body: Center( + child: const Text('Starknet Rust bridge initialized'), + ), + ), + ); + } +} diff --git a/cw_starknet/flutter_rust_bridge.yaml b/cw_starknet/flutter_rust_bridge.yaml new file mode 100644 index 0000000000..e15ed9167d --- /dev/null +++ b/cw_starknet/flutter_rust_bridge.yaml @@ -0,0 +1,3 @@ +rust_input: crate::api +rust_root: rust/ +dart_output: lib/src/rust \ No newline at end of file diff --git a/cw_starknet/ios/Classes/dummy_file.c b/cw_starknet/ios/Classes/dummy_file.c new file mode 100644 index 0000000000..e06dab9968 --- /dev/null +++ b/cw_starknet/ios/Classes/dummy_file.c @@ -0,0 +1 @@ +// This is an empty file to force CocoaPods to create a framework. diff --git a/cw_starknet/ios/cw_starknet.podspec b/cw_starknet/ios/cw_starknet.podspec new file mode 100644 index 0000000000..7788a57865 --- /dev/null +++ b/cw_starknet/ios/cw_starknet.podspec @@ -0,0 +1,46 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint cw_starknet_rust.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'cw_starknet' + s.version = '0.0.1' + s.summary = 'A new Flutter FFI plugin project.' + s.description = <<-DESC +A new Flutter FFI plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + s.module_name = 'cw_starknet_rust' + + # This will ensure the source files in Classes/ are included in the native + # builds of apps using this FFI plugin. Podspec does not support relative + # paths, so Classes contains a forwarder C file that relatively imports + # `../src/*` so that the C sources can be shared among all target platforms. + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'Flutter' + s.platform = :ios, '11.0' + + # Flutter.framework does not contain a i386 slice. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } + s.swift_version = '5.0' + + s.script_phase = { + :name => 'Build Rust library', + # First argument is relative path to the `rust` folder, second is name of rust library + :script => 'sh "$PODS_TARGET_SRCROOT/../cargokit/build_pod.sh" ../rust cw_starknet_rust', + :execution_position => :before_compile, + :input_files => ['${BUILT_PRODUCTS_DIR}/cargokit_phony'], + # Let XCode know that the static library referenced in -force_load below is + # created by this build step. + :output_files => ["${PODS_CONFIGURATION_BUILD_DIR}/cw_starknet_rust/libcw_starknet_rust.a"], + } + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + # Flutter.framework does not contain a i386 slice. + 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', + 'OTHER_LDFLAGS' => '-force_load ${PODS_CONFIGURATION_BUILD_DIR}/cw_starknet_rust/libcw_starknet_rust.a', + } +end \ No newline at end of file diff --git a/cw_starknet/lib/cw_starknet.dart b/cw_starknet/lib/cw_starknet.dart new file mode 100644 index 0000000000..e82a18e28a --- /dev/null +++ b/cw_starknet/lib/cw_starknet.dart @@ -0,0 +1,5 @@ +library cw_starknet; + +export 'src/rust/api/starknet.dart'; +export 'src/rust/frb_generated.dart' show RustLib; +export 'starknet_rust.dart'; diff --git a/cw_starknet/lib/default_starknet_tokens.dart b/cw_starknet/lib/default_starknet_tokens.dart new file mode 100644 index 0000000000..442dbaca24 --- /dev/null +++ b/cw_starknet/lib/default_starknet_tokens.dart @@ -0,0 +1,15 @@ +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/starknet_token.dart'; +import 'package:cw_starknet/starknet_client.dart'; + +class DefaultStarknetTokens { + List get initialStarknetTokens => [ + StarknetToken( + name: 'Ethereum', + symbol: 'ETH', + contractAddress: StarknetTokenAddresses.eth, + decimal: CryptoCurrency.eth.decimals, + iconPath: CryptoCurrency.eth.iconPath, + ), + ]; +} diff --git a/cw_starknet/lib/hardware/starknet_ledger_service.dart b/cw_starknet/lib/hardware/starknet_ledger_service.dart new file mode 100644 index 0000000000..5e7ceb674b --- /dev/null +++ b/cw_starknet/lib/hardware/starknet_ledger_service.dart @@ -0,0 +1,266 @@ +import 'dart:typed_data'; + +import 'package:cw_core/hardware/hardware_account_data.dart'; +import 'package:cw_core/hardware/hardware_wallet_service.dart'; +import 'package:cw_starknet/starknet_rust.dart'; +import 'package:cw_starknet/starknet_wallet.dart'; +import 'package:cw_starknet/src/rust/api/starknet.dart' as rust_api; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; +// ignore: implementation_imports +import 'package:ledger_flutter_plus/src/operations/ledger_simple_operation.dart'; + +class StarknetLedgerService extends HardwareWalletService { + StarknetLedgerService(this.ledgerConnection); + + static const int _claStarknet = 0x5a; + static const int _insGetVersion = 0x00; + static const int _insGetPublicKey = 0x01; + static const int _insSignHash = 0x02; + static const String defaultDerivationPathPrefix = + "m/2645'/1195502025'/1470455285'/0'/0'"; + + final LedgerConnection ledgerConnection; + + @override + Future> getAvailableAccounts({ + int index = 0, + int limit = 5, + }) async { + await ensureStarknetRustInitialized(); + await _getVersion(); + + final accounts = []; + final end = index + limit; + + for (var accountIndex = index; accountIndex < end; accountIndex++) { + final derivationPath = "$defaultDerivationPathPrefix/$accountIndex"; + final publicKey = await getPublicKeyHex(derivationPath: derivationPath); + final response = await rust_api.deriveAccountFromPublicKey( + publicKeyHex: publicKey, + accountClassHashHex: StarknetWalletBase.openZeppelinAccountClassHashHex, + ); + final accountData = unwrapDerivedAccountDataResponse(response); + + accounts.add( + HardwareAccountData( + address: accountData.accountAddressHex, + publicKey: accountData.publicKeyHex, + accountIndex: accountIndex, + derivationPath: derivationPath, + ), + ); + } + + return accounts; + } + + Future getPublicKeyHex({ + required String derivationPath, + bool display = false, + }) async { + final pathBytes = _encodeDerivationPath(derivationPath); + final response = await _sendSimpleOperation( + cla: _claStarknet, + ins: _insGetPublicKey, + p1: display ? 1 : 0, + p2: 0, + data: pathBytes, + ); + + final payload = _extractPayload(response); + if (payload.length != 65) { + throw Exception( + 'Unexpected Starknet Ledger public key response length: ${payload.length}', + ); + } + + return _bytesToHex(payload.sublist(1, 33)); + } + + @override + Future signMessage({ + required Uint8List message, + String? derivationPath, + }) async { + final signPath = derivationPath ?? '$defaultDerivationPathPrefix/0'; + if (message.length != 32) { + throw Exception( + 'Starknet Ledger signing expects a 32-byte hash, got ${message.length} bytes', + ); + } + + final pathBytes = _encodeDerivationPath(signPath); + await _sendSimpleOperation( + cla: _claStarknet, + ins: _insSignHash, + p1: 0, + p2: 0, + data: pathBytes, + ); + + final version = await _getVersion(); + final adjustedHash = _adjustHashForLegacyApp(message, version); + + final response = await _sendSimpleOperation( + cla: _claStarknet, + ins: _insSignHash, + p1: 1, + p2: 0, + data: adjustedHash, + ); + + final payload = _extractPayload(response); + if (payload.length != 66 || payload.first != 65) { + throw Exception( + 'Unexpected Starknet Ledger signature response length: ${payload.length}', + ); + } + + return Uint8List.fromList(payload.sublist(1, 65)); + } + + Future<_LedgerStarknetVersion> _getVersion() async { + final response = await _sendSimpleOperation( + cla: _claStarknet, + ins: _insGetVersion, + p1: 0, + p2: 0, + data: Uint8List(0), + ); + + final payload = _extractPayload(response); + if (payload.length != 3) { + throw Exception( + 'Unexpected Starknet Ledger version response length: ${payload.length}', + ); + } + + return _LedgerStarknetVersion( + major: payload[0], + minor: payload[1], + patch: payload[2], + ); + } + + Future _sendSimpleOperation({ + required int cla, + required int ins, + required int p1, + required int p2, + required Uint8List data, + }) async { + final reader = await ledgerConnection.sendOperation( + LedgerSimpleOperation( + cla: cla, + ins: ins, + p1: p1, + p2: p2, + data: data, + prependDataLength: true, + ), + ); + + return reader.read(reader.remainingLength); + } + + Uint8List _extractPayload(Uint8List response) { + if (response.length < 2) { + throw Exception('Malformed Starknet Ledger response'); + } + + final statusCode = (response[response.length - 2] << 8) | response[response.length - 1]; + if (statusCode != 0x9000) { + throw Exception( + 'Ledger Starknet app returned status 0x${statusCode.toRadixString(16).padLeft(4, '0')}', + ); + } + + return Uint8List.sublistView(response, 0, response.length - 2); + } + + Uint8List _encodeDerivationPath(String derivationPath) { + final normalized = derivationPath.trim(); + if (!normalized.startsWith('m/')) { + throw Exception('Invalid Starknet Ledger derivation path: $derivationPath'); + } + + final segments = normalized.substring(2).split('/'); + if (segments.length != 6) { + throw Exception('Invalid Starknet Ledger derivation path: $derivationPath'); + } + + final bytes = BytesBuilder(copy: false); + for (final segment in segments) { + final isHardened = segment.endsWith("'"); + final numericSegment = isHardened ? segment.substring(0, segment.length - 1) : segment; + final value = int.tryParse(numericSegment); + if (value == null || value < 0) { + throw Exception('Invalid Starknet Ledger derivation path segment: $segment'); + } + + final encodedValue = isHardened ? (value | 0x80000000) : value; + final segmentBytes = ByteData(4)..setUint32(0, encodedValue, Endian.big); + bytes.add(segmentBytes.buffer.asUint8List()); + } + + return bytes.toBytes(); + } + + Uint8List _adjustHashForLegacyApp( + Uint8List message, + _LedgerStarknetVersion version, + ) { + if (version >= const _LedgerStarknetVersion._tuple(2, 0, 0)) { + return message; + } + + final mask = (BigInt.one << 256) - BigInt.one; + var value = BigInt.zero; + for (final byte in message) { + value = (value << 8) | BigInt.from(byte); + } + final shifted = (value << 4) & mask; + final result = Uint8List(32); + var remaining = shifted; + for (var i = 31; i >= 0; i--) { + result[i] = (remaining & BigInt.from(0xff)).toInt(); + remaining >>= 8; + } + return result; + } + + String _bytesToHex(List bytes) { + final buffer = StringBuffer('0x'); + for (final byte in bytes) { + buffer.write(byte.toRadixString(16).padLeft(2, '0')); + } + return buffer.toString(); + } +} + +class _LedgerStarknetVersion implements Comparable<_LedgerStarknetVersion> { + const _LedgerStarknetVersion({ + required this.major, + required this.minor, + required this.patch, + }); + + const _LedgerStarknetVersion._tuple(this.major, this.minor, this.patch); + + final int major; + final int minor; + final int patch; + + @override + int compareTo(_LedgerStarknetVersion other) { + if (major != other.major) { + return major.compareTo(other.major); + } + if (minor != other.minor) { + return minor.compareTo(other.minor); + } + return patch.compareTo(other.patch); + } + + bool operator >=(_LedgerStarknetVersion other) => compareTo(other) >= 0; +} diff --git a/cw_starknet/lib/pending_starknet_transaction.dart b/cw_starknet/lib/pending_starknet_transaction.dart new file mode 100644 index 0000000000..6e28088037 --- /dev/null +++ b/cw_starknet/lib/pending_starknet_transaction.dart @@ -0,0 +1,64 @@ +import 'package:cw_core/format_fixed.dart'; +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_starknet/starknet_balance.dart'; + +class PendingStarknetTransaction with PendingTransaction { + PendingStarknetTransaction({ + required this.amountWei, + required this.amountDecimals, + required this.amountSymbol, + required this.destinationAddress, + required this.sendTransaction, + required this.feeWei, + this.transactionHash = '', + this.buildUnsignedTransactionUr, + this.onCommitted, + }); + + final String amountWei; + final int amountDecimals; + final String amountSymbol; + final String transactionHash; + final String destinationAddress; + final Future Function() sendTransaction; + final String feeWei; + final Future> Function()? buildUnsignedTransactionUr; + final Future Function(String txHash)? onCommitted; + String? _txHash; + + @override + String get amountFormatted => truncateDecimalString( + formatFixed(BigInt.parse(amountWei), amountDecimals, fractionalDigits: amountDecimals), + ); + + @override + Future commit() async { + _txHash = await sendTransaction(); + final txHash = _txHash; + if (txHash != null) { + await onCommitted?.call(txHash); + } + } + + @override + String get feeFormatted => '$feeFormattedValue STRK'; + + @override + String get feeFormattedValue => + truncateDecimalString(formatFixed(BigInt.parse(feeWei), 18, fractionalDigits: 18)); + + @override + String get hex => transactionHash; + + @override + String get id => _txHash ?? transactionHash; + + @override + bool shouldCommitUR() => buildUnsignedTransactionUr != null; + + @override + Future> commitUR() async => + await buildUnsignedTransactionUr?.call() ?? + (throw UnsupportedError( + 'Offline UR signing is not configured for this Starknet transaction.')); +} diff --git a/cw_starknet/lib/src/rust/api/starknet.dart b/cw_starknet/lib/src/rust/api/starknet.dart new file mode 100644 index 0000000000..34ae4d2401 --- /dev/null +++ b/cw_starknet/lib/src/rust/api/starknet.dart @@ -0,0 +1,796 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import + +import '../frb_generated.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; + +// These functions are ignored because they are not marked as `pub`: `action_name_for_selector`, `actual_fee_from_receipt`, `adjust_tip`, `apply_multiplier_u128`, `apply_multiplier_u64`, `biguint_to_uint256_words`, `build_deploy_transaction_hash`, `build_execution_calls`, `build_invoke_transaction_hash`, `calculate_overall_fee_from_params`, `call_token_decimals`, `call_token_string`, `decode_account_execute_calls`, `derive_account_address_from_public_key`, `derive_account_from_public_key_inner`, `derive_account_inner`, `derive_private_key_from_mnemonic`, `estimate_execute_fee_inner`, `execute_calls_external_signer_inner`, `execute_calls_inner`, `fee_priority_multipliers`, `felt_to_biguint`, `felt_to_hex`, `felt_to_short_string`, `felt_to_usize`, `fetch_block_timestamp`, `fetch_latest_block_tip`, `fetch_paginated_transfer_events`, `fetch_transaction_details`, `fetch_transaction_fee`, `finality_status_to_string`, `format_account_error`, `format_provider_error`, `format_starknet_error`, `from_prepared`, `from_raw`, `from_result`, `from_result`, `from_result`, `from_result`, `from_result`, `from_result`, `from_result`, `from_result`, `from_result`, `from_result`, `from_result`, `get_typed_data_message_hash_inner`, `infer_action_name`, `is_account_deployed_with_provider`, `known_selector_name`, `looks_like_swap_action`, `make_provider`, `make_single_owner_account`, `normalize_action_name`, `normalize_hex`, `parse_chain_id`, `parse_decimal_biguint`, `parse_entrypoint_selector`, `parse_felt_hex`, `parse_private_key`, `parse_signature_hex`, `parse_transfer_event`, `parse_u128_decimal`, `prepare_external_execution_inner`, `prepare_transaction_params_from_estimate_with_tip`, `prepare_transaction_params_from_fee_estimate`, `run_async`, `selector_display_name`, `sign_message_hash_inner`, `sign_typed_data_inner`, `split_result`, `summarize_account_invoke_transaction`, `summarize_transaction`, `to_prepared`, `transfer_calldata_hex`, `transfer_calldata`, `uint256_from_call_result`, `uint256_from_words`, `validate_derived_account_address`, `validate_private_key`, `verify_message_hash_signature_inner`, `wait_for_transaction` +// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `DecodedInvokeCall`, `DecodedTransactionSummary`, `ExternalSignerError`, `PreparedExternalExecution`, `PreparedTransactionParams`, `PublicKeyOnlySigner`, `SerializedExternalExecutionPlan`, `SerializedPreparedTransactionParams`, `StarknetFeePriority`, `TransferEventRecord` +// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `get_public_key`, `is_interactive`, `sign_hash` + +Future deriveAccount( + {String? mnemonic, + String? passphrase, + String? privateKeyHex, + required String accountClassHashHex}) => + RustLib.instance.api.crateApiStarknetDeriveAccount( + mnemonic: mnemonic, + passphrase: passphrase, + privateKeyHex: privateKeyHex, + accountClassHashHex: accountClassHashHex); + +Future deriveAccountFromPublicKey( + {required String publicKeyHex, required String accountClassHashHex}) => + RustLib.instance.api.crateApiStarknetDeriveAccountFromPublicKey( + publicKeyHex: publicKeyHex, accountClassHashHex: accountClassHashHex); + +Future getTokenBalance( + {required String nodeUrl, + required String accountAddressHex, + required String tokenAddressHex}) => + RustLib.instance.api.crateApiStarknetGetTokenBalance( + nodeUrl: nodeUrl, + accountAddressHex: accountAddressHex, + tokenAddressHex: tokenAddressHex); + +Future getTokenMetadata( + {required String nodeUrl, required String tokenAddressHex}) => + RustLib.instance.api.crateApiStarknetGetTokenMetadata( + nodeUrl: nodeUrl, tokenAddressHex: tokenAddressHex); + +Future isAccountDeployed( + {required String nodeUrl, required String accountAddressHex}) => + RustLib.instance.api.crateApiStarknetIsAccountDeployed( + nodeUrl: nodeUrl, accountAddressHex: accountAddressHex); + +Future estimateTransferFee( + {required String nodeUrl, + required String privateKeyHex, + required String accountAddressHex, + required String recipientAddressHex, + required String tokenAddressHex, + required String amountWei, + String? chainIdHex}) => + RustLib.instance.api.crateApiStarknetEstimateTransferFee( + nodeUrl: nodeUrl, + privateKeyHex: privateKeyHex, + accountAddressHex: accountAddressHex, + recipientAddressHex: recipientAddressHex, + tokenAddressHex: tokenAddressHex, + amountWei: amountWei, + chainIdHex: chainIdHex); + +Future estimateStandardTransferFee( + {required String nodeUrl, + required String privateKeyHex, + required String accountAddressHex, + required String tokenAddressHex, + String? chainIdHex}) => + RustLib.instance.api.crateApiStarknetEstimateStandardTransferFee( + nodeUrl: nodeUrl, + privateKeyHex: privateKeyHex, + accountAddressHex: accountAddressHex, + tokenAddressHex: tokenAddressHex, + chainIdHex: chainIdHex); + +Future sendTransfer( + {required String nodeUrl, + required String privateKeyHex, + required String accountAddressHex, + required String recipientAddressHex, + required String tokenAddressHex, + required String amountWei, + required String accountClassHashHex, + String? chainIdHex}) => + RustLib.instance.api.crateApiStarknetSendTransfer( + nodeUrl: nodeUrl, + privateKeyHex: privateKeyHex, + accountAddressHex: accountAddressHex, + recipientAddressHex: recipientAddressHex, + tokenAddressHex: tokenAddressHex, + amountWei: amountWei, + accountClassHashHex: accountClassHashHex, + chainIdHex: chainIdHex); + +Future estimateExecuteFee( + {required String nodeUrl, + required String privateKeyHex, + required String accountAddressHex, + required String accountClassHashHex, + required List calls, + required int feePriorityRaw, + String? chainIdHex}) => + RustLib.instance.api.crateApiStarknetEstimateExecuteFee( + nodeUrl: nodeUrl, + privateKeyHex: privateKeyHex, + accountAddressHex: accountAddressHex, + accountClassHashHex: accountClassHashHex, + calls: calls, + feePriorityRaw: feePriorityRaw, + chainIdHex: chainIdHex); + +Future estimateExecuteFeeExternalSigner( + {required String nodeUrl, + required String publicKeyHex, + required String accountAddressHex, + required String accountClassHashHex, + required List calls, + required int feePriorityRaw, + String? chainIdHex}) => + RustLib.instance.api.crateApiStarknetEstimateExecuteFeeExternalSigner( + nodeUrl: nodeUrl, + publicKeyHex: publicKeyHex, + accountAddressHex: accountAddressHex, + accountClassHashHex: accountClassHashHex, + calls: calls, + feePriorityRaw: feePriorityRaw, + chainIdHex: chainIdHex); + +Future executeCalls( + {required String nodeUrl, + required String privateKeyHex, + required String accountAddressHex, + required String accountClassHashHex, + required List calls, + required int feePriorityRaw, + String? chainIdHex}) => + RustLib.instance.api.crateApiStarknetExecuteCalls( + nodeUrl: nodeUrl, + privateKeyHex: privateKeyHex, + accountAddressHex: accountAddressHex, + accountClassHashHex: accountClassHashHex, + calls: calls, + feePriorityRaw: feePriorityRaw, + chainIdHex: chainIdHex); + +Future getExecuteTransactionHashesExternalSigner( + {required String nodeUrl, + required String publicKeyHex, + required String accountAddressHex, + required String accountClassHashHex, + required List calls, + required int feePriorityRaw, + String? chainIdHex}) => + RustLib.instance.api + .crateApiStarknetGetExecuteTransactionHashesExternalSigner( + nodeUrl: nodeUrl, + publicKeyHex: publicKeyHex, + accountAddressHex: accountAddressHex, + accountClassHashHex: accountClassHashHex, + calls: calls, + feePriorityRaw: feePriorityRaw, + chainIdHex: chainIdHex); + +Future executeCallsExternalSigner( + {required String nodeUrl, + required String planJson, + required String invokeRHex, + required String invokeSHex, + String? deployRHex, + String? deploySHex}) => + RustLib.instance.api.crateApiStarknetExecuteCallsExternalSigner( + nodeUrl: nodeUrl, + planJson: planJson, + invokeRHex: invokeRHex, + invokeSHex: invokeSHex, + deployRHex: deployRHex, + deploySHex: deploySHex); + +Future fetchTransferHistory( + {required String nodeUrl, + required String accountAddressHex, + required String tokenAddressHex, + required String tokenSymbol, + PlatformInt64? fromBlock, + int? maxPages}) => + RustLib.instance.api.crateApiStarknetFetchTransferHistory( + nodeUrl: nodeUrl, + accountAddressHex: accountAddressHex, + tokenAddressHex: tokenAddressHex, + tokenSymbol: tokenSymbol, + fromBlock: fromBlock, + maxPages: maxPages); + +Future getTransactionDetails( + {required String nodeUrl, required String transactionHashHex}) => + RustLib.instance.api.crateApiStarknetGetTransactionDetails( + nodeUrl: nodeUrl, transactionHashHex: transactionHashHex); + +Future getBlockNumber({required String nodeUrl}) => + RustLib.instance.api.crateApiStarknetGetBlockNumber(nodeUrl: nodeUrl); + +Future signMessageHash( + {required String privateKeyHex, required String messageHashHex}) => + RustLib.instance.api.crateApiStarknetSignMessageHash( + privateKeyHex: privateKeyHex, messageHashHex: messageHashHex); + +Future verifyMessageHashSignature( + {required String publicKeyHex, + required String messageHashHex, + required String rHex, + required String sHex}) => + RustLib.instance.api.crateApiStarknetVerifyMessageHashSignature( + publicKeyHex: publicKeyHex, + messageHashHex: messageHashHex, + rHex: rHex, + sHex: sHex); + +Future signTypedData( + {required String privateKeyHex, + required String accountAddressHex, + required String typedDataJson}) => + RustLib.instance.api.crateApiStarknetSignTypedData( + privateKeyHex: privateKeyHex, + accountAddressHex: accountAddressHex, + typedDataJson: typedDataJson); + +Future getTypedDataMessageHash( + {required String accountAddressHex, required String typedDataJson}) => + RustLib.instance.api.crateApiStarknetGetTypedDataMessageHash( + accountAddressHex: accountAddressHex, typedDataJson: typedDataJson); + +class BoolResponse { + final bool? value; + final String? error; + + const BoolResponse({ + this.value, + this.error, + }); + + @override + int get hashCode => value.hashCode ^ error.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is BoolResponse && + runtimeType == other.runtimeType && + value == other.value && + error == other.error; +} + +class DerivedAccountData { + final String privateKeyHex; + final String publicKeyHex; + final String accountAddressHex; + + const DerivedAccountData({ + required this.privateKeyHex, + required this.publicKeyHex, + required this.accountAddressHex, + }); + + @override + int get hashCode => + privateKeyHex.hashCode ^ + publicKeyHex.hashCode ^ + accountAddressHex.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is DerivedAccountData && + runtimeType == other.runtimeType && + privateKeyHex == other.privateKeyHex && + publicKeyHex == other.publicKeyHex && + accountAddressHex == other.accountAddressHex; +} + +class DerivedAccountDataResponse { + final DerivedAccountData? value; + final String? error; + + const DerivedAccountDataResponse({ + this.value, + this.error, + }); + + @override + int get hashCode => value.hashCode ^ error.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is DerivedAccountDataResponse && + runtimeType == other.runtimeType && + value == other.value && + error == other.error; +} + +class ExecutionPlanResponse { + final StarknetExecutionPlanData? value; + final String? error; + + const ExecutionPlanResponse({ + this.value, + this.error, + }); + + @override + int get hashCode => value.hashCode ^ error.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ExecutionPlanResponse && + runtimeType == other.runtimeType && + value == other.value && + error == other.error; +} + +class FeeQuoteResponse { + final StarknetFeeQuote? value; + final String? error; + + const FeeQuoteResponse({ + this.value, + this.error, + }); + + @override + int get hashCode => value.hashCode ^ error.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is FeeQuoteResponse && + runtimeType == other.runtimeType && + value == other.value && + error == other.error; +} + +class I64Response { + final PlatformInt64? value; + final String? error; + + const I64Response({ + this.value, + this.error, + }); + + @override + int get hashCode => value.hashCode ^ error.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is I64Response && + runtimeType == other.runtimeType && + value == other.value && + error == other.error; +} + +class StarknetCallInput { + final String contractAddressHex; + final String entrypoint; + final List calldataHex; + + const StarknetCallInput({ + required this.contractAddressHex, + required this.entrypoint, + required this.calldataHex, + }); + + @override + int get hashCode => + contractAddressHex.hashCode ^ entrypoint.hashCode ^ calldataHex.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is StarknetCallInput && + runtimeType == other.runtimeType && + contractAddressHex == other.contractAddressHex && + entrypoint == other.entrypoint && + calldataHex == other.calldataHex; +} + +class StarknetExecutionPlanData { + final String invokeTransactionHashHex; + final String? deployAccountTransactionHashHex; + final bool accountDeploymentRequired; + final String planJson; + + const StarknetExecutionPlanData({ + required this.invokeTransactionHashHex, + this.deployAccountTransactionHashHex, + required this.accountDeploymentRequired, + required this.planJson, + }); + + @override + int get hashCode => + invokeTransactionHashHex.hashCode ^ + deployAccountTransactionHashHex.hashCode ^ + accountDeploymentRequired.hashCode ^ + planJson.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is StarknetExecutionPlanData && + runtimeType == other.runtimeType && + invokeTransactionHashHex == other.invokeTransactionHashHex && + deployAccountTransactionHashHex == + other.deployAccountTransactionHashHex && + accountDeploymentRequired == other.accountDeploymentRequired && + planJson == other.planJson; +} + +class StarknetFeeQuote { + final String overallFeeWei; + final String executionFeeWei; + final String? deployAccountFeeWei; + final bool accountDeploymentRequired; + + const StarknetFeeQuote({ + required this.overallFeeWei, + required this.executionFeeWei, + this.deployAccountFeeWei, + required this.accountDeploymentRequired, + }); + + @override + int get hashCode => + overallFeeWei.hashCode ^ + executionFeeWei.hashCode ^ + deployAccountFeeWei.hashCode ^ + accountDeploymentRequired.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is StarknetFeeQuote && + runtimeType == other.runtimeType && + overallFeeWei == other.overallFeeWei && + executionFeeWei == other.executionFeeWei && + deployAccountFeeWei == other.deployAccountFeeWei && + accountDeploymentRequired == other.accountDeploymentRequired; +} + +class StarknetSignatureData { + final String rHex; + final String sHex; + + const StarknetSignatureData({ + required this.rHex, + required this.sHex, + }); + + @override + int get hashCode => rHex.hashCode ^ sHex.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is StarknetSignatureData && + runtimeType == other.runtimeType && + rHex == other.rHex && + sHex == other.sHex; +} + +class StarknetSignatureDataResponse { + final StarknetSignatureData? value; + final String? error; + + const StarknetSignatureDataResponse({ + this.value, + this.error, + }); + + @override + int get hashCode => value.hashCode ^ error.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is StarknetSignatureDataResponse && + runtimeType == other.runtimeType && + value == other.value && + error == other.error; +} + +class StarknetTokenMetadata { + final String tokenAddressHex; + final String name; + final String symbol; + final int decimals; + + const StarknetTokenMetadata({ + required this.tokenAddressHex, + required this.name, + required this.symbol, + required this.decimals, + }); + + @override + int get hashCode => + tokenAddressHex.hashCode ^ + name.hashCode ^ + symbol.hashCode ^ + decimals.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is StarknetTokenMetadata && + runtimeType == other.runtimeType && + tokenAddressHex == other.tokenAddressHex && + name == other.name && + symbol == other.symbol && + decimals == other.decimals; +} + +class StarknetTransactionDetails { + final String transactionHash; + final String transactionType; + final bool isPending; + final PlatformInt64? blockNumber; + final PlatformInt64? blockTimestamp; + final String? actualFeeWei; + final String? actionName; + final int? callCount; + final String? primaryContractAddressHex; + final String? primaryEntrypoint; + final String? senderAddressHex; + final String? finalityStatus; + final String? executionStatus; + final String? revertReason; + final bool accountDeploymentRequired; + final String? l1GasMaxAmount; + final String? l1GasMaxPriceWei; + final String? l2GasMaxAmount; + final String? l2GasMaxPriceWei; + final String? l1DataGasMaxAmount; + final String? l1DataGasMaxPriceWei; + final PlatformInt64? tip; + + const StarknetTransactionDetails({ + required this.transactionHash, + required this.transactionType, + required this.isPending, + this.blockNumber, + this.blockTimestamp, + this.actualFeeWei, + this.actionName, + this.callCount, + this.primaryContractAddressHex, + this.primaryEntrypoint, + this.senderAddressHex, + this.finalityStatus, + this.executionStatus, + this.revertReason, + required this.accountDeploymentRequired, + this.l1GasMaxAmount, + this.l1GasMaxPriceWei, + this.l2GasMaxAmount, + this.l2GasMaxPriceWei, + this.l1DataGasMaxAmount, + this.l1DataGasMaxPriceWei, + this.tip, + }); + + @override + int get hashCode => + transactionHash.hashCode ^ + transactionType.hashCode ^ + isPending.hashCode ^ + blockNumber.hashCode ^ + blockTimestamp.hashCode ^ + actualFeeWei.hashCode ^ + actionName.hashCode ^ + callCount.hashCode ^ + primaryContractAddressHex.hashCode ^ + primaryEntrypoint.hashCode ^ + senderAddressHex.hashCode ^ + finalityStatus.hashCode ^ + executionStatus.hashCode ^ + revertReason.hashCode ^ + accountDeploymentRequired.hashCode ^ + l1GasMaxAmount.hashCode ^ + l1GasMaxPriceWei.hashCode ^ + l2GasMaxAmount.hashCode ^ + l2GasMaxPriceWei.hashCode ^ + l1DataGasMaxAmount.hashCode ^ + l1DataGasMaxPriceWei.hashCode ^ + tip.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is StarknetTransactionDetails && + runtimeType == other.runtimeType && + transactionHash == other.transactionHash && + transactionType == other.transactionType && + isPending == other.isPending && + blockNumber == other.blockNumber && + blockTimestamp == other.blockTimestamp && + actualFeeWei == other.actualFeeWei && + actionName == other.actionName && + callCount == other.callCount && + primaryContractAddressHex == other.primaryContractAddressHex && + primaryEntrypoint == other.primaryEntrypoint && + senderAddressHex == other.senderAddressHex && + finalityStatus == other.finalityStatus && + executionStatus == other.executionStatus && + revertReason == other.revertReason && + accountDeploymentRequired == other.accountDeploymentRequired && + l1GasMaxAmount == other.l1GasMaxAmount && + l1GasMaxPriceWei == other.l1GasMaxPriceWei && + l2GasMaxAmount == other.l2GasMaxAmount && + l2GasMaxPriceWei == other.l2GasMaxPriceWei && + l1DataGasMaxAmount == other.l1DataGasMaxAmount && + l1DataGasMaxPriceWei == other.l1DataGasMaxPriceWei && + tip == other.tip; +} + +class StringListResponse { + final List items; + final String? error; + + const StringListResponse({ + required this.items, + this.error, + }); + + @override + int get hashCode => items.hashCode ^ error.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is StringListResponse && + runtimeType == other.runtimeType && + items == other.items && + error == other.error; +} + +class StringResponse { + final String? value; + final String? error; + + const StringResponse({ + this.value, + this.error, + }); + + @override + int get hashCode => value.hashCode ^ error.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is StringResponse && + runtimeType == other.runtimeType && + value == other.value && + error == other.error; +} + +class TokenMetadataResponse { + final StarknetTokenMetadata? value; + final String? error; + + const TokenMetadataResponse({ + this.value, + this.error, + }); + + @override + int get hashCode => value.hashCode ^ error.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is TokenMetadataResponse && + runtimeType == other.runtimeType && + value == other.value && + error == other.error; +} + +class TransactionDetailsResponse { + final StarknetTransactionDetails? value; + final String? error; + + const TransactionDetailsResponse({ + this.value, + this.error, + }); + + @override + int get hashCode => value.hashCode ^ error.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is TransactionDetailsResponse && + runtimeType == other.runtimeType && + value == other.value && + error == other.error; +} + +class TransferHistoryItem { + final String transactionHash; + final String eventId; + final PlatformInt64 eventIndex; + final PlatformInt64? blockNumber; + final String from; + final String to; + final String amountWei; + final bool isOutgoing; + final String tokenSymbol; + final String tokenAddressHex; + final PlatformInt64? blockTimestamp; + final String? txFeeWei; + + const TransferHistoryItem({ + required this.transactionHash, + required this.eventId, + required this.eventIndex, + this.blockNumber, + required this.from, + required this.to, + required this.amountWei, + required this.isOutgoing, + required this.tokenSymbol, + required this.tokenAddressHex, + this.blockTimestamp, + this.txFeeWei, + }); + + @override + int get hashCode => + transactionHash.hashCode ^ + eventId.hashCode ^ + eventIndex.hashCode ^ + blockNumber.hashCode ^ + from.hashCode ^ + to.hashCode ^ + amountWei.hashCode ^ + isOutgoing.hashCode ^ + tokenSymbol.hashCode ^ + tokenAddressHex.hashCode ^ + blockTimestamp.hashCode ^ + txFeeWei.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is TransferHistoryItem && + runtimeType == other.runtimeType && + transactionHash == other.transactionHash && + eventId == other.eventId && + eventIndex == other.eventIndex && + blockNumber == other.blockNumber && + from == other.from && + to == other.to && + amountWei == other.amountWei && + isOutgoing == other.isOutgoing && + tokenSymbol == other.tokenSymbol && + tokenAddressHex == other.tokenAddressHex && + blockTimestamp == other.blockTimestamp && + txFeeWei == other.txFeeWei; +} + +class TransferHistoryResponse { + final List items; + final String? error; + + const TransferHistoryResponse({ + required this.items, + this.error, + }); + + @override + int get hashCode => items.hashCode ^ error.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is TransferHistoryResponse && + runtimeType == other.runtimeType && + items == other.items && + error == other.error; +} diff --git a/cw_starknet/lib/src/rust/frb_generated.dart b/cw_starknet/lib/src/rust/frb_generated.dart new file mode 100644 index 0000000000..18ad983f6d --- /dev/null +++ b/cw_starknet/lib/src/rust/frb_generated.dart @@ -0,0 +1,2454 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field + +import 'api/starknet.dart'; +import 'dart:async'; +import 'dart:convert'; +import 'frb_generated.dart'; +import 'frb_generated.io.dart' + if (dart.library.js_interop) 'frb_generated.web.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; + +/// Main entrypoint of the Rust API +class RustLib extends BaseEntrypoint { + @internal + static final instance = RustLib._(); + + RustLib._(); + + /// Initialize flutter_rust_bridge + static Future init({ + RustLibApi? api, + BaseHandler? handler, + ExternalLibrary? externalLibrary, + bool forceSameCodegenVersion = true, + }) async { + await instance.initImpl( + api: api, + handler: handler, + externalLibrary: externalLibrary, + forceSameCodegenVersion: forceSameCodegenVersion, + ); + } + + /// Initialize flutter_rust_bridge in mock mode. + /// No libraries for FFI are loaded. + static void initMock({ + required RustLibApi api, + }) { + instance.initMockImpl( + api: api, + ); + } + + /// Dispose flutter_rust_bridge + /// + /// The call to this function is optional, since flutter_rust_bridge (and everything else) + /// is automatically disposed when the app stops. + static void dispose() => instance.disposeImpl(); + + @override + ApiImplConstructor get apiImplConstructor => + RustLibApiImpl.new; + + @override + WireConstructor get wireConstructor => + RustLibWire.fromExternalLibrary; + + @override + Future executeRustInitializers() async { + await api.crateApiStarknetInitApp(); + } + + @override + ExternalLibraryLoaderConfig get defaultExternalLibraryLoaderConfig => + kDefaultExternalLibraryLoaderConfig; + + @override + String get codegenVersion => '2.12.0'; + + @override + int get rustContentHash => -811430247; + + static const kDefaultExternalLibraryLoaderConfig = + ExternalLibraryLoaderConfig( + stem: 'cw_starknet_rust', + ioDirectory: 'rust/target/release/', + webPrefix: 'pkg/', + wasmBindgenName: 'wasm_bindgen', + ); +} + +abstract class RustLibApi extends BaseApi { + Future crateApiStarknetDeriveAccount( + {String? mnemonic, + String? passphrase, + String? privateKeyHex, + required String accountClassHashHex}); + + Future crateApiStarknetDeriveAccountFromPublicKey( + {required String publicKeyHex, required String accountClassHashHex}); + + Future crateApiStarknetEstimateExecuteFee( + {required String nodeUrl, + required String privateKeyHex, + required String accountAddressHex, + required String accountClassHashHex, + required List calls, + required int feePriorityRaw, + String? chainIdHex}); + + Future crateApiStarknetEstimateExecuteFeeExternalSigner( + {required String nodeUrl, + required String publicKeyHex, + required String accountAddressHex, + required String accountClassHashHex, + required List calls, + required int feePriorityRaw, + String? chainIdHex}); + + Future crateApiStarknetEstimateStandardTransferFee( + {required String nodeUrl, + required String privateKeyHex, + required String accountAddressHex, + required String tokenAddressHex, + String? chainIdHex}); + + Future crateApiStarknetEstimateTransferFee( + {required String nodeUrl, + required String privateKeyHex, + required String accountAddressHex, + required String recipientAddressHex, + required String tokenAddressHex, + required String amountWei, + String? chainIdHex}); + + Future crateApiStarknetExecuteCalls( + {required String nodeUrl, + required String privateKeyHex, + required String accountAddressHex, + required String accountClassHashHex, + required List calls, + required int feePriorityRaw, + String? chainIdHex}); + + Future crateApiStarknetExecuteCallsExternalSigner( + {required String nodeUrl, + required String planJson, + required String invokeRHex, + required String invokeSHex, + String? deployRHex, + String? deploySHex}); + + Future crateApiStarknetFetchTransferHistory( + {required String nodeUrl, + required String accountAddressHex, + required String tokenAddressHex, + required String tokenSymbol, + PlatformInt64? fromBlock, + int? maxPages}); + + Future crateApiStarknetGetBlockNumber({required String nodeUrl}); + + Future + crateApiStarknetGetExecuteTransactionHashesExternalSigner( + {required String nodeUrl, + required String publicKeyHex, + required String accountAddressHex, + required String accountClassHashHex, + required List calls, + required int feePriorityRaw, + String? chainIdHex}); + + Future crateApiStarknetGetTokenBalance( + {required String nodeUrl, + required String accountAddressHex, + required String tokenAddressHex}); + + Future crateApiStarknetGetTokenMetadata( + {required String nodeUrl, required String tokenAddressHex}); + + Future crateApiStarknetGetTransactionDetails( + {required String nodeUrl, required String transactionHashHex}); + + Future crateApiStarknetGetTypedDataMessageHash( + {required String accountAddressHex, required String typedDataJson}); + + Future crateApiStarknetInitApp(); + + Future crateApiStarknetIsAccountDeployed( + {required String nodeUrl, required String accountAddressHex}); + + Future crateApiStarknetSendTransfer( + {required String nodeUrl, + required String privateKeyHex, + required String accountAddressHex, + required String recipientAddressHex, + required String tokenAddressHex, + required String amountWei, + required String accountClassHashHex, + String? chainIdHex}); + + Future crateApiStarknetSignMessageHash( + {required String privateKeyHex, required String messageHashHex}); + + Future crateApiStarknetSignTypedData( + {required String privateKeyHex, + required String accountAddressHex, + required String typedDataJson}); + + Future crateApiStarknetVerifyMessageHashSignature( + {required String publicKeyHex, + required String messageHashHex, + required String rHex, + required String sHex}); +} + +class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { + RustLibApiImpl({ + required super.handler, + required super.wire, + required super.generalizedFrbRustBinding, + required super.portManager, + }); + + @override + Future crateApiStarknetDeriveAccount( + {String? mnemonic, + String? passphrase, + String? privateKeyHex, + required String accountClassHashHex}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_opt_String(mnemonic, serializer); + sse_encode_opt_String(passphrase, serializer); + sse_encode_opt_String(privateKeyHex, serializer); + sse_encode_String(accountClassHashHex, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 1, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_derived_account_data_response, + decodeErrorData: null, + ), + constMeta: kCrateApiStarknetDeriveAccountConstMeta, + argValues: [mnemonic, passphrase, privateKeyHex, accountClassHashHex], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiStarknetDeriveAccountConstMeta => + const TaskConstMeta( + debugName: "derive_account", + argNames: [ + "mnemonic", + "passphrase", + "privateKeyHex", + "accountClassHashHex" + ], + ); + + @override + Future crateApiStarknetDeriveAccountFromPublicKey( + {required String publicKeyHex, required String accountClassHashHex}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(publicKeyHex, serializer); + sse_encode_String(accountClassHashHex, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 2, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_derived_account_data_response, + decodeErrorData: null, + ), + constMeta: kCrateApiStarknetDeriveAccountFromPublicKeyConstMeta, + argValues: [publicKeyHex, accountClassHashHex], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiStarknetDeriveAccountFromPublicKeyConstMeta => + const TaskConstMeta( + debugName: "derive_account_from_public_key", + argNames: ["publicKeyHex", "accountClassHashHex"], + ); + + @override + Future crateApiStarknetEstimateExecuteFee( + {required String nodeUrl, + required String privateKeyHex, + required String accountAddressHex, + required String accountClassHashHex, + required List calls, + required int feePriorityRaw, + String? chainIdHex}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(nodeUrl, serializer); + sse_encode_String(privateKeyHex, serializer); + sse_encode_String(accountAddressHex, serializer); + sse_encode_String(accountClassHashHex, serializer); + sse_encode_list_starknet_call_input(calls, serializer); + sse_encode_i_32(feePriorityRaw, serializer); + sse_encode_opt_String(chainIdHex, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 3, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_fee_quote_response, + decodeErrorData: null, + ), + constMeta: kCrateApiStarknetEstimateExecuteFeeConstMeta, + argValues: [ + nodeUrl, + privateKeyHex, + accountAddressHex, + accountClassHashHex, + calls, + feePriorityRaw, + chainIdHex + ], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiStarknetEstimateExecuteFeeConstMeta => + const TaskConstMeta( + debugName: "estimate_execute_fee", + argNames: [ + "nodeUrl", + "privateKeyHex", + "accountAddressHex", + "accountClassHashHex", + "calls", + "feePriorityRaw", + "chainIdHex" + ], + ); + + @override + Future crateApiStarknetEstimateExecuteFeeExternalSigner( + {required String nodeUrl, + required String publicKeyHex, + required String accountAddressHex, + required String accountClassHashHex, + required List calls, + required int feePriorityRaw, + String? chainIdHex}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(nodeUrl, serializer); + sse_encode_String(publicKeyHex, serializer); + sse_encode_String(accountAddressHex, serializer); + sse_encode_String(accountClassHashHex, serializer); + sse_encode_list_starknet_call_input(calls, serializer); + sse_encode_i_32(feePriorityRaw, serializer); + sse_encode_opt_String(chainIdHex, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 4, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_fee_quote_response, + decodeErrorData: null, + ), + constMeta: kCrateApiStarknetEstimateExecuteFeeExternalSignerConstMeta, + argValues: [ + nodeUrl, + publicKeyHex, + accountAddressHex, + accountClassHashHex, + calls, + feePriorityRaw, + chainIdHex + ], + apiImpl: this, + )); + } + + TaskConstMeta + get kCrateApiStarknetEstimateExecuteFeeExternalSignerConstMeta => + const TaskConstMeta( + debugName: "estimate_execute_fee_external_signer", + argNames: [ + "nodeUrl", + "publicKeyHex", + "accountAddressHex", + "accountClassHashHex", + "calls", + "feePriorityRaw", + "chainIdHex" + ], + ); + + @override + Future crateApiStarknetEstimateStandardTransferFee( + {required String nodeUrl, + required String privateKeyHex, + required String accountAddressHex, + required String tokenAddressHex, + String? chainIdHex}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(nodeUrl, serializer); + sse_encode_String(privateKeyHex, serializer); + sse_encode_String(accountAddressHex, serializer); + sse_encode_String(tokenAddressHex, serializer); + sse_encode_opt_String(chainIdHex, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 5, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_string_response, + decodeErrorData: null, + ), + constMeta: kCrateApiStarknetEstimateStandardTransferFeeConstMeta, + argValues: [ + nodeUrl, + privateKeyHex, + accountAddressHex, + tokenAddressHex, + chainIdHex + ], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiStarknetEstimateStandardTransferFeeConstMeta => + const TaskConstMeta( + debugName: "estimate_standard_transfer_fee", + argNames: [ + "nodeUrl", + "privateKeyHex", + "accountAddressHex", + "tokenAddressHex", + "chainIdHex" + ], + ); + + @override + Future crateApiStarknetEstimateTransferFee( + {required String nodeUrl, + required String privateKeyHex, + required String accountAddressHex, + required String recipientAddressHex, + required String tokenAddressHex, + required String amountWei, + String? chainIdHex}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(nodeUrl, serializer); + sse_encode_String(privateKeyHex, serializer); + sse_encode_String(accountAddressHex, serializer); + sse_encode_String(recipientAddressHex, serializer); + sse_encode_String(tokenAddressHex, serializer); + sse_encode_String(amountWei, serializer); + sse_encode_opt_String(chainIdHex, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 6, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_string_response, + decodeErrorData: null, + ), + constMeta: kCrateApiStarknetEstimateTransferFeeConstMeta, + argValues: [ + nodeUrl, + privateKeyHex, + accountAddressHex, + recipientAddressHex, + tokenAddressHex, + amountWei, + chainIdHex + ], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiStarknetEstimateTransferFeeConstMeta => + const TaskConstMeta( + debugName: "estimate_transfer_fee", + argNames: [ + "nodeUrl", + "privateKeyHex", + "accountAddressHex", + "recipientAddressHex", + "tokenAddressHex", + "amountWei", + "chainIdHex" + ], + ); + + @override + Future crateApiStarknetExecuteCalls( + {required String nodeUrl, + required String privateKeyHex, + required String accountAddressHex, + required String accountClassHashHex, + required List calls, + required int feePriorityRaw, + String? chainIdHex}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(nodeUrl, serializer); + sse_encode_String(privateKeyHex, serializer); + sse_encode_String(accountAddressHex, serializer); + sse_encode_String(accountClassHashHex, serializer); + sse_encode_list_starknet_call_input(calls, serializer); + sse_encode_i_32(feePriorityRaw, serializer); + sse_encode_opt_String(chainIdHex, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 7, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_string_response, + decodeErrorData: null, + ), + constMeta: kCrateApiStarknetExecuteCallsConstMeta, + argValues: [ + nodeUrl, + privateKeyHex, + accountAddressHex, + accountClassHashHex, + calls, + feePriorityRaw, + chainIdHex + ], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiStarknetExecuteCallsConstMeta => + const TaskConstMeta( + debugName: "execute_calls", + argNames: [ + "nodeUrl", + "privateKeyHex", + "accountAddressHex", + "accountClassHashHex", + "calls", + "feePriorityRaw", + "chainIdHex" + ], + ); + + @override + Future crateApiStarknetExecuteCallsExternalSigner( + {required String nodeUrl, + required String planJson, + required String invokeRHex, + required String invokeSHex, + String? deployRHex, + String? deploySHex}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(nodeUrl, serializer); + sse_encode_String(planJson, serializer); + sse_encode_String(invokeRHex, serializer); + sse_encode_String(invokeSHex, serializer); + sse_encode_opt_String(deployRHex, serializer); + sse_encode_opt_String(deploySHex, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 8, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_string_response, + decodeErrorData: null, + ), + constMeta: kCrateApiStarknetExecuteCallsExternalSignerConstMeta, + argValues: [ + nodeUrl, + planJson, + invokeRHex, + invokeSHex, + deployRHex, + deploySHex + ], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiStarknetExecuteCallsExternalSignerConstMeta => + const TaskConstMeta( + debugName: "execute_calls_external_signer", + argNames: [ + "nodeUrl", + "planJson", + "invokeRHex", + "invokeSHex", + "deployRHex", + "deploySHex" + ], + ); + + @override + Future crateApiStarknetFetchTransferHistory( + {required String nodeUrl, + required String accountAddressHex, + required String tokenAddressHex, + required String tokenSymbol, + PlatformInt64? fromBlock, + int? maxPages}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(nodeUrl, serializer); + sse_encode_String(accountAddressHex, serializer); + sse_encode_String(tokenAddressHex, serializer); + sse_encode_String(tokenSymbol, serializer); + sse_encode_opt_box_autoadd_i_64(fromBlock, serializer); + sse_encode_opt_box_autoadd_i_32(maxPages, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 9, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_transfer_history_response, + decodeErrorData: null, + ), + constMeta: kCrateApiStarknetFetchTransferHistoryConstMeta, + argValues: [ + nodeUrl, + accountAddressHex, + tokenAddressHex, + tokenSymbol, + fromBlock, + maxPages + ], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiStarknetFetchTransferHistoryConstMeta => + const TaskConstMeta( + debugName: "fetch_transfer_history", + argNames: [ + "nodeUrl", + "accountAddressHex", + "tokenAddressHex", + "tokenSymbol", + "fromBlock", + "maxPages" + ], + ); + + @override + Future crateApiStarknetGetBlockNumber( + {required String nodeUrl}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(nodeUrl, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 10, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_i_64_response, + decodeErrorData: null, + ), + constMeta: kCrateApiStarknetGetBlockNumberConstMeta, + argValues: [nodeUrl], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiStarknetGetBlockNumberConstMeta => + const TaskConstMeta( + debugName: "get_block_number", + argNames: ["nodeUrl"], + ); + + @override + Future + crateApiStarknetGetExecuteTransactionHashesExternalSigner( + {required String nodeUrl, + required String publicKeyHex, + required String accountAddressHex, + required String accountClassHashHex, + required List calls, + required int feePriorityRaw, + String? chainIdHex}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(nodeUrl, serializer); + sse_encode_String(publicKeyHex, serializer); + sse_encode_String(accountAddressHex, serializer); + sse_encode_String(accountClassHashHex, serializer); + sse_encode_list_starknet_call_input(calls, serializer); + sse_encode_i_32(feePriorityRaw, serializer); + sse_encode_opt_String(chainIdHex, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 11, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_execution_plan_response, + decodeErrorData: null, + ), + constMeta: + kCrateApiStarknetGetExecuteTransactionHashesExternalSignerConstMeta, + argValues: [ + nodeUrl, + publicKeyHex, + accountAddressHex, + accountClassHashHex, + calls, + feePriorityRaw, + chainIdHex + ], + apiImpl: this, + )); + } + + TaskConstMeta + get kCrateApiStarknetGetExecuteTransactionHashesExternalSignerConstMeta => + const TaskConstMeta( + debugName: "get_execute_transaction_hashes_external_signer", + argNames: [ + "nodeUrl", + "publicKeyHex", + "accountAddressHex", + "accountClassHashHex", + "calls", + "feePriorityRaw", + "chainIdHex" + ], + ); + + @override + Future crateApiStarknetGetTokenBalance( + {required String nodeUrl, + required String accountAddressHex, + required String tokenAddressHex}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(nodeUrl, serializer); + sse_encode_String(accountAddressHex, serializer); + sse_encode_String(tokenAddressHex, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 12, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_string_response, + decodeErrorData: null, + ), + constMeta: kCrateApiStarknetGetTokenBalanceConstMeta, + argValues: [nodeUrl, accountAddressHex, tokenAddressHex], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiStarknetGetTokenBalanceConstMeta => + const TaskConstMeta( + debugName: "get_token_balance", + argNames: ["nodeUrl", "accountAddressHex", "tokenAddressHex"], + ); + + @override + Future crateApiStarknetGetTokenMetadata( + {required String nodeUrl, required String tokenAddressHex}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(nodeUrl, serializer); + sse_encode_String(tokenAddressHex, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 13, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_token_metadata_response, + decodeErrorData: null, + ), + constMeta: kCrateApiStarknetGetTokenMetadataConstMeta, + argValues: [nodeUrl, tokenAddressHex], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiStarknetGetTokenMetadataConstMeta => + const TaskConstMeta( + debugName: "get_token_metadata", + argNames: ["nodeUrl", "tokenAddressHex"], + ); + + @override + Future crateApiStarknetGetTransactionDetails( + {required String nodeUrl, required String transactionHashHex}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(nodeUrl, serializer); + sse_encode_String(transactionHashHex, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 14, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_transaction_details_response, + decodeErrorData: null, + ), + constMeta: kCrateApiStarknetGetTransactionDetailsConstMeta, + argValues: [nodeUrl, transactionHashHex], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiStarknetGetTransactionDetailsConstMeta => + const TaskConstMeta( + debugName: "get_transaction_details", + argNames: ["nodeUrl", "transactionHashHex"], + ); + + @override + Future crateApiStarknetGetTypedDataMessageHash( + {required String accountAddressHex, required String typedDataJson}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(accountAddressHex, serializer); + sse_encode_String(typedDataJson, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 15, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_string_response, + decodeErrorData: null, + ), + constMeta: kCrateApiStarknetGetTypedDataMessageHashConstMeta, + argValues: [accountAddressHex, typedDataJson], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiStarknetGetTypedDataMessageHashConstMeta => + const TaskConstMeta( + debugName: "get_typed_data_message_hash", + argNames: ["accountAddressHex", "typedDataJson"], + ); + + @override + Future crateApiStarknetInitApp() { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 16, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: null, + ), + constMeta: kCrateApiStarknetInitAppConstMeta, + argValues: [], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiStarknetInitAppConstMeta => const TaskConstMeta( + debugName: "init_app", + argNames: [], + ); + + @override + Future crateApiStarknetIsAccountDeployed( + {required String nodeUrl, required String accountAddressHex}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(nodeUrl, serializer); + sse_encode_String(accountAddressHex, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 17, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_bool_response, + decodeErrorData: null, + ), + constMeta: kCrateApiStarknetIsAccountDeployedConstMeta, + argValues: [nodeUrl, accountAddressHex], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiStarknetIsAccountDeployedConstMeta => + const TaskConstMeta( + debugName: "is_account_deployed", + argNames: ["nodeUrl", "accountAddressHex"], + ); + + @override + Future crateApiStarknetSendTransfer( + {required String nodeUrl, + required String privateKeyHex, + required String accountAddressHex, + required String recipientAddressHex, + required String tokenAddressHex, + required String amountWei, + required String accountClassHashHex, + String? chainIdHex}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(nodeUrl, serializer); + sse_encode_String(privateKeyHex, serializer); + sse_encode_String(accountAddressHex, serializer); + sse_encode_String(recipientAddressHex, serializer); + sse_encode_String(tokenAddressHex, serializer); + sse_encode_String(amountWei, serializer); + sse_encode_String(accountClassHashHex, serializer); + sse_encode_opt_String(chainIdHex, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 18, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_string_response, + decodeErrorData: null, + ), + constMeta: kCrateApiStarknetSendTransferConstMeta, + argValues: [ + nodeUrl, + privateKeyHex, + accountAddressHex, + recipientAddressHex, + tokenAddressHex, + amountWei, + accountClassHashHex, + chainIdHex + ], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiStarknetSendTransferConstMeta => + const TaskConstMeta( + debugName: "send_transfer", + argNames: [ + "nodeUrl", + "privateKeyHex", + "accountAddressHex", + "recipientAddressHex", + "tokenAddressHex", + "amountWei", + "accountClassHashHex", + "chainIdHex" + ], + ); + + @override + Future crateApiStarknetSignMessageHash( + {required String privateKeyHex, required String messageHashHex}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(privateKeyHex, serializer); + sse_encode_String(messageHashHex, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 19, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_starknet_signature_data_response, + decodeErrorData: null, + ), + constMeta: kCrateApiStarknetSignMessageHashConstMeta, + argValues: [privateKeyHex, messageHashHex], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiStarknetSignMessageHashConstMeta => + const TaskConstMeta( + debugName: "sign_message_hash", + argNames: ["privateKeyHex", "messageHashHex"], + ); + + @override + Future crateApiStarknetSignTypedData( + {required String privateKeyHex, + required String accountAddressHex, + required String typedDataJson}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(privateKeyHex, serializer); + sse_encode_String(accountAddressHex, serializer); + sse_encode_String(typedDataJson, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 20, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_string_list_response, + decodeErrorData: null, + ), + constMeta: kCrateApiStarknetSignTypedDataConstMeta, + argValues: [privateKeyHex, accountAddressHex, typedDataJson], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiStarknetSignTypedDataConstMeta => + const TaskConstMeta( + debugName: "sign_typed_data", + argNames: ["privateKeyHex", "accountAddressHex", "typedDataJson"], + ); + + @override + Future crateApiStarknetVerifyMessageHashSignature( + {required String publicKeyHex, + required String messageHashHex, + required String rHex, + required String sHex}) { + return handler.executeNormal(NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(publicKeyHex, serializer); + sse_encode_String(messageHashHex, serializer); + sse_encode_String(rHex, serializer); + sse_encode_String(sHex, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, + funcId: 21, port: port_); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_bool_response, + decodeErrorData: null, + ), + constMeta: kCrateApiStarknetVerifyMessageHashSignatureConstMeta, + argValues: [publicKeyHex, messageHashHex, rHex, sHex], + apiImpl: this, + )); + } + + TaskConstMeta get kCrateApiStarknetVerifyMessageHashSignatureConstMeta => + const TaskConstMeta( + debugName: "verify_message_hash_signature", + argNames: ["publicKeyHex", "messageHashHex", "rHex", "sHex"], + ); + + @protected + String dco_decode_String(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as String; + } + + @protected + bool dco_decode_bool(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as bool; + } + + @protected + BoolResponse dco_decode_bool_response(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return BoolResponse( + value: dco_decode_opt_box_autoadd_bool(arr[0]), + error: dco_decode_opt_String(arr[1]), + ); + } + + @protected + bool dco_decode_box_autoadd_bool(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as bool; + } + + @protected + DerivedAccountData dco_decode_box_autoadd_derived_account_data(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_derived_account_data(raw); + } + + @protected + int dco_decode_box_autoadd_i_32(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as int; + } + + @protected + PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_i_64(raw); + } + + @protected + StarknetExecutionPlanData dco_decode_box_autoadd_starknet_execution_plan_data( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_starknet_execution_plan_data(raw); + } + + @protected + StarknetFeeQuote dco_decode_box_autoadd_starknet_fee_quote(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_starknet_fee_quote(raw); + } + + @protected + StarknetSignatureData dco_decode_box_autoadd_starknet_signature_data( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_starknet_signature_data(raw); + } + + @protected + StarknetTokenMetadata dco_decode_box_autoadd_starknet_token_metadata( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_starknet_token_metadata(raw); + } + + @protected + StarknetTransactionDetails + dco_decode_box_autoadd_starknet_transaction_details(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dco_decode_starknet_transaction_details(raw); + } + + @protected + DerivedAccountData dco_decode_derived_account_data(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 3) + throw Exception('unexpected arr length: expect 3 but see ${arr.length}'); + return DerivedAccountData( + privateKeyHex: dco_decode_String(arr[0]), + publicKeyHex: dco_decode_String(arr[1]), + accountAddressHex: dco_decode_String(arr[2]), + ); + } + + @protected + DerivedAccountDataResponse dco_decode_derived_account_data_response( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return DerivedAccountDataResponse( + value: dco_decode_opt_box_autoadd_derived_account_data(arr[0]), + error: dco_decode_opt_String(arr[1]), + ); + } + + @protected + ExecutionPlanResponse dco_decode_execution_plan_response(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return ExecutionPlanResponse( + value: dco_decode_opt_box_autoadd_starknet_execution_plan_data(arr[0]), + error: dco_decode_opt_String(arr[1]), + ); + } + + @protected + FeeQuoteResponse dco_decode_fee_quote_response(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return FeeQuoteResponse( + value: dco_decode_opt_box_autoadd_starknet_fee_quote(arr[0]), + error: dco_decode_opt_String(arr[1]), + ); + } + + @protected + int dco_decode_i_32(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as int; + } + + @protected + PlatformInt64 dco_decode_i_64(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dcoDecodeI64(raw); + } + + @protected + I64Response dco_decode_i_64_response(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return I64Response( + value: dco_decode_opt_box_autoadd_i_64(arr[0]), + error: dco_decode_opt_String(arr[1]), + ); + } + + @protected + List dco_decode_list_String(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return (raw as List).map(dco_decode_String).toList(); + } + + @protected + Uint8List dco_decode_list_prim_u_8_strict(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as Uint8List; + } + + @protected + List dco_decode_list_starknet_call_input(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return (raw as List).map(dco_decode_starknet_call_input).toList(); + } + + @protected + List dco_decode_list_transfer_history_item(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return (raw as List) + .map(dco_decode_transfer_history_item) + .toList(); + } + + @protected + String? dco_decode_opt_String(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null ? null : dco_decode_String(raw); + } + + @protected + bool? dco_decode_opt_box_autoadd_bool(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null ? null : dco_decode_box_autoadd_bool(raw); + } + + @protected + DerivedAccountData? dco_decode_opt_box_autoadd_derived_account_data( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null + ? null + : dco_decode_box_autoadd_derived_account_data(raw); + } + + @protected + int? dco_decode_opt_box_autoadd_i_32(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null ? null : dco_decode_box_autoadd_i_32(raw); + } + + @protected + PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null ? null : dco_decode_box_autoadd_i_64(raw); + } + + @protected + StarknetExecutionPlanData? + dco_decode_opt_box_autoadd_starknet_execution_plan_data(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null + ? null + : dco_decode_box_autoadd_starknet_execution_plan_data(raw); + } + + @protected + StarknetFeeQuote? dco_decode_opt_box_autoadd_starknet_fee_quote(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null ? null : dco_decode_box_autoadd_starknet_fee_quote(raw); + } + + @protected + StarknetSignatureData? dco_decode_opt_box_autoadd_starknet_signature_data( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null + ? null + : dco_decode_box_autoadd_starknet_signature_data(raw); + } + + @protected + StarknetTokenMetadata? dco_decode_opt_box_autoadd_starknet_token_metadata( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null + ? null + : dco_decode_box_autoadd_starknet_token_metadata(raw); + } + + @protected + StarknetTransactionDetails? + dco_decode_opt_box_autoadd_starknet_transaction_details(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null + ? null + : dco_decode_box_autoadd_starknet_transaction_details(raw); + } + + @protected + StarknetCallInput dco_decode_starknet_call_input(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 3) + throw Exception('unexpected arr length: expect 3 but see ${arr.length}'); + return StarknetCallInput( + contractAddressHex: dco_decode_String(arr[0]), + entrypoint: dco_decode_String(arr[1]), + calldataHex: dco_decode_list_String(arr[2]), + ); + } + + @protected + StarknetExecutionPlanData dco_decode_starknet_execution_plan_data( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 4) + throw Exception('unexpected arr length: expect 4 but see ${arr.length}'); + return StarknetExecutionPlanData( + invokeTransactionHashHex: dco_decode_String(arr[0]), + deployAccountTransactionHashHex: dco_decode_opt_String(arr[1]), + accountDeploymentRequired: dco_decode_bool(arr[2]), + planJson: dco_decode_String(arr[3]), + ); + } + + @protected + StarknetFeeQuote dco_decode_starknet_fee_quote(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 4) + throw Exception('unexpected arr length: expect 4 but see ${arr.length}'); + return StarknetFeeQuote( + overallFeeWei: dco_decode_String(arr[0]), + executionFeeWei: dco_decode_String(arr[1]), + deployAccountFeeWei: dco_decode_opt_String(arr[2]), + accountDeploymentRequired: dco_decode_bool(arr[3]), + ); + } + + @protected + StarknetSignatureData dco_decode_starknet_signature_data(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return StarknetSignatureData( + rHex: dco_decode_String(arr[0]), + sHex: dco_decode_String(arr[1]), + ); + } + + @protected + StarknetSignatureDataResponse dco_decode_starknet_signature_data_response( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return StarknetSignatureDataResponse( + value: dco_decode_opt_box_autoadd_starknet_signature_data(arr[0]), + error: dco_decode_opt_String(arr[1]), + ); + } + + @protected + StarknetTokenMetadata dco_decode_starknet_token_metadata(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 4) + throw Exception('unexpected arr length: expect 4 but see ${arr.length}'); + return StarknetTokenMetadata( + tokenAddressHex: dco_decode_String(arr[0]), + name: dco_decode_String(arr[1]), + symbol: dco_decode_String(arr[2]), + decimals: dco_decode_i_32(arr[3]), + ); + } + + @protected + StarknetTransactionDetails dco_decode_starknet_transaction_details( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 22) + throw Exception('unexpected arr length: expect 22 but see ${arr.length}'); + return StarknetTransactionDetails( + transactionHash: dco_decode_String(arr[0]), + transactionType: dco_decode_String(arr[1]), + isPending: dco_decode_bool(arr[2]), + blockNumber: dco_decode_opt_box_autoadd_i_64(arr[3]), + blockTimestamp: dco_decode_opt_box_autoadd_i_64(arr[4]), + actualFeeWei: dco_decode_opt_String(arr[5]), + actionName: dco_decode_opt_String(arr[6]), + callCount: dco_decode_opt_box_autoadd_i_32(arr[7]), + primaryContractAddressHex: dco_decode_opt_String(arr[8]), + primaryEntrypoint: dco_decode_opt_String(arr[9]), + senderAddressHex: dco_decode_opt_String(arr[10]), + finalityStatus: dco_decode_opt_String(arr[11]), + executionStatus: dco_decode_opt_String(arr[12]), + revertReason: dco_decode_opt_String(arr[13]), + accountDeploymentRequired: dco_decode_bool(arr[14]), + l1GasMaxAmount: dco_decode_opt_String(arr[15]), + l1GasMaxPriceWei: dco_decode_opt_String(arr[16]), + l2GasMaxAmount: dco_decode_opt_String(arr[17]), + l2GasMaxPriceWei: dco_decode_opt_String(arr[18]), + l1DataGasMaxAmount: dco_decode_opt_String(arr[19]), + l1DataGasMaxPriceWei: dco_decode_opt_String(arr[20]), + tip: dco_decode_opt_box_autoadd_i_64(arr[21]), + ); + } + + @protected + StringListResponse dco_decode_string_list_response(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return StringListResponse( + items: dco_decode_list_String(arr[0]), + error: dco_decode_opt_String(arr[1]), + ); + } + + @protected + StringResponse dco_decode_string_response(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return StringResponse( + value: dco_decode_opt_String(arr[0]), + error: dco_decode_opt_String(arr[1]), + ); + } + + @protected + TokenMetadataResponse dco_decode_token_metadata_response(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return TokenMetadataResponse( + value: dco_decode_opt_box_autoadd_starknet_token_metadata(arr[0]), + error: dco_decode_opt_String(arr[1]), + ); + } + + @protected + TransactionDetailsResponse dco_decode_transaction_details_response( + dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return TransactionDetailsResponse( + value: dco_decode_opt_box_autoadd_starknet_transaction_details(arr[0]), + error: dco_decode_opt_String(arr[1]), + ); + } + + @protected + TransferHistoryItem dco_decode_transfer_history_item(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 12) + throw Exception('unexpected arr length: expect 12 but see ${arr.length}'); + return TransferHistoryItem( + transactionHash: dco_decode_String(arr[0]), + eventId: dco_decode_String(arr[1]), + eventIndex: dco_decode_i_64(arr[2]), + blockNumber: dco_decode_opt_box_autoadd_i_64(arr[3]), + from: dco_decode_String(arr[4]), + to: dco_decode_String(arr[5]), + amountWei: dco_decode_String(arr[6]), + isOutgoing: dco_decode_bool(arr[7]), + tokenSymbol: dco_decode_String(arr[8]), + tokenAddressHex: dco_decode_String(arr[9]), + blockTimestamp: dco_decode_opt_box_autoadd_i_64(arr[10]), + txFeeWei: dco_decode_opt_String(arr[11]), + ); + } + + @protected + TransferHistoryResponse dco_decode_transfer_history_response(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) + throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return TransferHistoryResponse( + items: dco_decode_list_transfer_history_item(arr[0]), + error: dco_decode_opt_String(arr[1]), + ); + } + + @protected + int dco_decode_u_8(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as int; + } + + @protected + void dco_decode_unit(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return; + } + + @protected + String sse_decode_String(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var inner = sse_decode_list_prim_u_8_strict(deserializer); + return utf8.decoder.convert(inner); + } + + @protected + bool sse_decode_bool(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getUint8() != 0; + } + + @protected + BoolResponse sse_decode_bool_response(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_value = sse_decode_opt_box_autoadd_bool(deserializer); + var var_error = sse_decode_opt_String(deserializer); + return BoolResponse(value: var_value, error: var_error); + } + + @protected + bool sse_decode_box_autoadd_bool(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_bool(deserializer)); + } + + @protected + DerivedAccountData sse_decode_box_autoadd_derived_account_data( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_derived_account_data(deserializer)); + } + + @protected + int sse_decode_box_autoadd_i_32(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_i_32(deserializer)); + } + + @protected + PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_i_64(deserializer)); + } + + @protected + StarknetExecutionPlanData sse_decode_box_autoadd_starknet_execution_plan_data( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_starknet_execution_plan_data(deserializer)); + } + + @protected + StarknetFeeQuote sse_decode_box_autoadd_starknet_fee_quote( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_starknet_fee_quote(deserializer)); + } + + @protected + StarknetSignatureData sse_decode_box_autoadd_starknet_signature_data( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_starknet_signature_data(deserializer)); + } + + @protected + StarknetTokenMetadata sse_decode_box_autoadd_starknet_token_metadata( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_starknet_token_metadata(deserializer)); + } + + @protected + StarknetTransactionDetails + sse_decode_box_autoadd_starknet_transaction_details( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_starknet_transaction_details(deserializer)); + } + + @protected + DerivedAccountData sse_decode_derived_account_data( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_privateKeyHex = sse_decode_String(deserializer); + var var_publicKeyHex = sse_decode_String(deserializer); + var var_accountAddressHex = sse_decode_String(deserializer); + return DerivedAccountData( + privateKeyHex: var_privateKeyHex, + publicKeyHex: var_publicKeyHex, + accountAddressHex: var_accountAddressHex); + } + + @protected + DerivedAccountDataResponse sse_decode_derived_account_data_response( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_value = + sse_decode_opt_box_autoadd_derived_account_data(deserializer); + var var_error = sse_decode_opt_String(deserializer); + return DerivedAccountDataResponse(value: var_value, error: var_error); + } + + @protected + ExecutionPlanResponse sse_decode_execution_plan_response( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_value = + sse_decode_opt_box_autoadd_starknet_execution_plan_data(deserializer); + var var_error = sse_decode_opt_String(deserializer); + return ExecutionPlanResponse(value: var_value, error: var_error); + } + + @protected + FeeQuoteResponse sse_decode_fee_quote_response(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_value = sse_decode_opt_box_autoadd_starknet_fee_quote(deserializer); + var var_error = sse_decode_opt_String(deserializer); + return FeeQuoteResponse(value: var_value, error: var_error); + } + + @protected + int sse_decode_i_32(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getInt32(); + } + + @protected + PlatformInt64 sse_decode_i_64(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getPlatformInt64(); + } + + @protected + I64Response sse_decode_i_64_response(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_value = sse_decode_opt_box_autoadd_i_64(deserializer); + var var_error = sse_decode_opt_String(deserializer); + return I64Response(value: var_value, error: var_error); + } + + @protected + List sse_decode_list_String(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var len_ = sse_decode_i_32(deserializer); + var ans_ = []; + for (var idx_ = 0; idx_ < len_; ++idx_) { + ans_.add(sse_decode_String(deserializer)); + } + return ans_; + } + + @protected + Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var len_ = sse_decode_i_32(deserializer); + return deserializer.buffer.getUint8List(len_); + } + + @protected + List sse_decode_list_starknet_call_input( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var len_ = sse_decode_i_32(deserializer); + var ans_ = []; + for (var idx_ = 0; idx_ < len_; ++idx_) { + ans_.add(sse_decode_starknet_call_input(deserializer)); + } + return ans_; + } + + @protected + List sse_decode_list_transfer_history_item( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var len_ = sse_decode_i_32(deserializer); + var ans_ = []; + for (var idx_ = 0; idx_ < len_; ++idx_) { + ans_.add(sse_decode_transfer_history_item(deserializer)); + } + return ans_; + } + + @protected + String? sse_decode_opt_String(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_String(deserializer)); + } else { + return null; + } + } + + @protected + bool? sse_decode_opt_box_autoadd_bool(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_box_autoadd_bool(deserializer)); + } else { + return null; + } + } + + @protected + DerivedAccountData? sse_decode_opt_box_autoadd_derived_account_data( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_box_autoadd_derived_account_data(deserializer)); + } else { + return null; + } + } + + @protected + int? sse_decode_opt_box_autoadd_i_32(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_box_autoadd_i_32(deserializer)); + } else { + return null; + } + } + + @protected + PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_box_autoadd_i_64(deserializer)); + } else { + return null; + } + } + + @protected + StarknetExecutionPlanData? + sse_decode_opt_box_autoadd_starknet_execution_plan_data( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_box_autoadd_starknet_execution_plan_data( + deserializer)); + } else { + return null; + } + } + + @protected + StarknetFeeQuote? sse_decode_opt_box_autoadd_starknet_fee_quote( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_box_autoadd_starknet_fee_quote(deserializer)); + } else { + return null; + } + } + + @protected + StarknetSignatureData? sse_decode_opt_box_autoadd_starknet_signature_data( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_box_autoadd_starknet_signature_data(deserializer)); + } else { + return null; + } + } + + @protected + StarknetTokenMetadata? sse_decode_opt_box_autoadd_starknet_token_metadata( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_box_autoadd_starknet_token_metadata(deserializer)); + } else { + return null; + } + } + + @protected + StarknetTransactionDetails? + sse_decode_opt_box_autoadd_starknet_transaction_details( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_box_autoadd_starknet_transaction_details( + deserializer)); + } else { + return null; + } + } + + @protected + StarknetCallInput sse_decode_starknet_call_input( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_contractAddressHex = sse_decode_String(deserializer); + var var_entrypoint = sse_decode_String(deserializer); + var var_calldataHex = sse_decode_list_String(deserializer); + return StarknetCallInput( + contractAddressHex: var_contractAddressHex, + entrypoint: var_entrypoint, + calldataHex: var_calldataHex); + } + + @protected + StarknetExecutionPlanData sse_decode_starknet_execution_plan_data( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_invokeTransactionHashHex = sse_decode_String(deserializer); + var var_deployAccountTransactionHashHex = + sse_decode_opt_String(deserializer); + var var_accountDeploymentRequired = sse_decode_bool(deserializer); + var var_planJson = sse_decode_String(deserializer); + return StarknetExecutionPlanData( + invokeTransactionHashHex: var_invokeTransactionHashHex, + deployAccountTransactionHashHex: var_deployAccountTransactionHashHex, + accountDeploymentRequired: var_accountDeploymentRequired, + planJson: var_planJson); + } + + @protected + StarknetFeeQuote sse_decode_starknet_fee_quote(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_overallFeeWei = sse_decode_String(deserializer); + var var_executionFeeWei = sse_decode_String(deserializer); + var var_deployAccountFeeWei = sse_decode_opt_String(deserializer); + var var_accountDeploymentRequired = sse_decode_bool(deserializer); + return StarknetFeeQuote( + overallFeeWei: var_overallFeeWei, + executionFeeWei: var_executionFeeWei, + deployAccountFeeWei: var_deployAccountFeeWei, + accountDeploymentRequired: var_accountDeploymentRequired); + } + + @protected + StarknetSignatureData sse_decode_starknet_signature_data( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_rHex = sse_decode_String(deserializer); + var var_sHex = sse_decode_String(deserializer); + return StarknetSignatureData(rHex: var_rHex, sHex: var_sHex); + } + + @protected + StarknetSignatureDataResponse sse_decode_starknet_signature_data_response( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_value = + sse_decode_opt_box_autoadd_starknet_signature_data(deserializer); + var var_error = sse_decode_opt_String(deserializer); + return StarknetSignatureDataResponse(value: var_value, error: var_error); + } + + @protected + StarknetTokenMetadata sse_decode_starknet_token_metadata( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_tokenAddressHex = sse_decode_String(deserializer); + var var_name = sse_decode_String(deserializer); + var var_symbol = sse_decode_String(deserializer); + var var_decimals = sse_decode_i_32(deserializer); + return StarknetTokenMetadata( + tokenAddressHex: var_tokenAddressHex, + name: var_name, + symbol: var_symbol, + decimals: var_decimals); + } + + @protected + StarknetTransactionDetails sse_decode_starknet_transaction_details( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_transactionHash = sse_decode_String(deserializer); + var var_transactionType = sse_decode_String(deserializer); + var var_isPending = sse_decode_bool(deserializer); + var var_blockNumber = sse_decode_opt_box_autoadd_i_64(deserializer); + var var_blockTimestamp = sse_decode_opt_box_autoadd_i_64(deserializer); + var var_actualFeeWei = sse_decode_opt_String(deserializer); + var var_actionName = sse_decode_opt_String(deserializer); + var var_callCount = sse_decode_opt_box_autoadd_i_32(deserializer); + var var_primaryContractAddressHex = sse_decode_opt_String(deserializer); + var var_primaryEntrypoint = sse_decode_opt_String(deserializer); + var var_senderAddressHex = sse_decode_opt_String(deserializer); + var var_finalityStatus = sse_decode_opt_String(deserializer); + var var_executionStatus = sse_decode_opt_String(deserializer); + var var_revertReason = sse_decode_opt_String(deserializer); + var var_accountDeploymentRequired = sse_decode_bool(deserializer); + var var_l1GasMaxAmount = sse_decode_opt_String(deserializer); + var var_l1GasMaxPriceWei = sse_decode_opt_String(deserializer); + var var_l2GasMaxAmount = sse_decode_opt_String(deserializer); + var var_l2GasMaxPriceWei = sse_decode_opt_String(deserializer); + var var_l1DataGasMaxAmount = sse_decode_opt_String(deserializer); + var var_l1DataGasMaxPriceWei = sse_decode_opt_String(deserializer); + var var_tip = sse_decode_opt_box_autoadd_i_64(deserializer); + return StarknetTransactionDetails( + transactionHash: var_transactionHash, + transactionType: var_transactionType, + isPending: var_isPending, + blockNumber: var_blockNumber, + blockTimestamp: var_blockTimestamp, + actualFeeWei: var_actualFeeWei, + actionName: var_actionName, + callCount: var_callCount, + primaryContractAddressHex: var_primaryContractAddressHex, + primaryEntrypoint: var_primaryEntrypoint, + senderAddressHex: var_senderAddressHex, + finalityStatus: var_finalityStatus, + executionStatus: var_executionStatus, + revertReason: var_revertReason, + accountDeploymentRequired: var_accountDeploymentRequired, + l1GasMaxAmount: var_l1GasMaxAmount, + l1GasMaxPriceWei: var_l1GasMaxPriceWei, + l2GasMaxAmount: var_l2GasMaxAmount, + l2GasMaxPriceWei: var_l2GasMaxPriceWei, + l1DataGasMaxAmount: var_l1DataGasMaxAmount, + l1DataGasMaxPriceWei: var_l1DataGasMaxPriceWei, + tip: var_tip); + } + + @protected + StringListResponse sse_decode_string_list_response( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_items = sse_decode_list_String(deserializer); + var var_error = sse_decode_opt_String(deserializer); + return StringListResponse(items: var_items, error: var_error); + } + + @protected + StringResponse sse_decode_string_response(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_value = sse_decode_opt_String(deserializer); + var var_error = sse_decode_opt_String(deserializer); + return StringResponse(value: var_value, error: var_error); + } + + @protected + TokenMetadataResponse sse_decode_token_metadata_response( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_value = + sse_decode_opt_box_autoadd_starknet_token_metadata(deserializer); + var var_error = sse_decode_opt_String(deserializer); + return TokenMetadataResponse(value: var_value, error: var_error); + } + + @protected + TransactionDetailsResponse sse_decode_transaction_details_response( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_value = + sse_decode_opt_box_autoadd_starknet_transaction_details(deserializer); + var var_error = sse_decode_opt_String(deserializer); + return TransactionDetailsResponse(value: var_value, error: var_error); + } + + @protected + TransferHistoryItem sse_decode_transfer_history_item( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_transactionHash = sse_decode_String(deserializer); + var var_eventId = sse_decode_String(deserializer); + var var_eventIndex = sse_decode_i_64(deserializer); + var var_blockNumber = sse_decode_opt_box_autoadd_i_64(deserializer); + var var_from = sse_decode_String(deserializer); + var var_to = sse_decode_String(deserializer); + var var_amountWei = sse_decode_String(deserializer); + var var_isOutgoing = sse_decode_bool(deserializer); + var var_tokenSymbol = sse_decode_String(deserializer); + var var_tokenAddressHex = sse_decode_String(deserializer); + var var_blockTimestamp = sse_decode_opt_box_autoadd_i_64(deserializer); + var var_txFeeWei = sse_decode_opt_String(deserializer); + return TransferHistoryItem( + transactionHash: var_transactionHash, + eventId: var_eventId, + eventIndex: var_eventIndex, + blockNumber: var_blockNumber, + from: var_from, + to: var_to, + amountWei: var_amountWei, + isOutgoing: var_isOutgoing, + tokenSymbol: var_tokenSymbol, + tokenAddressHex: var_tokenAddressHex, + blockTimestamp: var_blockTimestamp, + txFeeWei: var_txFeeWei); + } + + @protected + TransferHistoryResponse sse_decode_transfer_history_response( + SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_items = sse_decode_list_transfer_history_item(deserializer); + var var_error = sse_decode_opt_String(deserializer); + return TransferHistoryResponse(items: var_items, error: var_error); + } + + @protected + int sse_decode_u_8(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getUint8(); + } + + @protected + void sse_decode_unit(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + } + + @protected + void sse_encode_String(String self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_list_prim_u_8_strict(utf8.encoder.convert(self), serializer); + } + + @protected + void sse_encode_bool(bool self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putUint8(self ? 1 : 0); + } + + @protected + void sse_encode_bool_response(BoolResponse self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_opt_box_autoadd_bool(self.value, serializer); + sse_encode_opt_String(self.error, serializer); + } + + @protected + void sse_encode_box_autoadd_bool(bool self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_bool(self, serializer); + } + + @protected + void sse_encode_box_autoadd_derived_account_data( + DerivedAccountData self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_derived_account_data(self, serializer); + } + + @protected + void sse_encode_box_autoadd_i_32(int self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self, serializer); + } + + @protected + void sse_encode_box_autoadd_i_64( + PlatformInt64 self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_64(self, serializer); + } + + @protected + void sse_encode_box_autoadd_starknet_execution_plan_data( + StarknetExecutionPlanData self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_starknet_execution_plan_data(self, serializer); + } + + @protected + void sse_encode_box_autoadd_starknet_fee_quote( + StarknetFeeQuote self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_starknet_fee_quote(self, serializer); + } + + @protected + void sse_encode_box_autoadd_starknet_signature_data( + StarknetSignatureData self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_starknet_signature_data(self, serializer); + } + + @protected + void sse_encode_box_autoadd_starknet_token_metadata( + StarknetTokenMetadata self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_starknet_token_metadata(self, serializer); + } + + @protected + void sse_encode_box_autoadd_starknet_transaction_details( + StarknetTransactionDetails self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_starknet_transaction_details(self, serializer); + } + + @protected + void sse_encode_derived_account_data( + DerivedAccountData self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.privateKeyHex, serializer); + sse_encode_String(self.publicKeyHex, serializer); + sse_encode_String(self.accountAddressHex, serializer); + } + + @protected + void sse_encode_derived_account_data_response( + DerivedAccountDataResponse self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_opt_box_autoadd_derived_account_data(self.value, serializer); + sse_encode_opt_String(self.error, serializer); + } + + @protected + void sse_encode_execution_plan_response( + ExecutionPlanResponse self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_opt_box_autoadd_starknet_execution_plan_data( + self.value, serializer); + sse_encode_opt_String(self.error, serializer); + } + + @protected + void sse_encode_fee_quote_response( + FeeQuoteResponse self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_opt_box_autoadd_starknet_fee_quote(self.value, serializer); + sse_encode_opt_String(self.error, serializer); + } + + @protected + void sse_encode_i_32(int self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putInt32(self); + } + + @protected + void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putPlatformInt64(self); + } + + @protected + void sse_encode_i_64_response(I64Response self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_opt_box_autoadd_i_64(self.value, serializer); + sse_encode_opt_String(self.error, serializer); + } + + @protected + void sse_encode_list_String(List self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + for (final item in self) { + sse_encode_String(item, serializer); + } + } + + @protected + void sse_encode_list_prim_u_8_strict( + Uint8List self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + serializer.buffer.putUint8List(self); + } + + @protected + void sse_encode_list_starknet_call_input( + List self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + for (final item in self) { + sse_encode_starknet_call_input(item, serializer); + } + } + + @protected + void sse_encode_list_transfer_history_item( + List self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + for (final item in self) { + sse_encode_transfer_history_item(item, serializer); + } + } + + @protected + void sse_encode_opt_String(String? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_String(self, serializer); + } + } + + @protected + void sse_encode_opt_box_autoadd_bool(bool? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_box_autoadd_bool(self, serializer); + } + } + + @protected + void sse_encode_opt_box_autoadd_derived_account_data( + DerivedAccountData? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_box_autoadd_derived_account_data(self, serializer); + } + } + + @protected + void sse_encode_opt_box_autoadd_i_32(int? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_box_autoadd_i_32(self, serializer); + } + } + + @protected + void sse_encode_opt_box_autoadd_i_64( + PlatformInt64? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_box_autoadd_i_64(self, serializer); + } + } + + @protected + void sse_encode_opt_box_autoadd_starknet_execution_plan_data( + StarknetExecutionPlanData? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_box_autoadd_starknet_execution_plan_data(self, serializer); + } + } + + @protected + void sse_encode_opt_box_autoadd_starknet_fee_quote( + StarknetFeeQuote? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_box_autoadd_starknet_fee_quote(self, serializer); + } + } + + @protected + void sse_encode_opt_box_autoadd_starknet_signature_data( + StarknetSignatureData? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_box_autoadd_starknet_signature_data(self, serializer); + } + } + + @protected + void sse_encode_opt_box_autoadd_starknet_token_metadata( + StarknetTokenMetadata? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_box_autoadd_starknet_token_metadata(self, serializer); + } + } + + @protected + void sse_encode_opt_box_autoadd_starknet_transaction_details( + StarknetTransactionDetails? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_box_autoadd_starknet_transaction_details(self, serializer); + } + } + + @protected + void sse_encode_starknet_call_input( + StarknetCallInput self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.contractAddressHex, serializer); + sse_encode_String(self.entrypoint, serializer); + sse_encode_list_String(self.calldataHex, serializer); + } + + @protected + void sse_encode_starknet_execution_plan_data( + StarknetExecutionPlanData self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.invokeTransactionHashHex, serializer); + sse_encode_opt_String(self.deployAccountTransactionHashHex, serializer); + sse_encode_bool(self.accountDeploymentRequired, serializer); + sse_encode_String(self.planJson, serializer); + } + + @protected + void sse_encode_starknet_fee_quote( + StarknetFeeQuote self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.overallFeeWei, serializer); + sse_encode_String(self.executionFeeWei, serializer); + sse_encode_opt_String(self.deployAccountFeeWei, serializer); + sse_encode_bool(self.accountDeploymentRequired, serializer); + } + + @protected + void sse_encode_starknet_signature_data( + StarknetSignatureData self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.rHex, serializer); + sse_encode_String(self.sHex, serializer); + } + + @protected + void sse_encode_starknet_signature_data_response( + StarknetSignatureDataResponse self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_opt_box_autoadd_starknet_signature_data(self.value, serializer); + sse_encode_opt_String(self.error, serializer); + } + + @protected + void sse_encode_starknet_token_metadata( + StarknetTokenMetadata self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.tokenAddressHex, serializer); + sse_encode_String(self.name, serializer); + sse_encode_String(self.symbol, serializer); + sse_encode_i_32(self.decimals, serializer); + } + + @protected + void sse_encode_starknet_transaction_details( + StarknetTransactionDetails self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.transactionHash, serializer); + sse_encode_String(self.transactionType, serializer); + sse_encode_bool(self.isPending, serializer); + sse_encode_opt_box_autoadd_i_64(self.blockNumber, serializer); + sse_encode_opt_box_autoadd_i_64(self.blockTimestamp, serializer); + sse_encode_opt_String(self.actualFeeWei, serializer); + sse_encode_opt_String(self.actionName, serializer); + sse_encode_opt_box_autoadd_i_32(self.callCount, serializer); + sse_encode_opt_String(self.primaryContractAddressHex, serializer); + sse_encode_opt_String(self.primaryEntrypoint, serializer); + sse_encode_opt_String(self.senderAddressHex, serializer); + sse_encode_opt_String(self.finalityStatus, serializer); + sse_encode_opt_String(self.executionStatus, serializer); + sse_encode_opt_String(self.revertReason, serializer); + sse_encode_bool(self.accountDeploymentRequired, serializer); + sse_encode_opt_String(self.l1GasMaxAmount, serializer); + sse_encode_opt_String(self.l1GasMaxPriceWei, serializer); + sse_encode_opt_String(self.l2GasMaxAmount, serializer); + sse_encode_opt_String(self.l2GasMaxPriceWei, serializer); + sse_encode_opt_String(self.l1DataGasMaxAmount, serializer); + sse_encode_opt_String(self.l1DataGasMaxPriceWei, serializer); + sse_encode_opt_box_autoadd_i_64(self.tip, serializer); + } + + @protected + void sse_encode_string_list_response( + StringListResponse self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_list_String(self.items, serializer); + sse_encode_opt_String(self.error, serializer); + } + + @protected + void sse_encode_string_response( + StringResponse self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_opt_String(self.value, serializer); + sse_encode_opt_String(self.error, serializer); + } + + @protected + void sse_encode_token_metadata_response( + TokenMetadataResponse self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_opt_box_autoadd_starknet_token_metadata(self.value, serializer); + sse_encode_opt_String(self.error, serializer); + } + + @protected + void sse_encode_transaction_details_response( + TransactionDetailsResponse self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_opt_box_autoadd_starknet_transaction_details( + self.value, serializer); + sse_encode_opt_String(self.error, serializer); + } + + @protected + void sse_encode_transfer_history_item( + TransferHistoryItem self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.transactionHash, serializer); + sse_encode_String(self.eventId, serializer); + sse_encode_i_64(self.eventIndex, serializer); + sse_encode_opt_box_autoadd_i_64(self.blockNumber, serializer); + sse_encode_String(self.from, serializer); + sse_encode_String(self.to, serializer); + sse_encode_String(self.amountWei, serializer); + sse_encode_bool(self.isOutgoing, serializer); + sse_encode_String(self.tokenSymbol, serializer); + sse_encode_String(self.tokenAddressHex, serializer); + sse_encode_opt_box_autoadd_i_64(self.blockTimestamp, serializer); + sse_encode_opt_String(self.txFeeWei, serializer); + } + + @protected + void sse_encode_transfer_history_response( + TransferHistoryResponse self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_list_transfer_history_item(self.items, serializer); + sse_encode_opt_String(self.error, serializer); + } + + @protected + void sse_encode_u_8(int self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putUint8(self); + } + + @protected + void sse_encode_unit(void self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + } +} diff --git a/cw_starknet/lib/src/rust/frb_generated.io.dart b/cw_starknet/lib/src/rust/frb_generated.io.dart new file mode 100644 index 0000000000..81ec6bfd7f --- /dev/null +++ b/cw_starknet/lib/src/rust/frb_generated.io.dart @@ -0,0 +1,546 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field + +import 'api/starknet.dart'; +import 'dart:async'; +import 'dart:convert'; +import 'dart:ffi' as ffi; +import 'frb_generated.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_io.dart'; + +abstract class RustLibApiImplPlatform extends BaseApiImpl { + RustLibApiImplPlatform({ + required super.handler, + required super.wire, + required super.generalizedFrbRustBinding, + required super.portManager, + }); + + @protected + String dco_decode_String(dynamic raw); + + @protected + bool dco_decode_bool(dynamic raw); + + @protected + BoolResponse dco_decode_bool_response(dynamic raw); + + @protected + bool dco_decode_box_autoadd_bool(dynamic raw); + + @protected + DerivedAccountData dco_decode_box_autoadd_derived_account_data(dynamic raw); + + @protected + int dco_decode_box_autoadd_i_32(dynamic raw); + + @protected + PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw); + + @protected + StarknetExecutionPlanData dco_decode_box_autoadd_starknet_execution_plan_data( + dynamic raw); + + @protected + StarknetFeeQuote dco_decode_box_autoadd_starknet_fee_quote(dynamic raw); + + @protected + StarknetSignatureData dco_decode_box_autoadd_starknet_signature_data( + dynamic raw); + + @protected + StarknetTokenMetadata dco_decode_box_autoadd_starknet_token_metadata( + dynamic raw); + + @protected + StarknetTransactionDetails + dco_decode_box_autoadd_starknet_transaction_details(dynamic raw); + + @protected + DerivedAccountData dco_decode_derived_account_data(dynamic raw); + + @protected + DerivedAccountDataResponse dco_decode_derived_account_data_response( + dynamic raw); + + @protected + ExecutionPlanResponse dco_decode_execution_plan_response(dynamic raw); + + @protected + FeeQuoteResponse dco_decode_fee_quote_response(dynamic raw); + + @protected + int dco_decode_i_32(dynamic raw); + + @protected + PlatformInt64 dco_decode_i_64(dynamic raw); + + @protected + I64Response dco_decode_i_64_response(dynamic raw); + + @protected + List dco_decode_list_String(dynamic raw); + + @protected + Uint8List dco_decode_list_prim_u_8_strict(dynamic raw); + + @protected + List dco_decode_list_starknet_call_input(dynamic raw); + + @protected + List dco_decode_list_transfer_history_item(dynamic raw); + + @protected + String? dco_decode_opt_String(dynamic raw); + + @protected + bool? dco_decode_opt_box_autoadd_bool(dynamic raw); + + @protected + DerivedAccountData? dco_decode_opt_box_autoadd_derived_account_data( + dynamic raw); + + @protected + int? dco_decode_opt_box_autoadd_i_32(dynamic raw); + + @protected + PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw); + + @protected + StarknetExecutionPlanData? + dco_decode_opt_box_autoadd_starknet_execution_plan_data(dynamic raw); + + @protected + StarknetFeeQuote? dco_decode_opt_box_autoadd_starknet_fee_quote(dynamic raw); + + @protected + StarknetSignatureData? dco_decode_opt_box_autoadd_starknet_signature_data( + dynamic raw); + + @protected + StarknetTokenMetadata? dco_decode_opt_box_autoadd_starknet_token_metadata( + dynamic raw); + + @protected + StarknetTransactionDetails? + dco_decode_opt_box_autoadd_starknet_transaction_details(dynamic raw); + + @protected + StarknetCallInput dco_decode_starknet_call_input(dynamic raw); + + @protected + StarknetExecutionPlanData dco_decode_starknet_execution_plan_data( + dynamic raw); + + @protected + StarknetFeeQuote dco_decode_starknet_fee_quote(dynamic raw); + + @protected + StarknetSignatureData dco_decode_starknet_signature_data(dynamic raw); + + @protected + StarknetSignatureDataResponse dco_decode_starknet_signature_data_response( + dynamic raw); + + @protected + StarknetTokenMetadata dco_decode_starknet_token_metadata(dynamic raw); + + @protected + StarknetTransactionDetails dco_decode_starknet_transaction_details( + dynamic raw); + + @protected + StringListResponse dco_decode_string_list_response(dynamic raw); + + @protected + StringResponse dco_decode_string_response(dynamic raw); + + @protected + TokenMetadataResponse dco_decode_token_metadata_response(dynamic raw); + + @protected + TransactionDetailsResponse dco_decode_transaction_details_response( + dynamic raw); + + @protected + TransferHistoryItem dco_decode_transfer_history_item(dynamic raw); + + @protected + TransferHistoryResponse dco_decode_transfer_history_response(dynamic raw); + + @protected + int dco_decode_u_8(dynamic raw); + + @protected + void dco_decode_unit(dynamic raw); + + @protected + String sse_decode_String(SseDeserializer deserializer); + + @protected + bool sse_decode_bool(SseDeserializer deserializer); + + @protected + BoolResponse sse_decode_bool_response(SseDeserializer deserializer); + + @protected + bool sse_decode_box_autoadd_bool(SseDeserializer deserializer); + + @protected + DerivedAccountData sse_decode_box_autoadd_derived_account_data( + SseDeserializer deserializer); + + @protected + int sse_decode_box_autoadd_i_32(SseDeserializer deserializer); + + @protected + PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer); + + @protected + StarknetExecutionPlanData sse_decode_box_autoadd_starknet_execution_plan_data( + SseDeserializer deserializer); + + @protected + StarknetFeeQuote sse_decode_box_autoadd_starknet_fee_quote( + SseDeserializer deserializer); + + @protected + StarknetSignatureData sse_decode_box_autoadd_starknet_signature_data( + SseDeserializer deserializer); + + @protected + StarknetTokenMetadata sse_decode_box_autoadd_starknet_token_metadata( + SseDeserializer deserializer); + + @protected + StarknetTransactionDetails + sse_decode_box_autoadd_starknet_transaction_details( + SseDeserializer deserializer); + + @protected + DerivedAccountData sse_decode_derived_account_data( + SseDeserializer deserializer); + + @protected + DerivedAccountDataResponse sse_decode_derived_account_data_response( + SseDeserializer deserializer); + + @protected + ExecutionPlanResponse sse_decode_execution_plan_response( + SseDeserializer deserializer); + + @protected + FeeQuoteResponse sse_decode_fee_quote_response(SseDeserializer deserializer); + + @protected + int sse_decode_i_32(SseDeserializer deserializer); + + @protected + PlatformInt64 sse_decode_i_64(SseDeserializer deserializer); + + @protected + I64Response sse_decode_i_64_response(SseDeserializer deserializer); + + @protected + List sse_decode_list_String(SseDeserializer deserializer); + + @protected + Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer); + + @protected + List sse_decode_list_starknet_call_input( + SseDeserializer deserializer); + + @protected + List sse_decode_list_transfer_history_item( + SseDeserializer deserializer); + + @protected + String? sse_decode_opt_String(SseDeserializer deserializer); + + @protected + bool? sse_decode_opt_box_autoadd_bool(SseDeserializer deserializer); + + @protected + DerivedAccountData? sse_decode_opt_box_autoadd_derived_account_data( + SseDeserializer deserializer); + + @protected + int? sse_decode_opt_box_autoadd_i_32(SseDeserializer deserializer); + + @protected + PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer); + + @protected + StarknetExecutionPlanData? + sse_decode_opt_box_autoadd_starknet_execution_plan_data( + SseDeserializer deserializer); + + @protected + StarknetFeeQuote? sse_decode_opt_box_autoadd_starknet_fee_quote( + SseDeserializer deserializer); + + @protected + StarknetSignatureData? sse_decode_opt_box_autoadd_starknet_signature_data( + SseDeserializer deserializer); + + @protected + StarknetTokenMetadata? sse_decode_opt_box_autoadd_starknet_token_metadata( + SseDeserializer deserializer); + + @protected + StarknetTransactionDetails? + sse_decode_opt_box_autoadd_starknet_transaction_details( + SseDeserializer deserializer); + + @protected + StarknetCallInput sse_decode_starknet_call_input( + SseDeserializer deserializer); + + @protected + StarknetExecutionPlanData sse_decode_starknet_execution_plan_data( + SseDeserializer deserializer); + + @protected + StarknetFeeQuote sse_decode_starknet_fee_quote(SseDeserializer deserializer); + + @protected + StarknetSignatureData sse_decode_starknet_signature_data( + SseDeserializer deserializer); + + @protected + StarknetSignatureDataResponse sse_decode_starknet_signature_data_response( + SseDeserializer deserializer); + + @protected + StarknetTokenMetadata sse_decode_starknet_token_metadata( + SseDeserializer deserializer); + + @protected + StarknetTransactionDetails sse_decode_starknet_transaction_details( + SseDeserializer deserializer); + + @protected + StringListResponse sse_decode_string_list_response( + SseDeserializer deserializer); + + @protected + StringResponse sse_decode_string_response(SseDeserializer deserializer); + + @protected + TokenMetadataResponse sse_decode_token_metadata_response( + SseDeserializer deserializer); + + @protected + TransactionDetailsResponse sse_decode_transaction_details_response( + SseDeserializer deserializer); + + @protected + TransferHistoryItem sse_decode_transfer_history_item( + SseDeserializer deserializer); + + @protected + TransferHistoryResponse sse_decode_transfer_history_response( + SseDeserializer deserializer); + + @protected + int sse_decode_u_8(SseDeserializer deserializer); + + @protected + void sse_decode_unit(SseDeserializer deserializer); + + @protected + void sse_encode_String(String self, SseSerializer serializer); + + @protected + void sse_encode_bool(bool self, SseSerializer serializer); + + @protected + void sse_encode_bool_response(BoolResponse self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_bool(bool self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_derived_account_data( + DerivedAccountData self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_i_32(int self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_i_64( + PlatformInt64 self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_starknet_execution_plan_data( + StarknetExecutionPlanData self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_starknet_fee_quote( + StarknetFeeQuote self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_starknet_signature_data( + StarknetSignatureData self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_starknet_token_metadata( + StarknetTokenMetadata self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_starknet_transaction_details( + StarknetTransactionDetails self, SseSerializer serializer); + + @protected + void sse_encode_derived_account_data( + DerivedAccountData self, SseSerializer serializer); + + @protected + void sse_encode_derived_account_data_response( + DerivedAccountDataResponse self, SseSerializer serializer); + + @protected + void sse_encode_execution_plan_response( + ExecutionPlanResponse self, SseSerializer serializer); + + @protected + void sse_encode_fee_quote_response( + FeeQuoteResponse self, SseSerializer serializer); + + @protected + void sse_encode_i_32(int self, SseSerializer serializer); + + @protected + void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer); + + @protected + void sse_encode_i_64_response(I64Response self, SseSerializer serializer); + + @protected + void sse_encode_list_String(List self, SseSerializer serializer); + + @protected + void sse_encode_list_prim_u_8_strict( + Uint8List self, SseSerializer serializer); + + @protected + void sse_encode_list_starknet_call_input( + List self, SseSerializer serializer); + + @protected + void sse_encode_list_transfer_history_item( + List self, SseSerializer serializer); + + @protected + void sse_encode_opt_String(String? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_bool(bool? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_derived_account_data( + DerivedAccountData? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_i_32(int? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_i_64( + PlatformInt64? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_starknet_execution_plan_data( + StarknetExecutionPlanData? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_starknet_fee_quote( + StarknetFeeQuote? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_starknet_signature_data( + StarknetSignatureData? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_starknet_token_metadata( + StarknetTokenMetadata? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_starknet_transaction_details( + StarknetTransactionDetails? self, SseSerializer serializer); + + @protected + void sse_encode_starknet_call_input( + StarknetCallInput self, SseSerializer serializer); + + @protected + void sse_encode_starknet_execution_plan_data( + StarknetExecutionPlanData self, SseSerializer serializer); + + @protected + void sse_encode_starknet_fee_quote( + StarknetFeeQuote self, SseSerializer serializer); + + @protected + void sse_encode_starknet_signature_data( + StarknetSignatureData self, SseSerializer serializer); + + @protected + void sse_encode_starknet_signature_data_response( + StarknetSignatureDataResponse self, SseSerializer serializer); + + @protected + void sse_encode_starknet_token_metadata( + StarknetTokenMetadata self, SseSerializer serializer); + + @protected + void sse_encode_starknet_transaction_details( + StarknetTransactionDetails self, SseSerializer serializer); + + @protected + void sse_encode_string_list_response( + StringListResponse self, SseSerializer serializer); + + @protected + void sse_encode_string_response( + StringResponse self, SseSerializer serializer); + + @protected + void sse_encode_token_metadata_response( + TokenMetadataResponse self, SseSerializer serializer); + + @protected + void sse_encode_transaction_details_response( + TransactionDetailsResponse self, SseSerializer serializer); + + @protected + void sse_encode_transfer_history_item( + TransferHistoryItem self, SseSerializer serializer); + + @protected + void sse_encode_transfer_history_response( + TransferHistoryResponse self, SseSerializer serializer); + + @protected + void sse_encode_u_8(int self, SseSerializer serializer); + + @protected + void sse_encode_unit(void self, SseSerializer serializer); +} + +// Section: wire_class + +class RustLibWire implements BaseWire { + factory RustLibWire.fromExternalLibrary(ExternalLibrary lib) => + RustLibWire(lib.ffiDynamicLibrary); + + /// Holds the symbol lookup function. + final ffi.Pointer Function(String symbolName) + _lookup; + + /// The symbols are looked up in [dynamicLibrary]. + RustLibWire(ffi.DynamicLibrary dynamicLibrary) + : _lookup = dynamicLibrary.lookup; +} diff --git a/cw_starknet/lib/src/rust/frb_generated.web.dart b/cw_starknet/lib/src/rust/frb_generated.web.dart new file mode 100644 index 0000000000..658fcaa51f --- /dev/null +++ b/cw_starknet/lib/src/rust/frb_generated.web.dart @@ -0,0 +1,546 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field + +// Static analysis wrongly picks the IO variant, thus ignore this +// ignore_for_file: argument_type_not_assignable + +import 'api/starknet.dart'; +import 'dart:async'; +import 'dart:convert'; +import 'frb_generated.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_web.dart'; + +abstract class RustLibApiImplPlatform extends BaseApiImpl { + RustLibApiImplPlatform({ + required super.handler, + required super.wire, + required super.generalizedFrbRustBinding, + required super.portManager, + }); + + @protected + String dco_decode_String(dynamic raw); + + @protected + bool dco_decode_bool(dynamic raw); + + @protected + BoolResponse dco_decode_bool_response(dynamic raw); + + @protected + bool dco_decode_box_autoadd_bool(dynamic raw); + + @protected + DerivedAccountData dco_decode_box_autoadd_derived_account_data(dynamic raw); + + @protected + int dco_decode_box_autoadd_i_32(dynamic raw); + + @protected + PlatformInt64 dco_decode_box_autoadd_i_64(dynamic raw); + + @protected + StarknetExecutionPlanData dco_decode_box_autoadd_starknet_execution_plan_data( + dynamic raw); + + @protected + StarknetFeeQuote dco_decode_box_autoadd_starknet_fee_quote(dynamic raw); + + @protected + StarknetSignatureData dco_decode_box_autoadd_starknet_signature_data( + dynamic raw); + + @protected + StarknetTokenMetadata dco_decode_box_autoadd_starknet_token_metadata( + dynamic raw); + + @protected + StarknetTransactionDetails + dco_decode_box_autoadd_starknet_transaction_details(dynamic raw); + + @protected + DerivedAccountData dco_decode_derived_account_data(dynamic raw); + + @protected + DerivedAccountDataResponse dco_decode_derived_account_data_response( + dynamic raw); + + @protected + ExecutionPlanResponse dco_decode_execution_plan_response(dynamic raw); + + @protected + FeeQuoteResponse dco_decode_fee_quote_response(dynamic raw); + + @protected + int dco_decode_i_32(dynamic raw); + + @protected + PlatformInt64 dco_decode_i_64(dynamic raw); + + @protected + I64Response dco_decode_i_64_response(dynamic raw); + + @protected + List dco_decode_list_String(dynamic raw); + + @protected + Uint8List dco_decode_list_prim_u_8_strict(dynamic raw); + + @protected + List dco_decode_list_starknet_call_input(dynamic raw); + + @protected + List dco_decode_list_transfer_history_item(dynamic raw); + + @protected + String? dco_decode_opt_String(dynamic raw); + + @protected + bool? dco_decode_opt_box_autoadd_bool(dynamic raw); + + @protected + DerivedAccountData? dco_decode_opt_box_autoadd_derived_account_data( + dynamic raw); + + @protected + int? dco_decode_opt_box_autoadd_i_32(dynamic raw); + + @protected + PlatformInt64? dco_decode_opt_box_autoadd_i_64(dynamic raw); + + @protected + StarknetExecutionPlanData? + dco_decode_opt_box_autoadd_starknet_execution_plan_data(dynamic raw); + + @protected + StarknetFeeQuote? dco_decode_opt_box_autoadd_starknet_fee_quote(dynamic raw); + + @protected + StarknetSignatureData? dco_decode_opt_box_autoadd_starknet_signature_data( + dynamic raw); + + @protected + StarknetTokenMetadata? dco_decode_opt_box_autoadd_starknet_token_metadata( + dynamic raw); + + @protected + StarknetTransactionDetails? + dco_decode_opt_box_autoadd_starknet_transaction_details(dynamic raw); + + @protected + StarknetCallInput dco_decode_starknet_call_input(dynamic raw); + + @protected + StarknetExecutionPlanData dco_decode_starknet_execution_plan_data( + dynamic raw); + + @protected + StarknetFeeQuote dco_decode_starknet_fee_quote(dynamic raw); + + @protected + StarknetSignatureData dco_decode_starknet_signature_data(dynamic raw); + + @protected + StarknetSignatureDataResponse dco_decode_starknet_signature_data_response( + dynamic raw); + + @protected + StarknetTokenMetadata dco_decode_starknet_token_metadata(dynamic raw); + + @protected + StarknetTransactionDetails dco_decode_starknet_transaction_details( + dynamic raw); + + @protected + StringListResponse dco_decode_string_list_response(dynamic raw); + + @protected + StringResponse dco_decode_string_response(dynamic raw); + + @protected + TokenMetadataResponse dco_decode_token_metadata_response(dynamic raw); + + @protected + TransactionDetailsResponse dco_decode_transaction_details_response( + dynamic raw); + + @protected + TransferHistoryItem dco_decode_transfer_history_item(dynamic raw); + + @protected + TransferHistoryResponse dco_decode_transfer_history_response(dynamic raw); + + @protected + int dco_decode_u_8(dynamic raw); + + @protected + void dco_decode_unit(dynamic raw); + + @protected + String sse_decode_String(SseDeserializer deserializer); + + @protected + bool sse_decode_bool(SseDeserializer deserializer); + + @protected + BoolResponse sse_decode_bool_response(SseDeserializer deserializer); + + @protected + bool sse_decode_box_autoadd_bool(SseDeserializer deserializer); + + @protected + DerivedAccountData sse_decode_box_autoadd_derived_account_data( + SseDeserializer deserializer); + + @protected + int sse_decode_box_autoadd_i_32(SseDeserializer deserializer); + + @protected + PlatformInt64 sse_decode_box_autoadd_i_64(SseDeserializer deserializer); + + @protected + StarknetExecutionPlanData sse_decode_box_autoadd_starknet_execution_plan_data( + SseDeserializer deserializer); + + @protected + StarknetFeeQuote sse_decode_box_autoadd_starknet_fee_quote( + SseDeserializer deserializer); + + @protected + StarknetSignatureData sse_decode_box_autoadd_starknet_signature_data( + SseDeserializer deserializer); + + @protected + StarknetTokenMetadata sse_decode_box_autoadd_starknet_token_metadata( + SseDeserializer deserializer); + + @protected + StarknetTransactionDetails + sse_decode_box_autoadd_starknet_transaction_details( + SseDeserializer deserializer); + + @protected + DerivedAccountData sse_decode_derived_account_data( + SseDeserializer deserializer); + + @protected + DerivedAccountDataResponse sse_decode_derived_account_data_response( + SseDeserializer deserializer); + + @protected + ExecutionPlanResponse sse_decode_execution_plan_response( + SseDeserializer deserializer); + + @protected + FeeQuoteResponse sse_decode_fee_quote_response(SseDeserializer deserializer); + + @protected + int sse_decode_i_32(SseDeserializer deserializer); + + @protected + PlatformInt64 sse_decode_i_64(SseDeserializer deserializer); + + @protected + I64Response sse_decode_i_64_response(SseDeserializer deserializer); + + @protected + List sse_decode_list_String(SseDeserializer deserializer); + + @protected + Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer); + + @protected + List sse_decode_list_starknet_call_input( + SseDeserializer deserializer); + + @protected + List sse_decode_list_transfer_history_item( + SseDeserializer deserializer); + + @protected + String? sse_decode_opt_String(SseDeserializer deserializer); + + @protected + bool? sse_decode_opt_box_autoadd_bool(SseDeserializer deserializer); + + @protected + DerivedAccountData? sse_decode_opt_box_autoadd_derived_account_data( + SseDeserializer deserializer); + + @protected + int? sse_decode_opt_box_autoadd_i_32(SseDeserializer deserializer); + + @protected + PlatformInt64? sse_decode_opt_box_autoadd_i_64(SseDeserializer deserializer); + + @protected + StarknetExecutionPlanData? + sse_decode_opt_box_autoadd_starknet_execution_plan_data( + SseDeserializer deserializer); + + @protected + StarknetFeeQuote? sse_decode_opt_box_autoadd_starknet_fee_quote( + SseDeserializer deserializer); + + @protected + StarknetSignatureData? sse_decode_opt_box_autoadd_starknet_signature_data( + SseDeserializer deserializer); + + @protected + StarknetTokenMetadata? sse_decode_opt_box_autoadd_starknet_token_metadata( + SseDeserializer deserializer); + + @protected + StarknetTransactionDetails? + sse_decode_opt_box_autoadd_starknet_transaction_details( + SseDeserializer deserializer); + + @protected + StarknetCallInput sse_decode_starknet_call_input( + SseDeserializer deserializer); + + @protected + StarknetExecutionPlanData sse_decode_starknet_execution_plan_data( + SseDeserializer deserializer); + + @protected + StarknetFeeQuote sse_decode_starknet_fee_quote(SseDeserializer deserializer); + + @protected + StarknetSignatureData sse_decode_starknet_signature_data( + SseDeserializer deserializer); + + @protected + StarknetSignatureDataResponse sse_decode_starknet_signature_data_response( + SseDeserializer deserializer); + + @protected + StarknetTokenMetadata sse_decode_starknet_token_metadata( + SseDeserializer deserializer); + + @protected + StarknetTransactionDetails sse_decode_starknet_transaction_details( + SseDeserializer deserializer); + + @protected + StringListResponse sse_decode_string_list_response( + SseDeserializer deserializer); + + @protected + StringResponse sse_decode_string_response(SseDeserializer deserializer); + + @protected + TokenMetadataResponse sse_decode_token_metadata_response( + SseDeserializer deserializer); + + @protected + TransactionDetailsResponse sse_decode_transaction_details_response( + SseDeserializer deserializer); + + @protected + TransferHistoryItem sse_decode_transfer_history_item( + SseDeserializer deserializer); + + @protected + TransferHistoryResponse sse_decode_transfer_history_response( + SseDeserializer deserializer); + + @protected + int sse_decode_u_8(SseDeserializer deserializer); + + @protected + void sse_decode_unit(SseDeserializer deserializer); + + @protected + void sse_encode_String(String self, SseSerializer serializer); + + @protected + void sse_encode_bool(bool self, SseSerializer serializer); + + @protected + void sse_encode_bool_response(BoolResponse self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_bool(bool self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_derived_account_data( + DerivedAccountData self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_i_32(int self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_i_64( + PlatformInt64 self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_starknet_execution_plan_data( + StarknetExecutionPlanData self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_starknet_fee_quote( + StarknetFeeQuote self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_starknet_signature_data( + StarknetSignatureData self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_starknet_token_metadata( + StarknetTokenMetadata self, SseSerializer serializer); + + @protected + void sse_encode_box_autoadd_starknet_transaction_details( + StarknetTransactionDetails self, SseSerializer serializer); + + @protected + void sse_encode_derived_account_data( + DerivedAccountData self, SseSerializer serializer); + + @protected + void sse_encode_derived_account_data_response( + DerivedAccountDataResponse self, SseSerializer serializer); + + @protected + void sse_encode_execution_plan_response( + ExecutionPlanResponse self, SseSerializer serializer); + + @protected + void sse_encode_fee_quote_response( + FeeQuoteResponse self, SseSerializer serializer); + + @protected + void sse_encode_i_32(int self, SseSerializer serializer); + + @protected + void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer); + + @protected + void sse_encode_i_64_response(I64Response self, SseSerializer serializer); + + @protected + void sse_encode_list_String(List self, SseSerializer serializer); + + @protected + void sse_encode_list_prim_u_8_strict( + Uint8List self, SseSerializer serializer); + + @protected + void sse_encode_list_starknet_call_input( + List self, SseSerializer serializer); + + @protected + void sse_encode_list_transfer_history_item( + List self, SseSerializer serializer); + + @protected + void sse_encode_opt_String(String? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_bool(bool? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_derived_account_data( + DerivedAccountData? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_i_32(int? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_i_64( + PlatformInt64? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_starknet_execution_plan_data( + StarknetExecutionPlanData? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_starknet_fee_quote( + StarknetFeeQuote? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_starknet_signature_data( + StarknetSignatureData? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_starknet_token_metadata( + StarknetTokenMetadata? self, SseSerializer serializer); + + @protected + void sse_encode_opt_box_autoadd_starknet_transaction_details( + StarknetTransactionDetails? self, SseSerializer serializer); + + @protected + void sse_encode_starknet_call_input( + StarknetCallInput self, SseSerializer serializer); + + @protected + void sse_encode_starknet_execution_plan_data( + StarknetExecutionPlanData self, SseSerializer serializer); + + @protected + void sse_encode_starknet_fee_quote( + StarknetFeeQuote self, SseSerializer serializer); + + @protected + void sse_encode_starknet_signature_data( + StarknetSignatureData self, SseSerializer serializer); + + @protected + void sse_encode_starknet_signature_data_response( + StarknetSignatureDataResponse self, SseSerializer serializer); + + @protected + void sse_encode_starknet_token_metadata( + StarknetTokenMetadata self, SseSerializer serializer); + + @protected + void sse_encode_starknet_transaction_details( + StarknetTransactionDetails self, SseSerializer serializer); + + @protected + void sse_encode_string_list_response( + StringListResponse self, SseSerializer serializer); + + @protected + void sse_encode_string_response( + StringResponse self, SseSerializer serializer); + + @protected + void sse_encode_token_metadata_response( + TokenMetadataResponse self, SseSerializer serializer); + + @protected + void sse_encode_transaction_details_response( + TransactionDetailsResponse self, SseSerializer serializer); + + @protected + void sse_encode_transfer_history_item( + TransferHistoryItem self, SseSerializer serializer); + + @protected + void sse_encode_transfer_history_response( + TransferHistoryResponse self, SseSerializer serializer); + + @protected + void sse_encode_u_8(int self, SseSerializer serializer); + + @protected + void sse_encode_unit(void self, SseSerializer serializer); +} + +// Section: wire_class + +class RustLibWire implements BaseWire { + RustLibWire.fromExternalLibrary(ExternalLibrary lib); +} + +@JS('wasm_bindgen') +external RustLibWasmModule get wasmModule; + +@JS() +@anonymous +extension type RustLibWasmModule._(JSObject _) implements JSObject {} diff --git a/cw_starknet/lib/starknet_balance.dart b/cw_starknet/lib/starknet_balance.dart new file mode 100644 index 0000000000..a5fd6ebd67 --- /dev/null +++ b/cw_starknet/lib/starknet_balance.dart @@ -0,0 +1,51 @@ +import 'dart:convert'; + +import 'package:cw_core/balance.dart'; +import 'package:cw_core/format_fixed.dart'; + +String truncateDecimalString(String value, {int maxLength = 12}) => + value.length >= maxLength ? value.substring(0, maxLength) : value; + +class StarknetBalance extends Balance { + StarknetBalance( + this.rawBalance, { + required this.decimals, + }) : super(rawBalance, rawBalance); + + final BigInt rawBalance; + final int decimals; + + double get balance => double.tryParse(_balanceFormatted()) ?? 0.0; + + String get formattedAdditionalBalance => _balanceFormatted(); + + String get formattedAvailableBalance => _balanceFormatted(); + + String _balanceFormatted() => + truncateDecimalString(formatFixed(rawBalance, decimals, fractionalDigits: decimals)); + + static StarknetBalance zero({int decimals = 18}) => + StarknetBalance(BigInt.zero, decimals: decimals); + + static StarknetBalance? fromJSON(String? jsonSource) { + if (jsonSource == null) { + return null; + } + + final decoded = json.decode(jsonSource) as Map; + + try { + return StarknetBalance( + BigInt.parse(decoded['raw_balance']?.toString() ?? '0'), + decimals: int.tryParse(decoded['decimals']?.toString() ?? '') ?? 18, + ); + } catch (_) { + return StarknetBalance.zero(); + } + } + + String toJSON() => json.encode({ + 'raw_balance': rawBalance.toString(), + 'decimals': decimals, + }); +} diff --git a/cw_starknet/lib/starknet_client.dart b/cw_starknet/lib/starknet_client.dart new file mode 100644 index 0000000000..7bb8ea5166 --- /dev/null +++ b/cw_starknet/lib/starknet_client.dart @@ -0,0 +1,755 @@ +import 'dart:typed_data'; + +import 'package:cw_core/hardware/hardware_wallet_service.dart'; +import 'package:cw_core/node.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_starknet/pending_starknet_transaction.dart'; +import 'package:cw_starknet/starknet_balance.dart'; +import 'package:cw_starknet/starknet_exceptions.dart'; +import 'package:cw_starknet/starknet_ur.dart'; +import 'package:cw_starknet/starknet_rust.dart'; +import 'package:cw_starknet/src/rust/api/starknet.dart' as rust_api; +import 'package:hex/hex.dart'; + +class StarknetTokenAddresses { + static const String strk = '0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d'; + static const String eth = '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7'; +} + +class StarknetExecutionCall { + StarknetExecutionCall({ + required this.contractAddressHex, + required this.entrypoint, + required this.calldataHex, + }); + + final String contractAddressHex; + final String entrypoint; + final List calldataHex; + + rust_api.StarknetCallInput toRust() => rust_api.StarknetCallInput( + contractAddressHex: contractAddressHex, + entrypoint: entrypoint, + calldataHex: calldataHex, + ); +} + +class StarknetTransferEvent { + StarknetTransferEvent({ + required this.transactionHash, + required this.eventId, + required this.eventIndex, + required this.blockNumber, + required this.from, + required this.to, + required this.amountWei, + required this.isOutgoing, + required this.tokenSymbol, + required this.tokenAddressHex, + required this.blockTimestamp, + required this.txFeeWei, + }); + + final String transactionHash; + final String eventId; + final int eventIndex; + final int? blockNumber; + final String from; + final String to; + final String amountWei; + final bool isOutgoing; + final String tokenSymbol; + final String tokenAddressHex; + final int? blockTimestamp; + final String? txFeeWei; + + BigInt get amount => BigInt.parse(amountWei); +} + +class StarknetTransactionDetails { + StarknetTransactionDetails({ + required this.transactionHash, + required this.transactionType, + required this.isPending, + required this.blockNumber, + required this.blockTimestamp, + required this.actualFeeWei, + required this.actionName, + required this.callCount, + required this.primaryContractAddressHex, + required this.primaryEntrypoint, + required this.senderAddressHex, + required this.finalityStatus, + required this.executionStatus, + required this.revertReason, + required this.accountDeploymentRequired, + required this.l1GasMaxAmount, + required this.l1GasMaxPriceWei, + required this.l2GasMaxAmount, + required this.l2GasMaxPriceWei, + required this.l1DataGasMaxAmount, + required this.l1DataGasMaxPriceWei, + required this.tip, + }); + + final String transactionHash; + final String transactionType; + final bool isPending; + final int? blockNumber; + final int? blockTimestamp; + final String? actualFeeWei; + final String? actionName; + final int? callCount; + final String? primaryContractAddressHex; + final String? primaryEntrypoint; + final String? senderAddressHex; + final String? finalityStatus; + final String? executionStatus; + final String? revertReason; + final bool accountDeploymentRequired; + final String? l1GasMaxAmount; + final String? l1GasMaxPriceWei; + final String? l2GasMaxAmount; + final String? l2GasMaxPriceWei; + final String? l1DataGasMaxAmount; + final String? l1DataGasMaxPriceWei; + final int? tip; +} + +class StarknetWalletClient { + static const String mainnetChainIdHex = '0x534e5f4d41494e'; + String? _nodeUrl; + String? _accountPrivateKeyHex; + String? _accountAddressHex; + String? _accountPublicKeyHex; + String? _hardwareDerivationPath; + HardwareWalletService? _hardwareWalletService; + + bool connect(Node node) { + try { + _nodeUrl = node.uri.toString(); + return true; + } catch (e) { + printV('StarknetWalletClient connect error: $e'); + return false; + } + } + + void stop() { + _nodeUrl = null; + _accountPrivateKeyHex = null; + _accountAddressHex = null; + _accountPublicKeyHex = null; + _hardwareDerivationPath = null; + _hardwareWalletService = null; + } + + void setupAccount({ + required String privateKeyHex, + required String addressHex, + }) { + _accountPrivateKeyHex = privateKeyHex; + _accountAddressHex = addressHex; + _accountPublicKeyHex = null; + _hardwareDerivationPath = null; + _hardwareWalletService = null; + } + + void setupHardwareAccount({ + required String addressHex, + required String publicKeyHex, + required String derivationPath, + HardwareWalletService? service, + }) { + _accountPrivateKeyHex = null; + _accountAddressHex = addressHex; + _accountPublicKeyHex = publicKeyHex; + _hardwareDerivationPath = derivationPath; + _hardwareWalletService = service ?? _hardwareWalletService; + } + + void setHardwareWalletService(HardwareWalletService service) { + _hardwareWalletService = service; + } + + void setupExternalSignerAccount({ + required String addressHex, + required String publicKeyHex, + }) { + _accountPrivateKeyHex = null; + _accountAddressHex = addressHex; + _accountPublicKeyHex = publicKeyHex; + _hardwareDerivationPath = null; + _hardwareWalletService = null; + } + + Future getTokenBalance({ + required String accountAddressHex, + required String tokenAddressHex, + required int decimals, + }) async { + await ensureStarknetRustInitialized(); + + final balanceWei = await _withNodeUrl( + (nodeUrl) async { + final response = await rust_api.getTokenBalance( + nodeUrl: nodeUrl, + accountAddressHex: accountAddressHex, + tokenAddressHex: tokenAddressHex, + ); + return unwrapStringResponse(response); + }, + ); + + return StarknetBalance( + BigInt.parse(balanceWei), + decimals: decimals, + ); + } + + Future getTokenMetadata( + String tokenAddressHex, + ) async { + await ensureStarknetRustInitialized(); + return _withNodeUrl( + (nodeUrl) async { + final response = await rust_api.getTokenMetadata( + nodeUrl: nodeUrl, + tokenAddressHex: tokenAddressHex, + ); + return unwrapTokenMetadataResponse(response); + }, + ); + } + + Future estimateExecuteFee({ + required String accountAddressHex, + required String accountClassHashHex, + required List calls, + required int feePriorityRaw, + }) async { + await ensureStarknetRustInitialized(); + + return _withNodeUrl( + (nodeUrl) async { + final response = await rust_api.estimateExecuteFee( + nodeUrl: nodeUrl, + privateKeyHex: _requirePrivateKey(), + accountAddressHex: accountAddressHex, + accountClassHashHex: accountClassHashHex, + calls: calls.map((call) => call.toRust()).toList(), + feePriorityRaw: feePriorityRaw, + chainIdHex: mainnetChainIdHex, + ); + return unwrapFeeQuoteResponse(response); + }, + ); + } + + Future estimateHardwareExecuteFee({ + required String accountAddressHex, + required String publicKeyHex, + required String accountClassHashHex, + required List calls, + required int feePriorityRaw, + }) async { + await ensureStarknetRustInitialized(); + + return _withNodeUrl( + (nodeUrl) async { + final response = await rust_api.estimateExecuteFeeExternalSigner( + nodeUrl: nodeUrl, + publicKeyHex: publicKeyHex, + accountAddressHex: accountAddressHex, + accountClassHashHex: accountClassHashHex, + calls: calls.map((call) => call.toRust()).toList(), + feePriorityRaw: feePriorityRaw, + chainIdHex: mainnetChainIdHex, + ); + return unwrapFeeQuoteResponse(response); + }, + ); + } + + Future createTransaction({ + required List calls, + required String amountWei, + required int amountDecimals, + required String amountSymbol, + required String destinationAddress, + required String accountClassHashHex, + required String feeWei, + required int feePriorityRaw, + Future Function(String txHash)? onCommitted, + }) async { + final privateKeyHex = _requirePrivateKey(); + final accountAddressHex = _requireAccountAddress(); + + return PendingStarknetTransaction( + amountWei: amountWei, + amountDecimals: amountDecimals, + amountSymbol: amountSymbol, + destinationAddress: destinationAddress, + feeWei: feeWei, + sendTransaction: () async { + await ensureStarknetRustInitialized(); + return _withNodeUrl( + (nodeUrl) async { + final response = await rust_api.executeCalls( + nodeUrl: nodeUrl, + privateKeyHex: privateKeyHex, + accountAddressHex: accountAddressHex, + accountClassHashHex: accountClassHashHex, + calls: calls.map((call) => call.toRust()).toList(), + feePriorityRaw: feePriorityRaw, + chainIdHex: mainnetChainIdHex, + ); + return unwrapStringResponse(response); + }, + ); + }, + onCommitted: onCommitted, + ); + } + + Future createHardwareTransaction({ + required List calls, + required String amountWei, + required int amountDecimals, + required String amountSymbol, + required String destinationAddress, + required String accountClassHashHex, + required String feeWei, + required int feePriorityRaw, + Future Function(String txHash)? onCommitted, + }) async { + final accountAddressHex = _requireAccountAddress(); + final publicKeyHex = _requirePublicKey(); + final derivationPath = _requireHardwareDerivationPath(); + final hardwareWalletService = _requireHardwareWalletService(); + + return PendingStarknetTransaction( + amountWei: amountWei, + amountDecimals: amountDecimals, + amountSymbol: amountSymbol, + destinationAddress: destinationAddress, + feeWei: feeWei, + sendTransaction: () async { + await ensureStarknetRustInitialized(); + return _withNodeUrl( + (nodeUrl) async { + final hashesResponse = await rust_api.getExecuteTransactionHashesExternalSigner( + nodeUrl: nodeUrl, + publicKeyHex: publicKeyHex, + accountAddressHex: accountAddressHex, + accountClassHashHex: accountClassHashHex, + calls: calls.map((call) => call.toRust()).toList(), + feePriorityRaw: feePriorityRaw, + chainIdHex: mainnetChainIdHex, + ); + final hashes = unwrapExecutionPlanResponse(hashesResponse); + + final invokeSignature = await _signHashWithHardwareWallet( + hardwareWalletService, + derivationPath, + hashes.invokeTransactionHashHex, + ); + final deploySignature = hashes.deployAccountTransactionHashHex == null + ? null + : await _signHashWithHardwareWallet( + hardwareWalletService, + derivationPath, + hashes.deployAccountTransactionHashHex!, + ); + + final response = await rust_api.executeCallsExternalSigner( + nodeUrl: nodeUrl, + planJson: hashes.planJson, + invokeRHex: invokeSignature.$1, + invokeSHex: invokeSignature.$2, + deployRHex: deploySignature?.$1, + deploySHex: deploySignature?.$2, + ); + + return unwrapStringResponse(response); + }, + ); + }, + onCommitted: onCommitted, + ); + } + + Future createOfflineTransaction({ + required List calls, + required String amountWei, + required int amountDecimals, + required String amountSymbol, + required String destinationAddress, + required String accountClassHashHex, + required String feeWei, + required int feePriorityRaw, + String? summaryActionName, + String? summaryTokenAddress, + Map? summaryAdditionalInfo, + bool preferSummary = false, + }) async { + await ensureStarknetRustInitialized(); + final requestUr = await buildUnsignedTransactionUr( + calls: calls, + amountWei: amountWei, + amountDecimals: amountDecimals, + amountSymbol: amountSymbol, + destinationAddress: destinationAddress, + accountClassHashHex: accountClassHashHex, + feeWei: feeWei, + feePriorityRaw: feePriorityRaw, + summaryActionName: summaryActionName, + summaryTokenAddress: summaryTokenAddress, + summaryAdditionalInfo: summaryAdditionalInfo, + preferSummary: preferSummary, + ); + final requestPayload = decodeStarknetSignRequestUr(requestUr.values.first); + + return PendingStarknetTransaction( + amountWei: amountWei, + amountDecimals: amountDecimals, + amountSymbol: amountSymbol, + destinationAddress: destinationAddress, + feeWei: feeWei, + transactionHash: requestPayload.invokeTransactionHashHex, + sendTransaction: () async => throw UnsupportedError( + 'Offline Starknet transactions must be completed via commitUR().', + ), + buildUnsignedTransactionUr: () async => requestUr, + ); + } + + Future> buildUnsignedTransactionUr({ + required List calls, + required String amountWei, + required int amountDecimals, + required String amountSymbol, + required String destinationAddress, + required String accountClassHashHex, + required String feeWei, + required int feePriorityRaw, + String? summaryActionName, + String? summaryTokenAddress, + Map? summaryAdditionalInfo, + bool preferSummary = false, + }) async { + await ensureStarknetRustInitialized(); + + final accountAddressHex = _requireAccountAddress(); + final publicKeyHex = _requirePublicKey(); + + final executionPlan = await _withNodeUrl( + (nodeUrl) async { + final response = await rust_api.getExecuteTransactionHashesExternalSigner( + nodeUrl: nodeUrl, + publicKeyHex: publicKeyHex, + accountAddressHex: accountAddressHex, + accountClassHashHex: accountClassHashHex, + calls: calls.map((call) => call.toRust()).toList(), + feePriorityRaw: feePriorityRaw, + chainIdHex: mainnetChainIdHex, + ); + return unwrapExecutionPlanResponse(response); + }, + ); + + final requestPayload = StarknetSignRequestUrPayload( + planJson: executionPlan.planJson, + accountAddressHex: accountAddressHex, + publicKeyHex: publicKeyHex, + accountClassHashHex: accountClassHashHex, + invokeTransactionHashHex: executionPlan.invokeTransactionHashHex, + deployAccountTransactionHashHex: executionPlan.deployAccountTransactionHashHex, + amountWei: amountWei, + amountDecimals: amountDecimals, + amountSymbol: amountSymbol, + destinationAddress: destinationAddress, + feeWei: feeWei, + summaryActionName: summaryActionName, + summaryTokenAddress: summaryTokenAddress, + summaryAdditionalInfo: summaryAdditionalInfo, + preferSummary: preferSummary, + ); + + return encodeStarknetSignRequestUrMap(requestPayload); + } + + Future> fetchTransferEvents({ + required String accountAddressHex, + required String tokenAddressHex, + required String tokenSymbol, + int? fromBlock, + }) async { + if (_nodeUrl == null) { + return []; + } + + try { + await ensureStarknetRustInitialized(); + final items = await _withNodeUrl( + (nodeUrl) async { + final response = await rust_api.fetchTransferHistory( + nodeUrl: nodeUrl, + accountAddressHex: accountAddressHex, + tokenAddressHex: tokenAddressHex, + tokenSymbol: tokenSymbol, + fromBlock: fromBlock, + maxPages: 10, + ); + return unwrapTransferHistoryResponse(response); + }, + ); + + return items + .map( + (item) => StarknetTransferEvent( + transactionHash: item.transactionHash, + eventId: item.eventId, + eventIndex: item.eventIndex.toInt(), + blockNumber: item.blockNumber?.toInt(), + from: item.from, + to: item.to, + amountWei: item.amountWei, + isOutgoing: item.isOutgoing, + tokenSymbol: item.tokenSymbol, + tokenAddressHex: item.tokenAddressHex, + blockTimestamp: item.blockTimestamp?.toInt(), + txFeeWei: item.txFeeWei, + ), + ) + .toList(); + } catch (e) { + printV('Error fetching Starknet transfer history: $e'); + return []; + } + } + + Future getTransactionDetails({ + required String transactionHashHex, + }) async { + if (_nodeUrl == null) { + return null; + } + + try { + await ensureStarknetRustInitialized(); + return _withNodeUrl( + (nodeUrl) async { + final response = await rust_api.getTransactionDetails( + nodeUrl: nodeUrl, + transactionHashHex: transactionHashHex, + ); + final details = unwrapTransactionDetailsResponse(response); + return StarknetTransactionDetails( + transactionHash: details.transactionHash, + transactionType: details.transactionType, + isPending: details.isPending, + blockNumber: details.blockNumber?.toInt(), + blockTimestamp: details.blockTimestamp?.toInt(), + actualFeeWei: details.actualFeeWei, + actionName: details.actionName, + callCount: details.callCount?.toInt(), + primaryContractAddressHex: details.primaryContractAddressHex, + primaryEntrypoint: details.primaryEntrypoint, + senderAddressHex: details.senderAddressHex, + finalityStatus: details.finalityStatus, + executionStatus: details.executionStatus, + revertReason: details.revertReason, + accountDeploymentRequired: details.accountDeploymentRequired, + l1GasMaxAmount: details.l1GasMaxAmount, + l1GasMaxPriceWei: details.l1GasMaxPriceWei, + l2GasMaxAmount: details.l2GasMaxAmount, + l2GasMaxPriceWei: details.l2GasMaxPriceWei, + l1DataGasMaxAmount: details.l1DataGasMaxAmount, + l1DataGasMaxPriceWei: details.l1DataGasMaxPriceWei, + tip: details.tip?.toInt(), + ); + }, + ); + } catch (e) { + printV('Error fetching Starknet transaction details for $transactionHashHex: $e'); + return null; + } + } + + Future getTypedDataHash({ + required String accountAddressHex, + required String typedDataJson, + }) async { + await ensureStarknetRustInitialized(); + final response = await rust_api.getTypedDataMessageHash( + accountAddressHex: accountAddressHex, + typedDataJson: typedDataJson, + ); + return unwrapStringResponse(response); + } + + Future> signTypedData({ + required String accountAddressHex, + required String typedDataJson, + }) async { + await ensureStarknetRustInitialized(); + final response = await rust_api.signTypedData( + privateKeyHex: _requirePrivateKey(), + accountAddressHex: accountAddressHex, + typedDataJson: typedDataJson, + ); + return unwrapStringListResponse(response); + } + + Future getBlockNumber() async { + if (_nodeUrl == null) { + return null; + } + + try { + await ensureStarknetRustInitialized(); + return _withNodeUrl( + (nodeUrl) async { + final response = await rust_api.getBlockNumber(nodeUrl: nodeUrl); + return unwrapI64Response(response); + }, + ); + } catch (e) { + printV('Error fetching Starknet block number: $e'); + return null; + } + } + + static bool isValidAddress(String address) { + final regex = RegExp(r'^0x[0-9a-fA-F]{1,64}$'); + return regex.hasMatch(address); + } + + Future submitSignedTransactionUr(String urPayload) async { + await ensureStarknetRustInitialized(); + final responsePayload = decodeStarknetSignResponseUr(urPayload); + + if (responsePayload.invokeSignature.rHex.isEmpty || + responsePayload.invokeSignature.sHex.isEmpty) { + throw Exception('Missing Starknet invoke signature in UR response'); + } + + return _withNodeUrl( + (nodeUrl) async { + final response = await rust_api.executeCallsExternalSigner( + nodeUrl: nodeUrl, + planJson: responsePayload.planJson, + invokeRHex: responsePayload.invokeSignature.rHex, + invokeSHex: responsePayload.invokeSignature.sHex, + deployRHex: responsePayload.deploySignature?.rHex, + deploySHex: responsePayload.deploySignature?.sHex, + ); + return unwrapStringResponse(response); + }, + ); + } + + String _requireNodeUrl() { + final nodeUrl = _nodeUrl; + if (nodeUrl == null) { + throw StarknetProviderNotConnectedException(); + } + + return nodeUrl; + } + + String _requirePrivateKey() { + final privateKeyHex = _accountPrivateKeyHex; + if (privateKeyHex == null || privateKeyHex.isEmpty) { + throw StarknetTransactionCreationException.fromMessage( + 'Account private key not configured', + ); + } + + return privateKeyHex; + } + + String _requireAccountAddress() { + final accountAddressHex = _accountAddressHex; + if (accountAddressHex == null || accountAddressHex.isEmpty) { + throw StarknetTransactionCreationException.fromMessage( + 'Account address not configured', + ); + } + + return accountAddressHex; + } + + String _requirePublicKey() { + final publicKeyHex = _accountPublicKeyHex; + if (publicKeyHex == null || publicKeyHex.isEmpty) { + throw StarknetTransactionCreationException.fromMessage( + 'Account public key not configured', + ); + } + + return publicKeyHex; + } + + String _requireHardwareDerivationPath() { + final derivationPath = _hardwareDerivationPath; + if (derivationPath == null || derivationPath.isEmpty) { + throw StarknetTransactionCreationException.fromMessage( + 'Hardware wallet derivation path not configured', + ); + } + + return derivationPath; + } + + HardwareWalletService _requireHardwareWalletService() { + final service = _hardwareWalletService; + if (service == null) { + throw StarknetTransactionCreationException.fromMessage( + 'Starknet hardware wallet is not connected', + ); + } + + return service; + } + + Future<(String, String)> _signHashWithHardwareWallet( + HardwareWalletService service, + String derivationPath, + String messageHashHex, + ) async { + final signatureBytes = await service.signMessage( + message: _messageHashBytes(messageHashHex), + derivationPath: derivationPath, + ); + if (signatureBytes.length != 64) { + throw StarknetTransactionCreationException.fromMessage( + 'Unexpected Starknet hardware signature length: ${signatureBytes.length}', + ); + } + + return ( + '0x${HEX.encode(signatureBytes.sublist(0, 32))}', + '0x${HEX.encode(signatureBytes.sublist(32, 64))}', + ); + } + + Uint8List _messageHashBytes(String messageHashHex) { + final normalized = + messageHashHex.startsWith('0x') ? messageHashHex.substring(2) : messageHashHex; + final decoded = HEX.decode(normalized.padLeft(64, '0')); + if (decoded.length == 32) { + return Uint8List.fromList(decoded); + } + + final result = Uint8List(32); + result.setRange(32 - decoded.length, 32, decoded); + return result; + } + + Future _withNodeUrl( + Future Function(String nodeUrl) action, + ) => + action(_requireNodeUrl()); +} diff --git a/cw_starknet/lib/starknet_exceptions.dart b/cw_starknet/lib/starknet_exceptions.dart new file mode 100644 index 0000000000..5e11ae208c --- /dev/null +++ b/cw_starknet/lib/starknet_exceptions.dart @@ -0,0 +1,73 @@ +import 'package:cw_core/crypto_currency.dart'; + +class StarknetTransactionCreationException implements Exception { + final String exceptionMessage; + + StarknetTransactionCreationException(CryptoCurrency currency) + : exceptionMessage = 'Error creating ${currency.title} transaction.'; + + StarknetTransactionCreationException.fromMessage(this.exceptionMessage); + + @override + String toString() => exceptionMessage; +} + +class StarknetTransactionWrongBalanceException implements Exception { + final String exceptionMessage; + + StarknetTransactionWrongBalanceException(CryptoCurrency currency) + : exceptionMessage = + 'Wrong balance. Not enough ${currency.title} on your balance.'; + + @override + String toString() => exceptionMessage; +} + +class StarknetInsufficientFeeException implements Exception { + final String exceptionMessage; + + StarknetInsufficientFeeException({double? requiredFee}) + : exceptionMessage = requiredFee != null + ? 'Insufficient STRK for fees. Required: ~${requiredFee.toStringAsFixed(8)} STRK. ' + 'Please add STRK to your wallet to cover transaction fees.' + : 'Insufficient STRK for fees. ' + 'Please add STRK to your wallet to cover transaction fees.'; + + @override + String toString() => exceptionMessage; +} + +class StarknetInvalidAddressException implements Exception { + final String address; + + StarknetInvalidAddressException(this.address); + + @override + String toString() => + 'Invalid Starknet address: $address. ' + 'Address must be a hex string starting with 0x.'; +} + +class StarknetAccountNotDeployedException implements Exception { + @override + String toString() => + 'Account is not deployed on Starknet. ' + 'Please deploy your account before sending transactions.'; +} + +class StarknetProviderNotConnectedException implements Exception { + @override + String toString() => 'Starknet provider not connected. Please check your node connection.'; +} + +class StarknetNodeConnectionException implements Exception { + final String exceptionMessage; + + StarknetNodeConnectionException([String? details]) + : exceptionMessage = details != null + ? 'Failed to connect to Starknet node: $details' + : 'Failed to connect to Starknet node.'; + + @override + String toString() => exceptionMessage; +} diff --git a/cw_starknet/lib/starknet_mnemonics.dart b/cw_starknet/lib/starknet_mnemonics.dart new file mode 100644 index 0000000000..54f2fa0302 --- /dev/null +++ b/cw_starknet/lib/starknet_mnemonics.dart @@ -0,0 +1,11 @@ +// ignore: implementation_imports +import 'package:bip39/src/wordlists/english.dart' as english; + +class StarknetMnemonics { + static List englishWordlist = english.WORDLIST.toList(); +} + +class StarknetMnemonicIsIncorrectException implements Exception { + @override + String toString() => 'StarknetMnemonicIsIncorrectException: The mnemonic seed is not valid.'; +} diff --git a/cw_starknet/lib/starknet_rust.dart b/cw_starknet/lib/starknet_rust.dart new file mode 100644 index 0000000000..417baeee57 --- /dev/null +++ b/cw_starknet/lib/starknet_rust.dart @@ -0,0 +1,140 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:cw_starknet/src/rust/api/starknet.dart' as rust_api; +import 'package:cw_starknet/src/rust/frb_generated.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; + +Completer? _rustInitCompleter; + +Future ensureStarknetRustInitialized() { + final existing = _rustInitCompleter; + if (existing != null) { + return existing.future; + } + + final completer = Completer(); + _rustInitCompleter = completer; + + () async { + try { + await _initializeRust(); + completer.complete(); + } catch (error, stackTrace) { + _rustInitCompleter = null; + completer.completeError(error, stackTrace); + } + }(); + + return completer.future; +} + +Future _initializeRust() async { + try { + await RustLib.init(); + return; + } catch (error) { + final fallbackLibraryPath = _findLocalRustLibraryPath(); + if (fallbackLibraryPath == null) { + rethrow; + } + + await RustLib.init( + externalLibrary: ExternalLibrary.open(fallbackLibraryPath), + ); + } +} + +String? _findLocalRustLibraryPath() { + final candidates = switch (Platform.operatingSystem) { + 'macos' => [ + 'rust/target/debug/libcw_starknet_rust.dylib', + 'rust/target/release/libcw_starknet_rust.dylib', + 'cw_starknet/rust/target/debug/libcw_starknet_rust.dylib', + 'cw_starknet/rust/target/release/libcw_starknet_rust.dylib', + ], + 'linux' => [ + 'rust/target/debug/libcw_starknet_rust.so', + 'rust/target/release/libcw_starknet_rust.so', + 'cw_starknet/rust/target/debug/libcw_starknet_rust.so', + 'cw_starknet/rust/target/release/libcw_starknet_rust.so', + ], + 'windows' => [ + 'rust\\target\\debug\\cw_starknet_rust.dll', + 'rust\\target\\release\\cw_starknet_rust.dll', + 'cw_starknet\\rust\\target\\debug\\cw_starknet_rust.dll', + 'cw_starknet\\rust\\target\\release\\cw_starknet_rust.dll', + ], + _ => const [], + }; + + for (final candidate in candidates) { + final file = File(candidate); + if (file.existsSync()) { + return file.absolute.path; + } + } + + return null; +} + +T _unwrapValue(T? value, String? error, String label) { + if (error != null && error.isNotEmpty) { + throw Exception(error); + } + + if (value == null) { + throw Exception('Missing $label from Starknet Rust response'); + } + + return value; +} + +rust_api.DerivedAccountData unwrapDerivedAccountDataResponse( + rust_api.DerivedAccountDataResponse response) => + _unwrapValue(response.value, response.error, 'derived account data'); + +rust_api.StarknetSignatureData unwrapSignatureResponse( + rust_api.StarknetSignatureDataResponse response) => + _unwrapValue(response.value, response.error, 'signature'); + +String unwrapStringResponse(rust_api.StringResponse response) => + _unwrapValue(response.value, response.error, 'string value'); + +bool unwrapBoolResponse(rust_api.BoolResponse response) => + _unwrapValue(response.value, response.error, 'boolean value'); + +int unwrapI64Response(rust_api.I64Response response) => + _unwrapValue(response.value, response.error, 'integer value').toInt(); + +List unwrapTransferHistoryResponse( + rust_api.TransferHistoryResponse response) { + if (response.error != null && response.error!.isNotEmpty) { + throw Exception(response.error); + } + + return response.items; +} + +rust_api.StarknetTokenMetadata unwrapTokenMetadataResponse( + rust_api.TokenMetadataResponse response) => + _unwrapValue(response.value, response.error, 'token metadata'); + +rust_api.StarknetFeeQuote unwrapFeeQuoteResponse(rust_api.FeeQuoteResponse response) => + _unwrapValue(response.value, response.error, 'fee quote'); + +rust_api.StarknetExecutionPlanData unwrapExecutionPlanResponse( + rust_api.ExecutionPlanResponse response) => + _unwrapValue(response.value, response.error, 'execution plan'); + +rust_api.StarknetTransactionDetails unwrapTransactionDetailsResponse( + rust_api.TransactionDetailsResponse response) => + _unwrapValue(response.value, response.error, 'transaction details'); + +List unwrapStringListResponse(rust_api.StringListResponse response) { + if (response.error != null && response.error!.isNotEmpty) { + throw Exception(response.error); + } + + return response.items; +} diff --git a/cw_starknet/lib/starknet_transaction_credentials.dart b/cw_starknet/lib/starknet_transaction_credentials.dart new file mode 100644 index 0000000000..19b2800a66 --- /dev/null +++ b/cw_starknet/lib/starknet_transaction_credentials.dart @@ -0,0 +1,15 @@ +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/output_info.dart'; +import 'package:cw_core/transaction_priority.dart'; + +class StarknetTransactionCredentials { + StarknetTransactionCredentials( + this.outputs, { + required this.currency, + this.priority, + }); + + final List outputs; + final CryptoCurrency currency; + final TransactionPriority? priority; +} diff --git a/cw_starknet/lib/starknet_transaction_history.dart b/cw_starknet/lib/starknet_transaction_history.dart new file mode 100644 index 0000000000..e996de5701 --- /dev/null +++ b/cw_starknet/lib/starknet_transaction_history.dart @@ -0,0 +1,105 @@ +import 'dart:convert'; +import 'dart:core'; +import 'package:cw_core/encryption_file_utils.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_starknet/starknet_transaction_info.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cw_core/transaction_history.dart'; + +part 'starknet_transaction_history.g.dart'; + +const transactionsHistoryFileName = 'starknet_transactions.json'; + +class StarknetTransactionHistory = StarknetTransactionHistoryBase + with _$StarknetTransactionHistory; + +abstract class StarknetTransactionHistoryBase + extends TransactionHistoryBase with Store { + StarknetTransactionHistoryBase({ + required this.walletInfo, + required String password, + required this.encryptionFileUtils, + }) : _password = password { + transactions = ObservableMap(); + } + + final WalletInfo walletInfo; + final EncryptionFileUtils encryptionFileUtils; + String _password; + + Future init() async { + clear(); + await _load(); + } + + @override + Future save() async { + try { + final dirPath = + await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); + final path = '$dirPath/$transactionsHistoryFileName'; + final transactionMaps = + transactions.map((key, value) => MapEntry(key, value.toJson())); + final data = json.encode({'transactions': transactionMaps}); + await encryptionFileUtils.write( + path: path, password: _password, data: data); + } catch (e, s) { + printV( + 'Error while saving starknet transaction history: ${e.toString()}'); + printV(s); + } + } + + Future changePassword(String password) async { + _password = password; + await save(); + } + + Future reset() async { + clear(); + await save(); + } + + @override + void addOne(StarknetTransactionInfo transaction) => + transactions[transaction.id] = transaction; + + @override + void addMany(Map transactions) => + this.transactions.addAll(transactions); + + Future> _read() async { + final dirPath = + await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); + final path = '$dirPath/$transactionsHistoryFileName'; + final content = + await encryptionFileUtils.read(path: path, password: _password); + if (content.isEmpty) { + return {}; + } + return json.decode(content) as Map; + } + + Future _load() async { + try { + final content = await _read(); + final txs = content['transactions'] as Map? ?? {}; + + for (var entry in txs.entries) { + final val = entry.value; + + if (val is Map) { + final tx = StarknetTransactionInfo.fromJson(val); + _update(tx); + } + } + } catch (e) { + printV(e); + } + } + + void _update(StarknetTransactionInfo transaction) => + transactions[transaction.id] = transaction; +} diff --git a/cw_starknet/lib/starknet_transaction_info.dart b/cw_starknet/lib/starknet_transaction_info.dart new file mode 100644 index 0000000000..6b35ab9059 --- /dev/null +++ b/cw_starknet/lib/starknet_transaction_info.dart @@ -0,0 +1,256 @@ +// ignore_for_file: overridden_fields + +import 'package:cw_core/format_fixed.dart'; +import 'package:cw_core/format_amount.dart'; +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_starknet/starknet_balance.dart' show truncateDecimalString; + +class StarknetTransactionInfo extends TransactionInfo { + StarknetTransactionInfo({ + required this.id, + required this.transactionHash, + required this.blockTime, + required this.to, + required this.from, + required this.direction, + required this.amountWei, + required this.tokenAddress, + required this.tokenDecimals, + required this.tokenSymbol, + required this.isPending, + required this.txFeeWei, + this.evmSignatureName, + Map? additionalInfo, + this.height, + }) : amount = _safeAmountInt(amountWei) { + txHash = transactionHash; + this.additionalInfo = additionalInfo ?? {}; + } + + @override + final String id; + + final String transactionHash; + + @override + final String? to; + + @override + final String? from; + + @override + final int amount; + + @override + final bool isPending; + + final String amountWei; + final String tokenAddress; + final int tokenDecimals; + final String tokenSymbol; + final DateTime blockTime; + final String txFeeWei; + + @override + final String? evmSignatureName; + + @override + final int? height; + + @override + final TransactionDirection direction; + + String? _fiatAmount; + + @override + DateTime get date => blockTime; + + double rawAmountAsDouble() => + double.tryParse( + formatFixed(BigInt.parse(amountWei), tokenDecimals, fractionalDigits: tokenDecimals)) ?? + 0.0; + + @override + String amountFormatted() => + '${truncateDecimalString(formatFixed(BigInt.parse(amountWei), tokenDecimals, fractionalDigits: tokenDecimals))} $tokenSymbol'; + + @override + String fiatAmount() => _fiatAmount ?? ''; + + @override + void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); + + @override + String feeFormatted() => txFeeWei.isEmpty ? '' : _formatStrkFee(txFeeWei); + + String? actionLabel() { + final explicitLabel = additionalInfo['starknetActionLabel']?.toString().trim(); + if (explicitLabel != null && explicitLabel.isNotEmpty) { + return explicitLabel; + } + + final actionName = evmSignatureName?.trim(); + if (actionName == null || actionName.isEmpty) { + return null; + } + + final normalized = actionName.replaceAll('_', ' ').trim(); + if (normalized.isEmpty) { + return null; + } + + return normalized[0].toUpperCase() + normalized.substring(1); + } + + String? executionFeeFormatted() { + final value = additionalInfo['starknetExecutionFeeWei']?.toString(); + if (value == null || value.isEmpty) { + return null; + } + + return _formatStrkFee(value); + } + + String? deployAccountFeeFormatted() { + final value = additionalInfo['starknetDeployAccountFeeWei']?.toString(); + if (value == null || value.isEmpty) { + return null; + } + + return _formatStrkFee(value); + } + + String? feePriorityLabel() { + final value = additionalInfo['starknetFeePriorityLabel']?.toString().trim(); + if (value == null || value.isEmpty) { + return null; + } + + return value; + } + + String? transactionTypeLabel() => _humanizeAdditionalInfoValue('starknetTransactionType'); + + String? executionStatusLabel() => _humanizeAdditionalInfoValue('starknetExecutionStatus'); + + String? finalityStatusLabel() => _humanizeAdditionalInfoValue('starknetFinalityStatus'); + + String? revertReason() => _stringAdditionalInfo('starknetRevertReason'); + + String? callCountLabel() { + final value = additionalInfo['starknetCallCount']; + if (value == null) { + return null; + } + + return value.toString(); + } + + String? primaryContractAddress() => _stringAdditionalInfo('starknetPrimaryContract'); + + String? primaryEntrypoint() => _stringAdditionalInfo('starknetPrimaryEntrypoint'); + + String? transactionTipLabel() { + final value = additionalInfo['starknetTip']; + if (value == null) { + return null; + } + + return value.toString(); + } + + String? l1GasMaxAmount() => _stringAdditionalInfo('starknetL1GasMaxAmount'); + + String? l1GasMaxPriceWei() => _stringAdditionalInfo('starknetL1GasMaxPriceWei'); + + String? l2GasMaxAmount() => _stringAdditionalInfo('starknetL2GasMaxAmount'); + + String? l2GasMaxPriceWei() => _stringAdditionalInfo('starknetL2GasMaxPriceWei'); + + String? l1DataGasMaxAmount() => _stringAdditionalInfo('starknetL1DataGasMaxAmount'); + + String? l1DataGasMaxPriceWei() => _stringAdditionalInfo('starknetL1DataGasMaxPriceWei'); + + bool get accountDeploymentRequired => additionalInfo['starknetAccountDeploymentRequired'] == true; + + factory StarknetTransactionInfo.fromJson(Map data) { + return StarknetTransactionInfo( + id: data['id'] as String, + transactionHash: data['transactionHash'] as String? ?? data['id'] as String, + amountWei: data['amountWei']?.toString() ?? '0', + direction: parseTransactionDirectionFromInt(data['direction'] as int), + blockTime: DateTime.fromMillisecondsSinceEpoch(data['blockTime'] as int), + isPending: data['isPending'] as bool, + tokenAddress: data['tokenAddress']?.toString() ?? '', + tokenDecimals: (data['tokenDecimals'] as num?)?.toInt() ?? 18, + tokenSymbol: data['tokenSymbol'] as String? ?? 'STRK', + to: data['to'] as String?, + from: data['from'] as String?, + txFeeWei: data['txFeeWei']?.toString() ?? '', + evmSignatureName: data['evmSignatureName'] as String?, + additionalInfo: data['additionalInfo'] == null + ? null + : Map.from(data['additionalInfo'] as Map), + height: (data['height'] as num?)?.toInt(), + ); + } + + Map toJson() => { + 'id': id, + 'transactionHash': transactionHash, + 'amountWei': amountWei, + 'direction': direction.index, + 'blockTime': blockTime.millisecondsSinceEpoch, + 'isPending': isPending, + 'tokenAddress': tokenAddress, + 'tokenDecimals': tokenDecimals, + 'tokenSymbol': tokenSymbol, + 'to': to, + 'from': from, + 'txFeeWei': txFeeWei, + 'evmSignatureName': evmSignatureName, + 'additionalInfo': additionalInfo, + 'height': height, + }; + + static int _safeAmountInt(String value) { + final parsed = BigInt.tryParse(value) ?? BigInt.zero; + if (parsed > BigInt.from(0x7fffffffffffffff)) { + return 0x7fffffffffffffff; + } + + return parsed.toInt(); + } + + static String _formatStrkFee(String value) => '${truncateDecimalString( + formatFixed(BigInt.parse(value), 18, fractionalDigits: 18), + )} STRK'; + + String? _stringAdditionalInfo(String key) { + final value = additionalInfo[key]?.toString().trim(); + if (value == null || value.isEmpty) { + return null; + } + + return value; + } + + String? _humanizeAdditionalInfoValue(String key) { + final value = _stringAdditionalInfo(key); + if (value == null) { + return null; + } + + final words = value.split('_').where((word) => word.isNotEmpty).map((word) { + final lower = word.toLowerCase(); + if (lower == 'l1' || lower == 'l2') { + return word.toUpperCase(); + } + + return lower[0].toUpperCase() + lower.substring(1); + }).join(' '); + + return words.isEmpty ? null : words; + } +} diff --git a/cw_starknet/lib/starknet_transaction_priority.dart b/cw_starknet/lib/starknet_transaction_priority.dart new file mode 100644 index 0000000000..30570eb937 --- /dev/null +++ b/cw_starknet/lib/starknet_transaction_priority.dart @@ -0,0 +1,74 @@ +import 'package:cw_core/transaction_priority.dart'; +import 'package:flutter/foundation.dart'; + +class StarknetTransactionPriority extends TransactionPriority { + const StarknetTransactionPriority({ + required String title, + required int raw, + required this.gasAmountMultiplier, + required this.gasPriceMultiplier, + required this.tipMultiplier, + }) : super(title: title, raw: raw); + + final double gasAmountMultiplier; + final double gasPriceMultiplier; + final double tipMultiplier; + + static const List all = [slow, medium, fast]; + + static const StarknetTransactionPriority slow = StarknetTransactionPriority( + title: 'Slow', + raw: 0, + gasAmountMultiplier: 1.15, + gasPriceMultiplier: 1.10, + tipMultiplier: 0.5, + ); + + static const StarknetTransactionPriority medium = StarknetTransactionPriority( + title: 'Medium', + raw: 1, + gasAmountMultiplier: 1.35, + gasPriceMultiplier: 1.30, + tipMultiplier: 1.0, + ); + + static const StarknetTransactionPriority fast = StarknetTransactionPriority( + title: 'Fast', + raw: 2, + gasAmountMultiplier: 1.60, + gasPriceMultiplier: 1.50, + tipMultiplier: 2.0, + ); + + static StarknetTransactionPriority deserialize({required int raw}) { + switch (raw) { + case 0: + return slow; + case 1: + return medium; + case 2: + return fast; + default: + if (kDebugMode) { + throw Exception( + 'Unexpected token: $raw for StarknetTransactionPriority deserialize', + ); + } + return medium; + } + } + + @override + String toString() { + switch (this) { + case StarknetTransactionPriority.slow: + return 'Slow'; + case StarknetTransactionPriority.medium: + return 'Medium'; + case StarknetTransactionPriority.fast: + return 'Fast'; + default: + return title; + } + } +} diff --git a/cw_starknet/lib/starknet_ur.dart b/cw_starknet/lib/starknet_ur.dart new file mode 100644 index 0000000000..5f39f64a3e --- /dev/null +++ b/cw_starknet/lib/starknet_ur.dart @@ -0,0 +1,349 @@ +import 'dart:convert'; + +import 'package:ur/cbor_lite.dart'; +import 'package:ur/ur.dart'; +import 'package:ur/ur_decoder.dart'; +import 'package:ur/ur_encoder.dart'; + +const String starknetSignRequestUrType = 'starknet-sign-request'; +const String starknetSignResponseUrType = 'starknet-sign-response'; +const String starknetMessageSignRequestUrType = 'starknet-message-sign-request'; +const String starknetMessageSignResponseUrType = 'starknet-message-sign-response'; +const String starknetTypedDataSignRequestUrType = 'starknet-typed-data-sign-request'; +const String starknetTypedDataSignResponseUrType = 'starknet-typed-data-sign-response'; +const int _defaultUrFragmentLength = 120; + +class StarknetUrSignature { + StarknetUrSignature({ + required this.rHex, + required this.sHex, + }); + + factory StarknetUrSignature.fromJson(Map json) => StarknetUrSignature( + rHex: json['rHex'] as String, + sHex: json['sHex'] as String, + ); + + final String rHex; + final String sHex; + + Map toJson() => { + 'rHex': rHex, + 'sHex': sHex, + }; +} + +class StarknetSignRequestUrPayload { + StarknetSignRequestUrPayload({ + required this.planJson, + required this.accountAddressHex, + required this.publicKeyHex, + required this.accountClassHashHex, + required this.invokeTransactionHashHex, + required this.amountWei, + required this.amountDecimals, + required this.amountSymbol, + required this.destinationAddress, + required this.feeWei, + this.summaryActionName, + this.summaryTokenAddress, + this.summaryAdditionalInfo, + this.preferSummary = false, + this.deployAccountTransactionHashHex, + this.version = 1, + }); + + factory StarknetSignRequestUrPayload.fromJson(Map json) => + StarknetSignRequestUrPayload( + version: json['version'] as int? ?? 1, + planJson: json['planJson'] as String, + accountAddressHex: json['accountAddressHex'] as String, + publicKeyHex: json['publicKeyHex'] as String, + accountClassHashHex: json['accountClassHashHex'] as String, + invokeTransactionHashHex: json['invokeTransactionHashHex'] as String, + deployAccountTransactionHashHex: json['deployAccountTransactionHashHex'] as String?, + amountWei: json['amountWei'] as String, + amountDecimals: json['amountDecimals'] as int, + amountSymbol: json['amountSymbol'] as String, + destinationAddress: json['destinationAddress'] as String, + feeWei: json['feeWei'] as String, + summaryActionName: json['summaryActionName'] as String?, + summaryTokenAddress: json['summaryTokenAddress'] as String?, + summaryAdditionalInfo: json['summaryAdditionalInfo'] == null + ? null + : Map.from(json['summaryAdditionalInfo'] as Map), + preferSummary: json['preferSummary'] as bool? ?? false, + ); + + final int version; + final String planJson; + final String accountAddressHex; + final String publicKeyHex; + final String accountClassHashHex; + final String invokeTransactionHashHex; + final String? deployAccountTransactionHashHex; + final String amountWei; + final int amountDecimals; + final String amountSymbol; + final String destinationAddress; + final String feeWei; + final String? summaryActionName; + final String? summaryTokenAddress; + final Map? summaryAdditionalInfo; + final bool preferSummary; + + Map toJson() => { + 'version': version, + 'planJson': planJson, + 'accountAddressHex': accountAddressHex, + 'publicKeyHex': publicKeyHex, + 'accountClassHashHex': accountClassHashHex, + 'invokeTransactionHashHex': invokeTransactionHashHex, + 'deployAccountTransactionHashHex': deployAccountTransactionHashHex, + 'amountWei': amountWei, + 'amountDecimals': amountDecimals, + 'amountSymbol': amountSymbol, + 'destinationAddress': destinationAddress, + 'feeWei': feeWei, + 'summaryActionName': summaryActionName, + 'summaryTokenAddress': summaryTokenAddress, + 'summaryAdditionalInfo': summaryAdditionalInfo, + 'preferSummary': preferSummary, + }; +} + +class StarknetSignResponseUrPayload { + StarknetSignResponseUrPayload({ + required this.planJson, + required this.invokeTransactionHashHex, + required this.invokeSignature, + this.deployAccountTransactionHashHex, + this.deploySignature, + this.version = 1, + }); + + factory StarknetSignResponseUrPayload.fromJson(Map json) => + StarknetSignResponseUrPayload( + version: json['version'] as int? ?? 1, + planJson: json['planJson'] as String, + invokeTransactionHashHex: json['invokeTransactionHashHex'] as String, + invokeSignature: + StarknetUrSignature.fromJson(Map.from(json['invokeSignature'] as Map)), + deployAccountTransactionHashHex: json['deployAccountTransactionHashHex'] as String?, + deploySignature: json['deploySignature'] == null + ? null + : StarknetUrSignature.fromJson( + Map.from(json['deploySignature'] as Map), + ), + ); + + final int version; + final String planJson; + final String invokeTransactionHashHex; + final StarknetUrSignature invokeSignature; + final String? deployAccountTransactionHashHex; + final StarknetUrSignature? deploySignature; + + Map toJson() => { + 'version': version, + 'planJson': planJson, + 'invokeTransactionHashHex': invokeTransactionHashHex, + 'invokeSignature': invokeSignature.toJson(), + 'deployAccountTransactionHashHex': deployAccountTransactionHashHex, + 'deploySignature': deploySignature?.toJson(), + }; +} + +class StarknetMessageSignRequestUrPayload { + StarknetMessageSignRequestUrPayload({ + required this.accountAddressHex, + required this.publicKeyHex, + required this.message, + required this.messageHashHex, + this.version = 1, + }); + + factory StarknetMessageSignRequestUrPayload.fromJson(Map json) => + StarknetMessageSignRequestUrPayload( + version: json['version'] as int? ?? 1, + accountAddressHex: json['accountAddressHex'] as String, + publicKeyHex: json['publicKeyHex'] as String, + message: json['message'] as String, + messageHashHex: json['messageHashHex'] as String, + ); + + final int version; + final String accountAddressHex; + final String publicKeyHex; + final String message; + final String messageHashHex; + + Map toJson() => { + 'version': version, + 'accountAddressHex': accountAddressHex, + 'publicKeyHex': publicKeyHex, + 'message': message, + 'messageHashHex': messageHashHex, + }; +} + +class StarknetTypedDataSignRequestUrPayload { + StarknetTypedDataSignRequestUrPayload({ + required this.accountAddressHex, + required this.publicKeyHex, + required this.typedDataJson, + required this.typedDataHashHex, + this.version = 1, + }); + + factory StarknetTypedDataSignRequestUrPayload.fromJson(Map json) => + StarknetTypedDataSignRequestUrPayload( + version: json['version'] as int? ?? 1, + accountAddressHex: json['accountAddressHex'] as String, + publicKeyHex: json['publicKeyHex'] as String, + typedDataJson: json['typedDataJson'] as String, + typedDataHashHex: json['typedDataHashHex'] as String, + ); + + final int version; + final String accountAddressHex; + final String publicKeyHex; + final String typedDataJson; + final String typedDataHashHex; + + Map toJson() => { + 'version': version, + 'accountAddressHex': accountAddressHex, + 'publicKeyHex': publicKeyHex, + 'typedDataJson': typedDataJson, + 'typedDataHashHex': typedDataHashHex, + }; +} + +class StarknetSignatureResponseUrPayload { + StarknetSignatureResponseUrPayload({ + required this.messageHashHex, + required this.signature, + this.version = 1, + }); + + factory StarknetSignatureResponseUrPayload.fromJson(Map json) => + StarknetSignatureResponseUrPayload( + version: json['version'] as int? ?? 1, + messageHashHex: json['messageHashHex'] as String, + signature: StarknetUrSignature.fromJson( + Map.from(json['signature'] as Map), + ), + ); + + final int version; + final String messageHashHex; + final StarknetUrSignature signature; + + Map toJson() => { + 'version': version, + 'messageHashHex': messageHashHex, + 'signature': signature.toJson(), + }; +} + +Map encodeStarknetSignRequestUrMap(StarknetSignRequestUrPayload payload) => { + 'Cupcake ur': _encodeJsonUr(starknetSignRequestUrType, payload.toJson()), + }; + +String encodeStarknetSignResponseUr(StarknetSignResponseUrPayload payload) => + _encodeJsonUr(starknetSignResponseUrType, payload.toJson()); + +Map encodeStarknetMessageSignRequestUrMap( + StarknetMessageSignRequestUrPayload payload) => + { + 'Cupcake ur': _encodeJsonUr(starknetMessageSignRequestUrType, payload.toJson()), + }; + +String encodeStarknetMessageSignResponseUr(StarknetSignatureResponseUrPayload payload) => + _encodeJsonUr(starknetMessageSignResponseUrType, payload.toJson()); + +Map encodeStarknetTypedDataSignRequestUrMap( + StarknetTypedDataSignRequestUrPayload payload) => + { + 'Cupcake ur': _encodeJsonUr(starknetTypedDataSignRequestUrType, payload.toJson()), + }; + +String encodeStarknetTypedDataSignResponseUr(StarknetSignatureResponseUrPayload payload) => + _encodeJsonUr(starknetTypedDataSignResponseUrType, payload.toJson()); + +StarknetSignRequestUrPayload decodeStarknetSignRequestUr(String input) => + StarknetSignRequestUrPayload.fromJson(_decodeJsonUr(input, starknetSignRequestUrType)); + +StarknetSignResponseUrPayload decodeStarknetSignResponseUr(String input) => + StarknetSignResponseUrPayload.fromJson(_decodeJsonUr(input, starknetSignResponseUrType)); + +StarknetMessageSignRequestUrPayload decodeStarknetMessageSignRequestUr(String input) => + StarknetMessageSignRequestUrPayload.fromJson( + _decodeJsonUr(input, starknetMessageSignRequestUrType), + ); + +StarknetSignatureResponseUrPayload decodeStarknetMessageSignResponseUr(String input) => + StarknetSignatureResponseUrPayload.fromJson( + _decodeJsonUr(input, starknetMessageSignResponseUrType), + ); + +StarknetTypedDataSignRequestUrPayload decodeStarknetTypedDataSignRequestUr(String input) => + StarknetTypedDataSignRequestUrPayload.fromJson( + _decodeJsonUr(input, starknetTypedDataSignRequestUrType), + ); + +StarknetSignatureResponseUrPayload decodeStarknetTypedDataSignResponseUr(String input) => + StarknetSignatureResponseUrPayload.fromJson( + _decodeJsonUr(input, starknetTypedDataSignResponseUrType), + ); + +String _encodeJsonUr(String type, Map jsonMap) { + final sourceBytes = utf8.encode(json.encode(jsonMap)); + final cborEncoder = CBOREncoder(); + cborEncoder.encodeBytes(sourceBytes); + + final ur = UR(type, cborEncoder.getBytes()); + final encoder = UREncoder(ur, _defaultUrFragmentLength); + final parts = []; + + while (!encoder.isComplete) { + parts.add(encoder.nextPart()); + } + + return parts.join('\n'); +} + +Map _decodeJsonUr(String input, String expectedType) { + final decoder = URDecoder(); + + for (final rawPart in input.split('\n')) { + final part = rawPart.trim(); + if (part.isEmpty) { + continue; + } + + if (!part.startsWith('ur:')) { + throw Exception('Unexpected Starknet UR fragment: $part'); + } + + decoder.receivePart(part); + } + + if (!decoder.isComplete()) { + throw Exception('Incomplete Starknet UR payload'); + } + + final result = decoder.result; + if (result is! UR) { + throw Exception('Unexpected Starknet UR decode result: ${result.runtimeType}'); + } + + if (result.type != expectedType) { + throw Exception('Unexpected Starknet UR type: ${result.type}'); + } + + final cborDecoder = CBORDecoder(result.cbor); + final bytes = cborDecoder.decodeBytes().$1; + return Map.from(json.decode(utf8.decode(bytes)) as Map); +} diff --git a/cw_starknet/lib/starknet_wallet.dart b/cw_starknet/lib/starknet_wallet.dart new file mode 100644 index 0000000000..b1b3d7e391 --- /dev/null +++ b/cw_starknet/lib/starknet_wallet.dart @@ -0,0 +1,1873 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:crypto/crypto.dart' as crypto_lib; +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/format_fixed.dart'; +import 'package:cw_core/hardware/hardware_wallet_service.dart'; +import 'package:cw_core/node.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_core/starknet_token.dart'; +import 'package:cw_core/sync_status.dart'; +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_core/wallet_addresses.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; +import 'package:cw_starknet/default_starknet_tokens.dart'; +import 'package:cw_starknet/hardware/starknet_ledger_service.dart'; +import 'package:cw_starknet/starknet_balance.dart'; +import 'package:cw_starknet/starknet_client.dart'; +import 'package:cw_starknet/starknet_exceptions.dart'; +import 'package:cw_starknet/starknet_rust.dart'; +import 'package:cw_starknet/starknet_transaction_credentials.dart'; +import 'package:cw_starknet/starknet_transaction_history.dart'; +import 'package:cw_starknet/starknet_transaction_info.dart'; +import 'package:cw_starknet/starknet_transaction_priority.dart'; +import 'package:cw_starknet/starknet_ur.dart'; +import 'package:cw_starknet/starknet_wallet_addresses.dart'; +import 'package:cw_starknet/src/rust/api/starknet.dart' as rust_api; +import 'package:hex/hex.dart'; +import 'package:hive/hive.dart'; +import 'package:mobx/mobx.dart'; + +part 'starknet_wallet.g.dart'; + +class StarknetWallet = StarknetWalletBase with _$StarknetWallet; + +abstract class StarknetWalletBase + extends WalletBase + with Store, WalletKeysFile { + StarknetWalletBase({ + required WalletInfo walletInfo, + required DerivationInfo derivationInfo, + String? mnemonic, + String? privateKey, + required String password, + StarknetBalance? initialBalance, + required this.encryptionFileUtils, + this.passphrase, + String? accountClassHashHex, + String? hardwarePublicKeyHex, + String? hardwareDerivationPath, + int? initialLastSyncedBlock, + StarknetWalletClient? client, + }) : syncStatus = const NotConnectedSyncStatus(), + _password = password, + _mnemonic = mnemonic, + _hexPrivateKey = privateKey, + _hardwarePublicKeyHex = hardwarePublicKeyHex, + _hardwareDerivationPath = hardwareDerivationPath, + walletAddresses = StarknetWalletAddresses(walletInfo), + balance = ObservableMap.of( + {CryptoCurrency.strk: initialBalance ?? StarknetBalance.zero()}, + ), + super(walletInfo, derivationInfo) { + this.walletInfo = walletInfo; + transactionHistory = StarknetTransactionHistory( + walletInfo: walletInfo, + password: password, + encryptionFileUtils: encryptionFileUtils, + ); + + if (!CakeHive.isAdapterRegistered(StarknetToken.typeId)) { + CakeHive.registerAdapter(StarknetTokenAdapter()); + } + + _accountClassHashHex = _normalizeAccountClassHashHex(accountClassHashHex); + _lastSyncedBlock = initialLastSyncedBlock; + _client = client ?? StarknetWalletClient(); + } + + static const String openZeppelinAccountClassHashHex = + '0x01d1777db36cdd06dd62cfde77b1b6ae06412af95d57a13dc40ac77b8a702381'; + + String _password; + final String? _mnemonic; + final String? _hexPrivateKey; + final EncryptionFileUtils encryptionFileUtils; + + late final StarknetWalletClient _client; + late final Box starknetTokensBox; + late String _runtimePrivateKeyHex; + late String _starknetPublicKeyHex; + late String _accountAddressHex; + late String _accountClassHashHex; + String? _hardwarePublicKeyHex; + final String? _hardwareDerivationPath; + HardwareWalletService? _hardwareWalletService; + StarknetTransactionPriority _currentFeePriority = StarknetTransactionPriority.medium; + int? _estimatedFeePriorityRaw; + final Map _estimatedFeeQuotesByTokenAddress = {}; + + StarknetWalletClient get client => _client; + + Timer? _transactionsUpdateTimer; + bool _isTransactionUpdating = false; + int? _lastSyncedBlock; + + @observable + ObservableMap estimatedFeesWeiByTokenAddress = ObservableMap(); + + @override + WalletAddresses walletAddresses; + + @override + @observable + SyncStatus syncStatus; + + @override + @observable + ObservableMap balance = + ObservableMap(); + + @override + Object get keys => { + 'address': accountAddress, + 'publicKey': publicKey, + if (privateKey.isNotEmpty) 'privateKey': privateKey, + if (_hardwareDerivationPath?.isNotEmpty ?? false) + 'derivationPath': _hardwareDerivationPath!, + 'accountClassHash': accountClassHashHex, + }; + + @override + bool get hasRescan => true; + + String get accountAddress => _accountAddressHex; + + String get publicKey => _starknetPublicKeyHex; + + String get accountClassHashHex => _accountClassHashHex; + + bool get supportsOfflineUrSigning => + walletInfo.isHardwareWallet && + _starknetPublicKeyHex.isNotEmpty && + _hardwareWalletService == null; + + List get starknetTokenCurrencies => starknetTokensBox.values.toList(); + + @override + String? get seed => _mnemonic; + + @override + String get privateKey => _hexPrivateKey ?? ''; + + @override + WalletKeysData get walletKeysData => WalletKeysData( + mnemonic: _mnemonic, + privateKey: privateKey, + passphrase: passphrase, + publicKey: _hardwarePublicKeyHex, + derivationPath: _hardwareDerivationPath, + accountClassHashHex: _accountClassHashHex, + ); + + Future init() async { + await ensureStarknetRustInitialized(); + await _initStarknetTokensBox(); + + if (walletInfo.isHardwareWallet) { + _runtimePrivateKeyHex = ''; + _starknetPublicKeyHex = await _resolveHardwarePublicKeyHex(); + _accountAddressHex = await _resolveHardwareAccountAddress(_starknetPublicKeyHex); + } else { + final normalizedPrivateKeyHex = + (_hexPrivateKey?.trim().isEmpty ?? true) ? null : _hexPrivateKey!.trim(); + + final response = await rust_api.deriveAccount( + mnemonic: _mnemonic, + passphrase: passphrase, + privateKeyHex: normalizedPrivateKeyHex, + accountClassHashHex: _accountClassHashHex, + ); + final accountData = unwrapDerivedAccountDataResponse(response); + + _runtimePrivateKeyHex = accountData.privateKeyHex; + _starknetPublicKeyHex = accountData.publicKeyHex; + _accountAddressHex = accountData.accountAddressHex; + } + + walletInfo.address = _accountAddressHex; + + await walletAddresses.init(); + await transactionHistory.init(); + + _ensureBalanceEntries(); + await save(); + } + + void setHardwareWalletService(HardwareWalletService service) { + _hardwareWalletService = service; + _client.setHardwareWalletService(service); + } + + Future _initStarknetTokensBox() async { + final boxName = '${walletInfo.name.replaceAll(" ", "_")}_${StarknetToken.boxName}'; + starknetTokensBox = await CakeHive.openBox(boxName); + _addInitialTokens(); + } + + void _addInitialTokens() { + final initialTokens = DefaultStarknetTokens().initialStarknetTokens; + for (final token in initialTokens) { + if (!starknetTokensBox.containsKey(token.contractAddress)) { + starknetTokensBox.put(token.contractAddress, token); + } else { + final existingToken = starknetTokensBox.get(token.contractAddress)!; + starknetTokensBox.put( + token.contractAddress, + StarknetToken.copyWith(token, enabled: existingToken.enabled), + ); + } + } + } + + void _ensureBalanceEntries() { + balance[CryptoCurrency.strk] ??= StarknetBalance.zero(decimals: CryptoCurrency.strk.decimals); + for (final token in starknetTokenCurrencies.where((token) => token.enabled)) { + balance[token] ??= StarknetBalance.zero(decimals: token.decimal); + } + } + + @override + int calculateEstimatedFee(TransactionPriority priority, int? amount) { + final normalizedPriority = _normalizeTransactionPriority(priority); + if (_estimatedFeePriorityRaw != normalizedPriority.raw) { + return 0; + } + + final asset = balance.keys.contains(currency) ? currency : CryptoCurrency.strk; + final feeQuote = _estimatedFeeQuotesByTokenAddress[_tokenAddressFor(asset).toLowerCase()] ?? + _estimatedFeeQuotesByTokenAddress[StarknetTokenAddresses.strk.toLowerCase()]; + if (feeQuote == null) { + return 0; + } + + final parsed = BigInt.tryParse(feeQuote.overallFeeWei) ?? BigInt.zero; + if (parsed > BigInt.from(0x7fffffffffffffff)) { + return 0x7fffffffffffffff; + } + + return parsed.toInt(); + } + + @override + Future updateEstimatedFeesParams(TransactionPriority? priority) async { + _currentFeePriority = _normalizeTransactionPriority(priority); + await _refreshEstimatedFees(priority: _currentFeePriority); + } + + @override + Future changePassword(String password) async { + _password = password; + await transactionHistory.changePassword(password); + await saveKeysFile(password, encryptionFileUtils); + await saveKeysFile(password, encryptionFileUtils, true); + await save(); + } + + @override + Future close({bool shouldCleanup = false}) async { + _client.stop(); + _transactionsUpdateTimer?.cancel(); + } + + @action + @override + Future connectToNode({required Node node}) async { + try { + syncStatus = ConnectingSyncStatus(); + + final isConnected = _client.connect(node); + if (!isConnected) { + throw StarknetNodeConnectionException(); + } + + if (walletInfo.isHardwareWallet) { + if (_hardwareWalletService != null && + (_hardwareDerivationPath?.trim().isNotEmpty ?? false)) { + _client.setupHardwareAccount( + addressHex: _accountAddressHex, + publicKeyHex: _starknetPublicKeyHex, + derivationPath: _requireHardwareDerivationPath(), + service: _hardwareWalletService, + ); + } else { + _client.setupExternalSignerAccount( + addressHex: _accountAddressHex, + publicKeyHex: _starknetPublicKeyHex, + ); + } + } else { + _client.setupAccount( + privateKeyHex: _runtimePrivateKeyHex, + addressHex: _accountAddressHex, + ); + } + + _setTransactionUpdateTimer(); + syncStatus = ConnectedSyncStatus(); + } catch (e) { + printV('Failed to connect Starknet wallet to node: $e'); + syncStatus = FailedSyncStatus(); + } + } + + static String _messageHashHex(String message) => + _starknetFeltHex(crypto_lib.sha256.convert(utf8.encode(message)).bytes); + + @override + Future createTransaction(Object credentials) async { + final starkCredentials = credentials as StarknetTransactionCredentials; + final feePriority = _normalizeTransactionPriority(starkCredentials.priority); + _currentFeePriority = feePriority; + final outputs = starkCredentials.outputs; + if (outputs.isEmpty) { + throw StarknetTransactionCreationException.fromMessage('Missing Starknet outputs'); + } + + final sendAllOutputs = outputs.where((output) => output.sendAll).toList(); + if (sendAllOutputs.length > 1 || (sendAllOutputs.isNotEmpty && outputs.length > 1)) { + throw StarknetTransactionCreationException.fromMessage( + 'Send all is only supported for a single Starknet output', + ); + } + + final asset = _resolveAsset(starkCredentials.currency); + final tokenAddress = _tokenAddressFor(asset); + final tokenBalance = balance[asset]?.fullAvailableBalance ?? BigInt.zero; + + if (tokenBalance <= BigInt.zero) { + throw StarknetTransactionWrongBalanceException(asset); + } + + final resolvedOutputs = outputs + .map( + (output) => ( + output: output, + address: output.isParsedAddress ? output.extractedAddress! : output.address, + ), + ) + .toList(); + + for (final resolvedOutput in resolvedOutputs) { + if (!StarknetWalletClient.isValidAddress(resolvedOutput.address)) { + throw StarknetInvalidAddressException(resolvedOutput.address); + } + } + + final feeTokenBalance = balance[CryptoCurrency.strk]?.fullAvailableBalance ?? BigInt.zero; + final isNativeFeeToken = asset.titleAndTagEqual(CryptoCurrency.strk); + + BigInt totalAmountWei = BigInt.zero; + final transfers = <(String address, BigInt amountWei)>[]; + + if (sendAllOutputs.isNotEmpty) { + final destinationAddress = resolvedOutputs.first.address; + final quote = await _quoteTransferBatch( + tokenAddress: tokenAddress, + transfers: [(destinationAddress, tokenBalance)], + priority: feePriority, + ); + + BigInt sendAmountWei = tokenBalance; + if (isNativeFeeToken) { + sendAmountWei -= BigInt.parse(quote.overallFeeWei); + } else if (feeTokenBalance < BigInt.parse(quote.overallFeeWei)) { + throw StarknetTransactionCreationException.fromMessage( + 'Insufficient STRK balance to pay Starknet network fees', + ); + } + + if (sendAmountWei <= BigInt.zero) { + throw StarknetTransactionWrongBalanceException(asset); + } + + totalAmountWei = sendAmountWei; + transfers.add((destinationAddress, sendAmountWei)); + } else { + for (final resolvedOutput in resolvedOutputs) { + final cryptoAmount = resolvedOutput.output.cryptoAmount ?? '0'; + final parsedAmount = asset.tryParseAmount(cryptoAmount) ?? BigInt.zero; + if (parsedAmount <= BigInt.zero) { + throw StarknetTransactionCreationException.fromMessage( + 'Invalid Starknet amount for ${resolvedOutput.address}', + ); + } + totalAmountWei += parsedAmount; + transfers.add((resolvedOutput.address, parsedAmount)); + } + + final quote = await _quoteTransferBatch( + tokenAddress: tokenAddress, + transfers: transfers, + priority: feePriority, + ); + + if (totalAmountWei > tokenBalance) { + throw StarknetTransactionWrongBalanceException(asset); + } + + if (isNativeFeeToken && totalAmountWei + BigInt.parse(quote.overallFeeWei) > tokenBalance) { + throw StarknetTransactionWrongBalanceException(asset); + } + + if (!isNativeFeeToken && feeTokenBalance < BigInt.parse(quote.overallFeeWei)) { + throw StarknetTransactionCreationException.fromMessage( + 'Insufficient STRK balance to pay Starknet network fees', + ); + } + } + + final feeQuote = await _quoteTransferBatch( + tokenAddress: tokenAddress, + transfers: transfers, + priority: feePriority, + ); + final calls = transfers + .map( + (transfer) => _buildTransferCall( + tokenAddress: tokenAddress, + destinationAddress: transfer.$1, + amountWei: transfer.$2, + ), + ) + .toList(); + + final transferSummary = _buildTransferExecutionSummary( + asset: asset, + transfers: transfers, + feeQuote: feeQuote, + priority: feePriority, + ); + + return supportsOfflineUrSigning + ? _client.createOfflineTransaction( + calls: calls, + amountWei: totalAmountWei.toString(), + amountDecimals: asset.decimals, + amountSymbol: asset.title, + destinationAddress: + transfers.length == 1 ? transfers.first.$1 : '${transfers.length} recipients', + accountClassHashHex: _accountClassHashHex, + feeWei: feeQuote.overallFeeWei, + feePriorityRaw: feePriority.raw, + summaryActionName: transferSummary.actionName, + summaryTokenAddress: transferSummary.tokenAddress, + summaryAdditionalInfo: transferSummary.additionalInfo, + preferSummary: transferSummary.preferSummary, + ) + : walletInfo.isHardwareWallet + ? _client.createHardwareTransaction( + calls: calls, + amountWei: totalAmountWei.toString(), + amountDecimals: asset.decimals, + amountSymbol: asset.title, + destinationAddress: + transfers.length == 1 ? transfers.first.$1 : '${transfers.length} recipients', + accountClassHashHex: _accountClassHashHex, + feeWei: feeQuote.overallFeeWei, + feePriorityRaw: feePriority.raw, + onCommitted: transferSummary.preferSummary + ? (txHash) => _recordExecutionSummary( + txHash: txHash, + summary: transferSummary, + feeWei: feeQuote.overallFeeWei, + ) + : null, + ) + : _client.createTransaction( + calls: calls, + amountWei: totalAmountWei.toString(), + amountDecimals: asset.decimals, + amountSymbol: asset.title, + destinationAddress: + transfers.length == 1 ? transfers.first.$1 : '${transfers.length} recipients', + accountClassHashHex: _accountClassHashHex, + feeWei: feeQuote.overallFeeWei, + feePriorityRaw: feePriority.raw, + onCommitted: transferSummary.preferSummary + ? (txHash) => _recordExecutionSummary( + txHash: txHash, + summary: transferSummary, + feeWei: feeQuote.overallFeeWei, + ) + : null, + ); + } + + @override + Future> fetchTransactions() async { + try { + final assets = [ + CryptoCurrency.strk, + ...starknetTokenCurrencies.where((token) => token.enabled), + ]; + + final result = {}; + int? latestBlock = _lastSyncedBlock; + final summaryTransactions = transactionHistory.transactions.values + .where( + (transaction) => + transaction.additionalInfo['starknetPreferSummary'] == true && + transaction.transactionHash.isNotEmpty, + ) + .toList(growable: false); + final summaryTransactionHashes = summaryTransactions + .map((transaction) => transaction.transactionHash.toLowerCase()) + .toSet(); + + for (final asset in assets) { + final tokenAddress = _tokenAddressFor(asset); + final events = await _client.fetchTransferEvents( + accountAddressHex: _accountAddressHex, + tokenAddressHex: tokenAddress, + tokenSymbol: asset.title, + fromBlock: _lastSyncedBlock, + ); + + for (final event in events) { + if (summaryTransactionHashes.contains(event.transactionHash.toLowerCase())) { + continue; + } + + final timestampSeconds = + event.blockTimestamp ?? (DateTime.now().millisecondsSinceEpoch ~/ 1000); + final tokenAsset = _assetForTokenAddress(event.tokenAddressHex) ?? asset; + + result[event.eventId] = StarknetTransactionInfo( + id: event.eventId, + transactionHash: event.transactionHash, + amountWei: event.amountWei, + direction: + event.isOutgoing ? TransactionDirection.outgoing : TransactionDirection.incoming, + blockTime: DateTime.fromMillisecondsSinceEpoch(timestampSeconds * 1000), + isPending: false, + tokenAddress: event.tokenAddressHex, + tokenDecimals: tokenAsset.decimals, + tokenSymbol: tokenAsset.title, + to: event.to, + from: event.from, + txFeeWei: event.txFeeWei ?? '', + ); + + latestBlock = _maxBlock(latestBlock, event.blockNumber); + } + } + + final detailsByHash = await _fetchTransactionDetailsByHash( + { + ...result.values.map((transaction) => transaction.transactionHash), + ...summaryTransactions.map((transaction) => transaction.transactionHash), + }, + ); + + result.updateAll((_, transaction) { + final details = detailsByHash[transaction.transactionHash.toLowerCase()]; + final enriched = + details == null ? transaction : _applyTransactionDetails(transaction, details); + latestBlock = _maxBlock(latestBlock, enriched.height); + return enriched; + }); + + for (final summaryTransaction in summaryTransactions) { + final details = detailsByHash[summaryTransaction.transactionHash.toLowerCase()]; + if (details == null) { + continue; + } + + final enriched = _applyTransactionDetails(summaryTransaction, details); + result[summaryTransaction.id] = enriched; + latestBlock = _maxBlock(latestBlock, enriched.height); + } + + _lastSyncedBlock = latestBlock; + return result; + } catch (e) { + printV('Error fetching Starknet transactions: $e'); + return {}; + } + } + + @override + Future updateTransactionsHistory({List? specificTokenMints}) async { + if (_isTransactionUpdating) { + return; + } + + _isTransactionUpdating = true; + try { + final transactions = await fetchTransactions(); + if (transactions.isNotEmpty) { + transactionHistory.addMany(transactions); + await transactionHistory.save(); + } + } catch (e) { + printV('Error updating Starknet transaction history: $e'); + } finally { + _isTransactionUpdating = false; + } + } + + Future> _fetchTransactionDetailsByHash( + Set transactionHashes, + ) async { + final results = {}; + + for (final transactionHash in transactionHashes) { + final normalizedHash = transactionHash.trim(); + if (normalizedHash.isEmpty) { + continue; + } + + final details = await _client.getTransactionDetails( + transactionHashHex: normalizedHash, + ); + if (details != null) { + results[normalizedHash.toLowerCase()] = details; + } + } + + return results; + } + + StarknetTransactionInfo _applyTransactionDetails( + StarknetTransactionInfo transaction, + StarknetTransactionDetails details, + ) { + final nextAdditionalInfo = { + ...transaction.additionalInfo, + 'starknetTransactionType': details.transactionType, + 'starknetExecutionStatus': details.executionStatus, + 'starknetFinalityStatus': details.finalityStatus, + if ((details.revertReason?.isNotEmpty ?? false)) 'starknetRevertReason': details.revertReason, + if ((details.callCount ?? 0) > 0) 'starknetCallCount': details.callCount, + if ((details.primaryContractAddressHex?.isNotEmpty ?? false)) + 'starknetPrimaryContract': details.primaryContractAddressHex, + if ((details.primaryEntrypoint?.isNotEmpty ?? false)) + 'starknetPrimaryEntrypoint': details.primaryEntrypoint, + if ((details.senderAddressHex?.isNotEmpty ?? false)) + 'starknetSenderAddress': details.senderAddressHex, + if (details.accountDeploymentRequired) 'starknetAccountDeploymentRequired': true, + if ((details.l1GasMaxAmount?.isNotEmpty ?? false)) + 'starknetL1GasMaxAmount': details.l1GasMaxAmount, + if ((details.l1GasMaxPriceWei?.isNotEmpty ?? false)) + 'starknetL1GasMaxPriceWei': details.l1GasMaxPriceWei, + if ((details.l2GasMaxAmount?.isNotEmpty ?? false)) + 'starknetL2GasMaxAmount': details.l2GasMaxAmount, + if ((details.l2GasMaxPriceWei?.isNotEmpty ?? false)) + 'starknetL2GasMaxPriceWei': details.l2GasMaxPriceWei, + if ((details.l1DataGasMaxAmount?.isNotEmpty ?? false)) + 'starknetL1DataGasMaxAmount': details.l1DataGasMaxAmount, + if ((details.l1DataGasMaxPriceWei?.isNotEmpty ?? false)) + 'starknetL1DataGasMaxPriceWei': details.l1DataGasMaxPriceWei, + if (details.tip != null) 'starknetTip': details.tip, + }; + + final nextActionName = details.actionName ?? transaction.evmSignatureName; + if (nextActionName != null && nextActionName.isNotEmpty) { + nextAdditionalInfo['starknetActionLabel'] = _starknetActionLabelFor(nextActionName); + } + + final timestampSeconds = + details.blockTimestamp ?? (transaction.blockTime.millisecondsSinceEpoch ~/ 1000); + + return StarknetTransactionInfo( + id: transaction.id, + transactionHash: transaction.transactionHash, + blockTime: DateTime.fromMillisecondsSinceEpoch(timestampSeconds * 1000), + to: transaction.to, + from: transaction.from ?? details.senderAddressHex, + direction: transaction.direction, + amountWei: transaction.amountWei, + tokenAddress: transaction.tokenAddress, + tokenDecimals: transaction.tokenDecimals, + tokenSymbol: transaction.tokenSymbol, + isPending: details.isPending, + txFeeWei: details.actualFeeWei ?? transaction.txFeeWei, + evmSignatureName: nextActionName, + additionalInfo: nextAdditionalInfo, + height: details.blockNumber ?? transaction.height, + ); + } + + int? _maxBlock(int? left, int? right) { + if (right == null) { + return left; + } + + if (left == null) { + return right; + } + + return right > left ? right : left; + } + + @override + Future rescan({required int height}) async { + final shouldRestartTimer = _transactionsUpdateTimer?.isActive ?? false; + _transactionsUpdateTimer?.cancel(); + + try { + syncStatus = AttemptingSyncStatus(); + _lastSyncedBlock = height < 0 ? 0 : height; + _estimatedFeeQuotesByTokenAddress.clear(); + _estimatedFeePriorityRaw = null; + estimatedFeesWeiByTokenAddress.clear(); + + await transactionHistory.reset(); + await _updateBalance(throwOnError: true); + await updateTransactionsHistory(); + await _refreshEstimatedFees(priority: _currentFeePriority); + + syncStatus = SyncedSyncStatus(); + } catch (e) { + printV('Error rescanning Starknet wallet: $e'); + syncStatus = FailedSyncStatus(error: e.toString()); + rethrow; + } finally { + if (shouldRestartTimer) { + _setTransactionUpdateTimer(); + } + } + } + + @override + Future save() async { + if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) { + await saveKeysFile(_password, encryptionFileUtils); + await saveKeysFile(_password, encryptionFileUtils, true); + } + + await walletAddresses.updateAddressesInBox(); + final path = await makePath(); + await encryptionFileUtils.write( + path: path, + password: _password, + data: toJSON(), + ); + await transactionHistory.save(); + } + + @action + @override + Future startSync() async { + try { + syncStatus = AttemptingSyncStatus(); + + await _updateBalance(throwOnError: true); + await _refreshEstimatedFees(priority: _currentFeePriority); + await updateTransactionsHistory(); + + syncStatus = SyncedSyncStatus(); + } catch (e) { + printV('Failed to sync Starknet wallet balance: $e'); + syncStatus = FailedSyncStatus(); + } + } + + Future _updateBalance({bool throwOnError = false}) async { + try { + final nextBalance = {}; + nextBalance[CryptoCurrency.strk] = await _client.getTokenBalance( + accountAddressHex: _accountAddressHex, + tokenAddressHex: StarknetTokenAddresses.strk, + decimals: CryptoCurrency.strk.decimals, + ); + + for (final token in starknetTokenCurrencies.where((token) => token.enabled)) { + nextBalance[token] = await _client.getTokenBalance( + accountAddressHex: _accountAddressHex, + tokenAddressHex: token.contractAddress, + decimals: token.decimal, + ); + } + + balance + ..clear() + ..addAll(nextBalance); + await save(); + } catch (e) { + printV('Preserving previous Starknet balance after refresh failure: $e'); + if (throwOnError) { + rethrow; + } + } + } + + Future _refreshEstimatedFees({ + required StarknetTransactionPriority priority, + }) async { + _currentFeePriority = priority; + _estimatedFeePriorityRaw = priority.raw; + final nextQuotes = {}; + final nextQuoteModels = {}; + final assets = [ + CryptoCurrency.strk, + ...starknetTokenCurrencies.where((token) => token.enabled), + ]; + final feeTokenBalance = balance[CryptoCurrency.strk]?.fullAvailableBalance ?? BigInt.zero; + + for (final asset in assets) { + final assetBalance = balance[asset]?.fullAvailableBalance ?? BigInt.zero; + if (assetBalance <= BigInt.zero) { + continue; + } + + if (!asset.titleAndTagEqual(CryptoCurrency.strk) && feeTokenBalance <= BigInt.zero) { + continue; + } + + try { + final tokenAddress = _tokenAddressFor(asset); + final quote = await _quoteTransferBatch( + tokenAddress: tokenAddress, + transfers: [(_accountAddressHex, BigInt.one)], + priority: priority, + ); + nextQuotes[tokenAddress.toLowerCase()] = quote.overallFeeWei; + nextQuoteModels[tokenAddress.toLowerCase()] = quote; + } catch (e) { + printV('Skipping Starknet fee refresh for ${asset.title}: $e'); + } + } + + _estimatedFeeQuotesByTokenAddress + ..clear() + ..addAll(nextQuoteModels); + estimatedFeesWeiByTokenAddress + ..clear() + ..addAll(nextQuotes); + } + + String toJSON() => json.encode({ + 'mnemonic': _mnemonic, + 'private_key': _hexPrivateKey, + 'balance': balance[CryptoCurrency.strk]?.toJSON(), + 'passphrase': passphrase, + 'hardware_public_key_hex': _hardwarePublicKeyHex, + 'hardware_derivation_path': _hardwareDerivationPath, + 'account_class_hash_hex': _accountClassHashHex, + 'last_synced_block': _lastSyncedBlock, + }); + + 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 nativeBalance = + StarknetBalance.fromJSON(data?['balance'] as String?) ?? StarknetBalance.zero(); + final accountClassHashHex = data?['account_class_hash_hex'] as String?; + final lastSyncedBlock = data?['last_synced_block'] as int?; + final hardwarePublicKeyHex = data?['hardware_public_key_hex'] as String?; + final hardwareDerivationPath = data?['hardware_derivation_path'] as String?; + + final WalletKeysData keysData; + 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, + publicKey: hardwarePublicKeyHex, + derivationPath: hardwareDerivationPath, + accountClassHashHex: accountClassHashHex, + ); + } else { + keysData = await WalletKeysFile.readKeysFile( + name, + walletInfo.type, + password, + encryptionFileUtils, + ); + } + + final derivationInfo = await walletInfo.getDerivationInfo(); + + return StarknetWallet( + walletInfo: walletInfo, + derivationInfo: derivationInfo, + password: password, + passphrase: keysData.passphrase, + mnemonic: keysData.mnemonic, + privateKey: keysData.privateKey, + initialBalance: nativeBalance, + encryptionFileUtils: encryptionFileUtils, + hardwarePublicKeyHex: hardwarePublicKeyHex ?? keysData.publicKey, + hardwareDerivationPath: hardwareDerivationPath ?? keysData.derivationPath, + accountClassHashHex: accountClassHashHex ?? keysData.accountClassHashHex, + initialLastSyncedBlock: lastSyncedBlock, + ); + } + + @override + Future? updateBalance() async => _updateBalance(); + + @override + Future checkNodeHealth() async { + try { + final blockNumber = await _client.getBlockNumber(); + return blockNumber != null && blockNumber > 0; + } catch (_) { + return false; + } + } + + Future addStarknetToken(StarknetToken token) async { + await starknetTokensBox.put(token.contractAddress.toLowerCase(), token); + if (token.enabled) { + balance[token] ??= StarknetBalance.zero(decimals: token.decimal); + await _updateBalance(); + } + await save(); + } + + Future deleteStarknetToken(StarknetToken token) async { + await starknetTokensBox.delete(token.contractAddress.toLowerCase()); + balance.removeWhere((currency, _) => + currency is StarknetToken && + currency.contractAddress.toLowerCase() == token.contractAddress.toLowerCase()); + await save(); + } + + Future getStarknetToken(String contractAddress) async { + final normalized = contractAddress.toLowerCase(); + final existing = starknetTokensBox.get(normalized); + if (existing != null) { + return existing; + } + + try { + final metadata = await _client.getTokenMetadata(normalized); + return StarknetToken( + name: metadata.name, + symbol: metadata.symbol, + contractAddress: metadata.tokenAddressHex.toLowerCase(), + decimal: metadata.decimals, + iconPath: _defaultIconPathForSymbol(metadata.symbol), + ); + } catch (e) { + printV('Error loading Starknet token metadata for $contractAddress: $e'); + return null; + } + } + + double? estimatedFeeFor(CryptoCurrency currency) { + final feeQuote = _estimatedFeeQuotesByTokenAddress[_tokenAddressFor(currency).toLowerCase()] ?? + _estimatedFeeQuotesByTokenAddress[StarknetTokenAddresses.strk.toLowerCase()]; + if (feeQuote == null) { + return null; + } + + return double.tryParse( + formatFixed(BigInt.parse(feeQuote.overallFeeWei), 18, fractionalDigits: 18), + ); + } + + Future> buildMessageSignUr(String message, {String? address}) async { + final accountAddressHex = address ?? _accountAddressHex; + return encodeStarknetMessageSignRequestUrMap( + StarknetMessageSignRequestUrPayload( + accountAddressHex: accountAddressHex, + publicKeyHex: _starknetPublicKeyHex, + message: message, + messageHashHex: _messageHashHex(message), + ), + ); + } + + Future submitSignedMessageUr(String urPayload) async { + final responsePayload = decodeStarknetMessageSignResponseUr(urPayload); + await _verifyOfflineSignature( + messageHashHex: responsePayload.messageHashHex, + signature: responsePayload.signature, + ); + return _formatSignature(responsePayload.signature); + } + + Future> buildTypedDataSignUr(String typedDataJson, {String? address}) async { + final accountAddressHex = address ?? _accountAddressHex; + final typedDataHashHex = await _client.getTypedDataHash( + accountAddressHex: accountAddressHex, + typedDataJson: typedDataJson, + ); + return encodeStarknetTypedDataSignRequestUrMap( + StarknetTypedDataSignRequestUrPayload( + accountAddressHex: accountAddressHex, + publicKeyHex: _starknetPublicKeyHex, + typedDataJson: typedDataJson, + typedDataHashHex: typedDataHashHex, + ), + ); + } + + Future> submitSignedTypedDataUr(String urPayload) async { + final responsePayload = decodeStarknetTypedDataSignResponseUr(urPayload); + await _verifyOfflineSignature( + messageHashHex: responsePayload.messageHashHex, + signature: responsePayload.signature, + ); + return [responsePayload.signature.rHex, responsePayload.signature.sHex]; + } + + Future> signTypedData(String typedDataJson, {String? address}) async { + if (supportsOfflineUrSigning) { + throw UnsupportedError( + 'Offline Starknet typed-data signing must be completed via buildTypedDataSignUr().', + ); + } + + if (walletInfo.isHardwareWallet) { + final hashHex = await _client.getTypedDataHash( + accountAddressHex: address ?? _accountAddressHex, + typedDataJson: typedDataJson, + ); + return _signWithHardwareWallet(hashHex); + } + + return _client.signTypedData( + accountAddressHex: address ?? _accountAddressHex, + typedDataJson: typedDataJson, + ); + } + + Future> buildExecuteCallsUr(List calls) async { + final summary = await _buildExecutionSummary(calls); + final quote = await _quoteExecutionCalls( + calls, + priority: _currentFeePriority, + ); + return _client.buildUnsignedTransactionUr( + calls: calls, + amountWei: summary.amountWei, + amountDecimals: summary.tokenDecimals, + amountSymbol: summary.tokenSymbol, + destinationAddress: summary.destinationAddress, + accountClassHashHex: _accountClassHashHex, + feeWei: quote.overallFeeWei, + feePriorityRaw: _currentFeePriority.raw, + summaryActionName: summary.actionName, + summaryTokenAddress: summary.tokenAddress, + summaryAdditionalInfo: { + ...summary.additionalInfo, + ..._feeMetadataForQuote(quote, _currentFeePriority), + }, + preferSummary: summary.preferSummary, + ); + } + + Future executeCalls(List calls) async { + if (supportsOfflineUrSigning) { + throw UnsupportedError( + 'Offline Starknet execution must be completed via buildExecuteCallsUr().', + ); + } + + final summary = await _buildExecutionSummary(calls); + final quote = await _quoteExecutionCalls( + calls, + priority: _currentFeePriority, + ); + final pending = walletInfo.isHardwareWallet + ? await _client.createHardwareTransaction( + calls: calls, + amountWei: summary.amountWei, + amountDecimals: summary.tokenDecimals, + amountSymbol: summary.tokenSymbol, + destinationAddress: summary.destinationAddress, + accountClassHashHex: _accountClassHashHex, + feeWei: quote.overallFeeWei, + feePriorityRaw: _currentFeePriority.raw, + ) + : await _client.createTransaction( + calls: calls, + amountWei: summary.amountWei, + amountDecimals: summary.tokenDecimals, + amountSymbol: summary.tokenSymbol, + destinationAddress: summary.destinationAddress, + accountClassHashHex: _accountClassHashHex, + feeWei: quote.overallFeeWei, + feePriorityRaw: _currentFeePriority.raw, + ); + await pending.commit(); + await _recordExecutionSummary( + txHash: pending.id, + summary: summary.copyWith( + additionalInfo: { + ...summary.additionalInfo, + ..._feeMetadataForQuote(quote, _currentFeePriority), + }, + ), + feeWei: quote.overallFeeWei, + ); + return pending.id; + } + + Future submitSignedTransactionUR( + String urPayload, { + String? requestUrPayload, + }) async { + final txHash = await _client.submitSignedTransactionUr(urPayload); + if (requestUrPayload != null) { + final requestPayload = decodeStarknetSignRequestUr(requestUrPayload); + await _recordExecutionSummary( + txHash: txHash, + summary: _executionSummaryFromRequestPayload(requestPayload), + feeWei: requestPayload.feeWei, + ); + } + unawaited(() async { + await Future.delayed(const Duration(milliseconds: 250)); + await _updateBalance(); + await updateTransactionsHistory(); + }()); + return txHash; + } + + @override + Future renameWalletFiles(String newWalletName) async { + final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type); + final currentWalletFile = File(currentWalletPath); + + final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type); + final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName'); + + if (currentWalletFile.existsSync()) { + final newWalletPath = await pathForWallet(name: newWalletName, type: type); + await currentWalletFile.copy(newWalletPath); + } + if (currentTransactionsFile.existsSync()) { + final newDirPath = await pathForWalletDir(name: newWalletName, type: type); + await currentTransactionsFile.copy('$newDirPath/$transactionsHistoryFileName'); + } + + await Directory(currentDirPath).delete(recursive: true); + } + + void _setTransactionUpdateTimer() { + if (_transactionsUpdateTimer?.isActive ?? false) { + _transactionsUpdateTimer!.cancel(); + } + + _transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 30), (_) { + _updateBalance(); + updateTransactionsHistory(); + _refreshEstimatedFees(priority: _currentFeePriority); + }); + } + + @override + Future signMessage(String message, {String? address}) async { + await ensureStarknetRustInitialized(); + + if (supportsOfflineUrSigning) { + throw UnsupportedError( + 'Offline Starknet message signing must be completed via buildMessageSignUr().', + ); + } + + if (walletInfo.isHardwareWallet) { + final signature = await _signWithHardwareWallet(_messageHashHex(message)); + return signature.join(','); + } + + final response = await rust_api.signMessageHash( + privateKeyHex: _runtimePrivateKeyHex, + messageHashHex: _messageHashHex(message), + ); + final signature = unwrapSignatureResponse(response); + return '${signature.rHex},${signature.sHex}'; + } + + @override + Future verifyMessage(String message, String signature, {String? address}) async { + final components = signature.split(','); + if (components.length != 2) { + return false; + } + + try { + await ensureStarknetRustInitialized(); + final response = await rust_api.verifyMessageHashSignature( + publicKeyHex: _starknetPublicKeyHex, + messageHashHex: _messageHashHex(message), + rHex: components[0].trim(), + sHex: components[1].trim(), + ); + return unwrapBoolResponse(response); + } catch (e) { + printV('Error verifying Starknet signature: $e'); + return false; + } + } + + @override + String get password => _password; + + @override + final String? passphrase; + + Future _resolveHardwarePublicKeyHex() async { + final storedPublicKey = _hardwarePublicKeyHex?.trim(); + if (storedPublicKey != null && storedPublicKey.isNotEmpty) { + return storedPublicKey; + } + + final service = _hardwareWalletService; + final derivationPath = _hardwareDerivationPath?.trim(); + if (service is! StarknetLedgerService || derivationPath == null || derivationPath.isEmpty) { + throw Exception('Missing Starknet hardware wallet public key metadata'); + } + final publicKeyHex = await service.getPublicKeyHex(derivationPath: derivationPath); + _hardwarePublicKeyHex = publicKeyHex; + return publicKeyHex; + } + + Future _resolveHardwareAccountAddress(String publicKeyHex) async { + final storedAddress = walletInfo.address.trim(); + if (storedAddress.isNotEmpty) { + return storedAddress; + } + + final response = await rust_api.deriveAccountFromPublicKey( + publicKeyHex: publicKeyHex, + accountClassHashHex: _accountClassHashHex, + ); + final accountData = unwrapDerivedAccountDataResponse(response); + return accountData.accountAddressHex; + } + + HardwareWalletService _requireHardwareWalletService() { + final service = _hardwareWalletService; + if (service == null) { + throw Exception('Starknet hardware wallet is not connected'); + } + return service; + } + + String _requireHardwareDerivationPath() { + final derivationPath = _hardwareDerivationPath?.trim(); + if (derivationPath == null || derivationPath.isEmpty) { + throw Exception('Missing Starknet hardware wallet derivation path'); + } + return derivationPath; + } + + Uint8List _messageHashBytes(String messageHashHex) { + final normalized = + messageHashHex.startsWith('0x') ? messageHashHex.substring(2) : messageHashHex; + final bytes = Uint8List.fromList(HEX.decode(normalized.padLeft(64, '0'))); + if (bytes.length == 32) { + return bytes; + } + + final result = Uint8List(32); + result.setRange(32 - bytes.length, 32, bytes); + return result; + } + + Future> _signWithHardwareWallet(String messageHashHex) async { + final signature = await _requireHardwareWalletService().signMessage( + message: _messageHashBytes(messageHashHex), + derivationPath: _requireHardwareDerivationPath(), + ); + if (signature.length != 64) { + throw Exception( + 'Unexpected Starknet hardware signature length: ${signature.length}', + ); + } + + final rHex = '0x${HEX.encode(signature.sublist(0, 32))}'; + final sHex = '0x${HEX.encode(signature.sublist(32, 64))}'; + return [rHex, sHex]; + } + + Future _verifyOfflineSignature({ + required String messageHashHex, + required StarknetUrSignature signature, + }) async { + await ensureStarknetRustInitialized(); + final response = await rust_api.verifyMessageHashSignature( + publicKeyHex: _starknetPublicKeyHex, + messageHashHex: messageHashHex, + rHex: signature.rHex, + sHex: signature.sHex, + ); + + if (!unwrapBoolResponse(response)) { + throw Exception('Invalid Starknet signature received from UR response'); + } + } + + String _formatSignature(StarknetUrSignature signature) => '${signature.rHex},${signature.sHex}'; + + Future<_FeeQuoteModel> _quoteExecutionCalls( + List calls, { + required StarknetTransactionPriority priority, + }) async { + _currentFeePriority = priority; + final quote = walletInfo.isHardwareWallet + ? await _client.estimateHardwareExecuteFee( + accountAddressHex: _accountAddressHex, + publicKeyHex: _starknetPublicKeyHex, + accountClassHashHex: _accountClassHashHex, + calls: calls, + feePriorityRaw: priority.raw, + ) + : await _client.estimateExecuteFee( + accountAddressHex: _accountAddressHex, + accountClassHashHex: _accountClassHashHex, + calls: calls, + feePriorityRaw: priority.raw, + ); + + _estimatedFeePriorityRaw = priority.raw; + _estimatedFeeQuotesByTokenAddress[StarknetTokenAddresses.strk.toLowerCase()] = _FeeQuoteModel( + overallFeeWei: quote.overallFeeWei, + executionFeeWei: quote.executionFeeWei, + deployAccountFeeWei: quote.deployAccountFeeWei, + accountDeploymentRequired: quote.accountDeploymentRequired, + ); + estimatedFeesWeiByTokenAddress[StarknetTokenAddresses.strk.toLowerCase()] = quote.overallFeeWei; + return _estimatedFeeQuotesByTokenAddress[StarknetTokenAddresses.strk.toLowerCase()]!; + } + + Future<_FeeQuoteModel> _quoteTransferBatch({ + required String tokenAddress, + required List<(String, BigInt)> transfers, + required StarknetTransactionPriority priority, + }) async { + _currentFeePriority = priority; + final calls = transfers + .map( + (transfer) => _buildTransferCall( + tokenAddress: tokenAddress, + destinationAddress: transfer.$1, + amountWei: transfer.$2, + ), + ) + .toList(); + + final quote = walletInfo.isHardwareWallet + ? await _client.estimateHardwareExecuteFee( + accountAddressHex: _accountAddressHex, + publicKeyHex: _starknetPublicKeyHex, + accountClassHashHex: _accountClassHashHex, + calls: calls, + feePriorityRaw: priority.raw, + ) + : await _client.estimateExecuteFee( + accountAddressHex: _accountAddressHex, + accountClassHashHex: _accountClassHashHex, + calls: calls, + feePriorityRaw: priority.raw, + ); + + final feeQuote = _FeeQuoteModel( + overallFeeWei: quote.overallFeeWei, + executionFeeWei: quote.executionFeeWei, + deployAccountFeeWei: quote.deployAccountFeeWei, + accountDeploymentRequired: quote.accountDeploymentRequired, + ); + _estimatedFeePriorityRaw = priority.raw; + _estimatedFeeQuotesByTokenAddress[tokenAddress.toLowerCase()] = feeQuote; + estimatedFeesWeiByTokenAddress[tokenAddress.toLowerCase()] = quote.overallFeeWei; + return feeQuote; + } + + Future<_ExecutionSummaryModel> _buildExecutionSummary( + List calls, + ) async { + if (calls.isEmpty) { + throw StarknetTransactionCreationException.fromMessage('Missing Starknet execution calls'); + } + + final normalizedEntrypoints = + calls.map((call) => _normalizeEntrypoint(call.entrypoint)).toList(growable: false); + + if (normalizedEntrypoints.every((entrypoint) => entrypoint == 'transfer')) { + final asset = await _resolveSummaryAsset(calls.first.contractAddressHex); + var amountWei = BigInt.zero; + for (final call in calls) { + amountWei += _decodeUint256Amount(call.calldataHex, amountOffset: 1); + } + + return _ExecutionSummaryModel( + actionName: 'transfer', + amountWei: amountWei.toString(), + tokenAddress: asset.tokenAddress, + tokenDecimals: asset.decimals, + tokenSymbol: asset.symbol, + destinationAddress: calls.length == 1 + ? (calls.first.calldataHex.isNotEmpty + ? calls.first.calldataHex.first + : _accountAddressHex) + : '${calls.length} recipients', + additionalInfo: { + 'starknetActionLabel': calls.length == 1 ? 'Transfer' : 'Batch transfer', + 'starknetCallCount': calls.length, + }, + preferSummary: calls.length > 1, + ); + } + + if (normalizedEntrypoints.every((entrypoint) => entrypoint == 'approve')) { + final asset = await _resolveSummaryAsset(calls.first.contractAddressHex); + var amountWei = BigInt.zero; + for (final call in calls) { + amountWei += _decodeUint256Amount(call.calldataHex, amountOffset: 1); + } + + return _ExecutionSummaryModel( + actionName: 'approval', + amountWei: amountWei.toString(), + tokenAddress: asset.tokenAddress, + tokenDecimals: asset.decimals, + tokenSymbol: asset.symbol, + destinationAddress: calls.length == 1 + ? (calls.first.calldataHex.isNotEmpty + ? calls.first.calldataHex.first + : calls.first.contractAddressHex) + : '${calls.length} approvals', + additionalInfo: { + 'starknetActionLabel': calls.length == 1 ? 'Approval' : 'Batch approval', + 'starknetCallCount': calls.length, + 'starknetSpender': + calls.first.calldataHex.isNotEmpty ? calls.first.calldataHex.first : '', + }, + preferSummary: true, + ); + } + + if (normalizedEntrypoints.any(_looksLikeSwapEntrypoint)) { + final asset = await _resolveSummaryAsset(StarknetTokenAddresses.strk); + return _ExecutionSummaryModel( + actionName: 'swap', + amountWei: '0', + tokenAddress: asset.tokenAddress, + tokenDecimals: asset.decimals, + tokenSymbol: asset.symbol, + destinationAddress: calls.first.contractAddressHex, + additionalInfo: { + 'starknetActionLabel': 'Swap', + 'starknetCallCount': calls.length, + 'starknetPrimaryEntrypoint': calls.first.entrypoint, + }, + preferSummary: true, + ); + } + + final primaryEntrypoint = normalizedEntrypoints.first; + final actionName = calls.length > 1 + ? 'multicall' + : (primaryEntrypoint.isEmpty ? 'contract_call' : primaryEntrypoint); + final defaultAsset = await _resolveSummaryAsset(StarknetTokenAddresses.strk); + + return _ExecutionSummaryModel( + actionName: actionName, + amountWei: '0', + tokenAddress: defaultAsset.tokenAddress, + tokenDecimals: defaultAsset.decimals, + tokenSymbol: defaultAsset.symbol, + destinationAddress: + calls.length > 1 ? '${calls.length} calls' : calls.first.contractAddressHex, + additionalInfo: { + 'starknetActionLabel': _starknetActionLabelFor(actionName), + 'starknetCallCount': calls.length, + 'starknetPrimaryEntrypoint': calls.first.entrypoint, + 'starknetPrimaryContract': calls.first.contractAddressHex, + }, + preferSummary: true, + ); + } + + _ExecutionSummaryModel _executionSummaryFromRequestPayload( + StarknetSignRequestUrPayload requestPayload, + ) { + final tokenAddress = requestPayload.summaryTokenAddress ?? StarknetTokenAddresses.strk; + final tokenSymbol = requestPayload.amountSymbol; + final tokenDecimals = requestPayload.amountDecimals; + final actionName = requestPayload.summaryActionName ?? 'contract_call'; + + return _ExecutionSummaryModel( + actionName: actionName, + amountWei: requestPayload.amountWei, + tokenAddress: tokenAddress, + tokenDecimals: tokenDecimals, + tokenSymbol: tokenSymbol, + destinationAddress: requestPayload.destinationAddress, + additionalInfo: { + 'starknetActionLabel': _starknetActionLabelFor(actionName), + ...?requestPayload.summaryAdditionalInfo, + }, + preferSummary: requestPayload.preferSummary, + ); + } + + Future _recordExecutionSummary({ + required String txHash, + required _ExecutionSummaryModel summary, + required String feeWei, + }) async { + if (!summary.preferSummary || txHash.isEmpty) { + return; + } + + final transaction = StarknetTransactionInfo( + id: txHash, + transactionHash: txHash, + blockTime: DateTime.now(), + to: summary.destinationAddress, + from: _accountAddressHex, + direction: TransactionDirection.outgoing, + amountWei: summary.amountWei, + tokenAddress: summary.tokenAddress, + tokenDecimals: summary.tokenDecimals, + tokenSymbol: summary.tokenSymbol, + isPending: false, + txFeeWei: feeWei, + evmSignatureName: summary.actionName, + additionalInfo: { + 'starknetPreferSummary': true, + ...summary.additionalInfo, + }, + ); + + transactionHistory.addOne(transaction); + await transactionHistory.save(); + } + + Future<_SummaryAssetModel> _resolveSummaryAsset(String tokenAddress) async { + final normalized = tokenAddress.toLowerCase(); + final asset = _assetForTokenAddress(normalized); + if (asset != null) { + return _SummaryAssetModel( + tokenAddress: normalized, + decimals: asset.decimals, + symbol: asset.title, + ); + } + + try { + final metadata = await _client.getTokenMetadata(normalized); + return _SummaryAssetModel( + tokenAddress: metadata.tokenAddressHex.toLowerCase(), + decimals: metadata.decimals, + symbol: metadata.symbol, + ); + } catch (_) { + return _SummaryAssetModel( + tokenAddress: StarknetTokenAddresses.strk, + decimals: CryptoCurrency.strk.decimals, + symbol: CryptoCurrency.strk.title, + ); + } + } + + BigInt _decodeUint256Amount(List calldata, {int amountOffset = 0}) { + if (calldata.length <= amountOffset) { + return BigInt.zero; + } + + final low = _parseFeltBigInt(calldata[amountOffset]); + final high = calldata.length > amountOffset + 1 + ? _parseFeltBigInt(calldata[amountOffset + 1]) + : BigInt.zero; + return low + (high << 128); + } + + BigInt _parseFeltBigInt(String value) { + final normalized = value.trim().toLowerCase(); + if (normalized.isEmpty) { + return BigInt.zero; + } + + if (normalized.startsWith('0x')) { + return BigInt.parse(normalized.substring(2), radix: 16); + } + + return BigInt.parse(normalized); + } + + String _normalizeEntrypoint(String entrypoint) => entrypoint.trim().toLowerCase(); + + bool _looksLikeSwapEntrypoint(String entrypoint) { + final normalized = _normalizeEntrypoint(entrypoint); + return normalized.contains('swap') || + normalized.contains('exact_input') || + normalized.contains('exact_output') || + normalized.contains('multihop'); + } + + String _starknetActionLabelFor(String actionName) { + switch (_normalizeEntrypoint(actionName)) { + case 'approval': + case 'approve': + return 'Approval'; + case 'swap': + return 'Swap'; + case 'multicall': + return 'Multicall'; + case 'transfer': + return 'Transfer'; + case 'contract_call': + return 'Contract call'; + default: + final normalized = actionName.replaceAll('_', ' ').trim(); + if (normalized.isEmpty) { + return 'Contract call'; + } + return normalized[0].toUpperCase() + normalized.substring(1); + } + } + + CryptoCurrency _resolveAsset(CryptoCurrency currency) { + if (currency.titleAndTagEqual(CryptoCurrency.strk)) { + return CryptoCurrency.strk; + } + + if (currency is StarknetToken) { + return currency; + } + + return starknetTokenCurrencies.firstWhere( + (token) => token.titleAndTagEqual(currency) || token.title == currency.title, + orElse: () => throw StarknetTransactionCreationException.fromMessage( + 'Currency ${currency.title} is not enabled for this Starknet wallet', + ), + ); + } + + CryptoCurrency? _assetForTokenAddress(String tokenAddress) { + final normalized = tokenAddress.toLowerCase(); + if (normalized == StarknetTokenAddresses.strk.toLowerCase()) { + return CryptoCurrency.strk; + } + + for (final token in starknetTokenCurrencies) { + if (token.contractAddress.toLowerCase() == normalized) { + return token; + } + } + + return null; + } + + String _tokenAddressFor(CryptoCurrency asset) { + if (asset.titleAndTagEqual(CryptoCurrency.strk)) { + return StarknetTokenAddresses.strk; + } + + if (asset is StarknetToken) { + return asset.contractAddress; + } + + if (asset.title.toUpperCase() == 'ETH') { + return StarknetTokenAddresses.eth; + } + + throw StarknetTransactionCreationException.fromMessage( + 'Unknown Starknet token address for ${asset.title}', + ); + } + + StarknetExecutionCall _buildTransferCall({ + required String tokenAddress, + required String destinationAddress, + required BigInt amountWei, + }) { + final (low, high) = _uint256Words(amountWei); + return StarknetExecutionCall( + contractAddressHex: tokenAddress, + entrypoint: 'transfer', + calldataHex: [destinationAddress, low, high], + ); + } + + _ExecutionSummaryModel _buildTransferExecutionSummary({ + required CryptoCurrency asset, + required List<(String, BigInt)> transfers, + required _FeeQuoteModel feeQuote, + required StarknetTransactionPriority priority, + }) { + final totalAmountWei = transfers.fold( + BigInt.zero, + (total, transfer) => total + transfer.$2, + ); + + return _ExecutionSummaryModel( + actionName: 'transfer', + amountWei: totalAmountWei.toString(), + tokenAddress: _tokenAddressFor(asset), + tokenDecimals: asset.decimals, + tokenSymbol: asset.title, + destinationAddress: + transfers.length == 1 ? transfers.first.$1 : '${transfers.length} recipients', + additionalInfo: { + 'starknetActionLabel': transfers.length == 1 ? 'Transfer' : 'Batch transfer', + 'starknetCallCount': transfers.length, + ..._feeMetadataForQuote(feeQuote, priority), + }, + preferSummary: feeQuote.accountDeploymentRequired || transfers.length > 1, + ); + } + + Map _feeMetadataForQuote( + _FeeQuoteModel feeQuote, + StarknetTransactionPriority priority, + ) { + return { + 'starknetFeePriorityRaw': priority.raw, + 'starknetFeePriorityLabel': priority.title, + 'starknetExecutionFeeWei': feeQuote.executionFeeWei, + if ((feeQuote.deployAccountFeeWei?.isNotEmpty ?? false)) + 'starknetDeployAccountFeeWei': feeQuote.deployAccountFeeWei, + if (feeQuote.accountDeploymentRequired) 'starknetAccountDeploymentRequired': true, + }; + } + + StarknetTransactionPriority _normalizeTransactionPriority(TransactionPriority? priority) { + if (priority is StarknetTransactionPriority) { + return priority; + } + + if (priority != null) { + try { + return StarknetTransactionPriority.deserialize(raw: priority.raw); + } catch (_) {} + } + + return StarknetTransactionPriority.medium; + } + + String? _defaultIconPathForSymbol(String symbol) { + if (symbol.toUpperCase() == 'ETH') { + return CryptoCurrency.eth.iconPath; + } + + return null; + } + + (String, String) _uint256Words(BigInt amount) { + final mask = (BigInt.one << 128) - BigInt.one; + final low = amount & mask; + final high = amount >> 128; + return ('0x${low.toRadixString(16)}', '0x${high.toRadixString(16)}'); + } + + static String _normalizeAccountClassHashHex(String? value) { + final trimmed = value?.trim(); + if (trimmed == null || trimmed.isEmpty) { + return openZeppelinAccountClassHashHex; + } + + final normalized = trimmed.toLowerCase(); + return normalized.startsWith('0x') ? normalized : '0x$normalized'; + } +} + +class _FeeQuoteModel { + const _FeeQuoteModel({ + required this.overallFeeWei, + required this.executionFeeWei, + required this.deployAccountFeeWei, + required this.accountDeploymentRequired, + }); + + final String overallFeeWei; + final String executionFeeWei; + final String? deployAccountFeeWei; + final bool accountDeploymentRequired; +} + +class _ExecutionSummaryModel { + const _ExecutionSummaryModel({ + required this.actionName, + required this.amountWei, + required this.tokenAddress, + required this.tokenDecimals, + required this.tokenSymbol, + required this.destinationAddress, + required this.additionalInfo, + required this.preferSummary, + }); + + final String actionName; + final String amountWei; + final String tokenAddress; + final int tokenDecimals; + final String tokenSymbol; + final String destinationAddress; + final Map additionalInfo; + final bool preferSummary; + + _ExecutionSummaryModel copyWith({ + String? actionName, + String? amountWei, + String? tokenAddress, + int? tokenDecimals, + String? tokenSymbol, + String? destinationAddress, + Map? additionalInfo, + bool? preferSummary, + }) { + return _ExecutionSummaryModel( + actionName: actionName ?? this.actionName, + amountWei: amountWei ?? this.amountWei, + tokenAddress: tokenAddress ?? this.tokenAddress, + tokenDecimals: tokenDecimals ?? this.tokenDecimals, + tokenSymbol: tokenSymbol ?? this.tokenSymbol, + destinationAddress: destinationAddress ?? this.destinationAddress, + additionalInfo: additionalInfo ?? this.additionalInfo, + preferSummary: preferSummary ?? this.preferSummary, + ); + } +} + +class _SummaryAssetModel { + const _SummaryAssetModel({ + required this.tokenAddress, + required this.decimals, + required this.symbol, + }); + + final String tokenAddress; + final int decimals; + final String symbol; +} + +String _starknetFeltHex(List bytes) { + final hashedValue = BigInt.parse(HEX.encode(bytes), radix: 16); + return '0x${(hashedValue % _starknetFieldPrime).toRadixString(16)}'; +} + +final BigInt _starknetFieldPrime = BigInt.parse( + '800000000000011000000000000000000000000000000000000000000000001', + radix: 16, +); diff --git a/cw_starknet/lib/starknet_wallet_addresses.dart b/cw_starknet/lib/starknet_wallet_addresses.dart new file mode 100644 index 0000000000..860e8e5c17 --- /dev/null +++ b/cw_starknet/lib/starknet_wallet_addresses.dart @@ -0,0 +1,43 @@ +import 'package:cw_core/payment_uris.dart'; +import 'package:cw_core/wallet_addresses.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:mobx/mobx.dart'; + +part 'starknet_wallet_addresses.g.dart'; + +class StarknetWalletAddresses = StarknetWalletAddressesBase + with _$StarknetWalletAddresses; + +abstract class StarknetWalletAddressesBase extends WalletAddresses with Store { + StarknetWalletAddressesBase(WalletInfo walletInfo) + : address = '', + super(walletInfo); + + @override + String address; + + @override + String get primaryAddress => address; + + @override + Future init() async { + address = walletInfo.address; + await updateAddressesInBox(); + } + + @override + Future updateAddressesInBox() async { + try { + addressesMap.clear(); + addressesMap[address] = ''; + await saveAddressesInBox(); + } catch (e) { + printV(e.toString()); + } + } + + @override + PaymentURI getPaymentUri(String amount) => + StarknetURI(amount: amount, address: address); +} diff --git a/cw_starknet/lib/starknet_wallet_creation_credentials.dart b/cw_starknet/lib/starknet_wallet_creation_credentials.dart new file mode 100644 index 0000000000..43487ca14a --- /dev/null +++ b/cw_starknet/lib/starknet_wallet_creation_credentials.dart @@ -0,0 +1,73 @@ +import 'package:cw_core/hardware/hardware_account_data.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; + +class StarknetNewWalletCredentials extends WalletCredentials { + StarknetNewWalletCredentials({ + required String name, + WalletInfo? walletInfo, + String? password, + this.mnemonic, + String? passphrase, + }) : super( + name: name, + walletInfo: walletInfo, + password: password, + passphrase: passphrase, + ); + final String? mnemonic; +} + +class StarknetRestoreWalletFromSeedCredentials extends WalletCredentials { + StarknetRestoreWalletFromSeedCredentials({ + required String name, + required String password, + required this.mnemonic, + WalletInfo? walletInfo, + String? passphrase, + }) : super( + name: name, + password: password, + walletInfo: walletInfo, + passphrase: passphrase, + ); + + final String mnemonic; +} + +class StarknetRestoreWalletFromPrivateKey extends WalletCredentials { + StarknetRestoreWalletFromPrivateKey({ + required String name, + required String password, + required this.privateKey, + WalletInfo? walletInfo, + }) : publicKey = null, + accountClassHashHex = null, + super(name: name, password: password, walletInfo: walletInfo); + + StarknetRestoreWalletFromPrivateKey.publicKey({ + required String name, + required String password, + required this.publicKey, + this.accountClassHashHex, + WalletInfo? walletInfo, + }) : privateKey = null, + super(name: name, password: password, walletInfo: walletInfo); + + final String? privateKey; + final String? publicKey; + final String? accountClassHashHex; +} + +class StarknetRestoreWalletFromHardware extends WalletCredentials { + StarknetRestoreWalletFromHardware({ + required String name, + required this.hwAccountData, + String? password, + WalletInfo? walletInfo, + this.accountClassHashHex, + }) : super(name: name, walletInfo: walletInfo, password: password); + + final HardwareAccountData hwAccountData; + final String? accountClassHashHex; +} diff --git a/cw_starknet/lib/starknet_wallet_service.dart b/cw_starknet/lib/starknet_wallet_service.dart new file mode 100644 index 0000000000..7c4440dfce --- /dev/null +++ b/cw_starknet/lib/starknet_wallet_service.dart @@ -0,0 +1,207 @@ +import 'dart:io'; + +import 'package:bip39/bip39.dart' as bip39; +import 'package:cw_core/encryption_file_utils.dart'; +import 'package:cw_core/balance.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/transaction_history.dart'; +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cw_starknet/starknet_client.dart'; +import 'package:cw_starknet/starknet_exceptions.dart'; +import 'package:cw_starknet/starknet_mnemonics.dart'; +import 'package:cw_starknet/starknet_wallet.dart'; +import 'package:cw_starknet/starknet_wallet_creation_credentials.dart'; + +class StarknetWalletService extends WalletService< + StarknetNewWalletCredentials, + StarknetRestoreWalletFromSeedCredentials, + StarknetRestoreWalletFromPrivateKey, + StarknetRestoreWalletFromHardware> { + StarknetWalletService(this.isDirect, {StarknetWalletClient Function()? clientFactory}) + : _clientFactory = clientFactory; + + final bool isDirect; + final StarknetWalletClient Function()? _clientFactory; + + @override + Future create(StarknetNewWalletCredentials credentials, {bool? isTestnet}) async { + final strength = credentials.seedPhraseLength == 24 ? 256 : 128; + + final mnemonic = credentials.mnemonic ?? bip39.generateMnemonic(strength: strength); + + final wallet = StarknetWallet( + walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), + mnemonic: mnemonic, + password: credentials.password!, + passphrase: credentials.passphrase, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + client: _clientFactory?.call(), + ); + + await wallet.init(); + await wallet.save(); + return wallet; + } + + @override + WalletType getType() => WalletType.starknet; + + @override + Future isWalletExit(String name) async => + File(await pathForWallet(name: name, type: getType())).existsSync(); + + @override + Future openWallet(String name, String password) async { + final walletInfo = await WalletInfo.get(name, getType()); + if (walletInfo == null) { + throw StarknetTransactionCreationException.fromMessage('Wallet "$name" not found'); + } + + try { + final wallet = await _openAndInit(name, password, walletInfo); + saveBackup(name); + return wallet; + } catch (_) { + await restoreWalletFilesFromBackup(name); + return _openAndInit(name, password, walletInfo); + } + } + + Future _openAndInit(String name, String password, WalletInfo walletInfo) async { + final wallet = await StarknetWalletBase.open( + name: name, + password: password, + walletInfo: walletInfo, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + ); + await wallet.init(); + await wallet.save(); + return wallet; + } + + @override + Future remove(String wallet) async { + File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true); + final walletInfo = await WalletInfo.get(wallet, getType()); + if (walletInfo == null) { + throw StarknetTransactionCreationException.fromMessage('Wallet "$wallet" not found'); + } + await WalletInfo.delete(walletInfo); + } + + @override + Future restoreFromKeys(StarknetRestoreWalletFromPrivateKey credentials, + {bool? isTestnet}) async { + if (credentials.publicKey != null && credentials.privateKey == null) { + final wallet = StarknetWallet( + password: credentials.password!, + walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + hardwarePublicKeyHex: credentials.publicKey, + accountClassHashHex: credentials.accountClassHashHex, + client: _clientFactory?.call(), + ); + + await wallet.init(); + await wallet.save(); + + return wallet; + } + + final wallet = StarknetWallet( + password: credentials.password!, + privateKey: credentials.privateKey, + walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + client: _clientFactory?.call(), + ); + + await wallet.init(); + await wallet.save(); + + return wallet; + } + + @override + Future restoreFromSeed(StarknetRestoreWalletFromSeedCredentials credentials, + {bool? isTestnet}) async { + if (!bip39.validateMnemonic(credentials.mnemonic)) { + throw StarknetMnemonicIsIncorrectException(); + } + + final wallet = StarknetWallet( + password: credentials.password!, + mnemonic: credentials.mnemonic, + walletInfo: credentials.walletInfo!, + derivationInfo: await credentials.walletInfo!.getDerivationInfo(), + passphrase: credentials.passphrase, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + client: _clientFactory?.call(), + ); + + await wallet.init(); + await wallet.save(); + + return wallet; + } + + @override + Future rename(String currentName, String password, String newName) async { + final currentWalletInfo = await WalletInfo.get(currentName, getType()); + if (currentWalletInfo == null) { + throw StarknetTransactionCreationException.fromMessage('Wallet "$currentName" not found'); + } + final currentWallet = await StarknetWalletBase.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 newWalletInfo.save(); + } + + @override + Future, TransactionInfo>> + restoreFromHardwareWallet(StarknetRestoreWalletFromHardware credentials) async { + final derivationInfo = await credentials.walletInfo!.getDerivationInfo(); + derivationInfo.derivationType = DerivationType.bip39; + derivationInfo.derivationPath = credentials.hwAccountData.derivationPath; + await derivationInfo.save(); + + final walletInfo = credentials.walletInfo!; + walletInfo.hardwareWalletType = credentials.hardwareWalletType; + walletInfo.address = credentials.hwAccountData.address; + await walletInfo.save(); + + final wallet = StarknetWallet( + password: credentials.password!, + walletInfo: walletInfo, + derivationInfo: derivationInfo, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + hardwarePublicKeyHex: credentials.hwAccountData.publicKey, + hardwareDerivationPath: credentials.hwAccountData.derivationPath, + accountClassHashHex: credentials.accountClassHashHex, + client: _clientFactory?.call(), + ); + + await wallet.init(); + await wallet.save(); + + return wallet; + } +} diff --git a/cw_starknet/linux/CMakeLists.txt b/cw_starknet/linux/CMakeLists.txt new file mode 100644 index 0000000000..894abf206d --- /dev/null +++ b/cw_starknet/linux/CMakeLists.txt @@ -0,0 +1,19 @@ +# The Flutter tooling requires that developers have CMake 3.10 or later +# installed. You should not increase this version, as doing so will cause +# the plugin to fail to compile for some customers of the plugin. +cmake_minimum_required(VERSION 3.10) + +# Project-level configuration. +set(PROJECT_NAME "cw_starknet") +project(${PROJECT_NAME} LANGUAGES CXX) + +include("../cargokit/cmake/cargokit.cmake") +apply_cargokit(${PROJECT_NAME} ../rust cw_starknet_rust "") + +# List of absolute paths to libraries that should be bundled with the plugin. +# This list could contain prebuilt libraries, or libraries created by an +# external build triggered from this build file. +set(cw_starknet_bundled_libraries + "${${PROJECT_NAME}_cargokit_lib}" + PARENT_SCOPE +) diff --git a/cw_starknet/macos/Classes/dummy_file.c b/cw_starknet/macos/Classes/dummy_file.c new file mode 100644 index 0000000000..e06dab9968 --- /dev/null +++ b/cw_starknet/macos/Classes/dummy_file.c @@ -0,0 +1 @@ +// This is an empty file to force CocoaPods to create a framework. diff --git a/cw_starknet/macos/cw_starknet.podspec b/cw_starknet/macos/cw_starknet.podspec new file mode 100644 index 0000000000..90d5339986 --- /dev/null +++ b/cw_starknet/macos/cw_starknet.podspec @@ -0,0 +1,45 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint cw_starknet_rust.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'cw_starknet' + s.version = '0.0.1' + s.summary = 'A new Flutter FFI plugin project.' + s.description = <<-DESC +A new Flutter FFI plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + s.module_name = 'cw_starknet_rust' + + # This will ensure the source files in Classes/ are included in the native + # builds of apps using this FFI plugin. Podspec does not support relative + # paths, so Classes contains a forwarder C file that relatively imports + # `../src/*` so that the C sources can be shared among all target platforms. + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'FlutterMacOS' + + s.platform = :osx, '10.11' + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } + s.swift_version = '5.0' + + s.script_phase = { + :name => 'Build Rust library', + # First argument is relative path to the `rust` folder, second is name of rust library + :script => 'sh "$PODS_TARGET_SRCROOT/../cargokit/build_pod.sh" ../rust cw_starknet_rust', + :execution_position => :before_compile, + :input_files => ['${BUILT_PRODUCTS_DIR}/cargokit_phony'], + # Let XCode know that the static library referenced in -force_load below is + # created by this build step. + :output_files => ["${PODS_CONFIGURATION_BUILD_DIR}/cw_starknet_rust/libcw_starknet_rust.a"], + } + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + # Flutter.framework does not contain a i386 slice. + 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', + 'OTHER_LDFLAGS' => '-force_load ${PODS_CONFIGURATION_BUILD_DIR}/cw_starknet_rust/libcw_starknet_rust.a', + } +end \ No newline at end of file diff --git a/cw_starknet/pubspec.lock b/cw_starknet/pubspec.lock new file mode 100644 index 0000000000..e300b7aa2c --- /dev/null +++ b/cw_starknet/pubspec.lock @@ -0,0 +1,1033 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: f0bb5d1648339c8308cc0b9838d8456b3cfe5c91f9dc1a735b4d003269e5da9a + url: "https://pub.dev" + source: hosted + version: "88.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "0b7b9c329d2879f8f05d6c05b32ee9ec025f39b077864bdb5ac9a7b63418a98f" + url: "https://pub.dev" + source: hosted + version: "8.1.1" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + asn1lib: + dependency: transitive + description: + name: asn1lib + sha256: "9a8f69025044eb466b9b60ef3bc3ac99b4dc6c158ae9c56d25eeccf5bc56d024" + url: "https://pub.dev" + source: hosted + version: "1.6.5" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + bech32: + dependency: transitive + description: + path: "." + ref: HEAD + resolved-ref: "05755063b593aa6cca0a4820a318e0ce17de6192" + url: "https://github.com/cake-tech/bech32.git" + source: git + version: "0.2.2" + bip39: + dependency: "direct main" + description: + name: bip39 + sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc + url: "https://pub.dev" + source: hosted + version: "1.0.6" + blockchain_utils: + dependency: transitive + description: + path: "." + ref: cake-update-v2 + resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57" + url: "https://github.com/cake-tech/blockchain_utils" + source: git + version: "3.3.0" + bluez: + dependency: transitive + description: + name: bluez + sha256: "61a7204381925896a374301498f2f5399e59827c6498ae1e924aaa598751b545" + url: "https://pub.dev" + source: hosted + version: "0.8.3" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: "275bf6bb2a00a9852c28d4e0b410da1d833a734d57d39d44f94bfc895a484ec3" + url: "https://pub.dev" + source: hosted + version: "4.0.4" + build_cli_annotations: + dependency: transitive + description: + name: build_cli_annotations + sha256: e563c2e01de8974566a1998410d3f6f03521788160a02503b0b1f1a46c7b3d95 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 + url: "https://pub.dev" + source: hosted + version: "4.1.1" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "7981eb922842c77033026eb4341d5af651562008cdb116bdfa31fc46516b6462" + url: "https://pub.dev" + source: hosted + version: "2.12.2" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "6ae8a6435a8c6520c7077b107e77f1fb4ba7009633259a4d49a8afd8e7efc5e9" + url: "https://pub.dev" + source: hosted + version: "8.12.4" + cake_backup: + dependency: transitive + description: + path: "." + ref: main + resolved-ref: "3aba867dcab6737f6707782f5db15d71f303db38" + url: "https://github.com/cake-tech/cake_backup.git" + source: git + version: "1.0.0+1" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" + url: "https://pub.dev" + source: hosted + version: "4.11.1" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + crypto: + dependency: "direct main" + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + cryptography: + dependency: transitive + description: + name: cryptography + sha256: "3eda3029d34ec9095a27a198ac9785630fe525c0eb6a49f3d575272f8e792ef0" + url: "https://pub.dev" + source: hosted + version: "2.9.0" + cupertino_icons: + dependency: transitive + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + cw_core: + dependency: "direct main" + description: + path: "../cw_core" + relative: true + source: path + version: "0.0.1" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: c87dfe3d56f183ffe9106a18aebc6db431fc7c98c31a54b952a77f3d54a85697 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + dbus: + dependency: transitive + description: + name: dbus + sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 + url: "https://pub.dev" + source: hosted + version: "0.7.12" + decimal: + dependency: transitive + description: + name: decimal + sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + encrypt: + dependency: transitive + description: + name: encrypt + sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2" + url: "https://pub.dev" + source: hosted + version: "5.0.3" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_mobx: + dependency: transitive + description: + name: flutter_mobx + sha256: ba5e93467866a2991259dc51cffd41ef45f695c667c2b8e7b087bf24118b50fe + url: "https://pub.dev" + source: hosted + version: "2.3.0" + flutter_rust_bridge: + dependency: "direct main" + description: + name: flutter_rust_bridge + sha256: e87d6b9ee934dcd24a128ccb2bd91905d2d5fe5c06245d6a8f5477d4907a437a + url: "https://pub.dev" + source: hosted + version: "2.12.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_bluetooth: + dependency: transitive + description: + name: flutter_web_bluetooth + sha256: ad26a1b3fef95b86ea5f63793b9a0cdc1a33490f35d754e4e711046cae3ebbf8 + url: "https://pub.dev" + source: hosted + version: "1.1.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + hex: + dependency: "direct main" + description: + name: hex + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + hive: + dependency: "direct main" + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + intl: + dependency: transitive + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + url: "https://pub.dev" + source: hosted + version: "10.0.9" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + ledger_flutter_plus: + dependency: "direct main" + description: + name: ledger_flutter_plus + sha256: "59c5a6062505c721c2ca909fa085c52cffe93de995174b8418b54a6acf52eb70" + url: "https://pub.dev" + source: hosted + version: "1.5.6" + ledger_usb_plus: + dependency: transitive + description: + name: ledger_usb_plus + sha256: "911df2d1d55cdae612ff2c9db140cf71a9b28a67ba16adfdc4983cd9f02c531e" + url: "https://pub.dev" + source: hosted + version: "1.0.8" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + mobx: + dependency: "direct main" + description: + name: mobx + sha256: "25ea9a3ee69243d578fb6288721d8eeb18550d0391c32f896d377d994154bff2" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + mobx_codegen: + dependency: "direct dev" + description: + name: mobx_codegen + sha256: "2dbad72338df5959ff80312bb6796ec7045ba3188996ef9512c281cbe6c962cc" + url: "https://pub.dev" + source: hosted + version: "2.7.5" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + on_chain: + dependency: transitive + description: + path: "." + ref: "096865a8c6b89c260beadfec04f7e184c40a3273" + resolved-ref: "096865a8c6b89c260beadfec04f7e184c40a3273" + url: "https://github.com/cake-tech/on_chain.git" + source: git + version: "3.7.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37" + url: "https://pub.dev" + source: hosted + version: "2.2.19" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + url: "https://pub.dev" + source: hosted + version: "7.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + url: "https://pub.dev" + source: hosted + version: "3.9.1" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + process: + dependency: transitive + description: + name: process + sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" + url: "https://pub.dev" + source: hosted + version: "5.0.3" + provider: + dependency: transitive + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + rational: + dependency: transitive + description: + name: rational + sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336 + url: "https://pub.dev" + source: hosted + version: "2.2.3" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + socks5_proxy: + dependency: transitive + description: + path: "." + ref: "27ad7c2efae8d7460325c74b90f660085cbd0685" + resolved-ref: "27ad7c2efae8d7460325c74b90f660085cbd0685" + url: "https://github.com/LacticWhale/socks_dart" + source: git + version: "2.1.0" + socks_socket: + dependency: transitive + description: + path: "." + ref: e6232c53c1595469931ababa878759a067c02e94 + resolved-ref: e6232c53c1595469931ababa878759a067c02e94 + url: "https://github.com/sneurlax/socks_socket" + source: git + version: "1.1.1" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "1d562a3c1f713904ebbed50d2760217fd8a51ca170ac4b05b0db490699dbac17" + url: "https://pub.dev" + source: hosted + version: "4.2.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + sqflite_common_ffi: + dependency: transitive + description: + name: sqflite_common_ffi + sha256: "9faa2fedc5385ef238ce772589f7718c24cdddd27419b609bb9c6f703ea27988" + url: "https://pub.dev" + source: hosted + version: "2.3.6" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + sqlite3: + dependency: transitive + description: + name: sqlite3 + sha256: "3145bd74dcdb4fd6f5c6dda4d4e4490a8087d7f286a14dee5d37087290f0f8a2" + url: "https://pub.dev" + source: hosted + version: "2.9.4" + sqlite3_flutter_libs: + dependency: transitive + description: + name: sqlite3_flutter_libs + sha256: "69c80d812ef2500202ebd22002cbfc1b6565e9ff56b2f971e757fac5d42294df" + url: "https://pub.dev" + source: hosted + version: "0.5.40" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + sync_http: + dependency: transitive + description: + name: sync_http + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.dev" + source: hosted + version: "0.3.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + url: "https://pub.dev" + source: hosted + version: "3.4.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + torch_dart: + dependency: transitive + description: + path: "../scripts/torch_dart" + relative: true + source: path + version: "0.0.1" + tuple: + dependency: transitive + description: + name: tuple + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 + url: "https://pub.dev" + source: hosted + version: "2.0.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + universal_ble: + dependency: transitive + description: + name: universal_ble + sha256: "8325ca9f5cce2fba2b4efef74a4fcfe7144f303b409d6fc6ed16197b00a1a241" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + universal_platform: + dependency: transitive + description: + name: universal_platform + sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + unorm_dart: + dependency: transitive + description: + name: unorm_dart + sha256: "0c69186b03ca6addab0774bcc0f4f17b88d4ce78d9d4d8f0619e30a99ead58e7" + url: "https://pub.dev" + source: hosted + version: "0.3.2" + ur: + dependency: "direct main" + description: + path: "." + ref: "5738f70d0ec3d50977ac3dd01fed62939600238b" + resolved-ref: "5738f70d0ec3d50977ac3dd01fed62939600238b" + url: "https://github.com/bukata-sa/bc-ur-dart" + source: git + version: "0.1.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" + watcher: + dependency: "direct overridden" + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webdriver: + dependency: transitive + description: + name: webdriver + sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.8.0 <4.0.0" + flutter: ">=3.29.0" diff --git a/cw_starknet/pubspec.yaml b/cw_starknet/pubspec.yaml new file mode 100644 index 0000000000..971a4c6474 --- /dev/null +++ b/cw_starknet/pubspec.yaml @@ -0,0 +1,52 @@ +name: cw_starknet +description: Starknet wallet implementation for Cake Wallet. +version: 0.0.1 +publish_to: none +homepage: https://cakewallet.com + +environment: + sdk: ^3.5.0 + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + cw_core: + path: ../cw_core + hive: ^2.2.3 + bip39: ^1.0.6 + mobx: ^2.3.0+1 + hex: ^0.2.0 + crypto: ^3.0.5 + flutter_rust_bridge: 2.12.0 + ledger_flutter_plus: ^1.5.6 + ur: + git: + url: https://github.com/bukata-sa/bc-ur-dart + ref: 5738f70d0ec3d50977ac3dd01fed62939600238b + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + build_runner: ^2.4.15 + mobx_codegen: ^2.0.7 + integration_test: + sdk: flutter + +dependency_overrides: + watcher: ^1.1.0 + +flutter: + plugin: + platforms: + android: + ffiPlugin: true + ios: + ffiPlugin: true + macos: + ffiPlugin: true + linux: + ffiPlugin: true + windows: + ffiPlugin: true diff --git a/cw_starknet/rust/.gitignore b/cw_starknet/rust/.gitignore new file mode 100644 index 0000000000..ea8c4bf7f3 --- /dev/null +++ b/cw_starknet/rust/.gitignore @@ -0,0 +1 @@ +/target diff --git a/cw_starknet/rust/Cargo.lock b/cw_starknet/rust/Cargo.lock new file mode 100644 index 0000000000..7edeead208 --- /dev/null +++ b/cw_starknet/rust/Cargo.lock @@ -0,0 +1,3345 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allo-isolate" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "449e356a4864c017286dbbec0e12767ea07efba29e3b7d984194c2a7ff3c4550" +dependencies = [ + "anyhow", + "atomic", + "backtrace", +] + +[[package]] +name = "android_log-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" + +[[package]] +name = "android_logger" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb4e440d04be07da1f1bf44fb4495ebd58669372fe0cffa6e48595ac5bd88a3" +dependencies = [ + "android_log-sys", + "env_filter", + "log", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "auto_impl" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bip39" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" +dependencies = [ + "bitcoin_hashes", + "serde", + "unicode-normalization", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "hex-conservative", +] + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "build-target" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "832133bbabbbaa9fbdba793456a2827627a7d2b8fb96032fa1e7666d7895832b" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "cmov" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const_format" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpubits" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef0c543070d296ea414df2dd7625d1b24866ce206709d8a4a424f28377f5861" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a0d26b245348befa0c121944541476763dcc46ede886c88f9d12e1697d27c3" +dependencies = [ + "cpubits", + "ctutils", + "hybrid-array", + "num-traits", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + +[[package]] +name = "cw_starknet_rust" +version = "0.1.0" +dependencies = [ + "async-trait", + "bip39", + "flutter_rust_bridge", + "hmac", + "jni", + "num-bigint", + "once_cell", + "reqwest", + "rustls", + "rustls-platform-verifier", + "serde", + "serde_json", + "sha2", + "starknet-rust", + "starknet-rust-curve", + "tokio", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "dart-sys" +version = "4.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57967e4b200d767d091b961d6ab42cc7d0cc14fe9e052e75d0d3cf9eb732d895" +dependencies = [ + "cc", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "delegate-attr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51aac4c99b2e6775164b412ea33ae8441b2fde2dbf05a20bc0052a63d08c475b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "eth-keystore" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" +dependencies = [ + "aes", + "ctr", + "digest", + "hex", + "hmac", + "pbkdf2", + "rand 0.8.5", + "scrypt", + "serde", + "serde_json", + "sha2", + "sha3", + "thiserror 1.0.69", + "uuid", +] + +[[package]] +name = "ethbloom" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c321610643004cf908ec0f5f2aa0d8f1f8e14b540562a2887a1111ff1ecbf7b" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7326303c6c18bb03c7a3c4c22d4032ae60dfe673ca9109602aa4d8154b2637e" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flutter_rust_bridge" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0884853aae8a6517b5b58cf36f55da487f2fe110e1686938eb29b6640aae4a5" +dependencies = [ + "allo-isolate", + "android_logger", + "anyhow", + "build-target", + "bytemuck", + "byteorder", + "console_error_panic_hook", + "dart-sys", + "delegate-attr", + "flutter_rust_bridge_macros", + "futures", + "js-sys", + "lazy_static", + "log", + "oslog", + "portable-atomic", + "threadpool", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "flutter_rust_bridge_macros" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b5ce32f35f710ced8c5aa557f023f1a624e737b5460cee2b70fcd3a8df09e1b" +dependencies = [ + "hex", + "md-5", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hybrid-array" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "impl-codec" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d40b9d5e17727407e55028eafc22b2dc68781786e6d7eb8a21103f5058e3a14" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54ed8ad1f3877f7e775b8cbf30ed1bd3209a95401817f19a0eb4402d13f8cf90" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a143eada6a1ec4aefa5049037a26a6d597bfd64f8c026d07b77133e02b7dd0b" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lambdaworks-crypto" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58b1a1c1102a5a7fbbda117b79fb3a01e033459c738a3c1642269603484fd1c1" +dependencies = [ + "lambdaworks-math", + "rand 0.8.5", + "rand_chacha 0.3.1", + "serde", + "sha2", + "sha3", +] + +[[package]] +name = "lambdaworks-math" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "018a95aa873eb49896a858dee0d925c33f3978d073c64b08dd4f2c9b35a017c6" +dependencies = [ + "getrandom 0.2.17", + "num-bigint", + "num-traits", + "rand 0.8.5", + "serde", + "serde_json", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.185" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "oslog" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d2043d1f61d77cb2f4b1f7b7b2295f40507f5f8e9d1c8bf10a1ca5f97a3969" +dependencies = [ + "cc", + "dashmap", + "log", +] + +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "primitive-types" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721a1da530b5a2633218dc9f75713394c983c352be88d2d7c9ee85e2c4c21794" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +dependencies = [ + "base64", + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rlp" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa24e92bb2a83198bb76d661a71df9f7076b8c420b8696e4d3d97d50d94479e3" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustls" +version = "0.23.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +dependencies = [ + "aws-lc-rs", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scrypt" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" +dependencies = [ + "hmac", + "pbkdf2", + "salsa20", + "sha2", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_json_pythonic" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62212da9872ca2a0cad0093191ee33753eddff9266cbbc1b4a602d13a3a768db" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "starknet-rust" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d341a85d902801c29edfba1e1b5c8db66c619074265d6aac8cbee4be1736fb58" +dependencies = [ + "starknet-rust-accounts", + "starknet-rust-contract", + "starknet-rust-core", + "starknet-rust-macros", + "starknet-rust-providers", + "starknet-rust-signers", +] + +[[package]] +name = "starknet-rust-accounts" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23552b3f35b8535bade1e7d151071ec00216f1eb7ef7d2dec4d5fa732728edd8" +dependencies = [ + "async-trait", + "auto_impl", + "starknet-rust-core", + "starknet-rust-crypto", + "starknet-rust-providers", + "starknet-rust-signers", + "thiserror 2.0.18", +] + +[[package]] +name = "starknet-rust-contract" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93364bcf91df50f95c4a31230a629a68b0f51960bae0247eb10493c7efe36c76" +dependencies = [ + "serde_json", + "starknet-rust-accounts", + "starknet-rust-core", + "starknet-rust-providers", +] + +[[package]] +name = "starknet-rust-core" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3df45149a385127210bbd9bd06cbf5cee09b0e7c17ba65829eec64c6eff37b2" +dependencies = [ + "base64", + "crypto-bigint", + "flate2", + "foldhash", + "hex", + "indexmap 2.14.0", + "num-traits", + "semver", + "serde", + "serde_json", + "serde_json_pythonic", + "serde_with", + "sha3", + "starknet-rust-core-derive", + "starknet-rust-crypto", + "starknet-types-core", +] + +[[package]] +name = "starknet-rust-core-derive" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8500b18bdc6e7db3c012ee2a04bd134c23290e04a6d08b4d2c8e976be793d639" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "starknet-rust-crypto" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d8000df55d98f3910ef66e8e0cfeac398dd5f335613a497b0e2c905cb728fa" +dependencies = [ + "blake2", + "crypto-bigint", + "digest", + "hex", + "hmac", + "num-bigint", + "num-integer", + "num-traits", + "rfc6979", + "sha2", + "starknet-rust-curve", + "starknet-types-core", + "zeroize", +] + +[[package]] +name = "starknet-rust-curve" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2272de9f1fd3c41c32238500fab9c377571e706db87e1d96d60379536cd7d" +dependencies = [ + "starknet-types-core", +] + +[[package]] +name = "starknet-rust-macros" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5806f18cf68277105bcd20600190f43f5350de4ee0bc321e4cd8f29a3a60bf4e" +dependencies = [ + "starknet-rust-core", + "syn", +] + +[[package]] +name = "starknet-rust-providers" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c176e31616e2768129fa0b13adf8325b28f2ae2afb4f1a34e5abd21477caa276" +dependencies = [ + "async-trait", + "auto_impl", + "ethereum-types", + "flate2", + "getrandom 0.2.17", + "log", + "reqwest", + "serde", + "serde_json", + "serde_with", + "starknet-rust-core", + "thiserror 2.0.18", + "url", +] + +[[package]] +name = "starknet-rust-signers" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c3115dbe415b97eefa39f1176831bfcc4717360ceb25ac5cfdb1349bf16aff" +dependencies = [ + "async-trait", + "auto_impl", + "crypto-bigint", + "eth-keystore", + "getrandom 0.2.17", + "rand 0.8.5", + "starknet-rust-core", + "starknet-rust-crypto", + "starknet-rust-curve", + "thiserror 2.0.18", + "zeroize", +] + +[[package]] +name = "starknet-types-core" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90d23b1bc014ee4cce40056ab3114bcbcdc2dbc1e845bbfb1f8bd0bab63507d4" +dependencies = [ + "blake2", + "digest", + "lambdaworks-crypto", + "lambdaworks-math", + "num-bigint", + "num-integer", + "num-traits", + "rand 0.9.4", + "serde", + "zeroize", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a91135f59b1cbf38c91e73cf3386fca9bb77915c45ce2771460c9d92f0f3d776" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.17", + "serde", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/cw_starknet/rust/Cargo.toml b/cw_starknet/rust/Cargo.toml new file mode 100644 index 0000000000..99bea1d5a4 --- /dev/null +++ b/cw_starknet/rust/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "cw_starknet_rust" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "staticlib"] + +[dependencies] +flutter_rust_bridge = "=2.12.0" +async-trait = "0.1.89" +starknet-rust = "0.19.0-rc.2" +starknet-rust-curve = "0.19.0-rc.2" +bip39 = "2.2.0" +hmac = "0.12.1" +sha2 = "0.10.9" +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.145" +tokio = { version = "1.49.0", features = ["rt-multi-thread", "time"] } +once_cell = "1.21.3" +num-bigint = "0.4.6" +reqwest = { version = "0.13.2", default-features = false, features = ["rustls-no-provider"] } +rustls = { version = "0.23", default-features = false, features = ["ring", "std"] } + +[target.'cfg(target_os = "android")'.dependencies] +jni = { version = "0.21", default-features = false } +rustls-platform-verifier = "0.6" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(frb_expand)'] } diff --git a/cw_starknet/rust/src/android_init.rs b/cw_starknet/rust/src/android_init.rs new file mode 100644 index 0000000000..45adfba141 --- /dev/null +++ b/cw_starknet/rust/src/android_init.rs @@ -0,0 +1,22 @@ +#![cfg(target_os = "android")] + +use std::sync::Once; + +use jni::objects::{JClass, JObject}; +use jni::JNIEnv; + +static INIT: Once = Once::new(); + +#[no_mangle] +pub extern "system" fn Java_com_cakewallet_cake_1wallet_StarknetRust_nativeInit<'local>( + mut env: JNIEnv<'local>, + _class: JClass<'local>, + context: JObject<'local>, +) { + INIT.call_once(|| { + crate::ensure_crypto_provider(); + if let Err(err) = rustls_platform_verifier::android::init_hosted(&mut env, context) { + eprintln!("[cw_starknet] rustls_platform_verifier init_hosted failed: {err:?}"); + } + }); +} diff --git a/cw_starknet/rust/src/api/mod.rs b/cw_starknet/rust/src/api/mod.rs new file mode 100644 index 0000000000..a8dc8cd369 --- /dev/null +++ b/cw_starknet/rust/src/api/mod.rs @@ -0,0 +1 @@ +pub mod starknet; diff --git a/cw_starknet/rust/src/api/starknet.rs b/cw_starknet/rust/src/api/starknet.rs new file mode 100644 index 0000000000..75ddad8d84 --- /dev/null +++ b/cw_starknet/rust/src/api/starknet.rs @@ -0,0 +1,2664 @@ +use std::{ + collections::{HashMap, HashSet}, + future::Future, + time::Duration, +}; + +use async_trait::async_trait; +use bip39::{Language, Mnemonic}; +use hmac::{Hmac, Mac}; +use num_bigint::BigUint; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; +use sha2::Sha256; +use starknet_rust::{ + accounts::{ + Account, AccountFactory, ConnectedAccount, ExecutionEncoding, OpenZeppelinAccountFactory, + SingleOwnerAccount, + }, + core::{ + chain_id, + codec::Decode, + crypto::Signature, + types::{ + AddressFilter, BlockId, BlockTag, BroadcastedTransaction, ByteArray, Call, + DeployAccountTransaction, EmittedEvent, EventFilter, ExecutionResult, Felt, + FunctionCall, InvokeTransaction, MaybePreConfirmedBlockWithTxHashes, + ResourceBoundsMapping, SimulationFlagForEstimateFee, StarknetError, Transaction, + TransactionFinalityStatus, TransactionReceipt, TypedData, + }, + utils::{get_contract_address, get_selector_from_name}, + }, + providers::{ + jsonrpc::{HttpTransport, JsonRpcClient}, + Provider, ProviderError, Url, + }, + signers::{LocalWallet, Signer, SignerInteractivityContext, SigningKey, VerifyingKey}, +}; +use starknet_rust_curve::curve_params::EC_ORDER; +use tokio::runtime::{Builder, Runtime}; + +type HmacSha256 = Hmac; +type ApiResult = Result; + +const DERIVATION_CONTEXT: &[u8] = b"Starknet key derivation"; +const DEFAULT_PAGE_SIZE: u64 = 100; +const DEFAULT_MAX_EVENT_PAGES: usize = 10; +const WAIT_FOR_TRANSACTION_POLL_INTERVAL: Duration = Duration::from_secs(3); +const WAIT_FOR_TRANSACTION_TIMEOUT: Duration = Duration::from_secs(300); +const DEFAULT_OPENZEPPELIN_ACCOUNT_CLASS_HASH_HEX: &str = + "0x01d1777db36cdd06dd62cfde77b1b6ae06412af95d57a13dc40ac77b8a702381"; + +static TOKIO_RUNTIME: Lazy = Lazy::new(|| { + Builder::new_multi_thread() + .enable_all() + .build() + .expect("failed to initialize Tokio runtime for cw_starknet") +}); + +static CURVE_ORDER: Lazy = Lazy::new(|| BigUint::from_bytes_be(&EC_ORDER.to_bytes_be())); + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct DerivedAccountData { + pub private_key_hex: String, + pub public_key_hex: String, + pub account_address_hex: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StarknetSignatureData { + pub r_hex: String, + pub s_hex: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TransferHistoryItem { + pub transaction_hash: String, + pub event_id: String, + pub event_index: i64, + pub block_number: Option, + pub from: String, + pub to: String, + pub amount_wei: String, + pub is_outgoing: bool, + pub token_symbol: String, + pub token_address_hex: String, + pub block_timestamp: Option, + pub tx_fee_wei: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StarknetTokenMetadata { + pub token_address_hex: String, + pub name: String, + pub symbol: String, + pub decimals: i32, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct StarknetCallInput { + pub contract_address_hex: String, + pub entrypoint: String, + pub calldata_hex: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StarknetFeeQuote { + pub overall_fee_wei: String, + pub execution_fee_wei: String, + pub deploy_account_fee_wei: Option, + pub account_deployment_required: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StarknetExecutionPlanData { + pub invoke_transaction_hash_hex: String, + pub deploy_account_transaction_hash_hex: Option, + pub account_deployment_required: bool, + pub plan_json: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StarknetTransactionDetails { + pub transaction_hash: String, + pub transaction_type: String, + pub is_pending: bool, + pub block_number: Option, + pub block_timestamp: Option, + pub actual_fee_wei: Option, + pub action_name: Option, + pub call_count: Option, + pub primary_contract_address_hex: Option, + pub primary_entrypoint: Option, + pub sender_address_hex: Option, + pub finality_status: Option, + pub execution_status: Option, + pub revert_reason: Option, + pub account_deployment_required: bool, + pub l1_gas_max_amount: Option, + pub l1_gas_max_price_wei: Option, + pub l2_gas_max_amount: Option, + pub l2_gas_max_price_wei: Option, + pub l1_data_gas_max_amount: Option, + pub l1_data_gas_max_price_wei: Option, + pub tip: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct DerivedAccountDataResponse { + pub value: Option, + pub error: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StarknetSignatureDataResponse { + pub value: Option, + pub error: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StringResponse { + pub value: Option, + pub error: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BoolResponse { + pub value: Option, + pub error: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct I64Response { + pub value: Option, + pub error: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TransferHistoryResponse { + pub items: Vec, + pub error: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TokenMetadataResponse { + pub value: Option, + pub error: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct FeeQuoteResponse { + pub value: Option, + pub error: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ExecutionPlanResponse { + pub value: Option, + pub error: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TransactionDetailsResponse { + pub value: Option, + pub error: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StringListResponse { + pub items: Vec, + pub error: Option, +} + +#[derive(Clone, Debug)] +struct TransferEventRecord { + transaction_hash: Felt, + event_index: u64, + block_number: Option, + from: String, + to: String, + amount_wei: String, + is_outgoing: bool, + token_symbol: String, + token_address_hex: String, +} + +#[derive(Clone, Debug)] +struct ExternalSignerError(String); + +#[derive(Clone, Debug)] +struct PublicKeyOnlySigner { + public_key: Felt, +} + +#[derive(Clone, Debug)] +struct PreparedTransactionParams { + nonce: Felt, + l1_gas: u64, + l1_gas_price: u128, + l2_gas: u64, + l2_gas_price: u128, + l1_data_gas: u64, + l1_data_gas_price: u128, + tip: u64, +} + +#[derive(Clone, Debug)] +struct PreparedExternalExecution { + fee_quote: StarknetFeeQuote, + invoke_transaction_hash: Felt, + deploy_account_transaction_hash: Option, + serialized_plan: SerializedExternalExecutionPlan, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum StarknetFeePriority { + Slow, + Medium, + Fast, +} + +#[derive(Clone, Debug)] +struct DecodedInvokeCall { + contract_address: Felt, + selector: Felt, + calldata: Vec, +} + +#[derive(Clone, Debug)] +struct DecodedTransactionSummary { + transaction_type: String, + action_name: Option, + call_count: Option, + primary_contract_address_hex: Option, + primary_entrypoint: Option, + sender_address_hex: Option, + account_deployment_required: bool, + l1_gas_max_amount: Option, + l1_gas_max_price_wei: Option, + l2_gas_max_amount: Option, + l2_gas_max_price_wei: Option, + l1_data_gas_max_amount: Option, + l1_data_gas_max_price_wei: Option, + tip: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +struct SerializedPreparedTransactionParams { + nonce_hex: String, + l1_gas: u64, + l1_gas_price_wei: String, + l2_gas: u64, + l2_gas_price_wei: String, + l1_data_gas: u64, + l1_data_gas_price_wei: String, + tip: u64, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +struct SerializedExternalExecutionPlan { + public_key_hex: String, + account_address_hex: String, + account_class_hash_hex: String, + chain_id_hex: String, + calls: Vec, + invoke: SerializedPreparedTransactionParams, + deploy: Option, +} + +impl DerivedAccountDataResponse { + fn from_result(result: ApiResult) -> Self { + let (value, error) = split_result(result); + Self { value, error } + } +} + +impl StarknetSignatureDataResponse { + fn from_result(result: ApiResult) -> Self { + let (value, error) = split_result(result); + Self { value, error } + } +} + +impl StringResponse { + fn from_result(result: ApiResult) -> Self { + let (value, error) = split_result(result); + Self { value, error } + } +} + +impl BoolResponse { + fn from_result(result: ApiResult) -> Self { + let (value, error) = split_result(result); + Self { value, error } + } +} + +impl I64Response { + fn from_result(result: ApiResult) -> Self { + let (value, error) = split_result(result); + Self { value, error } + } +} + +impl TransferHistoryResponse { + fn from_result(result: ApiResult>) -> Self { + match result { + Ok(items) => Self { items, error: None }, + Err(error) => Self { + items: Vec::new(), + error: Some(error), + }, + } + } +} + +impl TokenMetadataResponse { + fn from_result(result: ApiResult) -> Self { + let (value, error) = split_result(result); + Self { value, error } + } +} + +impl FeeQuoteResponse { + fn from_result(result: ApiResult) -> Self { + let (value, error) = split_result(result); + Self { value, error } + } +} + +impl ExecutionPlanResponse { + fn from_result(result: ApiResult) -> Self { + let (value, error) = split_result(result); + Self { value, error } + } +} + +impl TransactionDetailsResponse { + fn from_result(result: ApiResult) -> Self { + let (value, error) = split_result(result); + Self { value, error } + } +} + +impl StringListResponse { + fn from_result(result: ApiResult>) -> Self { + match result { + Ok(items) => Self { items, error: None }, + Err(error) => Self { + items: Vec::new(), + error: Some(error), + }, + } + } +} + +impl std::fmt::Display for ExternalSignerError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0) + } +} + +impl std::error::Error for ExternalSignerError {} + +impl StarknetFeePriority { + fn from_raw(raw: i32) -> Self { + match raw { + 0 => Self::Slow, + 2 => Self::Fast, + _ => Self::Medium, + } + } +} + +#[async_trait] +impl Signer for PublicKeyOnlySigner { + type GetPublicKeyError = ExternalSignerError; + type SignError = ExternalSignerError; + + async fn get_public_key(&self) -> Result { + Ok(VerifyingKey::from_scalar(self.public_key)) + } + + async fn sign_hash(&self, _hash: &Felt) -> Result { + Err(ExternalSignerError( + "This Starknet signer only exposes public-key operations".to_string(), + )) + } + + fn is_interactive(&self, _context: SignerInteractivityContext<'_>) -> bool { + true + } +} + +impl SerializedPreparedTransactionParams { + fn from_prepared(params: &PreparedTransactionParams) -> Self { + Self { + nonce_hex: felt_to_hex(params.nonce), + l1_gas: params.l1_gas, + l1_gas_price_wei: params.l1_gas_price.to_string(), + l2_gas: params.l2_gas, + l2_gas_price_wei: params.l2_gas_price.to_string(), + l1_data_gas: params.l1_data_gas, + l1_data_gas_price_wei: params.l1_data_gas_price.to_string(), + tip: params.tip, + } + } + + fn to_prepared(&self, label: &str) -> ApiResult { + Ok(PreparedTransactionParams { + nonce: parse_felt_hex(&self.nonce_hex, &format!("{label}.nonce_hex"))?, + l1_gas: self.l1_gas, + l1_gas_price: parse_u128_decimal( + &self.l1_gas_price_wei, + &format!("{label}.l1_gas_price_wei"), + )?, + l2_gas: self.l2_gas, + l2_gas_price: parse_u128_decimal( + &self.l2_gas_price_wei, + &format!("{label}.l2_gas_price_wei"), + )?, + l1_data_gas: self.l1_data_gas, + l1_data_gas_price: parse_u128_decimal( + &self.l1_data_gas_price_wei, + &format!("{label}.l1_data_gas_price_wei"), + )?, + tip: self.tip, + }) + } +} + +#[flutter_rust_bridge::frb(init)] +pub fn init_app() { + flutter_rust_bridge::setup_default_user_utils(); +} + +pub fn derive_account( + mnemonic: Option, + passphrase: Option, + private_key_hex: Option, + account_class_hash_hex: String, +) -> DerivedAccountDataResponse { + DerivedAccountDataResponse::from_result(derive_account_inner( + mnemonic, + passphrase, + private_key_hex, + account_class_hash_hex, + )) +} + +pub fn derive_account_from_public_key( + public_key_hex: String, + account_class_hash_hex: String, +) -> DerivedAccountDataResponse { + DerivedAccountDataResponse::from_result(derive_account_from_public_key_inner( + public_key_hex, + account_class_hash_hex, + )) +} + +pub fn get_token_balance( + node_url: String, + account_address_hex: String, + token_address_hex: String, +) -> StringResponse { + StringResponse::from_result(run_async(async move { + let provider = make_provider(&node_url)?; + let account_address = parse_felt_hex(&account_address_hex, "account_address_hex")?; + let token_address = parse_felt_hex(&token_address_hex, "token_address_hex")?; + let selector = get_selector_from_name("balanceOf").map_err(|err| err.to_string())?; + + let result = provider + .call( + FunctionCall { + contract_address: token_address, + entry_point_selector: selector, + calldata: vec![account_address], + }, + BlockId::Tag(BlockTag::Latest), + ) + .await + .map_err(format_provider_error)?; + + Ok(uint256_from_call_result(&result).to_string()) + })) +} + +pub fn get_token_metadata(node_url: String, token_address_hex: String) -> TokenMetadataResponse { + TokenMetadataResponse::from_result(run_async(async move { + let provider = make_provider(&node_url)?; + let token_address = parse_felt_hex(&token_address_hex, "token_address_hex")?; + + let name = call_token_string(&provider, token_address, "name").await?; + let symbol = call_token_string(&provider, token_address, "symbol").await?; + let decimals = call_token_decimals(&provider, token_address).await?; + + Ok(StarknetTokenMetadata { + token_address_hex: felt_to_hex(token_address), + name, + symbol, + decimals, + }) + })) +} + +pub fn is_account_deployed(node_url: String, account_address_hex: String) -> BoolResponse { + BoolResponse::from_result(run_async(async move { + let provider = make_provider(&node_url)?; + let account_address = parse_felt_hex(&account_address_hex, "account_address_hex")?; + is_account_deployed_with_provider(&provider, account_address).await + })) +} + +pub fn estimate_transfer_fee( + node_url: String, + private_key_hex: String, + account_address_hex: String, + recipient_address_hex: String, + token_address_hex: String, + amount_wei: String, + chain_id_hex: Option, +) -> StringResponse { + StringResponse::from_result(run_async(async move { + let quote = estimate_execute_fee_inner( + node_url, + private_key_hex, + account_address_hex, + DEFAULT_OPENZEPPELIN_ACCOUNT_CLASS_HASH_HEX.to_string(), + vec![StarknetCallInput { + contract_address_hex: token_address_hex, + entrypoint: "transfer".to_string(), + calldata_hex: transfer_calldata_hex(recipient_address_hex, amount_wei)?, + }], + StarknetFeePriority::Medium, + chain_id_hex, + ) + .await?; + + Ok(quote.overall_fee_wei) + })) +} + +pub fn estimate_standard_transfer_fee( + node_url: String, + private_key_hex: String, + account_address_hex: String, + token_address_hex: String, + chain_id_hex: Option, +) -> StringResponse { + estimate_transfer_fee( + node_url, + private_key_hex, + account_address_hex.clone(), + account_address_hex, + token_address_hex, + "1000".to_string(), + chain_id_hex, + ) +} + +pub fn send_transfer( + node_url: String, + private_key_hex: String, + account_address_hex: String, + recipient_address_hex: String, + token_address_hex: String, + amount_wei: String, + account_class_hash_hex: String, + chain_id_hex: Option, +) -> StringResponse { + StringResponse::from_result(run_async(async move { + execute_calls_inner( + node_url, + private_key_hex, + account_address_hex, + account_class_hash_hex, + vec![StarknetCallInput { + contract_address_hex: token_address_hex, + entrypoint: "transfer".to_string(), + calldata_hex: transfer_calldata_hex(recipient_address_hex, amount_wei)?, + }], + StarknetFeePriority::Medium, + chain_id_hex, + ) + .await + })) +} + +pub fn estimate_execute_fee( + node_url: String, + private_key_hex: String, + account_address_hex: String, + account_class_hash_hex: String, + calls: Vec, + fee_priority_raw: i32, + chain_id_hex: Option, +) -> FeeQuoteResponse { + FeeQuoteResponse::from_result(run_async(estimate_execute_fee_inner( + node_url, + private_key_hex, + account_address_hex, + account_class_hash_hex, + calls, + StarknetFeePriority::from_raw(fee_priority_raw), + chain_id_hex, + ))) +} + +pub fn estimate_execute_fee_external_signer( + node_url: String, + public_key_hex: String, + account_address_hex: String, + account_class_hash_hex: String, + calls: Vec, + fee_priority_raw: i32, + chain_id_hex: Option, +) -> FeeQuoteResponse { + FeeQuoteResponse::from_result(run_async(async move { + Ok(prepare_external_execution_inner( + node_url, + public_key_hex, + account_address_hex, + account_class_hash_hex, + calls, + StarknetFeePriority::from_raw(fee_priority_raw), + chain_id_hex, + ) + .await? + .fee_quote) + })) +} + +pub fn execute_calls( + node_url: String, + private_key_hex: String, + account_address_hex: String, + account_class_hash_hex: String, + calls: Vec, + fee_priority_raw: i32, + chain_id_hex: Option, +) -> StringResponse { + StringResponse::from_result(run_async(execute_calls_inner( + node_url, + private_key_hex, + account_address_hex, + account_class_hash_hex, + calls, + StarknetFeePriority::from_raw(fee_priority_raw), + chain_id_hex, + ))) +} + +pub fn get_execute_transaction_hashes_external_signer( + node_url: String, + public_key_hex: String, + account_address_hex: String, + account_class_hash_hex: String, + calls: Vec, + fee_priority_raw: i32, + chain_id_hex: Option, +) -> ExecutionPlanResponse { + ExecutionPlanResponse::from_result(run_async(async move { + let prepared = prepare_external_execution_inner( + node_url, + public_key_hex, + account_address_hex, + account_class_hash_hex, + calls, + StarknetFeePriority::from_raw(fee_priority_raw), + chain_id_hex, + ) + .await?; + + Ok(StarknetExecutionPlanData { + invoke_transaction_hash_hex: felt_to_hex(prepared.invoke_transaction_hash), + deploy_account_transaction_hash_hex: prepared + .deploy_account_transaction_hash + .map(felt_to_hex), + account_deployment_required: prepared.fee_quote.account_deployment_required, + plan_json: serde_json::to_string(&prepared.serialized_plan) + .map_err(|err| format!("Failed to serialize Starknet execution plan: {err}"))?, + }) + })) +} + +pub fn execute_calls_external_signer( + node_url: String, + plan_json: String, + invoke_r_hex: String, + invoke_s_hex: String, + deploy_r_hex: Option, + deploy_s_hex: Option, +) -> StringResponse { + StringResponse::from_result(run_async(execute_calls_external_signer_inner( + node_url, + plan_json, + invoke_r_hex, + invoke_s_hex, + deploy_r_hex, + deploy_s_hex, + ))) +} + +pub fn fetch_transfer_history( + node_url: String, + account_address_hex: String, + token_address_hex: String, + token_symbol: String, + from_block: Option, + max_pages: Option, +) -> TransferHistoryResponse { + TransferHistoryResponse::from_result(run_async(async move { + let provider = make_provider(&node_url)?; + let account_address = parse_felt_hex(&account_address_hex, "account_address_hex")?; + let token_address = parse_felt_hex(&token_address_hex, "token_address_hex")?; + let transfer_selector = + get_selector_from_name("Transfer").map_err(|err| err.to_string())?; + let from_block = from_block + .map(|value| u64::try_from(value).map(BlockId::Number)) + .transpose() + .map_err(|_| "from_block must be greater than or equal to 0".to_string())?; + let max_pages = max_pages + .map(|value| usize::try_from(value).map_err(|_| ())) + .transpose() + .map_err(|_| "max_pages must be greater than or equal to 0".to_string())? + .unwrap_or(DEFAULT_MAX_EVENT_PAGES); + + let mut events = Vec::new(); + fetch_paginated_transfer_events( + &provider, + EventFilter { + from_block: from_block.clone(), + to_block: None, + address: Some(AddressFilter::Single(token_address)), + keys: Some(vec![vec![transfer_selector], vec![account_address]]), + }, + true, + token_symbol.clone(), + max_pages, + &mut events, + ) + .await; + fetch_paginated_transfer_events( + &provider, + EventFilter { + from_block, + to_block: None, + address: Some(AddressFilter::Single(token_address)), + keys: Some(vec![vec![transfer_selector], vec![], vec![account_address]]), + }, + false, + token_symbol, + max_pages, + &mut events, + ) + .await; + + let mut deduped = Vec::new(); + let mut seen = HashSet::new(); + for event in events { + let dedupe_key = format!("{:#x}_{}", event.transaction_hash, event.event_index); + if seen.insert(dedupe_key.clone()) { + deduped.push(event); + } else if event.is_outgoing { + if let Some(existing) = deduped.iter_mut().find(|item| { + format!("{:#x}_{}", item.transaction_hash, item.event_index) == dedupe_key + }) { + existing.is_outgoing = true; + } + } + } + + let mut block_timestamps = HashMap::new(); + for block_number in deduped.iter().filter_map(|event| event.block_number) { + block_timestamps.entry(block_number).or_insert(None); + } + let block_numbers = block_timestamps.keys().copied().collect::>(); + for block_number in block_numbers { + block_timestamps.insert( + block_number, + fetch_block_timestamp(&provider, block_number).await.ok(), + ); + } + + let mut tx_fees = HashMap::new(); + for tx_hash in deduped.iter().map(|event| event.transaction_hash) { + tx_fees.entry(tx_hash).or_insert(None); + } + let tx_hashes = tx_fees.keys().copied().collect::>(); + for tx_hash in tx_hashes { + tx_fees.insert( + tx_hash, + fetch_transaction_fee(&provider, tx_hash).await.ok(), + ); + } + + Ok(deduped + .into_iter() + .map(|event| TransferHistoryItem { + transaction_hash: felt_to_hex(event.transaction_hash), + event_id: format!( + "{}:{}:{}", + felt_to_hex(event.transaction_hash), + event.token_address_hex, + event.event_index + ), + event_index: i64::try_from(event.event_index).unwrap_or(i64::MAX), + block_number: event + .block_number + .and_then(|value| i64::try_from(value).ok()), + from: event.from, + to: event.to, + amount_wei: event.amount_wei, + is_outgoing: event.is_outgoing, + token_symbol: event.token_symbol, + token_address_hex: event.token_address_hex, + block_timestamp: event + .block_number + .and_then(|value| block_timestamps.get(&value).copied().flatten()), + tx_fee_wei: tx_fees.get(&event.transaction_hash).cloned().flatten(), + }) + .collect()) + })) +} + +pub fn get_transaction_details( + node_url: String, + transaction_hash_hex: String, +) -> TransactionDetailsResponse { + TransactionDetailsResponse::from_result(run_async(async move { + let provider = make_provider(&node_url)?; + let transaction_hash = parse_felt_hex(&transaction_hash_hex, "transaction_hash_hex")?; + fetch_transaction_details(&provider, transaction_hash).await + })) +} + +pub fn get_block_number(node_url: String) -> I64Response { + I64Response::from_result(run_async(async move { + let provider = make_provider(&node_url)?; + let block_number = provider + .block_number() + .await + .map_err(format_provider_error)?; + i64::try_from(block_number).map_err(|_| "Block number does not fit in i64".to_string()) + })) +} + +pub fn sign_message_hash( + private_key_hex: String, + message_hash_hex: String, +) -> StarknetSignatureDataResponse { + StarknetSignatureDataResponse::from_result(sign_message_hash_inner( + private_key_hex, + message_hash_hex, + )) +} + +pub fn verify_message_hash_signature( + public_key_hex: String, + message_hash_hex: String, + r_hex: String, + s_hex: String, +) -> BoolResponse { + BoolResponse::from_result(verify_message_hash_signature_inner( + public_key_hex, + message_hash_hex, + r_hex, + s_hex, + )) +} + +pub fn sign_typed_data( + private_key_hex: String, + account_address_hex: String, + typed_data_json: String, +) -> StringListResponse { + StringListResponse::from_result(sign_typed_data_inner( + private_key_hex, + account_address_hex, + typed_data_json, + )) +} + +pub fn get_typed_data_message_hash( + account_address_hex: String, + typed_data_json: String, +) -> StringResponse { + StringResponse::from_result(get_typed_data_message_hash_inner( + account_address_hex, + typed_data_json, + )) +} + +fn split_result(result: ApiResult) -> (Option, Option) { + match result { + Ok(value) => (Some(value), None), + Err(error) => (None, Some(error)), + } +} + +fn derive_account_inner( + mnemonic: Option, + passphrase: Option, + private_key_hex: Option, + account_class_hash_hex: String, +) -> ApiResult { + let private_key = if let Some(private_key_hex) = private_key_hex { + parse_private_key(&private_key_hex)? + } else if let Some(mnemonic) = mnemonic { + derive_private_key_from_mnemonic(&mnemonic, passphrase.as_deref())? + } else { + return Err("Either mnemonic or private_key_hex must be provided".to_string()); + }; + + validate_private_key(private_key)?; + + let signing_key = SigningKey::from_secret_scalar(private_key); + let public_key = signing_key.verifying_key().scalar(); + let account_class_hash = parse_felt_hex(&account_class_hash_hex, "account_class_hash_hex")?; + let account_address = + get_contract_address(public_key, account_class_hash, &[public_key], Felt::ZERO); + + Ok(DerivedAccountData { + private_key_hex: felt_to_hex(private_key), + public_key_hex: felt_to_hex(public_key), + account_address_hex: felt_to_hex(account_address), + }) +} + +fn derive_account_from_public_key_inner( + public_key_hex: String, + account_class_hash_hex: String, +) -> ApiResult { + let public_key = parse_felt_hex(&public_key_hex, "public_key_hex")?; + let account_class_hash = parse_felt_hex(&account_class_hash_hex, "account_class_hash_hex")?; + let account_address = derive_account_address_from_public_key(public_key, account_class_hash); + + Ok(DerivedAccountData { + private_key_hex: String::new(), + public_key_hex: felt_to_hex(public_key), + account_address_hex: felt_to_hex(account_address), + }) +} + +fn sign_message_hash_inner( + private_key_hex: String, + message_hash_hex: String, +) -> ApiResult { + let private_key = parse_private_key(&private_key_hex)?; + let message_hash = parse_felt_hex(&message_hash_hex, "message_hash_hex")?; + let signature = SigningKey::from_secret_scalar(private_key) + .sign(&message_hash) + .map_err(|err| err.to_string())?; + + Ok(StarknetSignatureData { + r_hex: felt_to_hex(signature.r), + s_hex: felt_to_hex(signature.s), + }) +} + +fn verify_message_hash_signature_inner( + public_key_hex: String, + message_hash_hex: String, + r_hex: String, + s_hex: String, +) -> ApiResult { + let public_key = parse_felt_hex(&public_key_hex, "public_key_hex")?; + let message_hash = parse_felt_hex(&message_hash_hex, "message_hash_hex")?; + let signature = Signature { + r: parse_felt_hex(&r_hex, "r_hex")?, + s: parse_felt_hex(&s_hex, "s_hex")?, + }; + + VerifyingKey::from_scalar(public_key) + .verify(&message_hash, &signature) + .map_err(|err| err.to_string()) +} + +fn sign_typed_data_inner( + private_key_hex: String, + account_address_hex: String, + typed_data_json: String, +) -> ApiResult> { + let private_key = parse_private_key(&private_key_hex)?; + let account_address = parse_felt_hex(&account_address_hex, "account_address_hex")?; + let typed_data: TypedData = serde_json::from_str(&typed_data_json) + .map_err(|err| format!("Invalid typedData: {err}"))?; + let message_hash = typed_data + .message_hash(account_address) + .map_err(|err| format!("Invalid typedData: {err}"))?; + let signature = SigningKey::from_secret_scalar(private_key) + .sign(&message_hash) + .map_err(|err| err.to_string())?; + + Ok(vec![felt_to_hex(signature.r), felt_to_hex(signature.s)]) +} + +fn get_typed_data_message_hash_inner( + account_address_hex: String, + typed_data_json: String, +) -> ApiResult { + let account_address = parse_felt_hex(&account_address_hex, "account_address_hex")?; + let typed_data: TypedData = serde_json::from_str(&typed_data_json) + .map_err(|err| format!("Invalid typedData: {err}"))?; + let message_hash = typed_data + .message_hash(account_address) + .map_err(|err| format!("Invalid typedData: {err}"))?; + + Ok(felt_to_hex(message_hash)) +} + +async fn estimate_execute_fee_inner( + node_url: String, + private_key_hex: String, + account_address_hex: String, + account_class_hash_hex: String, + calls: Vec, + fee_priority: StarknetFeePriority, + chain_id_hex: Option, +) -> ApiResult { + let provider = make_provider(&node_url)?; + let private_key = parse_private_key(&private_key_hex)?; + let account_address = parse_felt_hex(&account_address_hex, "account_address_hex")?; + let account_class_hash = parse_felt_hex(&account_class_hash_hex, "account_class_hash_hex")?; + let chain_id = parse_chain_id(chain_id_hex.as_deref())?; + let signing_key = SigningKey::from_secret_scalar(private_key); + let public_key = signing_key.verifying_key().scalar(); + let call_inputs = build_execution_calls(calls)?; + let is_deployed = is_account_deployed_with_provider(&provider, account_address).await?; + + if is_deployed { + let account = make_single_owner_account( + provider.clone(), + LocalWallet::from(signing_key), + account_address, + chain_id, + ); + let nonce = account.get_nonce().await.map_err(format_provider_error)?; + let invoke_params = prepare_transaction_params_from_fee_estimate( + &provider, + nonce, + account + .execute_v3(call_inputs) + .nonce(nonce) + .estimate_fee() + .await + .map_err(format_account_error)?, + fee_priority, + ) + .await?; + + let execution_fee = calculate_overall_fee_from_params(&invoke_params).to_string(); + return Ok(StarknetFeeQuote { + overall_fee_wei: execution_fee.clone(), + execution_fee_wei: execution_fee, + deploy_account_fee_wei: None, + account_deployment_required: false, + }); + } + + let factory = OpenZeppelinAccountFactory::new( + account_class_hash, + chain_id, + LocalWallet::from(signing_key.clone()), + provider.clone(), + ) + .await + .map_err(|_| "failed to derive Starknet public key".to_string())?; + + let deploy_request = factory + .deploy_v3(public_key) + .nonce(Felt::ZERO) + .l1_gas(0) + .l1_gas_price(0) + .l2_gas(0) + .l2_gas_price(0) + .l1_data_gas(0) + .l1_data_gas_price(0) + .tip(0) + .prepared() + .map_err(|_| "failed to prepare deploy account request".to_string())? + .get_deploy_request(true, false) + .await + .map_err(format_account_error)?; + + let account = make_single_owner_account( + provider.clone(), + LocalWallet::from(signing_key), + account_address, + chain_id, + ); + + let invoke_request = account + .execute_v3(call_inputs) + .nonce(Felt::ONE) + .l1_gas(0) + .l1_gas_price(0) + .l2_gas(0) + .l2_gas_price(0) + .l1_data_gas(0) + .l1_data_gas_price(0) + .tip(0) + .prepared() + .map_err(|_| "failed to prepare invoke request".to_string())? + .get_invoke_request(true, false) + .await + .map_err(format_account_error)?; + + let fee_estimates = provider + .estimate_fee( + [ + starknet_rust::core::types::BroadcastedTransaction::DeployAccount(deploy_request), + starknet_rust::core::types::BroadcastedTransaction::Invoke(invoke_request), + ], + Vec::::new(), + BlockId::Tag(BlockTag::Latest), + ) + .await + .map_err(format_provider_error)?; + + if fee_estimates.len() != 2 { + return Err(format!( + "Unexpected Starknet fee estimate response length: {}", + fee_estimates.len() + )); + } + + let tip = fetch_latest_block_tip(&provider).await?; + let deploy_params = prepare_transaction_params_from_estimate_with_tip( + Felt::ZERO, + &fee_estimates[0], + tip, + fee_priority, + )?; + let invoke_params = prepare_transaction_params_from_estimate_with_tip( + Felt::ONE, + &fee_estimates[1], + tip, + fee_priority, + )?; + let deploy_fee = calculate_overall_fee_from_params(&deploy_params); + let execution_fee = calculate_overall_fee_from_params(&invoke_params); + Ok(StarknetFeeQuote { + overall_fee_wei: (deploy_fee.clone() + execution_fee.clone()).to_string(), + execution_fee_wei: execution_fee.to_string(), + deploy_account_fee_wei: Some(deploy_fee.to_string()), + account_deployment_required: true, + }) +} + +async fn prepare_external_execution_inner( + node_url: String, + public_key_hex: String, + account_address_hex: String, + account_class_hash_hex: String, + calls: Vec, + fee_priority: StarknetFeePriority, + chain_id_hex: Option, +) -> ApiResult { + let provider = make_provider(&node_url)?; + let public_key = parse_felt_hex(&public_key_hex, "public_key_hex")?; + let account_address = parse_felt_hex(&account_address_hex, "account_address_hex")?; + let account_class_hash = parse_felt_hex(&account_class_hash_hex, "account_class_hash_hex")?; + let chain_id = parse_chain_id(chain_id_hex.as_deref())?; + let signer = PublicKeyOnlySigner { public_key }; + + validate_derived_account_address(public_key, account_class_hash, account_address)?; + + let execution_calls = build_execution_calls(calls.clone())?; + let account = + make_single_owner_account(provider.clone(), signer.clone(), account_address, chain_id); + + if is_account_deployed_with_provider(&provider, account_address).await? { + let nonce = account.get_nonce().await.map_err(format_provider_error)?; + let invoke_params = prepare_transaction_params_from_fee_estimate( + &provider, + nonce, + account + .execute_v3(execution_calls.clone()) + .nonce(nonce) + .estimate_fee() + .await + .map_err(format_account_error)?, + fee_priority, + ) + .await?; + let invoke_hash = build_invoke_transaction_hash(&account, execution_calls, &invoke_params)?; + + return Ok(PreparedExternalExecution { + fee_quote: StarknetFeeQuote { + overall_fee_wei: calculate_overall_fee_from_params(&invoke_params).to_string(), + execution_fee_wei: calculate_overall_fee_from_params(&invoke_params).to_string(), + deploy_account_fee_wei: None, + account_deployment_required: false, + }, + invoke_transaction_hash: invoke_hash, + deploy_account_transaction_hash: None, + serialized_plan: SerializedExternalExecutionPlan { + public_key_hex: felt_to_hex(public_key), + account_address_hex: felt_to_hex(account_address), + account_class_hash_hex: felt_to_hex(account_class_hash), + chain_id_hex: felt_to_hex(chain_id), + calls, + invoke: SerializedPreparedTransactionParams::from_prepared(&invoke_params), + deploy: None, + }, + }); + } + + let factory = + OpenZeppelinAccountFactory::new(account_class_hash, chain_id, signer, provider.clone()) + .await + .map_err(|err| err.to_string())?; + + let zero_deploy_request = factory + .deploy_v3(public_key) + .nonce(Felt::ZERO) + .l1_gas(0) + .l1_gas_price(0) + .l2_gas(0) + .l2_gas_price(0) + .l1_data_gas(0) + .l1_data_gas_price(0) + .tip(0) + .prepared() + .map_err(|_| "failed to prepare deploy account request".to_string())? + .get_deploy_request(true, true) + .await + .map_err(format_account_error)?; + + let zero_invoke_request = account + .execute_v3(execution_calls.clone()) + .nonce(Felt::ONE) + .l1_gas(0) + .l1_gas_price(0) + .l2_gas(0) + .l2_gas_price(0) + .l1_data_gas(0) + .l1_data_gas_price(0) + .tip(0) + .prepared() + .map_err(|_| "failed to prepare invoke request".to_string())? + .get_invoke_request(true, true) + .await + .map_err(format_account_error)?; + + let fee_estimates = provider + .estimate_fee( + [ + BroadcastedTransaction::DeployAccount(zero_deploy_request), + BroadcastedTransaction::Invoke(zero_invoke_request), + ], + vec![SimulationFlagForEstimateFee::SkipValidate], + BlockId::Tag(BlockTag::Latest), + ) + .await + .map_err(format_provider_error)?; + + if fee_estimates.len() != 2 { + return Err(format!( + "Unexpected Starknet fee estimate response length: {}", + fee_estimates.len() + )); + } + + let tip = fetch_latest_block_tip(&provider).await?; + let deploy_params = prepare_transaction_params_from_estimate_with_tip( + Felt::ZERO, + &fee_estimates[0], + tip, + fee_priority, + )?; + let invoke_params = prepare_transaction_params_from_estimate_with_tip( + Felt::ONE, + &fee_estimates[1], + tip, + fee_priority, + )?; + let deploy_hash = build_deploy_transaction_hash(&factory, public_key, &deploy_params)?; + let invoke_hash = build_invoke_transaction_hash(&account, execution_calls, &invoke_params)?; + let deploy_fee = calculate_overall_fee_from_params(&deploy_params); + let execution_fee = calculate_overall_fee_from_params(&invoke_params); + + Ok(PreparedExternalExecution { + fee_quote: StarknetFeeQuote { + overall_fee_wei: (deploy_fee.clone() + execution_fee.clone()).to_string(), + execution_fee_wei: execution_fee.to_string(), + deploy_account_fee_wei: Some(deploy_fee.to_string()), + account_deployment_required: true, + }, + invoke_transaction_hash: invoke_hash, + deploy_account_transaction_hash: Some(deploy_hash), + serialized_plan: SerializedExternalExecutionPlan { + public_key_hex: felt_to_hex(public_key), + account_address_hex: felt_to_hex(account_address), + account_class_hash_hex: felt_to_hex(account_class_hash), + chain_id_hex: felt_to_hex(chain_id), + calls, + invoke: SerializedPreparedTransactionParams::from_prepared(&invoke_params), + deploy: Some(SerializedPreparedTransactionParams::from_prepared( + &deploy_params, + )), + }, + }) +} + +async fn execute_calls_external_signer_inner( + node_url: String, + plan_json: String, + invoke_r_hex: String, + invoke_s_hex: String, + deploy_r_hex: Option, + deploy_s_hex: Option, +) -> ApiResult { + let plan: SerializedExternalExecutionPlan = serde_json::from_str(&plan_json) + .map_err(|err| format!("Invalid Starknet execution plan: {err}"))?; + let provider = make_provider(&node_url)?; + let public_key = parse_felt_hex(&plan.public_key_hex, "plan.public_key_hex")?; + let account_address = parse_felt_hex(&plan.account_address_hex, "plan.account_address_hex")?; + let account_class_hash = + parse_felt_hex(&plan.account_class_hash_hex, "plan.account_class_hash_hex")?; + let chain_id = parse_felt_hex(&plan.chain_id_hex, "plan.chain_id_hex")?; + let invoke_params = plan.invoke.to_prepared("plan.invoke")?; + + validate_derived_account_address(public_key, account_class_hash, account_address)?; + + let execution_calls = build_execution_calls(plan.calls.clone())?; + let account = make_single_owner_account( + provider.clone(), + PublicKeyOnlySigner { public_key }, + account_address, + chain_id, + ); + + if let Some(deploy_plan) = plan.deploy { + let deploy_r_hex = deploy_r_hex.ok_or_else(|| { + "Missing deploy_account signature for undeployed Starknet account".to_string() + })?; + let deploy_s_hex = deploy_s_hex.ok_or_else(|| { + "Missing deploy_account signature for undeployed Starknet account".to_string() + })?; + let deploy_signature = parse_signature_hex(&deploy_r_hex, &deploy_s_hex, "deploy")?; + let deploy_params = deploy_plan.to_prepared("plan.deploy")?; + let factory = OpenZeppelinAccountFactory::new( + account_class_hash, + chain_id, + PublicKeyOnlySigner { public_key }, + provider.clone(), + ) + .await + .map_err(|err| err.to_string())?; + let deployment = factory + .deploy_v3(public_key) + .nonce(deploy_params.nonce) + .l1_gas(deploy_params.l1_gas) + .l1_gas_price(deploy_params.l1_gas_price) + .l2_gas(deploy_params.l2_gas) + .l2_gas_price(deploy_params.l2_gas_price) + .l1_data_gas(deploy_params.l1_data_gas) + .l1_data_gas_price(deploy_params.l1_data_gas_price) + .tip(deploy_params.tip) + .prepared() + .map_err(|_| "failed to prepare deploy account request".to_string())?; + let mut deploy_request = deployment + .get_deploy_request(false, true) + .await + .map_err(format_account_error)?; + deploy_request.signature = vec![deploy_signature.r, deploy_signature.s]; + let deploy_result = provider + .add_deploy_account_transaction(deploy_request) + .await + .map_err(format_provider_error)?; + wait_for_transaction(&node_url, deploy_result.transaction_hash).await?; + } + + let invoke_signature = parse_signature_hex(&invoke_r_hex, &invoke_s_hex, "invoke")?; + let invocation = account + .execute_v3(execution_calls) + .nonce(invoke_params.nonce) + .l1_gas(invoke_params.l1_gas) + .l1_gas_price(invoke_params.l1_gas_price) + .l2_gas(invoke_params.l2_gas) + .l2_gas_price(invoke_params.l2_gas_price) + .l1_data_gas(invoke_params.l1_data_gas) + .l1_data_gas_price(invoke_params.l1_data_gas_price) + .tip(invoke_params.tip) + .prepared() + .map_err(|_| "failed to prepare invoke request".to_string())?; + let mut invoke_request = invocation + .get_invoke_request(false, true) + .await + .map_err(format_account_error)?; + invoke_request.broadcasted_invoke_txn_v3.signature = + vec![invoke_signature.r, invoke_signature.s]; + let invoke_result = provider + .add_invoke_transaction(invoke_request) + .await + .map_err(format_provider_error)?; + + wait_for_transaction(&node_url, invoke_result.transaction_hash).await?; + Ok(felt_to_hex(invoke_result.transaction_hash)) +} + +async fn execute_calls_inner( + node_url: String, + private_key_hex: String, + account_address_hex: String, + account_class_hash_hex: String, + calls: Vec, + fee_priority: StarknetFeePriority, + chain_id_hex: Option, +) -> ApiResult { + let private_key = parse_private_key(&private_key_hex)?; + let account_address = parse_felt_hex(&account_address_hex, "account_address_hex")?; + let account_class_hash = parse_felt_hex(&account_class_hash_hex, "account_class_hash_hex")?; + let chain_id = parse_chain_id(chain_id_hex.as_deref())?; + let signing_key = SigningKey::from_secret_scalar(private_key); + let public_key = signing_key.verifying_key().scalar(); + let call_inputs = build_execution_calls(calls)?; + + let provider = make_provider(&node_url)?; + if !is_account_deployed_with_provider(&provider, account_address).await? { + let factory = OpenZeppelinAccountFactory::new( + account_class_hash, + chain_id, + LocalWallet::from(signing_key.clone()), + provider.clone(), + ) + .await + .map_err(|_| "failed to derive Starknet public key".to_string())?; + let deploy_estimate = factory + .deploy_v3(public_key) + .estimate_fee() + .await + .map_err(format_account_error)?; + let deploy_params = prepare_transaction_params_from_fee_estimate( + &provider, + Felt::ZERO, + deploy_estimate, + fee_priority, + ) + .await?; + let deployment = factory + .deploy_v3(public_key) + .nonce(deploy_params.nonce) + .l1_gas(deploy_params.l1_gas) + .l1_gas_price(deploy_params.l1_gas_price) + .l2_gas(deploy_params.l2_gas) + .l2_gas_price(deploy_params.l2_gas_price) + .l1_data_gas(deploy_params.l1_data_gas) + .l1_data_gas_price(deploy_params.l1_data_gas_price) + .tip(deploy_params.tip); + + let deployment_result = deployment.send().await.map_err(format_account_error)?; + wait_for_transaction(&node_url, deployment_result.transaction_hash).await?; + } + + let execute_provider = make_provider(&node_url)?; + let account = make_single_owner_account( + execute_provider.clone(), + LocalWallet::from(signing_key), + account_address, + chain_id, + ); + let nonce = account.get_nonce().await.map_err(format_provider_error)?; + let invoke_params = prepare_transaction_params_from_fee_estimate( + &execute_provider, + nonce, + account + .execute_v3(call_inputs.clone()) + .nonce(nonce) + .estimate_fee() + .await + .map_err(format_account_error)?, + fee_priority, + ) + .await?; + let invoke_result = account + .execute_v3(call_inputs) + .nonce(invoke_params.nonce) + .l1_gas(invoke_params.l1_gas) + .l1_gas_price(invoke_params.l1_gas_price) + .l2_gas(invoke_params.l2_gas) + .l2_gas_price(invoke_params.l2_gas_price) + .l1_data_gas(invoke_params.l1_data_gas) + .l1_data_gas_price(invoke_params.l1_data_gas_price) + .tip(invoke_params.tip) + .send() + .await + .map_err(format_account_error)?; + + wait_for_transaction(&node_url, invoke_result.transaction_hash).await?; + Ok(felt_to_hex(invoke_result.transaction_hash)) +} + +fn run_async(future: F) -> ApiResult +where + F: Future>, +{ + TOKIO_RUNTIME.block_on(future) +} + +fn make_provider(node_url: &str) -> ApiResult> { + crate::ensure_crypto_provider(); + let url = Url::parse(node_url).map_err(|err| format!("Invalid Starknet node URL: {err}"))?; + Ok(JsonRpcClient::new(HttpTransport::new(url))) +} + +fn parse_private_key(private_key_hex: &str) -> ApiResult { + let private_key = parse_felt_hex(private_key_hex, "private_key_hex")?; + validate_private_key(private_key)?; + Ok(private_key) +} + +fn parse_felt_hex(value: &str, field_name: &str) -> ApiResult { + let normalized = normalize_hex(value); + Felt::from_hex(&normalized).map_err(|err| format!("Invalid {field_name} `{value}`: {err}")) +} + +fn parse_chain_id(chain_id_hex: Option<&str>) -> ApiResult { + match chain_id_hex { + Some(value) => parse_felt_hex(value, "chain_id_hex"), + None => Ok(chain_id::MAINNET), + } +} + +fn parse_u128_decimal(value: &str, field_name: &str) -> ApiResult { + value + .parse::() + .map_err(|_| format!("Invalid decimal {field_name} `{value}`")) +} + +fn normalize_hex(value: &str) -> String { + if value.starts_with("0x") || value.starts_with("0X") { + value.to_string() + } else { + format!("0x{value}") + } +} + +fn felt_to_hex(felt: Felt) -> String { + format!("{felt:#x}") +} + +fn validate_private_key(private_key: Felt) -> ApiResult<()> { + let value = felt_to_biguint(private_key); + if value == BigUint::default() || value >= *CURVE_ORDER { + return Err("Invalid Stark private key".to_string()); + } + + Ok(()) +} + +fn derive_private_key_from_mnemonic(mnemonic: &str, passphrase: Option<&str>) -> ApiResult { + let mnemonic = Mnemonic::parse_in(Language::English, mnemonic) + .or_else(|_| Mnemonic::parse_normalized(mnemonic)) + .map_err(|err| format!("Invalid mnemonic: {err}"))?; + let seed = mnemonic.to_seed(passphrase.unwrap_or_default()); + + let mut hmac = + HmacSha256::new_from_slice(seed.as_ref()).expect("HMAC supports arbitrary key lengths"); + hmac.update(DERIVATION_CONTEXT); + let mut derived = hmac.finalize().into_bytes().to_vec(); + + for i in 0..10_000u32 { + let candidate = BigUint::from_bytes_be(&derived) % &*CURVE_ORDER; + if candidate != BigUint::default() { + let candidate_bytes = candidate.to_bytes_be(); + return Ok(Felt::from_bytes_be_slice(&candidate_bytes)); + } + + let mut next_hmac = + HmacSha256::new_from_slice(&derived).expect("HMAC supports arbitrary key lengths"); + next_hmac.update(&[(i & 0xff) as u8]); + derived = next_hmac.finalize().into_bytes().to_vec(); + } + + Err("Failed to derive valid Stark private key".to_string()) +} + +fn make_single_owner_account( + provider: JsonRpcClient, + signer: S, + account_address: Felt, + chain_id: Felt, +) -> SingleOwnerAccount, S> +where + S: Signer + Sync + Send, +{ + SingleOwnerAccount::new( + provider, + signer, + account_address, + chain_id, + ExecutionEncoding::New, + ) +} + +fn derive_account_address_from_public_key(public_key: Felt, account_class_hash: Felt) -> Felt { + get_contract_address(public_key, account_class_hash, &[public_key], Felt::ZERO) +} + +fn validate_derived_account_address( + public_key: Felt, + account_class_hash: Felt, + account_address: Felt, +) -> ApiResult<()> { + let expected_address = derive_account_address_from_public_key(public_key, account_class_hash); + + if expected_address != account_address { + return Err(format!( + "Account address {} does not match Starknet public key {} for class hash {} (expected {})", + felt_to_hex(account_address), + felt_to_hex(public_key), + felt_to_hex(account_class_hash), + felt_to_hex(expected_address), + )); + } + + Ok(()) +} + +async fn fetch_latest_block_tip(provider: &JsonRpcClient) -> ApiResult { + Ok(provider + .get_block_with_txs(BlockId::Tag(BlockTag::Latest), None) + .await + .map_err(format_provider_error)? + .median_tip()) +} + +fn apply_multiplier_u64(value: u64, numerator: u64, denominator: u64) -> ApiResult { + let adjusted = (u128::from(value) * u128::from(numerator)) + .checked_add(u128::from(denominator.saturating_sub(1))) + .ok_or_else(|| "fee calculation overflow".to_string())? + / u128::from(denominator); + u64::try_from(adjusted).map_err(|_| "fee calculation overflow".to_string()) +} + +fn apply_multiplier_u128(value: u128, numerator: u64, denominator: u64) -> ApiResult { + value + .checked_mul(u128::from(numerator)) + .and_then(|intermediate| { + intermediate.checked_add(u128::from(denominator.saturating_sub(1))) + }) + .map(|adjusted| adjusted / u128::from(denominator)) + .ok_or_else(|| "fee calculation overflow".to_string()) +} + +fn fee_priority_multipliers(priority: StarknetFeePriority) -> (u64, u64, u64, u64, u64, u64) { + match priority { + StarknetFeePriority::Slow => (115, 100, 110, 100, 50, 100), + StarknetFeePriority::Medium => (135, 100, 130, 100, 100, 100), + StarknetFeePriority::Fast => (160, 100, 150, 100, 200, 100), + } +} + +fn adjust_tip(value: u64, numerator: u64, denominator: u64) -> ApiResult { + if value == 0 || numerator == 0 { + return Ok(0); + } + + Ok(apply_multiplier_u64(value, numerator, denominator)?.max(1)) +} + +fn prepare_transaction_params_from_estimate_with_tip( + nonce: Felt, + fee_estimate: &starknet_rust::core::types::FeeEstimate, + tip: u64, + fee_priority: StarknetFeePriority, +) -> ApiResult { + let ( + gas_amount_numerator, + gas_amount_denominator, + gas_price_numerator, + gas_price_denominator, + tip_numerator, + tip_denominator, + ) = fee_priority_multipliers(fee_priority); + + Ok(PreparedTransactionParams { + nonce, + l1_gas: apply_multiplier_u64( + fee_estimate.l1_gas_consumed, + gas_amount_numerator, + gas_amount_denominator, + )?, + l1_gas_price: apply_multiplier_u128( + fee_estimate.l1_gas_price, + gas_price_numerator, + gas_price_denominator, + )?, + l2_gas: apply_multiplier_u64( + fee_estimate.l2_gas_consumed, + gas_amount_numerator, + gas_amount_denominator, + )?, + l2_gas_price: apply_multiplier_u128( + fee_estimate.l2_gas_price, + gas_price_numerator, + gas_price_denominator, + )?, + l1_data_gas: apply_multiplier_u64( + fee_estimate.l1_data_gas_consumed, + gas_amount_numerator, + gas_amount_denominator, + )?, + l1_data_gas_price: apply_multiplier_u128( + fee_estimate.l1_data_gas_price, + gas_price_numerator, + gas_price_denominator, + )?, + tip: adjust_tip(tip, tip_numerator, tip_denominator)?, + }) +} + +async fn prepare_transaction_params_from_fee_estimate( + provider: &JsonRpcClient, + nonce: Felt, + fee_estimate: starknet_rust::core::types::FeeEstimate, + fee_priority: StarknetFeePriority, +) -> ApiResult { + let tip = fetch_latest_block_tip(provider).await?; + prepare_transaction_params_from_estimate_with_tip(nonce, &fee_estimate, tip, fee_priority) +} + +fn calculate_overall_fee_from_params(params: &PreparedTransactionParams) -> BigUint { + (BigUint::from(params.l1_gas) * BigUint::from(params.l1_gas_price)) + + (BigUint::from(params.l2_gas) * BigUint::from(params.l2_gas_price)) + + (BigUint::from(params.l1_data_gas) * BigUint::from(params.l1_data_gas_price)) +} + +fn build_invoke_transaction_hash( + account: &SingleOwnerAccount, S>, + calls: Vec, + params: &PreparedTransactionParams, +) -> ApiResult +where + S: Signer + Sync + Send, +{ + let invocation = account + .execute_v3(calls) + .nonce(params.nonce) + .l1_gas(params.l1_gas) + .l1_gas_price(params.l1_gas_price) + .l2_gas(params.l2_gas) + .l2_gas_price(params.l2_gas_price) + .l1_data_gas(params.l1_data_gas) + .l1_data_gas_price(params.l1_data_gas_price) + .tip(params.tip) + .prepared() + .map_err(|_| "failed to prepare invoke request".to_string())?; + + Ok(invocation.transaction_hash(false)) +} + +fn build_deploy_transaction_hash( + factory: &OpenZeppelinAccountFactory>, + public_key: Felt, + params: &PreparedTransactionParams, +) -> ApiResult +where + S: Signer + Sync + Send, +{ + let deployment = factory + .deploy_v3(public_key) + .nonce(params.nonce) + .l1_gas(params.l1_gas) + .l1_gas_price(params.l1_gas_price) + .l2_gas(params.l2_gas) + .l2_gas_price(params.l2_gas_price) + .l1_data_gas(params.l1_data_gas) + .l1_data_gas_price(params.l1_data_gas_price) + .tip(params.tip) + .prepared() + .map_err(|_| "failed to prepare deploy account request".to_string())?; + + Ok(deployment.transaction_hash(false)) +} + +fn parse_signature_hex(r_hex: &str, s_hex: &str, label: &str) -> ApiResult { + Ok(Signature { + r: parse_felt_hex(r_hex, &format!("{label}_r_hex"))?, + s: parse_felt_hex(s_hex, &format!("{label}_s_hex"))?, + }) +} + +fn build_execution_calls(calls: Vec) -> ApiResult> { + calls + .into_iter() + .enumerate() + .map(|(index, call)| { + let contract_address = parse_felt_hex( + &call.contract_address_hex, + &format!("calls[{index}].contractAddress"), + )?; + let selector = parse_entrypoint_selector(&call.entrypoint, index)?; + let calldata = call + .calldata_hex + .iter() + .enumerate() + .map(|(calldata_index, value)| { + parse_felt_hex(value, &format!("calls[{index}].calldata[{calldata_index}]")) + }) + .collect::>>()?; + + Ok(Call { + to: contract_address, + selector, + calldata, + }) + }) + .collect() +} + +fn parse_entrypoint_selector(value: &str, index: usize) -> ApiResult { + if value.starts_with("0x") || value.starts_with("0X") { + parse_felt_hex(value, &format!("calls[{index}].entrypoint")) + } else { + get_selector_from_name(value) + .map_err(|err| format!("Invalid calls[{index}].entrypoint `{value}`: {err}")) + } +} + +fn transfer_calldata_hex( + recipient_address_hex: String, + amount_wei: String, +) -> ApiResult> { + let recipient_address = parse_felt_hex(&recipient_address_hex, "recipient_address_hex")?; + Ok(transfer_calldata(recipient_address, &amount_wei)? + .into_iter() + .map(felt_to_hex) + .collect()) +} + +fn transfer_calldata(recipient_address: Felt, amount_wei: &str) -> ApiResult> { + let amount = parse_decimal_biguint(amount_wei, "amount_wei")?; + let (low, high) = biguint_to_uint256_words(&amount)?; + Ok(vec![recipient_address, low, high]) +} + +fn parse_decimal_biguint(value: &str, field_name: &str) -> ApiResult { + BigUint::parse_bytes(value.as_bytes(), 10) + .ok_or_else(|| format!("Invalid decimal {field_name} `{value}`")) +} + +fn biguint_to_uint256_words(value: &BigUint) -> ApiResult<(Felt, Felt)> { + let bytes = value.to_bytes_be(); + if bytes.len() > 32 { + return Err("uint256 value is too large".to_string()); + } + + let mut padded = [0u8; 32]; + padded[(32 - bytes.len())..].copy_from_slice(&bytes); + + let high = u128::from_be_bytes(padded[0..16].try_into().unwrap()); + let low = u128::from_be_bytes(padded[16..32].try_into().unwrap()); + + Ok((Felt::from(low), Felt::from(high))) +} + +fn uint256_from_call_result(values: &[Felt]) -> BigUint { + let low = values.first().copied().unwrap_or(Felt::ZERO); + let high = values.get(1).copied().unwrap_or(Felt::ZERO); + uint256_from_words(low, high) +} + +fn uint256_from_words(low: Felt, high: Felt) -> BigUint { + (felt_to_biguint(high) << 128usize) | felt_to_biguint(low) +} + +fn felt_to_biguint(value: Felt) -> BigUint { + BigUint::from_bytes_be(&value.to_bytes_be()) +} + +fn felt_to_usize(value: Felt) -> Result { + felt_to_biguint(value) + .to_string() + .parse::() + .map_err(|_| ()) +} + +fn felt_to_short_string(value: Felt) -> String { + let bytes = value.to_bytes_be(); + let start = bytes + .iter() + .position(|byte| *byte != 0) + .unwrap_or(bytes.len()); + String::from_utf8_lossy(&bytes[start..]) + .trim_matches('\u{0}') + .to_string() +} + +async fn call_token_string( + provider: &JsonRpcClient, + token_address: Felt, + entrypoint: &str, +) -> ApiResult { + let selector = get_selector_from_name(entrypoint).map_err(|err| err.to_string())?; + let result = provider + .call( + FunctionCall { + contract_address: token_address, + entry_point_selector: selector, + calldata: vec![], + }, + BlockId::Tag(BlockTag::Latest), + ) + .await + .map_err(format_provider_error)?; + + if let Ok(value) = ByteArray::decode(&result).and_then(|value| { + String::try_from(value).map_err(|err| { + starknet_rust::core::codec::Error::custom(format!("Invalid UTF-8 token string: {err}")) + }) + }) { + if !value.is_empty() { + return Ok(value); + } + } + + result + .first() + .copied() + .map(felt_to_short_string) + .filter(|value| !value.is_empty()) + .ok_or_else(|| format!("Missing token {entrypoint} response")) +} + +async fn call_token_decimals( + provider: &JsonRpcClient, + token_address: Felt, +) -> ApiResult { + let selector = get_selector_from_name("decimals").map_err(|err| err.to_string())?; + let result = provider + .call( + FunctionCall { + contract_address: token_address, + entry_point_selector: selector, + calldata: vec![], + }, + BlockId::Tag(BlockTag::Latest), + ) + .await + .map_err(format_provider_error)?; + + let decimals = result + .first() + .copied() + .ok_or_else(|| "Missing token decimals response".to_string())?; + felt_to_biguint(decimals) + .to_string() + .parse::() + .map_err(|_| "Token decimals do not fit in i32".to_string()) +} + +async fn is_account_deployed_with_provider( + provider: &JsonRpcClient, + account_address: Felt, +) -> ApiResult { + match provider + .get_class_hash_at(BlockId::Tag(BlockTag::Latest), account_address) + .await + { + Ok(_) => Ok(true), + Err(ProviderError::StarknetError(StarknetError::ContractNotFound)) => Ok(false), + Err(err) => Err(format_provider_error(err)), + } +} + +async fn fetch_paginated_transfer_events( + provider: &JsonRpcClient, + filter: EventFilter, + is_outgoing: bool, + token_symbol: String, + max_pages: usize, + events: &mut Vec, +) { + let mut continuation_token = None; + let mut page_count = 0usize; + + loop { + let result = provider + .get_events( + filter.clone(), + continuation_token.clone(), + DEFAULT_PAGE_SIZE, + ) + .await; + + let page = match result { + Ok(page) => page, + Err(_) => break, + }; + + for event in page.events { + events.push(parse_transfer_event( + event, + is_outgoing, + token_symbol.clone(), + )); + } + + continuation_token = page.continuation_token; + page_count += 1; + if continuation_token.is_none() || page_count >= max_pages { + break; + } + } +} + +fn parse_transfer_event( + event: EmittedEvent, + is_outgoing: bool, + token_symbol: String, +) -> TransferEventRecord { + let low = event.data.first().copied().unwrap_or(Felt::ZERO); + let high = event.data.get(1).copied().unwrap_or(Felt::ZERO); + + TransferEventRecord { + transaction_hash: event.transaction_hash, + event_index: event.event_index, + block_number: event.block_number, + from: event + .keys + .get(1) + .copied() + .map(felt_to_hex) + .unwrap_or_default(), + to: event + .keys + .get(2) + .copied() + .map(felt_to_hex) + .unwrap_or_default(), + amount_wei: uint256_from_words(low, high).to_string(), + is_outgoing, + token_symbol, + token_address_hex: felt_to_hex(event.from_address), + } +} + +async fn fetch_block_timestamp( + provider: &JsonRpcClient, + block_number: u64, +) -> ApiResult { + let block = provider + .get_block_with_tx_hashes(BlockId::Number(block_number)) + .await + .map_err(format_provider_error)?; + + let timestamp = match block { + MaybePreConfirmedBlockWithTxHashes::Block(block) => block.timestamp, + MaybePreConfirmedBlockWithTxHashes::PreConfirmedBlock(block) => block.timestamp, + }; + + i64::try_from(timestamp).map_err(|_| "Block timestamp does not fit in i64".to_string()) +} + +async fn fetch_transaction_fee( + provider: &JsonRpcClient, + transaction_hash: Felt, +) -> ApiResult { + let receipt = provider + .get_transaction_receipt(transaction_hash) + .await + .map_err(format_provider_error)?; + Ok(actual_fee_from_receipt(&receipt.receipt).to_string()) +} + +fn actual_fee_from_receipt(receipt: &TransactionReceipt) -> BigUint { + let fee_amount = match receipt { + TransactionReceipt::Invoke(inner) => inner.actual_fee.amount, + TransactionReceipt::L1Handler(inner) => inner.actual_fee.amount, + TransactionReceipt::Declare(inner) => inner.actual_fee.amount, + TransactionReceipt::Deploy(inner) => inner.actual_fee.amount, + TransactionReceipt::DeployAccount(inner) => inner.actual_fee.amount, + }; + + felt_to_biguint(fee_amount) +} + +async fn fetch_transaction_details( + provider: &JsonRpcClient, + transaction_hash: Felt, +) -> ApiResult { + let transaction = provider + .get_transaction_by_hash(transaction_hash, None) + .await + .map_err(format_provider_error)?; + let receipt = provider + .get_transaction_receipt(transaction_hash) + .await + .map_err(format_provider_error)?; + let summary = summarize_transaction(&transaction)?; + let block_number = i64::try_from(receipt.block.block_number()) + .map_err(|_| "Block number does not fit in i64".to_string()) + .ok(); + let block_timestamp = if receipt.block.is_pre_confirmed() { + None + } else { + fetch_block_timestamp(provider, receipt.block.block_number()) + .await + .ok() + }; + + let (execution_status, revert_reason) = match receipt.receipt.execution_result() { + ExecutionResult::Succeeded => (Some("SUCCEEDED".to_string()), None), + ExecutionResult::Reverted { reason } => { + (Some("REVERTED".to_string()), Some(reason.clone())) + } + }; + + Ok(StarknetTransactionDetails { + transaction_hash: felt_to_hex(transaction_hash), + transaction_type: summary.transaction_type, + is_pending: matches!( + receipt.receipt.finality_status(), + TransactionFinalityStatus::PreConfirmed + ), + block_number, + block_timestamp, + actual_fee_wei: Some(actual_fee_from_receipt(&receipt.receipt).to_string()), + action_name: summary.action_name, + call_count: summary.call_count, + primary_contract_address_hex: summary.primary_contract_address_hex, + primary_entrypoint: summary.primary_entrypoint, + sender_address_hex: summary.sender_address_hex, + finality_status: Some(finality_status_to_string(receipt.receipt.finality_status())), + execution_status, + revert_reason, + account_deployment_required: summary.account_deployment_required, + l1_gas_max_amount: summary.l1_gas_max_amount, + l1_gas_max_price_wei: summary.l1_gas_max_price_wei, + l2_gas_max_amount: summary.l2_gas_max_amount, + l2_gas_max_price_wei: summary.l2_gas_max_price_wei, + l1_data_gas_max_amount: summary.l1_data_gas_max_amount, + l1_data_gas_max_price_wei: summary.l1_data_gas_max_price_wei, + tip: summary.tip, + }) +} + +fn summarize_transaction(transaction: &Transaction) -> ApiResult { + match transaction { + Transaction::Invoke(InvokeTransaction::V0(tx)) => { + let calls = vec![DecodedInvokeCall { + contract_address: tx.contract_address, + selector: tx.entry_point_selector, + calldata: tx.calldata.clone(), + }]; + Ok(DecodedTransactionSummary { + transaction_type: "INVOKE".to_string(), + action_name: infer_action_name(&calls), + call_count: Some(1), + primary_contract_address_hex: Some(felt_to_hex(tx.contract_address)), + primary_entrypoint: Some(selector_display_name(tx.entry_point_selector)), + sender_address_hex: Some(felt_to_hex(tx.contract_address)), + account_deployment_required: false, + l1_gas_max_amount: None, + l1_gas_max_price_wei: None, + l2_gas_max_amount: None, + l2_gas_max_price_wei: None, + l1_data_gas_max_amount: None, + l1_data_gas_max_price_wei: None, + tip: None, + }) + } + Transaction::Invoke(InvokeTransaction::V1(tx)) => Ok(summarize_account_invoke_transaction( + "INVOKE".to_string(), + tx.sender_address, + &tx.calldata, + None, + None, + )), + Transaction::Invoke(InvokeTransaction::V3(tx)) => Ok(summarize_account_invoke_transaction( + "INVOKE".to_string(), + tx.sender_address, + &tx.calldata, + Some(&tx.resource_bounds), + Some(tx.tip), + )), + Transaction::DeployAccount(DeployAccountTransaction::V1(_)) => { + Ok(DecodedTransactionSummary { + transaction_type: "DEPLOY_ACCOUNT".to_string(), + action_name: Some("deploy_account".to_string()), + call_count: Some(1), + primary_contract_address_hex: None, + primary_entrypoint: Some("constructor".to_string()), + sender_address_hex: None, + account_deployment_required: true, + l1_gas_max_amount: None, + l1_gas_max_price_wei: None, + l2_gas_max_amount: None, + l2_gas_max_price_wei: None, + l1_data_gas_max_amount: None, + l1_data_gas_max_price_wei: None, + tip: None, + }) + } + Transaction::DeployAccount(DeployAccountTransaction::V3(tx)) => { + Ok(DecodedTransactionSummary { + transaction_type: "DEPLOY_ACCOUNT".to_string(), + action_name: Some("deploy_account".to_string()), + call_count: Some(1), + primary_contract_address_hex: None, + primary_entrypoint: Some("constructor".to_string()), + sender_address_hex: None, + account_deployment_required: true, + l1_gas_max_amount: Some(tx.resource_bounds.l1_gas.max_amount.to_string()), + l1_gas_max_price_wei: Some( + tx.resource_bounds.l1_gas.max_price_per_unit.to_string(), + ), + l2_gas_max_amount: Some(tx.resource_bounds.l2_gas.max_amount.to_string()), + l2_gas_max_price_wei: Some( + tx.resource_bounds.l2_gas.max_price_per_unit.to_string(), + ), + l1_data_gas_max_amount: Some(tx.resource_bounds.l1_data_gas.max_amount.to_string()), + l1_data_gas_max_price_wei: Some( + tx.resource_bounds + .l1_data_gas + .max_price_per_unit + .to_string(), + ), + tip: i64::try_from(tx.tip).ok(), + }) + } + Transaction::Declare(_) => Ok(DecodedTransactionSummary { + transaction_type: "DECLARE".to_string(), + action_name: Some("declare".to_string()), + call_count: Some(1), + primary_contract_address_hex: None, + primary_entrypoint: Some("declare".to_string()), + sender_address_hex: None, + account_deployment_required: false, + l1_gas_max_amount: None, + l1_gas_max_price_wei: None, + l2_gas_max_amount: None, + l2_gas_max_price_wei: None, + l1_data_gas_max_amount: None, + l1_data_gas_max_price_wei: None, + tip: transaction + .tip() + .and_then(|value| i64::try_from(value).ok()), + }), + Transaction::Deploy(_) => Ok(DecodedTransactionSummary { + transaction_type: "DEPLOY".to_string(), + action_name: Some("deploy".to_string()), + call_count: Some(1), + primary_contract_address_hex: None, + primary_entrypoint: Some("constructor".to_string()), + sender_address_hex: None, + account_deployment_required: false, + l1_gas_max_amount: None, + l1_gas_max_price_wei: None, + l2_gas_max_amount: None, + l2_gas_max_price_wei: None, + l1_data_gas_max_amount: None, + l1_data_gas_max_price_wei: None, + tip: None, + }), + Transaction::L1Handler(_) => Ok(DecodedTransactionSummary { + transaction_type: "L1_HANDLER".to_string(), + action_name: Some("l1_handler".to_string()), + call_count: Some(1), + primary_contract_address_hex: None, + primary_entrypoint: Some("l1_handler".to_string()), + sender_address_hex: None, + account_deployment_required: false, + l1_gas_max_amount: None, + l1_gas_max_price_wei: None, + l2_gas_max_amount: None, + l2_gas_max_price_wei: None, + l1_data_gas_max_amount: None, + l1_data_gas_max_price_wei: None, + tip: None, + }), + } +} + +fn summarize_account_invoke_transaction( + transaction_type: String, + sender_address: Felt, + calldata: &[Felt], + resource_bounds: Option<&ResourceBoundsMapping>, + tip: Option, +) -> DecodedTransactionSummary { + let decoded_calls = decode_account_execute_calls(calldata); + let primary_call = decoded_calls.first(); + + DecodedTransactionSummary { + transaction_type, + action_name: infer_action_name(&decoded_calls), + call_count: i32::try_from(decoded_calls.len()).ok(), + primary_contract_address_hex: primary_call.map(|call| felt_to_hex(call.contract_address)), + primary_entrypoint: primary_call.map(|call| selector_display_name(call.selector)), + sender_address_hex: Some(felt_to_hex(sender_address)), + account_deployment_required: false, + l1_gas_max_amount: resource_bounds.map(|bounds| bounds.l1_gas.max_amount.to_string()), + l1_gas_max_price_wei: resource_bounds + .map(|bounds| bounds.l1_gas.max_price_per_unit.to_string()), + l2_gas_max_amount: resource_bounds.map(|bounds| bounds.l2_gas.max_amount.to_string()), + l2_gas_max_price_wei: resource_bounds + .map(|bounds| bounds.l2_gas.max_price_per_unit.to_string()), + l1_data_gas_max_amount: resource_bounds + .map(|bounds| bounds.l1_data_gas.max_amount.to_string()), + l1_data_gas_max_price_wei: resource_bounds + .map(|bounds| bounds.l1_data_gas.max_price_per_unit.to_string()), + tip: tip.and_then(|value| i64::try_from(value).ok()), + } +} + +fn decode_account_execute_calls(calldata: &[Felt]) -> Vec { + let Some(first) = calldata.first() else { + return Vec::new(); + }; + + let Ok(call_count) = felt_to_usize(*first) else { + return Vec::new(); + }; + let descriptors_start = 1usize; + let descriptors_end = descriptors_start.saturating_add(call_count.saturating_mul(4)); + if calldata.len() <= descriptors_end { + return Vec::new(); + } + + let Ok(flat_calldata_len) = felt_to_usize(calldata[descriptors_end]) else { + return Vec::new(); + }; + let flat_calldata_start = descriptors_end + 1; + let flat_calldata_end = flat_calldata_start.saturating_add(flat_calldata_len); + if calldata.len() < flat_calldata_end { + return Vec::new(); + } + + let flat_calldata = &calldata[flat_calldata_start..flat_calldata_end]; + let mut decoded = Vec::with_capacity(call_count); + + for index in 0..call_count { + let base = descriptors_start + index * 4; + let contract_address = calldata[base]; + let selector = calldata[base + 1]; + let Ok(data_offset) = felt_to_usize(calldata[base + 2]) else { + return Vec::new(); + }; + let Ok(data_len) = felt_to_usize(calldata[base + 3]) else { + return Vec::new(); + }; + let data_end = data_offset.saturating_add(data_len); + if data_end > flat_calldata.len() { + return Vec::new(); + } + + decoded.push(DecodedInvokeCall { + contract_address, + selector, + calldata: flat_calldata[data_offset..data_end].to_vec(), + }); + } + + decoded +} + +fn infer_action_name(calls: &[DecodedInvokeCall]) -> Option { + if calls.is_empty() { + return None; + } + + let normalized_entrypoints = calls + .iter() + .map(|call| action_name_for_selector(call.selector)) + .collect::>(); + + if normalized_entrypoints + .iter() + .all(|entrypoint| entrypoint == "transfer") + { + return Some("transfer".to_string()); + } + + if normalized_entrypoints + .iter() + .all(|entrypoint| entrypoint == "approve") + { + return Some("approval".to_string()); + } + + if normalized_entrypoints + .iter() + .any(|entrypoint| looks_like_swap_action(entrypoint)) + { + return Some("swap".to_string()); + } + + if calls.len() > 1 { + return Some("multicall".to_string()); + } + + normalized_entrypoints.first().cloned() +} + +fn selector_display_name(selector: Felt) -> String { + known_selector_name(selector) + .map(|name| name.to_string()) + .unwrap_or_else(|| felt_to_hex(selector)) +} + +fn known_selector_name(selector: Felt) -> Option<&'static str> { + static KNOWN_SELECTORS: Lazy> = Lazy::new(|| { + [ + "transfer", + "approve", + "transferFrom", + "swap", + "multi_route_swap", + "exact_input", + "exact_output", + "multihop_swap", + "deposit", + "withdraw", + "mint", + "burn", + "claim", + "stake", + "unstake", + ] + .into_iter() + .filter_map(|name| { + get_selector_from_name(name) + .ok() + .map(|selector| (selector, name)) + }) + .collect() + }); + + KNOWN_SELECTORS + .iter() + .find_map(|(known_selector, name)| (*known_selector == selector).then_some(*name)) +} + +fn action_name_for_selector(selector: Felt) -> String { + known_selector_name(selector) + .map(normalize_action_name) + .filter(|value| !value.is_empty()) + .unwrap_or_else(|| "contract_call".to_string()) +} + +fn normalize_action_name(value: &str) -> String { + let mut output = String::with_capacity(value.len()); + let mut previous_was_separator = false; + + for (index, ch) in value.chars().enumerate() { + if ch == '_' || ch == '-' || ch == ' ' { + if !output.is_empty() && !previous_was_separator { + output.push('_'); + previous_was_separator = true; + } + continue; + } + + if ch.is_ascii_uppercase() { + if index > 0 && !previous_was_separator && !output.ends_with('_') { + output.push('_'); + } + output.push(ch.to_ascii_lowercase()); + previous_was_separator = false; + continue; + } + + output.push(ch.to_ascii_lowercase()); + previous_was_separator = false; + } + + output.trim_matches('_').to_string() +} + +fn looks_like_swap_action(value: &str) -> bool { + value.contains("swap") || value.contains("exact_input") || value.contains("exact_output") +} + +fn finality_status_to_string(status: &TransactionFinalityStatus) -> String { + match status { + TransactionFinalityStatus::PreConfirmed => "PRE_CONFIRMED".to_string(), + TransactionFinalityStatus::AcceptedOnL2 => "ACCEPTED_ON_L2".to_string(), + TransactionFinalityStatus::AcceptedOnL1 => "ACCEPTED_ON_L1".to_string(), + } +} + +async fn wait_for_transaction(node_url: &str, tx_hash: Felt) -> ApiResult<()> { + let provider = make_provider(node_url)?; + let deadline = tokio::time::Instant::now() + WAIT_FOR_TRANSACTION_TIMEOUT; + + loop { + match provider.get_transaction_receipt(tx_hash).await { + Ok(receipt) => { + return match receipt.receipt.execution_result() { + ExecutionResult::Succeeded => Ok(()), + ExecutionResult::Reverted { reason } => { + Err(format!("Transaction reverted: {reason}")) + } + }; + } + Err(ProviderError::StarknetError(StarknetError::TransactionHashNotFound)) => {} + Err(err) => return Err(format_provider_error(err)), + } + + if tokio::time::Instant::now() >= deadline { + return Err(format!( + "Timed out while waiting for transaction {}", + felt_to_hex(tx_hash) + )); + } + + tokio::time::sleep(WAIT_FOR_TRANSACTION_POLL_INTERVAL).await; + } +} + +fn format_provider_error(error: ProviderError) -> String { + match error { + ProviderError::StarknetError(error) => format_starknet_error(error), + other => format!("{other:?}"), + } +} + +fn format_starknet_error(error: StarknetError) -> String { + let base = format!("StarknetRpcError({}): {}", error.code(), error.message()); + + match error { + StarknetError::TransactionExecutionError(data) => { + format!("{base}: {:?}", data.execution_error) + } + StarknetError::ContractError(data) => format!("{base}: {data:?}"), + StarknetError::InvalidTransactionNonce(data) => format!("{base}: {data:?}"), + StarknetError::ValidationFailure(data) => format!("{base}: {data:?}"), + StarknetError::CompilationFailed(data) => format!("{base}: {data:?}"), + StarknetError::UnexpectedError(data) => format!("{base}: {data:?}"), + StarknetError::NoTraceAvailable(data) => format!("{base}: {data:?}"), + _ => base, + } +} + +fn format_account_error(error: E) -> String { + error.to_string() +} + +#[cfg(test)] +mod tests { + use super::*; + + const TEST_MNEMONIC: &str = + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; + const TEST_CLASS_HASH: &str = + "0x01d1777db36cdd06dd62cfde77b1b6ae06412af95d57a13dc40ac77b8a702381"; + + #[test] + fn derivation_is_deterministic() { + let account_1 = derive_account_inner( + Some(TEST_MNEMONIC.to_string()), + None, + None, + TEST_CLASS_HASH.to_string(), + ) + .unwrap(); + let account_2 = derive_account_inner( + Some(TEST_MNEMONIC.to_string()), + None, + None, + TEST_CLASS_HASH.to_string(), + ) + .unwrap(); + + assert_eq!(account_1, account_2); + } + + #[test] + fn passphrase_changes_derived_account() { + let no_passphrase = derive_account_inner( + Some(TEST_MNEMONIC.to_string()), + None, + None, + TEST_CLASS_HASH.to_string(), + ) + .unwrap(); + let with_passphrase = derive_account_inner( + Some(TEST_MNEMONIC.to_string()), + Some("cake-wallet".to_string()), + None, + TEST_CLASS_HASH.to_string(), + ) + .unwrap(); + + assert_ne!(no_passphrase, with_passphrase); + } + + #[test] + fn signing_and_verification_round_trip() { + let account = derive_account_inner( + Some(TEST_MNEMONIC.to_string()), + None, + None, + TEST_CLASS_HASH.to_string(), + ) + .unwrap(); + let signature = + sign_message_hash_inner(account.private_key_hex.clone(), "0xdeadbeef".to_string()) + .unwrap(); + + assert!(verify_message_hash_signature_inner( + account.public_key_hex, + "0xdeadbeef".to_string(), + signature.r_hex, + signature.s_hex, + ) + .unwrap()); + } + + #[test] + fn decimal_to_uint256_words_round_trip() { + let amount = parse_decimal_biguint("12345678901234567890", "amount").unwrap(); + let (low, high) = biguint_to_uint256_words(&amount).unwrap(); + + assert_eq!( + uint256_from_words(low, high).to_string(), + amount.to_string() + ); + } +} diff --git a/cw_starknet/rust/src/frb_generated.rs b/cw_starknet/rust/src/frb_generated.rs new file mode 100644 index 0000000000..9a89ca4624 --- /dev/null +++ b/cw_starknet/rust/src/frb_generated.rs @@ -0,0 +1,2434 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +#![allow( + non_camel_case_types, + unused, + non_snake_case, + clippy::needless_return, + clippy::redundant_closure_call, + clippy::redundant_closure, + clippy::useless_conversion, + clippy::unit_arg, + clippy::unused_unit, + clippy::double_parens, + clippy::let_and_return, + clippy::too_many_arguments, + clippy::match_single_binding, + clippy::clone_on_copy, + clippy::let_unit_value, + clippy::deref_addrof, + clippy::explicit_auto_deref, + clippy::borrow_deref_ref, + clippy::uninlined_format_args, + clippy::needless_borrow +)] + +// Section: imports + +use flutter_rust_bridge::for_generated::byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt}; +use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable}; +use flutter_rust_bridge::{Handler, IntoIntoDart}; + +// Section: boilerplate + +flutter_rust_bridge::frb_generated_boilerplate!( + default_stream_sink_codec = SseCodec, + default_rust_opaque = RustOpaqueMoi, + default_rust_auto_opaque = RustAutoOpaqueMoi, +); +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0"; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -811430247; + +// Section: executor + +flutter_rust_bridge::frb_generated_default_handler!(); + +// Section: wire_funcs + +fn wire__crate__api__starknet__derive_account_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "derive_account", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_mnemonic = >::sse_decode(&mut deserializer); + let api_passphrase = >::sse_decode(&mut deserializer); + let api_private_key_hex = >::sse_decode(&mut deserializer); + let api_account_class_hash_hex = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = Result::<_, ()>::Ok(crate::api::starknet::derive_account( + api_mnemonic, + api_passphrase, + api_private_key_hex, + api_account_class_hash_hex, + ))?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__starknet__derive_account_from_public_key_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "derive_account_from_public_key", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_public_key_hex = ::sse_decode(&mut deserializer); + let api_account_class_hash_hex = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = + Result::<_, ()>::Ok(crate::api::starknet::derive_account_from_public_key( + api_public_key_hex, + api_account_class_hash_hex, + ))?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__starknet__estimate_execute_fee_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "estimate_execute_fee", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_node_url = ::sse_decode(&mut deserializer); + let api_private_key_hex = ::sse_decode(&mut deserializer); + let api_account_address_hex = ::sse_decode(&mut deserializer); + let api_account_class_hash_hex = ::sse_decode(&mut deserializer); + let api_calls = + >::sse_decode(&mut deserializer); + let api_fee_priority_raw = ::sse_decode(&mut deserializer); + let api_chain_id_hex = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = + Result::<_, ()>::Ok(crate::api::starknet::estimate_execute_fee( + api_node_url, + api_private_key_hex, + api_account_address_hex, + api_account_class_hash_hex, + api_calls, + api_fee_priority_raw, + api_chain_id_hex, + ))?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__starknet__estimate_execute_fee_external_signer_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "estimate_execute_fee_external_signer", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_node_url = ::sse_decode(&mut deserializer); + let api_public_key_hex = ::sse_decode(&mut deserializer); + let api_account_address_hex = ::sse_decode(&mut deserializer); + let api_account_class_hash_hex = ::sse_decode(&mut deserializer); + let api_calls = + >::sse_decode(&mut deserializer); + let api_fee_priority_raw = ::sse_decode(&mut deserializer); + let api_chain_id_hex = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = Result::<_, ()>::Ok( + crate::api::starknet::estimate_execute_fee_external_signer( + api_node_url, + api_public_key_hex, + api_account_address_hex, + api_account_class_hash_hex, + api_calls, + api_fee_priority_raw, + api_chain_id_hex, + ), + )?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__starknet__estimate_standard_transfer_fee_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "estimate_standard_transfer_fee", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_node_url = ::sse_decode(&mut deserializer); + let api_private_key_hex = ::sse_decode(&mut deserializer); + let api_account_address_hex = ::sse_decode(&mut deserializer); + let api_token_address_hex = ::sse_decode(&mut deserializer); + let api_chain_id_hex = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = + Result::<_, ()>::Ok(crate::api::starknet::estimate_standard_transfer_fee( + api_node_url, + api_private_key_hex, + api_account_address_hex, + api_token_address_hex, + api_chain_id_hex, + ))?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__starknet__estimate_transfer_fee_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "estimate_transfer_fee", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_node_url = ::sse_decode(&mut deserializer); + let api_private_key_hex = ::sse_decode(&mut deserializer); + let api_account_address_hex = ::sse_decode(&mut deserializer); + let api_recipient_address_hex = ::sse_decode(&mut deserializer); + let api_token_address_hex = ::sse_decode(&mut deserializer); + let api_amount_wei = ::sse_decode(&mut deserializer); + let api_chain_id_hex = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = + Result::<_, ()>::Ok(crate::api::starknet::estimate_transfer_fee( + api_node_url, + api_private_key_hex, + api_account_address_hex, + api_recipient_address_hex, + api_token_address_hex, + api_amount_wei, + api_chain_id_hex, + ))?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__starknet__execute_calls_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "execute_calls", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_node_url = ::sse_decode(&mut deserializer); + let api_private_key_hex = ::sse_decode(&mut deserializer); + let api_account_address_hex = ::sse_decode(&mut deserializer); + let api_account_class_hash_hex = ::sse_decode(&mut deserializer); + let api_calls = + >::sse_decode(&mut deserializer); + let api_fee_priority_raw = ::sse_decode(&mut deserializer); + let api_chain_id_hex = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = Result::<_, ()>::Ok(crate::api::starknet::execute_calls( + api_node_url, + api_private_key_hex, + api_account_address_hex, + api_account_class_hash_hex, + api_calls, + api_fee_priority_raw, + api_chain_id_hex, + ))?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__starknet__execute_calls_external_signer_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "execute_calls_external_signer", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_node_url = ::sse_decode(&mut deserializer); + let api_plan_json = ::sse_decode(&mut deserializer); + let api_invoke_r_hex = ::sse_decode(&mut deserializer); + let api_invoke_s_hex = ::sse_decode(&mut deserializer); + let api_deploy_r_hex = >::sse_decode(&mut deserializer); + let api_deploy_s_hex = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = + Result::<_, ()>::Ok(crate::api::starknet::execute_calls_external_signer( + api_node_url, + api_plan_json, + api_invoke_r_hex, + api_invoke_s_hex, + api_deploy_r_hex, + api_deploy_s_hex, + ))?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__starknet__fetch_transfer_history_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "fetch_transfer_history", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_node_url = ::sse_decode(&mut deserializer); + let api_account_address_hex = ::sse_decode(&mut deserializer); + let api_token_address_hex = ::sse_decode(&mut deserializer); + let api_token_symbol = ::sse_decode(&mut deserializer); + let api_from_block = >::sse_decode(&mut deserializer); + let api_max_pages = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = + Result::<_, ()>::Ok(crate::api::starknet::fetch_transfer_history( + api_node_url, + api_account_address_hex, + api_token_address_hex, + api_token_symbol, + api_from_block, + api_max_pages, + ))?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__starknet__get_block_number_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "get_block_number", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_node_url = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = + Result::<_, ()>::Ok(crate::api::starknet::get_block_number(api_node_url))?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__starknet__get_execute_transaction_hashes_external_signer_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "get_execute_transaction_hashes_external_signer", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_node_url = ::sse_decode(&mut deserializer); + let api_public_key_hex = ::sse_decode(&mut deserializer); + let api_account_address_hex = ::sse_decode(&mut deserializer); + let api_account_class_hash_hex = ::sse_decode(&mut deserializer); + let api_calls = + >::sse_decode(&mut deserializer); + let api_fee_priority_raw = ::sse_decode(&mut deserializer); + let api_chain_id_hex = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = Result::<_, ()>::Ok( + crate::api::starknet::get_execute_transaction_hashes_external_signer( + api_node_url, + api_public_key_hex, + api_account_address_hex, + api_account_class_hash_hex, + api_calls, + api_fee_priority_raw, + api_chain_id_hex, + ), + )?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__starknet__get_token_balance_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "get_token_balance", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_node_url = ::sse_decode(&mut deserializer); + let api_account_address_hex = ::sse_decode(&mut deserializer); + let api_token_address_hex = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = Result::<_, ()>::Ok(crate::api::starknet::get_token_balance( + api_node_url, + api_account_address_hex, + api_token_address_hex, + ))?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__starknet__get_token_metadata_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "get_token_metadata", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_node_url = ::sse_decode(&mut deserializer); + let api_token_address_hex = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = Result::<_, ()>::Ok(crate::api::starknet::get_token_metadata( + api_node_url, + api_token_address_hex, + ))?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__starknet__get_transaction_details_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "get_transaction_details", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_node_url = ::sse_decode(&mut deserializer); + let api_transaction_hash_hex = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = + Result::<_, ()>::Ok(crate::api::starknet::get_transaction_details( + api_node_url, + api_transaction_hash_hex, + ))?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__starknet__get_typed_data_message_hash_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "get_typed_data_message_hash", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_account_address_hex = ::sse_decode(&mut deserializer); + let api_typed_data_json = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = + Result::<_, ()>::Ok(crate::api::starknet::get_typed_data_message_hash( + api_account_address_hex, + api_typed_data_json, + ))?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__starknet__init_app_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "init_app", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = Result::<_, ()>::Ok({ + crate::api::starknet::init_app(); + })?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__starknet__is_account_deployed_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "is_account_deployed", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_node_url = ::sse_decode(&mut deserializer); + let api_account_address_hex = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = + Result::<_, ()>::Ok(crate::api::starknet::is_account_deployed( + api_node_url, + api_account_address_hex, + ))?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__starknet__send_transfer_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "send_transfer", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_node_url = ::sse_decode(&mut deserializer); + let api_private_key_hex = ::sse_decode(&mut deserializer); + let api_account_address_hex = ::sse_decode(&mut deserializer); + let api_recipient_address_hex = ::sse_decode(&mut deserializer); + let api_token_address_hex = ::sse_decode(&mut deserializer); + let api_amount_wei = ::sse_decode(&mut deserializer); + let api_account_class_hash_hex = ::sse_decode(&mut deserializer); + let api_chain_id_hex = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = Result::<_, ()>::Ok(crate::api::starknet::send_transfer( + api_node_url, + api_private_key_hex, + api_account_address_hex, + api_recipient_address_hex, + api_token_address_hex, + api_amount_wei, + api_account_class_hash_hex, + api_chain_id_hex, + ))?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__starknet__sign_message_hash_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "sign_message_hash", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_private_key_hex = ::sse_decode(&mut deserializer); + let api_message_hash_hex = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = Result::<_, ()>::Ok(crate::api::starknet::sign_message_hash( + api_private_key_hex, + api_message_hash_hex, + ))?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__starknet__sign_typed_data_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "sign_typed_data", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_private_key_hex = ::sse_decode(&mut deserializer); + let api_account_address_hex = ::sse_decode(&mut deserializer); + let api_typed_data_json = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = Result::<_, ()>::Ok(crate::api::starknet::sign_typed_data( + api_private_key_hex, + api_account_address_hex, + api_typed_data_json, + ))?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__starknet__verify_message_hash_signature_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "verify_message_hash_signature", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_public_key_hex = ::sse_decode(&mut deserializer); + let api_message_hash_hex = ::sse_decode(&mut deserializer); + let api_r_hex = ::sse_decode(&mut deserializer); + let api_s_hex = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let output_ok = + Result::<_, ()>::Ok(crate::api::starknet::verify_message_hash_signature( + api_public_key_hex, + api_message_hash_hex, + api_r_hex, + api_s_hex, + ))?; + Ok(output_ok) + })()) + } + }, + ) +} + +// Section: dart2rust + +impl SseDecode for String { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = >::sse_decode(deserializer); + return String::from_utf8(inner).unwrap(); + } +} + +impl SseDecode for bool { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_u8().unwrap() != 0 + } +} + +impl SseDecode for crate::api::starknet::BoolResponse { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_value = >::sse_decode(deserializer); + let mut var_error = >::sse_decode(deserializer); + return crate::api::starknet::BoolResponse { + value: var_value, + error: var_error, + }; + } +} + +impl SseDecode for crate::api::starknet::DerivedAccountData { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_privateKeyHex = ::sse_decode(deserializer); + let mut var_publicKeyHex = ::sse_decode(deserializer); + let mut var_accountAddressHex = ::sse_decode(deserializer); + return crate::api::starknet::DerivedAccountData { + private_key_hex: var_privateKeyHex, + public_key_hex: var_publicKeyHex, + account_address_hex: var_accountAddressHex, + }; + } +} + +impl SseDecode for crate::api::starknet::DerivedAccountDataResponse { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_value = + >::sse_decode(deserializer); + let mut var_error = >::sse_decode(deserializer); + return crate::api::starknet::DerivedAccountDataResponse { + value: var_value, + error: var_error, + }; + } +} + +impl SseDecode for crate::api::starknet::ExecutionPlanResponse { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_value = + >::sse_decode(deserializer); + let mut var_error = >::sse_decode(deserializer); + return crate::api::starknet::ExecutionPlanResponse { + value: var_value, + error: var_error, + }; + } +} + +impl SseDecode for crate::api::starknet::FeeQuoteResponse { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_value = + >::sse_decode(deserializer); + let mut var_error = >::sse_decode(deserializer); + return crate::api::starknet::FeeQuoteResponse { + value: var_value, + error: var_error, + }; + } +} + +impl SseDecode for i32 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_i32::().unwrap() + } +} + +impl SseDecode for i64 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_i64::().unwrap() + } +} + +impl SseDecode for crate::api::starknet::I64Response { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_value = >::sse_decode(deserializer); + let mut var_error = >::sse_decode(deserializer); + return crate::api::starknet::I64Response { + value: var_value, + error: var_error, + }; + } +} + +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = Vec::with_capacity(len_ as usize); + for idx_ in 0..len_ { + ans_.push(::sse_decode(deserializer)); + } + return ans_; + } +} + +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = Vec::with_capacity(len_ as usize); + for idx_ in 0..len_ { + ans_.push(::sse_decode(deserializer)); + } + return ans_; + } +} + +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = Vec::with_capacity(len_ as usize); + for idx_ in 0..len_ { + ans_.push(::sse_decode( + deserializer, + )); + } + return ans_; + } +} + +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = Vec::with_capacity(len_ as usize); + for idx_ in 0..len_ { + ans_.push(::sse_decode( + deserializer, + )); + } + return ans_; + } +} + +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(::sse_decode(deserializer)); + } else { + return None; + } + } +} + +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(::sse_decode(deserializer)); + } else { + return None; + } + } +} + +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(::sse_decode( + deserializer, + )); + } else { + return None; + } + } +} + +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(::sse_decode(deserializer)); + } else { + return None; + } + } +} + +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(::sse_decode(deserializer)); + } else { + return None; + } + } +} + +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some( + ::sse_decode(deserializer), + ); + } else { + return None; + } + } +} + +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(::sse_decode( + deserializer, + )); + } else { + return None; + } + } +} + +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(::sse_decode( + deserializer, + )); + } else { + return None; + } + } +} + +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(::sse_decode( + deserializer, + )); + } else { + return None; + } + } +} + +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some( + ::sse_decode(deserializer), + ); + } else { + return None; + } + } +} + +impl SseDecode for crate::api::starknet::StarknetCallInput { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_contractAddressHex = ::sse_decode(deserializer); + let mut var_entrypoint = ::sse_decode(deserializer); + let mut var_calldataHex = >::sse_decode(deserializer); + return crate::api::starknet::StarknetCallInput { + contract_address_hex: var_contractAddressHex, + entrypoint: var_entrypoint, + calldata_hex: var_calldataHex, + }; + } +} + +impl SseDecode for crate::api::starknet::StarknetExecutionPlanData { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_invokeTransactionHashHex = ::sse_decode(deserializer); + let mut var_deployAccountTransactionHashHex = >::sse_decode(deserializer); + let mut var_accountDeploymentRequired = ::sse_decode(deserializer); + let mut var_planJson = ::sse_decode(deserializer); + return crate::api::starknet::StarknetExecutionPlanData { + invoke_transaction_hash_hex: var_invokeTransactionHashHex, + deploy_account_transaction_hash_hex: var_deployAccountTransactionHashHex, + account_deployment_required: var_accountDeploymentRequired, + plan_json: var_planJson, + }; + } +} + +impl SseDecode for crate::api::starknet::StarknetFeeQuote { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_overallFeeWei = ::sse_decode(deserializer); + let mut var_executionFeeWei = ::sse_decode(deserializer); + let mut var_deployAccountFeeWei = >::sse_decode(deserializer); + let mut var_accountDeploymentRequired = ::sse_decode(deserializer); + return crate::api::starknet::StarknetFeeQuote { + overall_fee_wei: var_overallFeeWei, + execution_fee_wei: var_executionFeeWei, + deploy_account_fee_wei: var_deployAccountFeeWei, + account_deployment_required: var_accountDeploymentRequired, + }; + } +} + +impl SseDecode for crate::api::starknet::StarknetSignatureData { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_rHex = ::sse_decode(deserializer); + let mut var_sHex = ::sse_decode(deserializer); + return crate::api::starknet::StarknetSignatureData { + r_hex: var_rHex, + s_hex: var_sHex, + }; + } +} + +impl SseDecode for crate::api::starknet::StarknetSignatureDataResponse { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_value = + >::sse_decode(deserializer); + let mut var_error = >::sse_decode(deserializer); + return crate::api::starknet::StarknetSignatureDataResponse { + value: var_value, + error: var_error, + }; + } +} + +impl SseDecode for crate::api::starknet::StarknetTokenMetadata { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_tokenAddressHex = ::sse_decode(deserializer); + let mut var_name = ::sse_decode(deserializer); + let mut var_symbol = ::sse_decode(deserializer); + let mut var_decimals = ::sse_decode(deserializer); + return crate::api::starknet::StarknetTokenMetadata { + token_address_hex: var_tokenAddressHex, + name: var_name, + symbol: var_symbol, + decimals: var_decimals, + }; + } +} + +impl SseDecode for crate::api::starknet::StarknetTransactionDetails { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_transactionHash = ::sse_decode(deserializer); + let mut var_transactionType = ::sse_decode(deserializer); + let mut var_isPending = ::sse_decode(deserializer); + let mut var_blockNumber = >::sse_decode(deserializer); + let mut var_blockTimestamp = >::sse_decode(deserializer); + let mut var_actualFeeWei = >::sse_decode(deserializer); + let mut var_actionName = >::sse_decode(deserializer); + let mut var_callCount = >::sse_decode(deserializer); + let mut var_primaryContractAddressHex = >::sse_decode(deserializer); + let mut var_primaryEntrypoint = >::sse_decode(deserializer); + let mut var_senderAddressHex = >::sse_decode(deserializer); + let mut var_finalityStatus = >::sse_decode(deserializer); + let mut var_executionStatus = >::sse_decode(deserializer); + let mut var_revertReason = >::sse_decode(deserializer); + let mut var_accountDeploymentRequired = ::sse_decode(deserializer); + let mut var_l1GasMaxAmount = >::sse_decode(deserializer); + let mut var_l1GasMaxPriceWei = >::sse_decode(deserializer); + let mut var_l2GasMaxAmount = >::sse_decode(deserializer); + let mut var_l2GasMaxPriceWei = >::sse_decode(deserializer); + let mut var_l1DataGasMaxAmount = >::sse_decode(deserializer); + let mut var_l1DataGasMaxPriceWei = >::sse_decode(deserializer); + let mut var_tip = >::sse_decode(deserializer); + return crate::api::starknet::StarknetTransactionDetails { + transaction_hash: var_transactionHash, + transaction_type: var_transactionType, + is_pending: var_isPending, + block_number: var_blockNumber, + block_timestamp: var_blockTimestamp, + actual_fee_wei: var_actualFeeWei, + action_name: var_actionName, + call_count: var_callCount, + primary_contract_address_hex: var_primaryContractAddressHex, + primary_entrypoint: var_primaryEntrypoint, + sender_address_hex: var_senderAddressHex, + finality_status: var_finalityStatus, + execution_status: var_executionStatus, + revert_reason: var_revertReason, + account_deployment_required: var_accountDeploymentRequired, + l1_gas_max_amount: var_l1GasMaxAmount, + l1_gas_max_price_wei: var_l1GasMaxPriceWei, + l2_gas_max_amount: var_l2GasMaxAmount, + l2_gas_max_price_wei: var_l2GasMaxPriceWei, + l1_data_gas_max_amount: var_l1DataGasMaxAmount, + l1_data_gas_max_price_wei: var_l1DataGasMaxPriceWei, + tip: var_tip, + }; + } +} + +impl SseDecode for crate::api::starknet::StringListResponse { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_items = >::sse_decode(deserializer); + let mut var_error = >::sse_decode(deserializer); + return crate::api::starknet::StringListResponse { + items: var_items, + error: var_error, + }; + } +} + +impl SseDecode for crate::api::starknet::StringResponse { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_value = >::sse_decode(deserializer); + let mut var_error = >::sse_decode(deserializer); + return crate::api::starknet::StringResponse { + value: var_value, + error: var_error, + }; + } +} + +impl SseDecode for crate::api::starknet::TokenMetadataResponse { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_value = + >::sse_decode(deserializer); + let mut var_error = >::sse_decode(deserializer); + return crate::api::starknet::TokenMetadataResponse { + value: var_value, + error: var_error, + }; + } +} + +impl SseDecode for crate::api::starknet::TransactionDetailsResponse { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_value = + >::sse_decode(deserializer); + let mut var_error = >::sse_decode(deserializer); + return crate::api::starknet::TransactionDetailsResponse { + value: var_value, + error: var_error, + }; + } +} + +impl SseDecode for crate::api::starknet::TransferHistoryItem { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_transactionHash = ::sse_decode(deserializer); + let mut var_eventId = ::sse_decode(deserializer); + let mut var_eventIndex = ::sse_decode(deserializer); + let mut var_blockNumber = >::sse_decode(deserializer); + let mut var_from = ::sse_decode(deserializer); + let mut var_to = ::sse_decode(deserializer); + let mut var_amountWei = ::sse_decode(deserializer); + let mut var_isOutgoing = ::sse_decode(deserializer); + let mut var_tokenSymbol = ::sse_decode(deserializer); + let mut var_tokenAddressHex = ::sse_decode(deserializer); + let mut var_blockTimestamp = >::sse_decode(deserializer); + let mut var_txFeeWei = >::sse_decode(deserializer); + return crate::api::starknet::TransferHistoryItem { + transaction_hash: var_transactionHash, + event_id: var_eventId, + event_index: var_eventIndex, + block_number: var_blockNumber, + from: var_from, + to: var_to, + amount_wei: var_amountWei, + is_outgoing: var_isOutgoing, + token_symbol: var_tokenSymbol, + token_address_hex: var_tokenAddressHex, + block_timestamp: var_blockTimestamp, + tx_fee_wei: var_txFeeWei, + }; + } +} + +impl SseDecode for crate::api::starknet::TransferHistoryResponse { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_items = + >::sse_decode(deserializer); + let mut var_error = >::sse_decode(deserializer); + return crate::api::starknet::TransferHistoryResponse { + items: var_items, + error: var_error, + }; + } +} + +impl SseDecode for u8 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_u8().unwrap() + } +} + +impl SseDecode for () { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {} +} + +fn pde_ffi_dispatcher_primary_impl( + func_id: i32, + port: flutter_rust_bridge::for_generated::MessagePort, + ptr: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len: i32, + data_len: i32, +) { + // Codec=Pde (Serialization + dispatch), see doc to use other codecs + match func_id { + 1 => wire__crate__api__starknet__derive_account_impl(port, ptr, rust_vec_len, data_len), + 2 => wire__crate__api__starknet__derive_account_from_public_key_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 3 => { + wire__crate__api__starknet__estimate_execute_fee_impl(port, ptr, rust_vec_len, data_len) + } + 4 => wire__crate__api__starknet__estimate_execute_fee_external_signer_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 5 => wire__crate__api__starknet__estimate_standard_transfer_fee_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 6 => wire__crate__api__starknet__estimate_transfer_fee_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 7 => wire__crate__api__starknet__execute_calls_impl(port, ptr, rust_vec_len, data_len), + 8 => wire__crate__api__starknet__execute_calls_external_signer_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 9 => wire__crate__api__starknet__fetch_transfer_history_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 10 => wire__crate__api__starknet__get_block_number_impl(port, ptr, rust_vec_len, data_len), + 11 => wire__crate__api__starknet__get_execute_transaction_hashes_external_signer_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 12 => wire__crate__api__starknet__get_token_balance_impl(port, ptr, rust_vec_len, data_len), + 13 => { + wire__crate__api__starknet__get_token_metadata_impl(port, ptr, rust_vec_len, data_len) + } + 14 => wire__crate__api__starknet__get_transaction_details_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 15 => wire__crate__api__starknet__get_typed_data_message_hash_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 16 => wire__crate__api__starknet__init_app_impl(port, ptr, rust_vec_len, data_len), + 17 => { + wire__crate__api__starknet__is_account_deployed_impl(port, ptr, rust_vec_len, data_len) + } + 18 => wire__crate__api__starknet__send_transfer_impl(port, ptr, rust_vec_len, data_len), + 19 => wire__crate__api__starknet__sign_message_hash_impl(port, ptr, rust_vec_len, data_len), + 20 => wire__crate__api__starknet__sign_typed_data_impl(port, ptr, rust_vec_len, data_len), + 21 => wire__crate__api__starknet__verify_message_hash_signature_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + _ => unreachable!(), + } +} + +fn pde_ffi_dispatcher_sync_impl( + func_id: i32, + ptr: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len: i32, + data_len: i32, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { + // Codec=Pde (Serialization + dispatch), see doc to use other codecs + match func_id { + _ => unreachable!(), + } +} + +// Section: rust2dart + +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::starknet::BoolResponse { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.value.into_into_dart().into_dart(), + self.error.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::starknet::BoolResponse +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::starknet::BoolResponse +{ + fn into_into_dart(self) -> crate::api::starknet::BoolResponse { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::starknet::DerivedAccountData { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.private_key_hex.into_into_dart().into_dart(), + self.public_key_hex.into_into_dart().into_dart(), + self.account_address_hex.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::starknet::DerivedAccountData +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::starknet::DerivedAccountData +{ + fn into_into_dart(self) -> crate::api::starknet::DerivedAccountData { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::starknet::DerivedAccountDataResponse { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.value.into_into_dart().into_dart(), + self.error.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::starknet::DerivedAccountDataResponse +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::starknet::DerivedAccountDataResponse +{ + fn into_into_dart(self) -> crate::api::starknet::DerivedAccountDataResponse { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::starknet::ExecutionPlanResponse { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.value.into_into_dart().into_dart(), + self.error.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::starknet::ExecutionPlanResponse +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::starknet::ExecutionPlanResponse +{ + fn into_into_dart(self) -> crate::api::starknet::ExecutionPlanResponse { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::starknet::FeeQuoteResponse { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.value.into_into_dart().into_dart(), + self.error.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::starknet::FeeQuoteResponse +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::starknet::FeeQuoteResponse +{ + fn into_into_dart(self) -> crate::api::starknet::FeeQuoteResponse { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::starknet::I64Response { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.value.into_into_dart().into_dart(), + self.error.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::starknet::I64Response +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::starknet::I64Response +{ + fn into_into_dart(self) -> crate::api::starknet::I64Response { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::starknet::StarknetCallInput { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.contract_address_hex.into_into_dart().into_dart(), + self.entrypoint.into_into_dart().into_dart(), + self.calldata_hex.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::starknet::StarknetCallInput +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::starknet::StarknetCallInput +{ + fn into_into_dart(self) -> crate::api::starknet::StarknetCallInput { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::starknet::StarknetExecutionPlanData { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.invoke_transaction_hash_hex + .into_into_dart() + .into_dart(), + self.deploy_account_transaction_hash_hex + .into_into_dart() + .into_dart(), + self.account_deployment_required + .into_into_dart() + .into_dart(), + self.plan_json.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::starknet::StarknetExecutionPlanData +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::starknet::StarknetExecutionPlanData +{ + fn into_into_dart(self) -> crate::api::starknet::StarknetExecutionPlanData { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::starknet::StarknetFeeQuote { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.overall_fee_wei.into_into_dart().into_dart(), + self.execution_fee_wei.into_into_dart().into_dart(), + self.deploy_account_fee_wei.into_into_dart().into_dart(), + self.account_deployment_required + .into_into_dart() + .into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::starknet::StarknetFeeQuote +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::starknet::StarknetFeeQuote +{ + fn into_into_dart(self) -> crate::api::starknet::StarknetFeeQuote { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::starknet::StarknetSignatureData { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.r_hex.into_into_dart().into_dart(), + self.s_hex.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::starknet::StarknetSignatureData +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::starknet::StarknetSignatureData +{ + fn into_into_dart(self) -> crate::api::starknet::StarknetSignatureData { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::starknet::StarknetSignatureDataResponse { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.value.into_into_dart().into_dart(), + self.error.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::starknet::StarknetSignatureDataResponse +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::starknet::StarknetSignatureDataResponse +{ + fn into_into_dart(self) -> crate::api::starknet::StarknetSignatureDataResponse { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::starknet::StarknetTokenMetadata { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.token_address_hex.into_into_dart().into_dart(), + self.name.into_into_dart().into_dart(), + self.symbol.into_into_dart().into_dart(), + self.decimals.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::starknet::StarknetTokenMetadata +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::starknet::StarknetTokenMetadata +{ + fn into_into_dart(self) -> crate::api::starknet::StarknetTokenMetadata { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::starknet::StarknetTransactionDetails { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.transaction_hash.into_into_dart().into_dart(), + self.transaction_type.into_into_dart().into_dart(), + self.is_pending.into_into_dart().into_dart(), + self.block_number.into_into_dart().into_dart(), + self.block_timestamp.into_into_dart().into_dart(), + self.actual_fee_wei.into_into_dart().into_dart(), + self.action_name.into_into_dart().into_dart(), + self.call_count.into_into_dart().into_dart(), + self.primary_contract_address_hex + .into_into_dart() + .into_dart(), + self.primary_entrypoint.into_into_dart().into_dart(), + self.sender_address_hex.into_into_dart().into_dart(), + self.finality_status.into_into_dart().into_dart(), + self.execution_status.into_into_dart().into_dart(), + self.revert_reason.into_into_dart().into_dart(), + self.account_deployment_required + .into_into_dart() + .into_dart(), + self.l1_gas_max_amount.into_into_dart().into_dart(), + self.l1_gas_max_price_wei.into_into_dart().into_dart(), + self.l2_gas_max_amount.into_into_dart().into_dart(), + self.l2_gas_max_price_wei.into_into_dart().into_dart(), + self.l1_data_gas_max_amount.into_into_dart().into_dart(), + self.l1_data_gas_max_price_wei.into_into_dart().into_dart(), + self.tip.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::starknet::StarknetTransactionDetails +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::starknet::StarknetTransactionDetails +{ + fn into_into_dart(self) -> crate::api::starknet::StarknetTransactionDetails { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::starknet::StringListResponse { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.items.into_into_dart().into_dart(), + self.error.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::starknet::StringListResponse +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::starknet::StringListResponse +{ + fn into_into_dart(self) -> crate::api::starknet::StringListResponse { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::starknet::StringResponse { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.value.into_into_dart().into_dart(), + self.error.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::starknet::StringResponse +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::starknet::StringResponse +{ + fn into_into_dart(self) -> crate::api::starknet::StringResponse { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::starknet::TokenMetadataResponse { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.value.into_into_dart().into_dart(), + self.error.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::starknet::TokenMetadataResponse +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::starknet::TokenMetadataResponse +{ + fn into_into_dart(self) -> crate::api::starknet::TokenMetadataResponse { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::starknet::TransactionDetailsResponse { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.value.into_into_dart().into_dart(), + self.error.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::starknet::TransactionDetailsResponse +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::starknet::TransactionDetailsResponse +{ + fn into_into_dart(self) -> crate::api::starknet::TransactionDetailsResponse { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::starknet::TransferHistoryItem { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.transaction_hash.into_into_dart().into_dart(), + self.event_id.into_into_dart().into_dart(), + self.event_index.into_into_dart().into_dart(), + self.block_number.into_into_dart().into_dart(), + self.from.into_into_dart().into_dart(), + self.to.into_into_dart().into_dart(), + self.amount_wei.into_into_dart().into_dart(), + self.is_outgoing.into_into_dart().into_dart(), + self.token_symbol.into_into_dart().into_dart(), + self.token_address_hex.into_into_dart().into_dart(), + self.block_timestamp.into_into_dart().into_dart(), + self.tx_fee_wei.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::starknet::TransferHistoryItem +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::starknet::TransferHistoryItem +{ + fn into_into_dart(self) -> crate::api::starknet::TransferHistoryItem { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::starknet::TransferHistoryResponse { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.items.into_into_dart().into_dart(), + self.error.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::starknet::TransferHistoryResponse +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::starknet::TransferHistoryResponse +{ + fn into_into_dart(self) -> crate::api::starknet::TransferHistoryResponse { + self + } +} + +impl SseEncode for String { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode(self.into_bytes(), serializer); + } +} + +impl SseEncode for bool { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_u8(self as _).unwrap(); + } +} + +impl SseEncode for crate::api::starknet::BoolResponse { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode(self.value, serializer); + >::sse_encode(self.error, serializer); + } +} + +impl SseEncode for crate::api::starknet::DerivedAccountData { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.private_key_hex, serializer); + ::sse_encode(self.public_key_hex, serializer); + ::sse_encode(self.account_address_hex, serializer); + } +} + +impl SseEncode for crate::api::starknet::DerivedAccountDataResponse { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode(self.value, serializer); + >::sse_encode(self.error, serializer); + } +} + +impl SseEncode for crate::api::starknet::ExecutionPlanResponse { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode( + self.value, serializer, + ); + >::sse_encode(self.error, serializer); + } +} + +impl SseEncode for crate::api::starknet::FeeQuoteResponse { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode(self.value, serializer); + >::sse_encode(self.error, serializer); + } +} + +impl SseEncode for i32 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_i32::(self).unwrap(); + } +} + +impl SseEncode for i64 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_i64::(self).unwrap(); + } +} + +impl SseEncode for crate::api::starknet::I64Response { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode(self.value, serializer); + >::sse_encode(self.error, serializer); + } +} + +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + +impl SseEncode for crate::api::starknet::StarknetCallInput { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.contract_address_hex, serializer); + ::sse_encode(self.entrypoint, serializer); + >::sse_encode(self.calldata_hex, serializer); + } +} + +impl SseEncode for crate::api::starknet::StarknetExecutionPlanData { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.invoke_transaction_hash_hex, serializer); + >::sse_encode(self.deploy_account_transaction_hash_hex, serializer); + ::sse_encode(self.account_deployment_required, serializer); + ::sse_encode(self.plan_json, serializer); + } +} + +impl SseEncode for crate::api::starknet::StarknetFeeQuote { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.overall_fee_wei, serializer); + ::sse_encode(self.execution_fee_wei, serializer); + >::sse_encode(self.deploy_account_fee_wei, serializer); + ::sse_encode(self.account_deployment_required, serializer); + } +} + +impl SseEncode for crate::api::starknet::StarknetSignatureData { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.r_hex, serializer); + ::sse_encode(self.s_hex, serializer); + } +} + +impl SseEncode for crate::api::starknet::StarknetSignatureDataResponse { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode(self.value, serializer); + >::sse_encode(self.error, serializer); + } +} + +impl SseEncode for crate::api::starknet::StarknetTokenMetadata { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.token_address_hex, serializer); + ::sse_encode(self.name, serializer); + ::sse_encode(self.symbol, serializer); + ::sse_encode(self.decimals, serializer); + } +} + +impl SseEncode for crate::api::starknet::StarknetTransactionDetails { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.transaction_hash, serializer); + ::sse_encode(self.transaction_type, serializer); + ::sse_encode(self.is_pending, serializer); + >::sse_encode(self.block_number, serializer); + >::sse_encode(self.block_timestamp, serializer); + >::sse_encode(self.actual_fee_wei, serializer); + >::sse_encode(self.action_name, serializer); + >::sse_encode(self.call_count, serializer); + >::sse_encode(self.primary_contract_address_hex, serializer); + >::sse_encode(self.primary_entrypoint, serializer); + >::sse_encode(self.sender_address_hex, serializer); + >::sse_encode(self.finality_status, serializer); + >::sse_encode(self.execution_status, serializer); + >::sse_encode(self.revert_reason, serializer); + ::sse_encode(self.account_deployment_required, serializer); + >::sse_encode(self.l1_gas_max_amount, serializer); + >::sse_encode(self.l1_gas_max_price_wei, serializer); + >::sse_encode(self.l2_gas_max_amount, serializer); + >::sse_encode(self.l2_gas_max_price_wei, serializer); + >::sse_encode(self.l1_data_gas_max_amount, serializer); + >::sse_encode(self.l1_data_gas_max_price_wei, serializer); + >::sse_encode(self.tip, serializer); + } +} + +impl SseEncode for crate::api::starknet::StringListResponse { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode(self.items, serializer); + >::sse_encode(self.error, serializer); + } +} + +impl SseEncode for crate::api::starknet::StringResponse { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode(self.value, serializer); + >::sse_encode(self.error, serializer); + } +} + +impl SseEncode for crate::api::starknet::TokenMetadataResponse { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode(self.value, serializer); + >::sse_encode(self.error, serializer); + } +} + +impl SseEncode for crate::api::starknet::TransactionDetailsResponse { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode( + self.value, serializer, + ); + >::sse_encode(self.error, serializer); + } +} + +impl SseEncode for crate::api::starknet::TransferHistoryItem { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.transaction_hash, serializer); + ::sse_encode(self.event_id, serializer); + ::sse_encode(self.event_index, serializer); + >::sse_encode(self.block_number, serializer); + ::sse_encode(self.from, serializer); + ::sse_encode(self.to, serializer); + ::sse_encode(self.amount_wei, serializer); + ::sse_encode(self.is_outgoing, serializer); + ::sse_encode(self.token_symbol, serializer); + ::sse_encode(self.token_address_hex, serializer); + >::sse_encode(self.block_timestamp, serializer); + >::sse_encode(self.tx_fee_wei, serializer); + } +} + +impl SseEncode for crate::api::starknet::TransferHistoryResponse { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode(self.items, serializer); + >::sse_encode(self.error, serializer); + } +} + +impl SseEncode for u8 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_u8(self).unwrap(); + } +} + +impl SseEncode for () { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {} +} + +#[cfg(not(target_family = "wasm"))] +mod io { + // This file is automatically generated, so please do not edit it. + // @generated by `flutter_rust_bridge`@ 2.12.0. + + // Section: imports + + use super::*; + use flutter_rust_bridge::for_generated::byteorder::{ + NativeEndian, ReadBytesExt, WriteBytesExt, + }; + use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable}; + use flutter_rust_bridge::{Handler, IntoIntoDart}; + + // Section: boilerplate + + flutter_rust_bridge::frb_generated_boilerplate_io!(); +} +#[cfg(not(target_family = "wasm"))] +pub use io::*; + +/// cbindgen:ignore +#[cfg(target_family = "wasm")] +mod web { + // This file is automatically generated, so please do not edit it. + // @generated by `flutter_rust_bridge`@ 2.12.0. + + // Section: imports + + use super::*; + use flutter_rust_bridge::for_generated::byteorder::{ + NativeEndian, ReadBytesExt, WriteBytesExt, + }; + use flutter_rust_bridge::for_generated::wasm_bindgen; + use flutter_rust_bridge::for_generated::wasm_bindgen::prelude::*; + use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable}; + use flutter_rust_bridge::{Handler, IntoIntoDart}; + + // Section: boilerplate + + flutter_rust_bridge::frb_generated_boilerplate_web!(); +} +#[cfg(target_family = "wasm")] +pub use web::*; diff --git a/cw_starknet/rust/src/lib.rs b/cw_starknet/rust/src/lib.rs new file mode 100644 index 0000000000..897d88ce70 --- /dev/null +++ b/cw_starknet/rust/src/lib.rs @@ -0,0 +1,13 @@ +pub mod api; +mod frb_generated; + +#[cfg(target_os = "android")] +mod android_init; + +pub(crate) fn ensure_crypto_provider() { + use std::sync::Once; + static INIT: Once = Once::new(); + INIT.call_once(|| { + let _ = rustls::crypto::ring::default_provider().install_default(); + }); +} diff --git a/cw_starknet/test/starknet_balance_test.dart b/cw_starknet/test/starknet_balance_test.dart new file mode 100644 index 0000000000..92be6a0c8c --- /dev/null +++ b/cw_starknet/test/starknet_balance_test.dart @@ -0,0 +1,83 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:cw_starknet/starknet_balance.dart'; + +void main() { + group('StarknetBalance', () { + test('creates balance from raw units', () { + final balance = StarknetBalance(BigInt.from(1500), decimals: 3); + expect(balance.balance, 1.5); + }); + + test('zero balance', () { + final balance = StarknetBalance.zero(); + expect(balance.balance, 0.0); + expect(balance.formattedAvailableBalance, '0'); + }); + + test('formattedAvailableBalance truncates long strings', () { + final balance = StarknetBalance( + BigInt.parse('1123456789012345678'), + decimals: 18, + ); + final formatted = balance.formattedAvailableBalance; + expect(formatted.length, lessThanOrEqualTo(12)); + }); + + test('formattedAdditionalBalance equals formattedAvailableBalance', () { + final balance = StarknetBalance(BigInt.from(2500), decimals: 3); + expect(balance.formattedAdditionalBalance, + balance.formattedAvailableBalance); + }); + + group('JSON serialization', () { + test('toJSON produces valid JSON', () { + final balance = StarknetBalance(BigInt.from(1250), decimals: 3); + final json = balance.toJSON(); + expect(json.contains('raw_balance'), true); + expect(json.contains('1250'), true); + }); + + test('fromJSON round-trips correctly', () { + final original = StarknetBalance(BigInt.parse('314159'), decimals: 5); + final json = original.toJSON(); + final restored = StarknetBalance.fromJSON(json); + + expect(restored, isNotNull); + expect(restored!.balance, closeTo(original.balance, 0.0001)); + expect(restored.rawBalance, original.rawBalance); + expect(restored.decimals, original.decimals); + }); + + test('fromJSON returns null for null input', () { + final result = StarknetBalance.fromJSON(null); + expect(result, isNull); + }); + + test('fromJSON returns zero for malformed input', () { + final result = StarknetBalance.fromJSON('{"raw_balance": "not_a_number"}'); + // Should not crash; returns 0.0 on parse failure + expect(result, isNotNull); + expect(result!.rawBalance, BigInt.zero); + }); + + test('fromJSON handles integer balance', () { + final result = StarknetBalance.fromJSON('{"raw_balance": "5", "decimals": 0}'); + expect(result, isNotNull); + expect(result!.balance, 5.0); + }); + }); + + group('Large and small values', () { + test('very small balance', () { + final balance = StarknetBalance(BigInt.one, decimals: 18); + expect(balance.balance, greaterThanOrEqualTo(0)); + }); + + test('large balance', () { + final balance = StarknetBalance(BigInt.parse('1000000123456'), decimals: 6); + expect(balance.formattedAvailableBalance, '1000000.1234'); + expect(balance.balance, 1000000.1234); + }); + }); + }); +} diff --git a/cw_starknet/test/starknet_credentials_test.dart b/cw_starknet/test/starknet_credentials_test.dart new file mode 100644 index 0000000000..8902e03570 --- /dev/null +++ b/cw_starknet/test/starknet_credentials_test.dart @@ -0,0 +1,151 @@ +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/output_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; +import 'package:cw_starknet/starknet_transaction_credentials.dart'; +import 'package:cw_starknet/starknet_wallet_creation_credentials.dart'; + +void main() { + group('StarknetTransactionCredentials', () { + test('stores outputs and currency', () { + final outputs = [ + OutputInfo( + address: '0xrecipient', + cryptoAmount: '1.5', + sendAll: false, + isParsedAddress: false, + ), + ]; + + final credentials = StarknetTransactionCredentials( + outputs, + currency: CryptoCurrency.strk, + ); + + expect(credentials.outputs.length, 1); + expect(credentials.outputs.first.address, '0xrecipient'); + expect(credentials.currency, CryptoCurrency.strk); + }); + + test('supports multiple outputs', () { + final outputs = [ + OutputInfo(address: '0x1', cryptoAmount: '1.0', sendAll: false, isParsedAddress: false), + OutputInfo(address: '0x2', cryptoAmount: '2.0', sendAll: false, isParsedAddress: false), + OutputInfo(address: '0x3', cryptoAmount: '3.0', sendAll: false, isParsedAddress: false), + ]; + + final credentials = StarknetTransactionCredentials( + outputs, + currency: CryptoCurrency.strk, + ); + + expect(credentials.outputs.length, 3); + }); + + test('supports sendAll flag', () { + final outputs = [ + OutputInfo(address: '0xrecipient', sendAll: true, isParsedAddress: false), + ]; + + final credentials = StarknetTransactionCredentials( + outputs, + currency: CryptoCurrency.strk, + ); + + expect(credentials.outputs.first.sendAll, true); + }); + }); + + group('StarknetNewWalletCredentials', () { + test('stores name and password', () { + final creds = StarknetNewWalletCredentials( + name: 'my-wallet', + password: 'secret', + ); + + expect(creds.name, 'my-wallet'); + expect(creds.password, 'secret'); + }); + + test('mnemonic is optional', () { + final creds = StarknetNewWalletCredentials( + name: 'my-wallet', + password: 'secret', + ); + + expect(creds.mnemonic, isNull); + }); + + test('passphrase is optional', () { + final creds = StarknetNewWalletCredentials( + name: 'my-wallet', + password: 'secret', + passphrase: 'my-pass', + ); + + expect(creds.passphrase, 'my-pass'); + }); + }); + + group('StarknetRestoreWalletFromSeedCredentials', () { + test('stores mnemonic and password', () { + final creds = StarknetRestoreWalletFromSeedCredentials( + name: 'restored', + password: 'secret', + mnemonic: 'abandon abandon abandon abandon abandon abandon ' + 'abandon abandon abandon abandon abandon about', + ); + + expect(creds.name, 'restored'); + expect(creds.mnemonic.split(' ').length, 12); + }); + }); + + group('StarknetRestoreWalletFromPrivateKey', () { + test('stores private key', () { + final creds = StarknetRestoreWalletFromPrivateKey( + name: 'imported', + password: 'secret', + privateKey: '0x1234567890abcdef', + ); + + expect(creds.name, 'imported'); + expect(creds.privateKey, '0x1234567890abcdef'); + }); + + test('supports public-key-only restore', () { + final creds = StarknetRestoreWalletFromPrivateKey.publicKey( + name: 'airgapped', + password: 'secret', + publicKey: '0xabcdef', + accountClassHashHex: '0x123', + ); + + expect(creds.name, 'airgapped'); + expect(creds.privateKey, isNull); + expect(creds.publicKey, '0xabcdef'); + expect(creds.accountClassHashHex, '0x123'); + }); + }); + + group('WalletKeysData', () { + test('preserves Starknet account class hash in JSON', () { + final keys = WalletKeysData( + mnemonic: 'seed words', + privateKey: '0xabc', + accountClassHashHex: '0x01d1777db36cdd06dd62cfde77b1b6ae06412af95d57a13dc40ac77b8a702381', + ); + + final restored = WalletKeysData.fromJSON( + Map.from( + const JsonDecoder().convert(keys.toJSON()) as Map, + ), + ); + + expect(restored.accountClassHashHex, keys.accountClassHashHex); + expect(restored.privateKey, keys.privateKey); + }); + }); +} diff --git a/cw_starknet/test/starknet_key_derivation_test.dart b/cw_starknet/test/starknet_key_derivation_test.dart new file mode 100644 index 0000000000..83fbffd90c --- /dev/null +++ b/cw_starknet/test/starknet_key_derivation_test.dart @@ -0,0 +1,179 @@ +import 'package:cw_starknet/cw_starknet.dart'; +import 'package:flutter_test/flutter_test.dart'; + +const _testMnemonic = + 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'; +const _testMnemonicAlt = 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong'; +const _accountClassHashHex = '0x01d1777db36cdd06dd62cfde77b1b6ae06412af95d57a13dc40ac77b8a702381'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + setUpAll(() async { + await ensureStarknetRustInitialized(); + }); + + group('Stark key derivation', () { + test('derives a valid account from mnemonic', () async { + final account = unwrapDerivedAccountDataResponse(await deriveAccount( + mnemonic: _testMnemonic, + accountClassHashHex: _accountClassHashHex, + )); + + expect(account.privateKeyHex, startsWith('0x')); + expect(account.publicKeyHex, startsWith('0x')); + expect(account.accountAddressHex, startsWith('0x')); + }); + + test('derivation is deterministic', () async { + final account1 = unwrapDerivedAccountDataResponse(await deriveAccount( + mnemonic: _testMnemonic, + accountClassHashHex: _accountClassHashHex, + )); + final account2 = unwrapDerivedAccountDataResponse(await deriveAccount( + mnemonic: _testMnemonic, + accountClassHashHex: _accountClassHashHex, + )); + + expect(account1, equals(account2)); + }); + + test('different mnemonics produce different accounts', () async { + final account1 = unwrapDerivedAccountDataResponse(await deriveAccount( + mnemonic: _testMnemonic, + accountClassHashHex: _accountClassHashHex, + )); + final account2 = unwrapDerivedAccountDataResponse(await deriveAccount( + mnemonic: _testMnemonicAlt, + accountClassHashHex: _accountClassHashHex, + )); + + expect(account1, isNot(equals(account2))); + }); + + test('passphrase changes the derived account', () async { + final account1 = unwrapDerivedAccountDataResponse(await deriveAccount( + mnemonic: _testMnemonic, + accountClassHashHex: _accountClassHashHex, + )); + final account2 = unwrapDerivedAccountDataResponse(await deriveAccount( + mnemonic: _testMnemonic, + passphrase: 'my-passphrase', + accountClassHashHex: _accountClassHashHex, + )); + + expect(account1, isNot(equals(account2))); + }); + + test('empty passphrase equals no passphrase', () async { + final account1 = unwrapDerivedAccountDataResponse(await deriveAccount( + mnemonic: _testMnemonic, + accountClassHashHex: _accountClassHashHex, + )); + final account2 = unwrapDerivedAccountDataResponse(await deriveAccount( + mnemonic: _testMnemonic, + passphrase: '', + accountClassHashHex: _accountClassHashHex, + )); + + expect(account1, equals(account2)); + }); + }); + + group('Signing', () { + test('signs a message hash and returns valid signature', () async { + final account = unwrapDerivedAccountDataResponse(await deriveAccount( + mnemonic: _testMnemonic, + accountClassHashHex: _accountClassHashHex, + )); + + final signature = unwrapSignatureResponse(await signMessageHash( + privateKeyHex: account.privateKeyHex, + messageHashHex: '0xdeadbeef', + )); + + expect(signature.rHex, startsWith('0x')); + expect(signature.sHex, startsWith('0x')); + }); + + test('signing is deterministic', () async { + final account = unwrapDerivedAccountDataResponse(await deriveAccount( + mnemonic: _testMnemonic, + accountClassHashHex: _accountClassHashHex, + )); + + final sig1 = unwrapSignatureResponse(await signMessageHash( + privateKeyHex: account.privateKeyHex, + messageHashHex: '0xcafebabe', + )); + final sig2 = unwrapSignatureResponse(await signMessageHash( + privateKeyHex: account.privateKeyHex, + messageHashHex: '0xcafebabe', + )); + + expect(sig1, equals(sig2)); + }); + + test('different messages produce different signatures', () async { + final account = unwrapDerivedAccountDataResponse(await deriveAccount( + mnemonic: _testMnemonic, + accountClassHashHex: _accountClassHashHex, + )); + + final sig1 = unwrapSignatureResponse(await signMessageHash( + privateKeyHex: account.privateKeyHex, + messageHashHex: '0x1', + )); + final sig2 = unwrapSignatureResponse(await signMessageHash( + privateKeyHex: account.privateKeyHex, + messageHashHex: '0x2', + )); + + expect(sig1, isNot(equals(sig2))); + }); + + test('signature can be verified', () async { + final account = unwrapDerivedAccountDataResponse(await deriveAccount( + mnemonic: _testMnemonic, + accountClassHashHex: _accountClassHashHex, + )); + final signature = unwrapSignatureResponse(await signMessageHash( + privateKeyHex: account.privateKeyHex, + messageHashHex: '0xdeadbeef', + )); + + final isValid = unwrapBoolResponse(await verifyMessageHashSignature( + publicKeyHex: account.publicKeyHex, + messageHashHex: '0xdeadbeef', + rHex: signature.rHex, + sHex: signature.sHex, + )); + + expect(isValid, true); + }); + + test('verification fails with wrong public key', () async { + final account = unwrapDerivedAccountDataResponse(await deriveAccount( + mnemonic: _testMnemonic, + accountClassHashHex: _accountClassHashHex, + )); + final wrongAccount = unwrapDerivedAccountDataResponse(await deriveAccount( + mnemonic: _testMnemonicAlt, + accountClassHashHex: _accountClassHashHex, + )); + final signature = unwrapSignatureResponse(await signMessageHash( + privateKeyHex: account.privateKeyHex, + messageHashHex: '0xdeadbeef', + )); + + final isValid = unwrapBoolResponse(await verifyMessageHashSignature( + publicKeyHex: wrongAccount.publicKeyHex, + messageHashHex: '0xdeadbeef', + rHex: signature.rHex, + sHex: signature.sHex, + )); + + expect(isValid, false); + }); + }); +} diff --git a/cw_starknet/test/starknet_mnemonics_test.dart b/cw_starknet/test/starknet_mnemonics_test.dart new file mode 100644 index 0000000000..6ac5216595 --- /dev/null +++ b/cw_starknet/test/starknet_mnemonics_test.dart @@ -0,0 +1,58 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:bip39/bip39.dart' as bip39; +import 'package:cw_starknet/starknet_mnemonics.dart'; + +void main() { + group('StarknetMnemonics', () { + test('english wordlist is not empty', () { + expect(StarknetMnemonics.englishWordlist.isNotEmpty, true); + }); + + test('english wordlist has 2048 words (BIP39)', () { + expect(StarknetMnemonics.englishWordlist.length, 2048); + }); + + test('wordlist contains known BIP39 words', () { + final wordlist = StarknetMnemonics.englishWordlist; + expect(wordlist.contains('abandon'), true); + expect(wordlist.contains('zoo'), true); + expect(wordlist.contains('abstract'), true); + }); + + test('wordlist does not contain non-BIP39 words', () { + final wordlist = StarknetMnemonics.englishWordlist; + expect(wordlist.contains('starknet'), false); + expect(wordlist.contains(''), false); + }); + + test('generated mnemonic uses words from wordlist', () { + final mnemonic = bip39.generateMnemonic(); + final words = mnemonic.split(' '); + final wordlist = StarknetMnemonics.englishWordlist; + + for (final word in words) { + expect(wordlist.contains(word), true, + reason: 'Word "$word" not in wordlist'); + } + }); + + test('12-word mnemonic is valid', () { + final mnemonic = bip39.generateMnemonic(strength: 128); + expect(mnemonic.split(' ').length, 12); + expect(bip39.validateMnemonic(mnemonic), true); + }); + + test('24-word mnemonic is valid', () { + final mnemonic = bip39.generateMnemonic(strength: 256); + expect(mnemonic.split(' ').length, 24); + expect(bip39.validateMnemonic(mnemonic), true); + }); + }); + + group('StarknetMnemonicIsIncorrectException', () { + test('toString returns descriptive message', () { + final exception = StarknetMnemonicIsIncorrectException(); + expect(exception.toString(), contains('not valid')); + }); + }); +} diff --git a/cw_starknet/test/starknet_transaction_info_test.dart b/cw_starknet/test/starknet_transaction_info_test.dart new file mode 100644 index 0000000000..020a40d122 --- /dev/null +++ b/cw_starknet/test/starknet_transaction_info_test.dart @@ -0,0 +1,218 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_starknet/starknet_transaction_info.dart'; + +void main() { + group('StarknetTransactionInfo', () { + late StarknetTransactionInfo txInfo; + + setUp(() { + txInfo = StarknetTransactionInfo( + id: '0xabc123:0', + transactionHash: '0xabc123', + blockTime: DateTime(2025, 1, 15, 12, 30), + to: '0xrecipient', + from: '0xsender', + direction: TransactionDirection.outgoing, + amountWei: '1500', + tokenAddress: '0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d', + tokenDecimals: 3, + tokenSymbol: 'STRK', + isPending: false, + txFeeWei: '1000000000000000', + evmSignatureName: 'approval', + additionalInfo: {'starknetActionLabel': 'Approval'}, + height: 42, + ); + }); + + test('properties are set correctly', () { + expect(txInfo.id, '0xabc123:0'); + expect(txInfo.transactionHash, '0xabc123'); + expect(txInfo.to, '0xrecipient'); + expect(txInfo.from, '0xsender'); + expect(txInfo.direction, TransactionDirection.outgoing); + expect(txInfo.rawAmountAsDouble(), 1.5); + expect(txInfo.tokenSymbol, 'STRK'); + expect(txInfo.isPending, false); + expect(txInfo.txFeeWei, '1000000000000000'); + expect(txInfo.evmSignatureName, 'approval'); + expect(txInfo.additionalInfo['starknetActionLabel'], 'Approval'); + expect(txInfo.height, 42); + }); + + test('amount stores raw token units', () { + expect(txInfo.amount, 1500); + }); + + test('date returns blockTime', () { + expect(txInfo.date, txInfo.blockTime); + }); + + test('amountFormatted includes token symbol', () { + final formatted = txInfo.amountFormatted(); + expect(formatted.contains('1.5'), true); + expect(formatted.contains('STRK'), true); + }); + + test('amountFormatted truncates long amounts', () { + final longTx = StarknetTransactionInfo( + id: '0x1:0', + transactionHash: '0x1', + blockTime: DateTime.now(), + to: '0x1', + from: '0x2', + direction: TransactionDirection.incoming, + amountWei: '1123456789012345678', + tokenAddress: '0x1', + tokenDecimals: 18, + tokenSymbol: 'STRK', + isPending: false, + txFeeWei: '0', + ); + final formatted = longTx.amountFormatted(); + // Amount part (before space+symbol) should be truncated + expect(formatted.length, lessThan(30)); + }); + + test('feeFormatted shows fee in STRK', () { + expect(txInfo.feeFormatted(), '0.001 STRK'); + }); + + test('default token symbol is STRK', () { + final tx = StarknetTransactionInfo( + id: '0x1:0', + transactionHash: '0x1', + blockTime: DateTime.now(), + to: '0x1', + from: '0x2', + direction: TransactionDirection.incoming, + amountWei: '1000', + tokenAddress: '0x1', + tokenDecimals: 3, + isPending: false, + tokenSymbol: 'STRK', + txFeeWei: '0', + ); + expect(tx.tokenSymbol, 'STRK'); + }); + + test('fiatAmount starts empty', () { + expect(txInfo.fiatAmount(), ''); + }); + + test('changeFiatAmount sets fiat value', () { + txInfo.changeFiatAmount('12.50'); + expect(txInfo.fiatAmount(), isNotEmpty); + }); + + test('formats Starknet decoded details', () { + final detailedTx = StarknetTransactionInfo( + id: '0x2:0', + transactionHash: '0x2', + blockTime: DateTime.now(), + to: '0xrouter', + from: '0xsender', + direction: TransactionDirection.outgoing, + amountWei: '0', + tokenAddress: '0xstrk', + tokenDecimals: 18, + tokenSymbol: 'STRK', + isPending: false, + txFeeWei: '2000000000000000', + evmSignatureName: 'swap', + additionalInfo: { + 'starknetTransactionType': 'INVOKE', + 'starknetExecutionStatus': 'SUCCEEDED', + 'starknetFinalityStatus': 'ACCEPTED_ON_L2', + 'starknetExecutionFeeWei': '1500000000000000', + 'starknetDeployAccountFeeWei': '500000000000000', + 'starknetFeePriorityLabel': 'Fast', + 'starknetCallCount': 3, + 'starknetPrimaryContract': '0xrouter', + 'starknetPrimaryEntrypoint': 'swap_exact_tokens_for_tokens', + 'starknetTip': 7, + 'starknetAccountDeploymentRequired': true, + }, + ); + + expect(detailedTx.transactionTypeLabel(), 'Invoke'); + expect(detailedTx.executionStatusLabel(), 'Succeeded'); + expect(detailedTx.finalityStatusLabel(), 'Accepted On L2'); + expect(detailedTx.executionFeeFormatted(), '0.0015 STRK'); + expect(detailedTx.deployAccountFeeFormatted(), '0.0005 STRK'); + expect(detailedTx.feePriorityLabel(), 'Fast'); + expect(detailedTx.callCountLabel(), '3'); + expect(detailedTx.primaryContractAddress(), '0xrouter'); + expect(detailedTx.primaryEntrypoint(), 'swap_exact_tokens_for_tokens'); + expect(detailedTx.transactionTipLabel(), '7'); + expect(detailedTx.accountDeploymentRequired, isTrue); + }); + + group('JSON serialization', () { + test('toJson contains all fields', () { + final json = txInfo.toJson(); + + expect(json['id'], '0xabc123:0'); + expect(json['transactionHash'], '0xabc123'); + expect(json['amountWei'], '1500'); + expect(json['direction'], TransactionDirection.outgoing.index); + expect(json['isPending'], false); + expect(json['tokenSymbol'], 'STRK'); + expect(json['tokenAddress'], isNotEmpty); + expect(json['tokenDecimals'], 3); + expect(json['to'], '0xrecipient'); + expect(json['from'], '0xsender'); + expect(json['txFeeWei'], '1000000000000000'); + expect(json['blockTime'], isA()); + expect(json['evmSignatureName'], 'approval'); + expect(json['additionalInfo'], {'starknetActionLabel': 'Approval'}); + expect(json['height'], 42); + }); + + test('fromJson round-trips correctly', () { + final json = txInfo.toJson(); + final restored = StarknetTransactionInfo.fromJson(json); + + expect(restored.id, txInfo.id); + expect(restored.transactionHash, txInfo.transactionHash); + expect(restored.amountWei, txInfo.amountWei); + expect(restored.direction, txInfo.direction); + expect(restored.isPending, txInfo.isPending); + expect(restored.tokenSymbol, txInfo.tokenSymbol); + expect(restored.tokenAddress, txInfo.tokenAddress); + expect(restored.tokenDecimals, txInfo.tokenDecimals); + expect(restored.to, txInfo.to); + expect(restored.from, txInfo.from); + expect(restored.txFeeWei, txInfo.txFeeWei); + expect(restored.blockTime.millisecondsSinceEpoch, txInfo.blockTime.millisecondsSinceEpoch); + expect(restored.evmSignatureName, txInfo.evmSignatureName); + expect(restored.additionalInfo, txInfo.additionalInfo); + expect(restored.height, txInfo.height); + }); + + test('fromJson handles incoming direction', () { + final json = { + 'id': '0x1:0', + 'transactionHash': '0x1', + 'amountWei': '2000000', + 'direction': TransactionDirection.incoming.index, + 'blockTime': DateTime.now().millisecondsSinceEpoch, + 'isPending': true, + 'tokenAddress': '0xeth', + 'tokenDecimals': 6, + 'tokenSymbol': 'ETH', + 'to': '0xme', + 'from': '0xyou', + 'txFeeWei': '10000000000000000', + }; + + final tx = StarknetTransactionInfo.fromJson(json); + expect(tx.direction, TransactionDirection.incoming); + expect(tx.isPending, true); + expect(tx.tokenSymbol, 'ETH'); + expect(tx.rawAmountAsDouble(), 2.0); + }); + }); + }); +} diff --git a/cw_starknet/test/starknet_ur_test.dart b/cw_starknet/test/starknet_ur_test.dart new file mode 100644 index 0000000000..0c5b09447a --- /dev/null +++ b/cw_starknet/test/starknet_ur_test.dart @@ -0,0 +1,122 @@ +import 'package:cw_starknet/starknet_ur.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('Starknet UR codec', () { + test('encodes and decodes a sign request', () { + final payload = StarknetSignRequestUrPayload( + planJson: '{"plan":"value"}', + accountAddressHex: '0x123', + publicKeyHex: '0x456', + accountClassHashHex: '0x789', + invokeTransactionHashHex: '0xabc', + deployAccountTransactionHashHex: '0xdef', + amountWei: '1000', + amountDecimals: 18, + amountSymbol: 'STRK', + destinationAddress: '0xfeed', + feeWei: '10', + summaryActionName: 'approval', + summaryTokenAddress: '0x4718', + summaryAdditionalInfo: {'starknetActionLabel': 'Approval'}, + preferSummary: true, + ); + + final encoded = encodeStarknetSignRequestUrMap(payload); + final decoded = decodeStarknetSignRequestUr(encoded.values.first); + + expect(decoded.planJson, payload.planJson); + expect(decoded.accountAddressHex, payload.accountAddressHex); + expect(decoded.publicKeyHex, payload.publicKeyHex); + expect(decoded.accountClassHashHex, payload.accountClassHashHex); + expect(decoded.invokeTransactionHashHex, payload.invokeTransactionHashHex); + expect(decoded.deployAccountTransactionHashHex, payload.deployAccountTransactionHashHex); + expect(decoded.amountWei, payload.amountWei); + expect(decoded.amountDecimals, payload.amountDecimals); + expect(decoded.amountSymbol, payload.amountSymbol); + expect(decoded.destinationAddress, payload.destinationAddress); + expect(decoded.feeWei, payload.feeWei); + expect(decoded.summaryActionName, payload.summaryActionName); + expect(decoded.summaryTokenAddress, payload.summaryTokenAddress); + expect(decoded.summaryAdditionalInfo, payload.summaryAdditionalInfo); + expect(decoded.preferSummary, isTrue); + }); + + test('encodes and decodes a sign response', () { + final payload = StarknetSignResponseUrPayload( + planJson: '{"plan":"value"}', + invokeTransactionHashHex: '0xabc', + invokeSignature: StarknetUrSignature(rHex: '0x1', sHex: '0x2'), + deployAccountTransactionHashHex: '0xdef', + deploySignature: StarknetUrSignature(rHex: '0x3', sHex: '0x4'), + ); + + final encoded = encodeStarknetSignResponseUr(payload); + final decoded = decodeStarknetSignResponseUr(encoded); + + expect(decoded.planJson, payload.planJson); + expect(decoded.invokeTransactionHashHex, payload.invokeTransactionHashHex); + expect(decoded.invokeSignature.rHex, payload.invokeSignature.rHex); + expect(decoded.invokeSignature.sHex, payload.invokeSignature.sHex); + expect(decoded.deployAccountTransactionHashHex, payload.deployAccountTransactionHashHex); + expect(decoded.deploySignature?.rHex, payload.deploySignature?.rHex); + expect(decoded.deploySignature?.sHex, payload.deploySignature?.sHex); + }); + + test('encodes and decodes a message sign request and response', () { + final request = StarknetMessageSignRequestUrPayload( + accountAddressHex: '0x123', + publicKeyHex: '0x456', + message: 'hello starknet', + messageHashHex: '0xabc', + ); + + final encodedRequest = encodeStarknetMessageSignRequestUrMap(request); + final decodedRequest = decodeStarknetMessageSignRequestUr(encodedRequest.values.first); + + expect(decodedRequest.accountAddressHex, request.accountAddressHex); + expect(decodedRequest.publicKeyHex, request.publicKeyHex); + expect(decodedRequest.message, request.message); + expect(decodedRequest.messageHashHex, request.messageHashHex); + + final response = StarknetSignatureResponseUrPayload( + messageHashHex: '0xabc', + signature: StarknetUrSignature(rHex: '0x1', sHex: '0x2'), + ); + final encodedResponse = encodeStarknetMessageSignResponseUr(response); + final decodedResponse = decodeStarknetMessageSignResponseUr(encodedResponse); + + expect(decodedResponse.messageHashHex, response.messageHashHex); + expect(decodedResponse.signature.rHex, response.signature.rHex); + expect(decodedResponse.signature.sHex, response.signature.sHex); + }); + + test('encodes and decodes a typed-data sign request and response', () { + final request = StarknetTypedDataSignRequestUrPayload( + accountAddressHex: '0x123', + publicKeyHex: '0x456', + typedDataJson: '{"types":{},"message":{"hello":"world"}}', + typedDataHashHex: '0xdef', + ); + + final encodedRequest = encodeStarknetTypedDataSignRequestUrMap(request); + final decodedRequest = decodeStarknetTypedDataSignRequestUr(encodedRequest.values.first); + + expect(decodedRequest.accountAddressHex, request.accountAddressHex); + expect(decodedRequest.publicKeyHex, request.publicKeyHex); + expect(decodedRequest.typedDataJson, request.typedDataJson); + expect(decodedRequest.typedDataHashHex, request.typedDataHashHex); + + final response = StarknetSignatureResponseUrPayload( + messageHashHex: '0xdef', + signature: StarknetUrSignature(rHex: '0xa', sHex: '0xb'), + ); + final encodedResponse = encodeStarknetTypedDataSignResponseUr(response); + final decodedResponse = decodeStarknetTypedDataSignResponseUr(encodedResponse); + + expect(decodedResponse.messageHashHex, response.messageHashHex); + expect(decodedResponse.signature.rHex, response.signature.rHex); + expect(decodedResponse.signature.sHex, response.signature.sHex); + }); + }); +} diff --git a/cw_starknet/windows/.gitignore b/cw_starknet/windows/.gitignore new file mode 100644 index 0000000000..b3eb2be169 --- /dev/null +++ b/cw_starknet/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/cw_starknet/windows/CMakeLists.txt b/cw_starknet/windows/CMakeLists.txt new file mode 100644 index 0000000000..f727091257 --- /dev/null +++ b/cw_starknet/windows/CMakeLists.txt @@ -0,0 +1,20 @@ +# The Flutter tooling requires that developers have a version of Visual Studio +# installed that includes CMake 3.14 or later. You should not increase this +# version, as doing so will cause the plugin to fail to compile for some +# customers of the plugin. +cmake_minimum_required(VERSION 3.14) + +# Project-level configuration. +set(PROJECT_NAME "cw_starknet") +project(${PROJECT_NAME} LANGUAGES CXX) + +include("../cargokit/cmake/cargokit.cmake") +apply_cargokit(${PROJECT_NAME} ../rust cw_starknet_rust "") + +# List of absolute paths to libraries that should be bundled with the plugin. +# This list could contain prebuilt libraries, or libraries created by an +# external build triggered from this build file. +set(cw_starknet_bundled_libraries + "${${PROJECT_NAME}_cargokit_lib}" + PARENT_SCOPE +) diff --git a/integration_test/helpers/starknet_integration_harness.dart b/integration_test/helpers/starknet_integration_harness.dart new file mode 100644 index 0000000000..a395445a07 --- /dev/null +++ b/integration_test/helpers/starknet_integration_harness.dart @@ -0,0 +1,895 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:crypto/crypto.dart' as crypto; +import 'package:cw_core/cake_hive.dart'; +import 'package:cw_core/db/sqlite.dart'; +import 'package:cw_core/hardware/hardware_account_data.dart'; +import 'package:cw_core/hardware/hardware_wallet_service.dart'; +import 'package:cw_core/node.dart'; +import 'package:cw_core/pathForWallet.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_starknet/pending_starknet_transaction.dart'; +import 'package:cw_starknet/starknet_balance.dart'; +import 'package:cw_starknet/starknet_client.dart'; +import 'package:cw_starknet/starknet_rust.dart'; +import 'package:cw_starknet/starknet_ur.dart'; +import 'package:cw_starknet/starknet_wallet.dart'; +import 'package:cw_starknet/starknet_wallet_creation_credentials.dart'; +import 'package:cw_starknet/starknet_wallet_service.dart'; +import 'package:cw_starknet/src/rust/api/starknet.dart' as rust_api; +import 'package:hex/hex.dart'; +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +class StarknetIntegrationHarness { + StarknetIntegrationHarness() + : rootDirectory = + Directory.systemTemp.createTempSync('cake_starknet_it_'); + + final Directory rootDirectory; + int _walletIndex = 0; + final List _openWallets = []; + + Future setUp() async { + PathProviderPlatform.instance = + _HarnessPathProviderPlatform(rootDirectory.path); + await initDb(); + CakeHive.init(rootDirectory.path); + } + + Future tearDown() async { + for (final wallet in _openWallets) { + await wallet.close(); + } + _openWallets.clear(); + await CakeHive.close(); + await db?.close(); + db = null; + if (rootDirectory.existsSync()) { + await rootDirectory.delete(recursive: true); + } + } + + Future createWalletInfo( + String prefix, { + HardwareWalletType? hardwareWalletType, + }) async { + final name = '${prefix}_${++_walletIndex}'; + final dirPath = + await pathForWalletDir(name: name, type: WalletType.starknet); + final path = await pathForWallet(name: name, type: WalletType.starknet); + await Directory(dirPath).create(recursive: true); + final walletInfo = WalletInfo.external( + id: WalletBase.idFor(name, WalletType.starknet), + name: name, + type: WalletType.starknet, + isRecovery: false, + restoreHeight: 0, + date: DateTime.utc(2026, 4, 15), + dirPath: dirPath, + path: path, + address: '', + hardwareWalletType: hardwareWalletType, + ); + await walletInfo.save(); + return walletInfo; + } + + Future deriveAccount({ + String? mnemonic, + String? passphrase, + String? privateKeyHex, + String accountClassHashHex = + StarknetWalletBase.openZeppelinAccountClassHashHex, + }) async { + await ensureStarknetRustInitialized(); + final response = await rust_api.deriveAccount( + mnemonic: mnemonic, + passphrase: passphrase, + privateKeyHex: privateKeyHex, + accountClassHashHex: accountClassHashHex, + ); + return unwrapDerivedAccountDataResponse(response); + } + + Future createWallet({ + required FakeStarknetWalletClient client, + required String mnemonic, + String password = '123456', + }) async { + final walletInfo = await createWalletInfo('starknet_create'); + final service = StarknetWalletService(true, clientFactory: () => client); + final credentials = StarknetNewWalletCredentials( + name: walletInfo.name, + walletInfo: walletInfo, + password: password, + mnemonic: mnemonic, + ); + final wallet = await service.create(credentials); + await connectAndSync(wallet); + return _trackWallet(wallet); + } + + Future restoreWalletFromPrivateKey({ + required FakeStarknetWalletClient client, + required String privateKeyHex, + String password = '123456', + }) async { + final walletInfo = await createWalletInfo('starknet_software'); + final service = StarknetWalletService(true, clientFactory: () => client); + final credentials = StarknetRestoreWalletFromPrivateKey( + name: walletInfo.name, + walletInfo: walletInfo, + password: password, + privateKey: privateKeyHex, + ); + final wallet = await service.restoreFromKeys(credentials); + await connectAndSync(wallet); + return _trackWallet(wallet); + } + + Future restoreWalletFromPublicKey({ + required FakeStarknetWalletClient client, + required String publicKeyHex, + String password = '123456', + String? accountClassHashHex, + HardwareWalletType hardwareWalletType = HardwareWalletType.ledger, + }) async { + final walletInfo = await createWalletInfo( + 'starknet_airgapped', + hardwareWalletType: hardwareWalletType, + ); + final service = StarknetWalletService(true, clientFactory: () => client); + final credentials = StarknetRestoreWalletFromPrivateKey.publicKey( + name: walletInfo.name, + walletInfo: walletInfo, + password: password, + publicKey: publicKeyHex, + accountClassHashHex: accountClassHashHex, + ); + final wallet = await service.restoreFromKeys(credentials); + await connectAndSync(wallet); + return _trackWallet(wallet); + } + + Future restoreLedgerWallet({ + required FakeStarknetWalletClient client, + required HardwareAccountData accountData, + required String accountClassHashHex, + HardwareWalletService? hardwareWalletService, + String password = '123456', + }) async { + final walletInfo = await createWalletInfo( + 'starknet_ledger', + hardwareWalletType: HardwareWalletType.ledger, + ); + final service = StarknetWalletService(true, clientFactory: () => client); + final credentials = StarknetRestoreWalletFromHardware( + name: walletInfo.name, + walletInfo: walletInfo, + password: password, + hwAccountData: accountData, + accountClassHashHex: accountClassHashHex, + ); + credentials.hardwareWalletType = HardwareWalletType.ledger; + final wallet = + await service.restoreFromHardwareWallet(credentials) as StarknetWallet; + if (hardwareWalletService != null) { + wallet.setHardwareWalletService(hardwareWalletService); + } + await connectAndSync(wallet); + return _trackWallet(wallet); + } + + Future connectAndSync(StarknetWallet wallet) async { + await wallet.connectToNode( + node: Node( + uri: 'starknet.integration.test', + type: WalletType.starknet, + ), + ); + await wallet.startSync(); + } + + StarknetWallet _trackWallet(StarknetWallet wallet) { + _openWallets.add(wallet); + return wallet; + } + + Future signMessageRequestUr({ + required String privateKeyHex, + required String requestUr, + }) async { + await ensureStarknetRustInitialized(); + final payload = decodeStarknetMessageSignRequestUr(requestUr); + final response = await rust_api.signMessageHash( + privateKeyHex: privateKeyHex, + messageHashHex: payload.messageHashHex, + ); + final signature = unwrapSignatureResponse(response); + return encodeStarknetMessageSignResponseUr( + StarknetSignatureResponseUrPayload( + messageHashHex: payload.messageHashHex, + signature: StarknetUrSignature( + rHex: signature.rHex, + sHex: signature.sHex, + ), + ), + ); + } + + Future signTypedDataRequestUr({ + required String privateKeyHex, + required String requestUr, + }) async { + await ensureStarknetRustInitialized(); + final payload = decodeStarknetTypedDataSignRequestUr(requestUr); + final response = await rust_api.signMessageHash( + privateKeyHex: privateKeyHex, + messageHashHex: payload.typedDataHashHex, + ); + final signature = unwrapSignatureResponse(response); + return encodeStarknetTypedDataSignResponseUr( + StarknetSignatureResponseUrPayload( + messageHashHex: payload.typedDataHashHex, + signature: StarknetUrSignature( + rHex: signature.rHex, + sHex: signature.sHex, + ), + ), + ); + } + + Future signTransactionRequestUr({ + required String privateKeyHex, + required String requestUr, + }) async { + await ensureStarknetRustInitialized(); + final payload = decodeStarknetSignRequestUr(requestUr); + final invokeResponse = await rust_api.signMessageHash( + privateKeyHex: privateKeyHex, + messageHashHex: payload.invokeTransactionHashHex, + ); + final invokeSignature = unwrapSignatureResponse(invokeResponse); + + StarknetUrSignature? deploySignature; + if (payload.deployAccountTransactionHashHex != null) { + final deployResponse = await rust_api.signMessageHash( + privateKeyHex: privateKeyHex, + messageHashHex: payload.deployAccountTransactionHashHex!, + ); + final decodedDeploySignature = unwrapSignatureResponse(deployResponse); + deploySignature = StarknetUrSignature( + rHex: decodedDeploySignature.rHex, + sHex: decodedDeploySignature.sHex, + ); + } + + return encodeStarknetSignResponseUr( + StarknetSignResponseUrPayload( + planJson: payload.planJson, + invokeTransactionHashHex: payload.invokeTransactionHashHex, + invokeSignature: StarknetUrSignature( + rHex: invokeSignature.rHex, + sHex: invokeSignature.sHex, + ), + deployAccountTransactionHashHex: + payload.deployAccountTransactionHashHex, + deploySignature: deploySignature, + ), + ); + } +} + +class FakeStarknetWalletClient extends StarknetWalletClient { + FakeStarknetWalletClient({ + Map? initialBalancesByToken, + Map? tokenMetadataByAddress, + bool initiallyDeployed = true, + String defaultFeeWei = '10000000000000000', + }) : _balancesByToken = { + StarknetTokenAddresses.strk.toLowerCase(): BigInt.zero, + ...?initialBalancesByToken + ?.map((key, value) => MapEntry(key.toLowerCase(), value)), + }, + _tokenMetadataByAddress = { + StarknetTokenAddresses.strk.toLowerCase(): + const rust_api.StarknetTokenMetadata( + tokenAddressHex: StarknetTokenAddresses.strk, + name: 'Starknet Token', + symbol: 'STRK', + decimals: 18, + ), + StarknetTokenAddresses.eth.toLowerCase(): + const rust_api.StarknetTokenMetadata( + tokenAddressHex: StarknetTokenAddresses.eth, + name: 'Ether', + symbol: 'ETH', + decimals: 18, + ), + ...?tokenMetadataByAddress + ?.map((key, value) => MapEntry(key.toLowerCase(), value)), + }, + _accountDeployed = initiallyDeployed, + _defaultFeeWei = defaultFeeWei; + + final Map _balancesByToken; + final Map _tokenMetadataByAddress; + final List _events = []; + final Map _plansByJson = + {}; + final Map _transactionDetailsByHash = + {}; + final String _defaultFeeWei; + + bool _connected = false; + bool _accountDeployed; + String? _accountAddressHex; + String? _publicKeyHex; + String? _privateKeyHex; + String? _hardwareDerivationPath; + HardwareWalletService? _hardwareWalletService; + int _nextHashValue = 1; + int _nextBlockNumber = 1000; + + String? lastCommittedTransactionHash; + bool lastExecutionRequiredDeployment = false; + int hardwareCommitCount = 0; + + @override + bool connect(Node node) { + _connected = true; + return true; + } + + @override + void stop() { + _connected = false; + } + + @override + void setupAccount({ + required String privateKeyHex, + required String addressHex, + }) { + _privateKeyHex = privateKeyHex; + _accountAddressHex = addressHex; + } + + @override + void setupHardwareAccount({ + required String addressHex, + required String publicKeyHex, + required String derivationPath, + HardwareWalletService? service, + }) { + _accountAddressHex = addressHex; + _publicKeyHex = publicKeyHex; + _hardwareDerivationPath = derivationPath; + _hardwareWalletService = service; + } + + @override + void setHardwareWalletService(HardwareWalletService service) { + _hardwareWalletService = service; + } + + @override + void setupExternalSignerAccount({ + required String addressHex, + required String publicKeyHex, + }) { + _accountAddressHex = addressHex; + _publicKeyHex = publicKeyHex; + _privateKeyHex = null; + _hardwareDerivationPath = null; + } + + @override + Future getTokenBalance({ + required String accountAddressHex, + required String tokenAddressHex, + required int decimals, + }) async { + return StarknetBalance( + _balancesByToken[tokenAddressHex.toLowerCase()] ?? BigInt.zero, + decimals: decimals, + ); + } + + @override + Future getTokenMetadata( + String tokenAddressHex) async { + return _tokenMetadataByAddress[tokenAddressHex.toLowerCase()] ?? + rust_api.StarknetTokenMetadata( + tokenAddressHex: tokenAddressHex.toLowerCase(), + name: 'Token', + symbol: 'TKN', + decimals: 18, + ); + } + + @override + Future estimateExecuteFee({ + required String accountAddressHex, + required String accountClassHashHex, + required List calls, + required int feePriorityRaw, + }) async => + _buildFeeQuote(); + + @override + Future estimateHardwareExecuteFee({ + required String accountAddressHex, + required String publicKeyHex, + required String accountClassHashHex, + required List calls, + required int feePriorityRaw, + }) async => + _buildFeeQuote(); + + @override + Future createTransaction({ + required List calls, + required String amountWei, + required int amountDecimals, + required String amountSymbol, + required String destinationAddress, + required String accountClassHashHex, + required String feeWei, + required int feePriorityRaw, + Future Function(String txHash)? onCommitted, + }) async { + return PendingStarknetTransaction( + amountWei: amountWei, + amountDecimals: amountDecimals, + amountSymbol: amountSymbol, + destinationAddress: destinationAddress, + feeWei: feeWei, + transactionHash: _nextHashHex(), + sendTransaction: () async => _commitExecution( + calls: calls, + feeWei: feeWei, + ), + onCommitted: onCommitted, + ); + } + + @override + Future createHardwareTransaction({ + required List calls, + required String amountWei, + required int amountDecimals, + required String amountSymbol, + required String destinationAddress, + required String accountClassHashHex, + required String feeWei, + required int feePriorityRaw, + Future Function(String txHash)? onCommitted, + }) async { + return PendingStarknetTransaction( + amountWei: amountWei, + amountDecimals: amountDecimals, + amountSymbol: amountSymbol, + destinationAddress: destinationAddress, + feeWei: feeWei, + transactionHash: _nextHashHex(), + sendTransaction: () async { + final service = _hardwareWalletService; + if (service == null || _hardwareDerivationPath == null) { + throw Exception('Missing fake hardware signer'); + } + + hardwareCommitCount++; + await service.signMessage( + message: _hashBytes(_nextHashHex()), + derivationPath: _hardwareDerivationPath, + ); + + return _commitExecution( + calls: calls, + feeWei: feeWei, + ); + }, + onCommitted: onCommitted, + ); + } + + @override + Future createOfflineTransaction({ + required List calls, + required String amountWei, + required int amountDecimals, + required String amountSymbol, + required String destinationAddress, + required String accountClassHashHex, + required String feeWei, + required int feePriorityRaw, + String? summaryActionName, + String? summaryTokenAddress, + Map? summaryAdditionalInfo, + bool preferSummary = false, + }) async { + final requestUr = await buildUnsignedTransactionUr( + calls: calls, + amountWei: amountWei, + amountDecimals: amountDecimals, + amountSymbol: amountSymbol, + destinationAddress: destinationAddress, + accountClassHashHex: accountClassHashHex, + feeWei: feeWei, + feePriorityRaw: feePriorityRaw, + summaryActionName: summaryActionName, + summaryTokenAddress: summaryTokenAddress, + summaryAdditionalInfo: summaryAdditionalInfo, + preferSummary: preferSummary, + ); + final requestPayload = decodeStarknetSignRequestUr(requestUr.values.first); + + return PendingStarknetTransaction( + amountWei: amountWei, + amountDecimals: amountDecimals, + amountSymbol: amountSymbol, + destinationAddress: destinationAddress, + feeWei: feeWei, + transactionHash: requestPayload.invokeTransactionHashHex, + sendTransaction: () async => throw UnsupportedError( + 'Offline transaction requires commitUR()', + ), + buildUnsignedTransactionUr: () async => requestUr, + ); + } + + @override + Future> buildUnsignedTransactionUr({ + required List calls, + required String amountWei, + required int amountDecimals, + required String amountSymbol, + required String destinationAddress, + required String accountClassHashHex, + required String feeWei, + required int feePriorityRaw, + String? summaryActionName, + String? summaryTokenAddress, + Map? summaryAdditionalInfo, + bool preferSummary = false, + }) async { + final invokeHashHex = _nextHashHex(); + final deployHashHex = !_accountDeployed ? _nextHashHex() : null; + final planJson = jsonEncode({ + 'invokeHashHex': invokeHashHex, + 'deployHashHex': deployHashHex, + 'callCount': calls.length, + }); + + _plansByJson[planJson] = _FakeExecutionPlan( + calls: calls, + feeWei: feeWei, + invokeHashHex: invokeHashHex, + deployHashHex: deployHashHex, + ); + + final requestPayload = StarknetSignRequestUrPayload( + planJson: planJson, + accountAddressHex: _requireAccountAddress(), + publicKeyHex: _requirePublicKey(), + accountClassHashHex: accountClassHashHex, + invokeTransactionHashHex: invokeHashHex, + deployAccountTransactionHashHex: deployHashHex, + amountWei: amountWei, + amountDecimals: amountDecimals, + amountSymbol: amountSymbol, + destinationAddress: destinationAddress, + feeWei: feeWei, + summaryActionName: summaryActionName, + summaryTokenAddress: summaryTokenAddress, + summaryAdditionalInfo: summaryAdditionalInfo, + preferSummary: preferSummary, + ); + + return encodeStarknetSignRequestUrMap(requestPayload); + } + + @override + Future> fetchTransferEvents({ + required String accountAddressHex, + required String tokenAddressHex, + required String tokenSymbol, + int? fromBlock, + }) async { + return _events.where((event) { + final matchesToken = + event.tokenAddressHex.toLowerCase() == tokenAddressHex.toLowerCase(); + final matchesBlock = + fromBlock == null || (event.blockNumber ?? 0) >= fromBlock; + return matchesToken && matchesBlock; + }).toList(); + } + + @override + Future getTypedDataHash({ + required String accountAddressHex, + required String typedDataJson, + }) async { + return _feltHashHex('typed-data:$accountAddressHex:$typedDataJson'); + } + + @override + Future> signTypedData({ + required String accountAddressHex, + required String typedDataJson, + }) async { + await ensureStarknetRustInitialized(); + final privateKeyHex = _privateKeyHex; + if (privateKeyHex == null) { + throw Exception('Private key unavailable for fake typed-data signing'); + } + + final response = await rust_api.signMessageHash( + privateKeyHex: privateKeyHex, + messageHashHex: await getTypedDataHash( + accountAddressHex: accountAddressHex, + typedDataJson: typedDataJson, + ), + ); + final signature = unwrapSignatureResponse(response); + return [signature.rHex, signature.sHex]; + } + + @override + Future getBlockNumber() async => _connected ? _nextBlockNumber : null; + + @override + Future getTransactionDetails({ + required String transactionHashHex, + }) async => + _transactionDetailsByHash[transactionHashHex]; + + @override + Future submitSignedTransactionUr(String urPayload) async { + final responsePayload = decodeStarknetSignResponseUr(urPayload); + final plan = _plansByJson[responsePayload.planJson]; + if (plan == null) { + throw Exception('Unknown fake execution plan'); + } + + return _commitExecution( + calls: plan.calls, + feeWei: plan.feeWei, + transactionHash: responsePayload.invokeTransactionHashHex, + deploymentHash: responsePayload.deployAccountTransactionHashHex, + ); + } + + rust_api.StarknetFeeQuote _buildFeeQuote() => rust_api.StarknetFeeQuote( + overallFeeWei: _defaultFeeWei, + executionFeeWei: _defaultFeeWei, + deployAccountFeeWei: !_accountDeployed ? _defaultFeeWei : null, + accountDeploymentRequired: !_accountDeployed, + ); + + Future _commitExecution({ + required List calls, + required String feeWei, + String? transactionHash, + String? deploymentHash, + }) async { + final txHash = transactionHash ?? _nextHashHex(); + lastCommittedTransactionHash = txHash; + lastExecutionRequiredDeployment = + !_accountDeployed || deploymentHash != null; + final deploymentRequired = lastExecutionRequiredDeployment; + _accountDeployed = true; + + final accountAddress = _requireAccountAddress(); + final fee = BigInt.tryParse(feeWei) ?? BigInt.zero; + _balancesByToken[StarknetTokenAddresses.strk.toLowerCase()] = + (_balancesByToken[StarknetTokenAddresses.strk.toLowerCase()] ?? + BigInt.zero) - + fee; + + for (var index = 0; index < calls.length; index++) { + final call = calls[index]; + final entrypoint = call.entrypoint.toLowerCase(); + final tokenAddress = call.contractAddressHex.toLowerCase(); + if (entrypoint == 'transfer' && call.calldataHex.length >= 3) { + final recipient = call.calldataHex[0]; + final amount = + _uint256FromWords(call.calldataHex[1], call.calldataHex[2]); + _balancesByToken[tokenAddress] = + (_balancesByToken[tokenAddress] ?? BigInt.zero) - amount; + final metadata = _tokenMetadataByAddress[tokenAddress] ?? + rust_api.StarknetTokenMetadata( + tokenAddressHex: tokenAddress, + name: 'Token', + symbol: 'TKN', + decimals: 18, + ); + _events.add( + StarknetTransferEvent( + transactionHash: txHash, + eventId: '$txHash:$index', + eventIndex: index, + blockNumber: _nextBlockNumber, + from: accountAddress, + to: recipient, + amountWei: amount.toString(), + isOutgoing: true, + tokenSymbol: metadata.symbol, + tokenAddressHex: tokenAddress, + blockTimestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + txFeeWei: feeWei, + ), + ); + } + } + + _transactionDetailsByHash[txHash] = StarknetTransactionDetails( + transactionHash: txHash, + transactionType: deploymentRequired ? 'DEPLOY_ACCOUNT' : 'INVOKE', + isPending: false, + blockNumber: _nextBlockNumber, + blockTimestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + actualFeeWei: feeWei, + actionName: _inferActionName(calls), + callCount: calls.length, + primaryContractAddressHex: + calls.isEmpty ? null : calls.first.contractAddressHex, + primaryEntrypoint: calls.isEmpty ? null : calls.first.entrypoint, + senderAddressHex: accountAddress, + finalityStatus: 'ACCEPTED_ON_L2', + executionStatus: 'SUCCEEDED', + revertReason: null, + accountDeploymentRequired: deploymentRequired, + l1GasMaxAmount: null, + l1GasMaxPriceWei: null, + l2GasMaxAmount: null, + l2GasMaxPriceWei: null, + l1DataGasMaxAmount: null, + l1DataGasMaxPriceWei: null, + tip: null, + ); + + _nextBlockNumber++; + return txHash; + } + + String _inferActionName(List calls) { + if (calls.isEmpty) { + return 'contract_call'; + } + + final entrypoints = calls + .map((call) => call.entrypoint.toLowerCase()) + .toList(growable: false); + if (entrypoints.every((entrypoint) => entrypoint == 'transfer')) { + return 'transfer'; + } + + if (entrypoints.every((entrypoint) => entrypoint == 'approve')) { + return 'approval'; + } + + if (entrypoints.any((entrypoint) => entrypoint.contains('swap'))) { + return 'swap'; + } + + return calls.length > 1 ? 'multicall' : entrypoints.first; + } + + String _requireAccountAddress() => + _accountAddressHex ?? + (throw Exception('Fake account address not configured')); + + String _requirePublicKey() => + _publicKeyHex ?? (throw Exception('Fake public key not configured')); + + BigInt _uint256FromWords(String lowHex, String highHex) { + final low = _feltToBigInt(lowHex); + final high = _feltToBigInt(highHex); + return low + (high << 128); + } + + BigInt _feltToBigInt(String value) { + final normalized = value.toLowerCase(); + if (normalized.startsWith('0x')) { + return BigInt.parse(normalized.substring(2), radix: 16); + } + return BigInt.parse(normalized); + } + + String _feltHashHex(String input) { + final hashedValue = BigInt.parse( + crypto.sha256.convert(utf8.encode(input)).toString(), + radix: 16, + ); + return '0x${(hashedValue % _starknetFieldPrime).toRadixString(16)}'; + } + + String _nextHashHex() => + '0x${(_nextHashValue++).toRadixString(16).padLeft(64, '0')}'; + + Uint8List _hashBytes(String hashHex) { + final normalized = + hashHex.startsWith('0x') ? hashHex.substring(2) : hashHex; + return Uint8List.fromList(HEX.decode(normalized.padLeft(64, '0'))); + } +} + +class TestHardwareWalletService extends HardwareWalletService { + TestHardwareWalletService({ + required this.privateKeyHex, + required this.accountData, + }); + + final String privateKeyHex; + final HardwareAccountData accountData; + int signCount = 0; + + @override + Future> getAvailableAccounts( + {int index = 0, int limit = 5}) async => + [accountData]; + + @override + Future signMessage({ + required Uint8List message, + String? derivationPath, + }) async { + signCount++; + await ensureStarknetRustInitialized(); + final messageHashHex = '0x${HEX.encode(message)}'; + final response = await rust_api.signMessageHash( + privateKeyHex: privateKeyHex, + messageHashHex: messageHashHex, + ); + final signature = unwrapSignatureResponse(response); + return Uint8List.fromList( + [ + ...HEX.decode(signature.rHex.replaceFirst('0x', '').padLeft(64, '0')), + ...HEX.decode(signature.sHex.replaceFirst('0x', '').padLeft(64, '0')), + ], + ); + } +} + +class _FakeExecutionPlan { + const _FakeExecutionPlan({ + required this.calls, + required this.feeWei, + required this.invokeHashHex, + required this.deployHashHex, + }); + + final List calls; + final String feeWei; + final String invokeHashHex; + final String? deployHashHex; +} + +class _HarnessPathProviderPlatform extends PathProviderPlatform + with MockPlatformInterfaceMixin { + _HarnessPathProviderPlatform(this.rootPath); + + final String rootPath; + + @override + Future getApplicationSupportPath() async => rootPath; + + @override + Future getApplicationDocumentsPath() async => rootPath; + + @override + Future getTemporaryPath() async => rootPath; +} + +final BigInt _starknetFieldPrime = BigInt.parse( + '800000000000011000000000000000000000000000000000000000000000001', + radix: 16, +); diff --git a/integration_test/test_suites/starknet_integration_flow_test.dart b/integration_test/test_suites/starknet_integration_flow_test.dart new file mode 100644 index 0000000000..73627938e6 --- /dev/null +++ b/integration_test/test_suites/starknet_integration_flow_test.dart @@ -0,0 +1,318 @@ +import 'package:cake_wallet/starknet/starknet.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/hardware/hardware_account_data.dart'; +import 'package:cw_core/output_info.dart'; +import 'package:cw_core/starknet_token.dart'; +import 'package:cw_starknet/starknet_client.dart'; +import 'package:cw_starknet/starknet_transaction_credentials.dart'; +import 'package:cw_starknet/src/rust/api/starknet.dart' as rust_api; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../helpers/starknet_integration_harness.dart'; + +const _testMnemonic = + 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'; +const _testTypedDataJson = + '{"types":{"Message":[{"name":"contents","type":"felt"}]},"primaryType":"Message","domain":{},"message":{"contents":"0x1"}}'; +const _testTokenAddress = + '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; +const _testRecipientAddress = + '0x1111111111111111111111111111111111111111111111111111111111111111'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + late StarknetIntegrationHarness harness; + + setUp(() async { + harness = StarknetIntegrationHarness(); + await harness.setUp(); + }); + + tearDown(() async { + await harness.tearDown(); + }); + + testWidgets('creates and syncs a Starknet wallet', (tester) async { + final client = FakeStarknetWalletClient( + initialBalancesByToken: { + StarknetTokenAddresses.strk.toLowerCase(): _strk('5'), + }, + ); + + final wallet = await harness.createWallet( + client: client, + mnemonic: _testMnemonic, + ); + final derivedAccount = await harness.deriveAccount( + mnemonic: _testMnemonic, + accountClassHashHex: wallet.accountClassHashHex, + ); + + expect(wallet.accountAddress, startsWith('0x')); + expect(wallet.publicKey, startsWith('0x')); + expect(wallet.accountAddress, derivedAccount.accountAddressHex); + expect(wallet.publicKey, derivedAccount.publicKeyHex); + expect( + wallet.balance[CryptoCurrency.strk]?.fullAvailableBalance, + _strk('5'), + ); + }); + + testWidgets('handles undeployed first send', (tester) async { + final client = FakeStarknetWalletClient( + initialBalancesByToken: { + StarknetTokenAddresses.strk.toLowerCase(): _strk('5'), + }, + initiallyDeployed: false, + ); + + final wallet = await harness.createWallet( + client: client, + mnemonic: _testMnemonic, + ); + + final pending = await wallet.createTransaction( + StarknetTransactionCredentials( + const [ + OutputInfo( + address: _testRecipientAddress, + sendAll: false, + isParsedAddress: false, + cryptoAmount: '2', + ), + ], + currency: CryptoCurrency.strk, + ), + ); + + await pending.commit(); + await wallet.updateBalance(); + await wallet.updateTransactionsHistory(); + + expect(client.lastExecutionRequiredDeployment, isTrue); + expect(pending.id, isNotEmpty); + expect( + wallet.balance[CryptoCurrency.strk]?.fullAvailableBalance, + _strk('5') - _strk('2') - BigInt.parse('10000000000000000'), + ); + expect( + wallet.transactionHistory.transactions.values.any( + (tx) => tx.transactionHash == pending.id, + ), + isTrue, + ); + }); + + testWidgets('imports a Starknet token and sends it', (tester) async { + final token = StarknetToken( + name: 'Integration Token', + symbol: 'ITK', + contractAddress: _testTokenAddress, + decimal: 6, + ); + + final client = FakeStarknetWalletClient( + initialBalancesByToken: { + StarknetTokenAddresses.strk.toLowerCase(): _strk('5'), + _testTokenAddress.toLowerCase(): BigInt.from(100000000), + }, + tokenMetadataByAddress: { + _testTokenAddress.toLowerCase(): rust_api.StarknetTokenMetadata( + tokenAddressHex: _testTokenAddress.toLowerCase(), + name: token.name, + symbol: token.symbol, + decimals: token.decimal, + ), + }, + ); + + final wallet = await harness.createWallet( + client: client, + mnemonic: _testMnemonic, + ); + await wallet.addStarknetToken(token); + await wallet.updateBalance(); + + final pending = await wallet.createTransaction( + StarknetTransactionCredentials( + const [ + OutputInfo( + address: _testRecipientAddress, + sendAll: false, + isParsedAddress: false, + cryptoAmount: '25', + ), + ], + currency: token, + ), + ); + + await pending.commit(); + await wallet.updateBalance(); + await wallet.updateTransactionsHistory(); + + expect(wallet.balance[token]?.fullAvailableBalance, BigInt.from(75000000)); + expect( + wallet.transactionHistory.transactions.values.any( + (tx) => + tx.tokenAddress.toLowerCase() == _testTokenAddress.toLowerCase(), + ), + isTrue, + ); + }); + + testWidgets('handles Ledger restore and send', (tester) async { + final baseClient = FakeStarknetWalletClient( + initialBalancesByToken: { + StarknetTokenAddresses.strk.toLowerCase(): _strk('5'), + }, + ); + final baseWallet = await harness.createWallet( + client: baseClient, + mnemonic: _testMnemonic, + ); + final baseAccount = await harness.deriveAccount( + mnemonic: _testMnemonic, + accountClassHashHex: baseWallet.accountClassHashHex, + ); + + final ledgerClient = FakeStarknetWalletClient( + initialBalancesByToken: { + StarknetTokenAddresses.strk.toLowerCase(): _strk('5'), + }, + ); + final hardwareAccount = HardwareAccountData( + address: baseWallet.accountAddress, + accountIndex: 0, + derivationPath: "m/44'/9004'/0'/0/0", + publicKey: baseWallet.publicKey, + ); + final hardwareService = TestHardwareWalletService( + privateKeyHex: baseAccount.privateKeyHex, + accountData: hardwareAccount, + ); + + final ledgerWallet = await harness.restoreLedgerWallet( + client: ledgerClient, + accountData: hardwareAccount, + accountClassHashHex: baseWallet.accountClassHashHex, + hardwareWalletService: hardwareService, + ); + + final pending = await ledgerWallet.createTransaction( + StarknetTransactionCredentials( + const [ + OutputInfo( + address: _testRecipientAddress, + sendAll: false, + isParsedAddress: false, + cryptoAmount: '1', + ), + ], + currency: CryptoCurrency.strk, + ), + ); + + await pending.commit(); + + expect(hardwareService.signCount, greaterThan(0)); + expect(pending.id, isNotEmpty); + }); + + testWidgets('handles air-gapped Starknet UR round-trip', (tester) async { + final baseClient = FakeStarknetWalletClient( + initialBalancesByToken: { + StarknetTokenAddresses.strk.toLowerCase(): _strk('5'), + }, + ); + final baseWallet = await harness.createWallet( + client: baseClient, + mnemonic: _testMnemonic, + ); + final baseAccount = await harness.deriveAccount( + mnemonic: _testMnemonic, + accountClassHashHex: baseWallet.accountClassHashHex, + ); + + final offlineClient = FakeStarknetWalletClient( + initialBalancesByToken: { + StarknetTokenAddresses.strk.toLowerCase(): _strk('5'), + }, + initiallyDeployed: false, + ); + final offlineWallet = await harness.restoreWalletFromPublicKey( + client: offlineClient, + publicKeyHex: baseWallet.publicKey, + accountClassHashHex: baseWallet.accountClassHashHex, + ); + + final messageRequest = await starknet!.buildMessageSignUr( + offlineWallet, + 'integration-message', + ); + final messageResponse = await harness.signMessageRequestUr( + privateKeyHex: baseAccount.privateKeyHex, + requestUr: messageRequest.values.first, + ); + final signedMessage = await starknet!.commitMessageUR( + offlineWallet, + messageResponse, + ); + expect(signedMessage.split(','), hasLength(2)); + + final typedDataRequest = await starknet!.buildTypedDataSignUr( + offlineWallet, + _testTypedDataJson, + ); + final typedDataResponse = await harness.signTypedDataRequestUr( + privateKeyHex: baseAccount.privateKeyHex, + requestUr: typedDataRequest.values.first, + ); + final typedDataSignature = await starknet!.commitTypedDataUR( + offlineWallet, + typedDataResponse, + ); + expect(typedDataSignature, hasLength(2)); + + final pending = await offlineWallet.createTransaction( + StarknetTransactionCredentials( + const [ + OutputInfo( + address: _testRecipientAddress, + sendAll: false, + isParsedAddress: false, + cryptoAmount: '1', + ), + ], + currency: CryptoCurrency.strk, + ), + ); + expect(pending.shouldCommitUR(), isTrue); + + final transactionRequest = await pending.commitUR(); + final transactionResponse = await harness.signTransactionRequestUr( + privateKeyHex: baseAccount.privateKeyHex, + requestUr: transactionRequest.values.first, + ); + final txHash = await starknet!.commitTransactionUR( + offlineWallet, + transactionResponse, + requestUr: transactionRequest.values.first, + ); + + expect(txHash, isNotEmpty); + }); +} + +BigInt _strk(String amount) { + final parts = amount.split('.'); + final whole = BigInt.parse(parts.first) * BigInt.from(10).pow(18); + if (parts.length == 1) { + return whole; + } + + final fraction = parts[1].padRight(18, '0').substring(0, 18); + return whole + BigInt.parse(fraction); +} diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 66410e642b..663b2bd983 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -10,6 +10,8 @@ PODS: - Flutter - cw_mweb (0.0.1): - Flutter + - cw_starknet (0.0.1): + - Flutter - device_display_brightness (0.0.1): - Flutter - device_info_plus (0.0.1): @@ -163,6 +165,7 @@ DEPENDENCIES: - CryptoSwift - cw_decred (from `.symlinks/plugins/cw_decred/ios`) - cw_mweb (from `.symlinks/plugins/cw_mweb/ios`) + - cw_starknet (from `.symlinks/plugins/cw_starknet/ios`) - device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - devicelocale (from `.symlinks/plugins/devicelocale/ios`) @@ -203,7 +206,7 @@ DEPENDENCIES: - warp_api (from `.symlinks/plugins/warp_api/ios`) SPEC REPOS: - https://github.com/CocoaPods/Specs.git: + trunk: - CryptoSwift - DKImagePickerController - DKPhotoGallery @@ -224,6 +227,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/cw_decred/ios" cw_mweb: :path: ".symlinks/plugins/cw_mweb/ios" + cw_starknet: + :path: ".symlinks/plugins/cw_starknet/ios" device_display_brightness: :path: ".symlinks/plugins/device_display_brightness/ios" device_info_plus: @@ -308,6 +313,7 @@ SPEC CHECKSUMS: CryptoSwift: e64e11850ede528a02a0f3e768cec8e9d92ecb90 cw_decred: a02cf30175a46971c1e2fa22c48407534541edc6 cw_mweb: 3aea2fb35b2bd04d8b2d21b83216f3b8fb768d85 + cw_starknet: 1af40c733f4554f4169251dbb6ab6c44f0be9a38 device_display_brightness: 04374ebd653619292c1d996f00f42877ea19f17f device_info_plus: 335f3ce08d2e174b9fdc3db3db0f4e3b1f66bd89 devicelocale: bd64aa714485a8afdaded0892c1e7d5b7f680cf8 @@ -354,6 +360,6 @@ SPEC CHECKSUMS: warp_api: 997d8fb3e777669bc6260d0c12e56cd40b9db1d1 YttriumWrapper: 31e937fe9fbe0f1314d2ca6be9ce9b379a059966 -PODFILE CHECKSUM: 5296465b1c6d14d506230356756826012f65d97a +PODFILE CHECKSUM: dbf34f4e0af89cc5d6eae66277813be6492c7735 COCOAPODS: 1.16.2 diff --git a/lib/buy/robinhood/robinhood_buy_provider.dart b/lib/buy/robinhood/robinhood_buy_provider.dart index 7cd9928121..d0380c895e 100644 --- a/lib/buy/robinhood/robinhood_buy_provider.dart +++ b/lib/buy/robinhood/robinhood_buy_provider.dart @@ -125,6 +125,7 @@ class RobinhoodBuyProvider extends BuyProvider { case WalletType.zano: case WalletType.zcash: case WalletType.decred: + case WalletType.starknet: throw Exception("Wallet Type ${wallet.type.name} is not available for Robinhood"); } } diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index 0d44bad676..724ce048bf 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/zano/zano.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/erc20_token.dart'; +import 'package:cw_core/starknet_token.dart'; const BEFORE_REGEX = '(^|\\s)'; const AFTER_REGEX = '(\$|\\s)'; @@ -41,6 +42,9 @@ class AddressValidator extends TextValidator { if (type is Erc20Token) { pattern = '0x[0-9a-zA-Z]+'; } + if (type is StarknetToken) { + pattern = '0x[0-9a-fA-F]{50,66}'; + } switch (type) { case CryptoCurrency.xmr: pattern = '4[0-9a-zA-Z]{94}|8[0-9a-zA-Z]{94}|[0-9a-zA-Z]{106}'; @@ -175,6 +179,8 @@ class AddressValidator extends TextValidator { pattern = r'([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d.-]+)'; case CryptoCurrency.doge: pattern = r'^D[a-km-zA-HJ-NP-Z1-9]{25,34}'; + case CryptoCurrency.strk: + pattern = '0x[0-9a-fA-F]{50,66}'; default: return ''; } @@ -187,6 +193,10 @@ class AddressValidator extends TextValidator { return [42]; } + if (type is StarknetToken) { + return null; + } + if (solana != null) { final length = solana!.getValidationLength(type); if (length != null) return length; @@ -312,6 +322,8 @@ class AddressValidator extends TextValidator { return [45]; case CryptoCurrency.near: return [64]; + case CryptoCurrency.strk: + return null; case CryptoCurrency.btcln: case CryptoCurrency.kaspa: case CryptoCurrency.zano: @@ -377,6 +389,8 @@ class AddressValidator extends TextValidator { '|zxviews[a-z0-9]{278}' '|u1[a-z0-9]{1,300}' ')'; + case CryptoCurrency.strk: + pattern = '0x[0-9a-fA-F]{50,66}'; default: if (type.tag == CryptoCurrency.eth.title) { pattern = '0x[0-9a-zA-Z]+'; diff --git a/lib/core/seed_validator.dart b/lib/core/seed_validator.dart index 1edfd9c332..f04d5e5e67 100644 --- a/lib/core/seed_validator.dart +++ b/lib/core/seed_validator.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/evm/evm.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/solana/solana.dart'; +import 'package:cake_wallet/starknet/starknet.dart'; import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cake_wallet/zano/zano.dart'; @@ -56,6 +57,8 @@ class SeedValidator extends Validator { return decred!.getDecredWordList(); case WalletType.zcash: return zcash!.getZcashWordList(language); + case WalletType.starknet: + return starknet!.getStarknetWordList(language); case WalletType.none: case WalletType.haven: return []; diff --git a/lib/core/universal_address_detector.dart b/lib/core/universal_address_detector.dart index ab90c72536..e9f46174f8 100644 --- a/lib/core/universal_address_detector.dart +++ b/lib/core/universal_address_detector.dart @@ -172,7 +172,13 @@ class UniversalAddressDetector { currency: CryptoCurrency.bch, ), - // Ethereum + // Starknet (0x + 50-66 hex chars — longer than Ethereum's 40, must come first) + _DetectionPattern( + pattern: RegExp(r'^0x[a-fA-F0-9]{50,66}$'), + currency: CryptoCurrency.strk, + ), + + // Ethereum (0x + exactly 40 hex chars) _DetectionPattern( pattern: RegExp(r'^0x[a-fA-F0-9]{40}$'), currency: CryptoCurrency.eth, diff --git a/lib/core/wallet_creation_service.dart b/lib/core/wallet_creation_service.dart index a5cc435e4e..1ccf831268 100644 --- a/lib/core/wallet_creation_service.dart +++ b/lib/core/wallet_creation_service.dart @@ -85,6 +85,7 @@ class WalletCreationService { case WalletType.dogecoin: case WalletType.nano: case WalletType.zcash: + case WalletType.starknet: return true; case WalletType.monero: case WalletType.wownero: diff --git a/lib/di.dart b/lib/di.dart index 7d386e58c0..a3519bfac2 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -71,6 +71,7 @@ import 'package:cake_wallet/order/order.dart'; import 'package:cake_wallet/reactions/on_authentication_state_change.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/solana/solana.dart'; +import 'package:cake_wallet/starknet/starknet.dart'; import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dart'; import 'package:cake_wallet/src/screens/auth/auth_page.dart'; import 'package:cake_wallet/src/screens/backup/backup_page.dart'; @@ -1359,6 +1360,8 @@ Future setup({ return HavenWalletService(); case WalletType.zcash: return zcash!.createZcashWalletService(SettingsStoreBase.walletPasswordDirectInput); + case WalletType.starknet: + return starknet!.createStarknetWalletService(SettingsStoreBase.walletPasswordDirectInput); case WalletType.none: throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService'); } diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index d625654b7b..cf3ca9e116 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -57,6 +57,12 @@ const baseDefaultNodeUri = 'base.nownodes.io'; const arbitrumDefaultNodeUri = 'arbitrum.nownodes.io'; const bscDefaultNodeUri = 'bsc-dataseed.bnbchain.org'; const zcashDefaultNodeUri = 'zec-node.cakewallet.com:443'; +const starknetDefaultNodeUri = 'api.cartridge.gg/x/starknet/mainnet'; +const deprecatedStarknetNodeUris = [ + 'starknet-mainnet.public.blastapi.io', + 'free-rpc.nethermind.io', + 'starknet.drpc.org', +]; Future defaultSettingsMigration( {required int version, @@ -624,6 +630,16 @@ Future defaultSettingsMigration( enabled: true, ); break; + case 65: + await addWalletNodeList(nodes: nodes, type: WalletType.starknet); + await _repairStarknetNodes( + nodes: nodes, + sharedPreferences: sharedPreferences, + ); + break; + case 66: + await addWalletNodeList(nodes: nodes, type: WalletType.starknet); + break; default: break; } @@ -702,6 +718,33 @@ Future _changeDefaultNode({ } } +Future _repairStarknetNodes({ + required Box nodes, + required SharedPreferences sharedPreferences, +}) async { + for (final node in nodes.values.where((node) => node.type == WalletType.starknet)) { + if (node.uriRaw == starknetDefaultNodeUri) { + node.useSSL = true; + node.isEnabledForAutoSwitching = true; + await node.save(); + continue; + } + + if (deprecatedStarknetNodeUris.any(node.uriRaw.contains)) { + node.isEnabledForAutoSwitching = false; + await node.save(); + } + } + + await _changeDefaultNode( + nodes: nodes, + sharedPreferences: sharedPreferences, + type: WalletType.starknet, + currentNodePreferenceKey: PreferencesKey.currentStarknetNodeIdKey, + oldUri: deprecatedStarknetNodeUris, + ); +} + String _getDefaultNodeUri(WalletType type) { switch (type) { case WalletType.monero: @@ -740,6 +783,8 @@ String _getDefaultNodeUri(WalletType type) { return bscDefaultNodeUri; case WalletType.zcash: return zcashDefaultNodeUri; + case WalletType.starknet: + return starknetDefaultNodeUri; case WalletType.banano: case WalletType.none: return ''; diff --git a/lib/entities/node_list.dart b/lib/entities/node_list.dart index bf4e058692..24b6e74237 100644 --- a/lib/entities/node_list.dart +++ b/lib/entities/node_list.dart @@ -62,6 +62,9 @@ Future> loadDefaultNodes(WalletType type) async { case WalletType.bsc: path = 'assets/bsc_node_list.yml'; break; + case WalletType.starknet: + path = 'assets/starknet_node_list.yml'; + break; case WalletType.banano: case WalletType.none: path = ''; @@ -117,6 +120,7 @@ Future resetToDefault(Box nodeSource) async { final arbitrumNodes = await loadDefaultNodes(WalletType.arbitrum); final zcashNodes = await loadDefaultNodes(WalletType.zcash); final bscNodes = await loadDefaultNodes(WalletType.bsc); + final starknetNodes = await loadDefaultNodes(WalletType.starknet); final nodes = moneroNodes + bitcoinElectrumServerList + @@ -134,7 +138,8 @@ Future resetToDefault(Box nodeSource) async { baseNodes + arbitrumNodes + zcashNodes + - bscNodes; + bscNodes + + starknetNodes; await nodeSource.clear(); await nodeSource.addAll(nodes); diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index 9e30e2eb8b..e71ef6f833 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -24,13 +24,15 @@ class PreferencesKey { static const currentTronNodeIdKey = 'current_node_id_trx'; static const currentWowneroNodeIdKey = 'current_node_id_wow'; static const currentZcashNodeIdKey = 'current_node_id_zec'; + static const currentStarknetNodeIdKey = 'current_node_id_strk'; static const currentTransactionPriorityKeyLegacy = 'current_fee_priority'; static const currentBalanceDisplayModeKey = 'current_balance_display_mode'; static const displayAmountsInSatoshi = 'display_amounts_in_satoshi'; static const shouldSaveRecipientAddressKey = 'save_recipient_address'; static const isAppSecureKey = 'is_app_secure'; static const disableTradeOption = 'disable_buy'; - static const disableAutomaticExchangeStatusUpdates = 'disable_automatic_exchange_status_updates'; + static const disableAutomaticExchangeStatusUpdates = + 'disable_automatic_exchange_status_updates'; static const disableBulletinKey = 'disable_bulletin'; static const walletListOrder = 'wallet_list_order'; static const contactListOrder = 'contact_list_order'; @@ -59,11 +61,13 @@ class PreferencesKey { static const polygonTransactionPriority = 'current_fee_priority_polygon'; static const baseTransactionPriority = 'current_fee_priority_base'; static const bscTransactionPriority = 'current_fee_priority_bsc'; - static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash'; + static const bitcoinCashTransactionPriority = + 'current_fee_priority_bitcoin_cash'; static const zanoTransactionPriority = 'current_fee_priority_zano'; static const wowneroTransactionPriority = 'current_fee_priority_wownero'; static const decredTransactionPriority = 'current_fee_priority_decred'; static const zcashTransactionPriority = 'current_fee_priority_decred'; + static const starknetTransactionPriority = 'current_fee_priority_starknet'; static const customBitcoinFeeRate = 'custom_electrum_fee_rate'; static const silentPaymentsCardDisplay = 'silentPaymentsCardDisplay'; static const mwebCardDisplay = 'mwebCardDisplay'; @@ -108,14 +112,16 @@ class PreferencesKey { static const showCameraConsent = 'show_camera_consent'; static const showDecredInfoCard = 'show_decred_info_card'; static const forceDecentralizedExchanges = 'force_decentralized_exchanges'; - static const decentralizedExchangesPromptDismissed = 'decentralized_exchanges_prompt_dismissed'; + static const decentralizedExchangesPromptDismissed = + 'decentralized_exchanges_prompt_dismissed'; static String moneroWalletUpdateV1Key(String name) => '${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}'; static const exchangeProvidersSelection = 'exchange-providers-selection'; static const trocadorProviderStatesKey = 'trocador_provider_states'; - static const autoGenerateSubaddressStatusKey = 'auto_generate_subaddress_status'; + static const autoGenerateSubaddressStatusKey = + 'auto_generate_subaddress_status'; static const moneroSeedType = 'monero_seed_type'; static const bitcoinSeedType = 'bitcoin_seed_type'; static const nanoSeedType = 'nano_seed_type'; @@ -123,15 +129,19 @@ class PreferencesKey { static const onionDonationLink = 'onion_donation_link'; static const donationLinkWalletName = 'donation_link_wallet_name'; static const lastSeenAppVersion = 'last_seen_app_version'; - static const shouldShowMarketPlaceInDashboard = 'should_show_marketplace_in_dashboard'; + static const shouldShowMarketPlaceInDashboard = + 'should_show_marketplace_in_dashboard'; static const showAddressBookPopupEnabled = 'show_address_book_popup_enabled'; static const isNewInstall = 'is_new_install'; static const serviceStatusShaKey = 'service_status_sha_key'; - static const walletConnectPairingTopicsList = 'wallet_connect_pairing_topics_list'; + static const walletConnectPairingTopicsList = + 'wallet_connect_pairing_topics_list'; static String walletConnectPairingTopicsListForWallet(String publicKey) => '${PreferencesKey.walletConnectPairingTopicsList}_${publicKey}'; - static String backgroundSyncLastTrigger(String walletId) => 'background_sync_last_trigger_${walletId}'; - static const backgroundSyncNotificationsEnabled = 'background_sync_notifications_enabled'; + static String backgroundSyncLastTrigger(String walletId) => + 'background_sync_last_trigger_${walletId}'; + static const backgroundSyncNotificationsEnabled = + 'background_sync_notifications_enabled'; static const enableAutomaticNodeSwitching = 'enable_automatic_node_switching'; static const syncStatusDisplayMode = 'sync_status_display_mode'; static const backgroundImage = 'background_image'; diff --git a/lib/entities/priority_for_wallet_type.dart b/lib/entities/priority_for_wallet_type.dart index f41c3b34b2..5fea742c8a 100644 --- a/lib/entities/priority_for_wallet_type.dart +++ b/lib/entities/priority_for_wallet_type.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cake_wallet/zano/zano.dart'; import 'package:cake_wallet/decred/decred.dart'; +import 'package:cake_wallet/starknet/starknet.dart'; import 'package:cake_wallet/zcash/zcash.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_type.dart'; @@ -27,6 +28,8 @@ List priorityForWalletType(WalletType type) { return evm!.getTransactionPriorities(); case WalletType.bitcoinCash: return bitcoinCash!.getTransactionPriorities(); + case WalletType.starknet: + return starknet!.getTransactionPriorities(); case WalletType.dogecoin: return dogecoin!.getTransactionPriorities(); case WalletType.arbitrum: diff --git a/lib/main.dart b/lib/main.dart index 8a4690d56a..95f17c8516 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -49,6 +49,7 @@ import 'package:cw_core/mweb_utxo.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/payjoin_session.dart'; import 'package:cw_core/root_dir.dart'; +import 'package:cw_core/starknet_token.dart'; import 'package:cw_core/spl_token.dart'; import 'package:cw_core/tron_token.dart'; import 'package:cw_core/unspent_coins_info.dart'; @@ -73,7 +74,8 @@ import 'package:trezor_connect/trezor_connect.dart'; final navigatorKey = GlobalKey(); final rootKey = GlobalKey(); -final RouteObserver> routeObserver = RouteObserver>(); +final RouteObserver> routeObserver = + RouteObserver>(); final quickActionsStream = StreamController.broadcast(); Future main({Key? topLevelKey}) async { @@ -105,7 +107,10 @@ Future runAppWithZone({Key? topLevelKey}) async { quickActions.setShortcutItems([ const ShortcutItem( - type: 'send', icon: 'send', localizedTitle: 'Send', localizedSubtitle: 'Send funds'), + type: 'send', + icon: 'send', + localizedTitle: 'Send', + localizedSubtitle: 'Send funds'), const ShortcutItem( type: 'receive', icon: 'receive', @@ -129,7 +134,8 @@ Future runAppWithZone({Key? topLevelKey}) async { /// A callback that is invoked when an unhandled error occurs in the root /// isolate. PlatformDispatcher.instance.onError = (error, stack) { - ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack)); + ExceptionHandler.onError( + FlutterErrorDetails(exception: error, stack: stack)); return true; }; @@ -165,10 +171,12 @@ Future runAppWithZone({Key? topLevelKey}) async { if (FeatureFlag.hasDevOptions) { ProxyWrapper.logger = MemoryProxyLogger(); } - var zcashPassword = await secureStorageShared.read(key: "com.cakewallet.cw_zcash/zec.db"); + var zcashPassword = + await secureStorageShared.read(key: "com.cakewallet.cw_zcash/zec.db"); if (zcashPassword == null || zcashPassword.isEmpty) { zcashPassword = generateKey().substring(0, 32); - secureStorageShared.write(key: "com.cakewallet.cw_zcash/zec.db", value: zcashPassword); + secureStorageShared.write( + key: "com.cakewallet.cw_zcash/zec.db", value: zcashPassword); } zcash?.unlockDatabase(zcashPassword); @@ -198,7 +206,8 @@ Future runAppWithZone({Key? topLevelKey}) async { ); } - await ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stackTrace)); + await ExceptionHandler.onError( + FlutterErrorDetails(exception: error, stack: stackTrace)); }); } @@ -280,31 +289,45 @@ Future initializeAppConfigs({bool loadWallet = true}) async { if (!CakeHive.isAdapterRegistered(TronToken.typeId)) { CakeHive.registerAdapter(TronTokenAdapter()); } + + if (!CakeHive.isAdapterRegistered(StarknetToken.typeId)) { + CakeHive.registerAdapter(StarknetTokenAdapter()); + } await performHiveMigration(); final secureStorage = secureStorageShared; - final transactionDescriptionsBoxKey = - await getEncryptionKey(secureStorage: secureStorage, forKey: TransactionDescription.boxKey); - final tradesBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Trade.boxKey); - final ordersBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Order.boxKey); + final transactionDescriptionsBoxKey = await getEncryptionKey( + secureStorage: secureStorage, forKey: TransactionDescription.boxKey); + final tradesBoxKey = await getEncryptionKey( + secureStorage: secureStorage, forKey: Trade.boxKey); + final ordersBoxKey = await getEncryptionKey( + secureStorage: secureStorage, forKey: Order.boxKey); final contacts = await CakeHive.openBox(Contact.boxName); final nodes = await CakeHive.openBox(Node.boxName); - final powNodes = - await CakeHive.openBox(Node.boxName + "pow"); // must be different from Node.boxName - final transactionDescriptions = await CakeHive.openBox( + final powNodes = await CakeHive.openBox( + Node.boxName + "pow"); // must be different from Node.boxName + final transactionDescriptions = + await CakeHive.openBox( TransactionDescription.boxName, encryptionKey: transactionDescriptionsBoxKey); - final trades = await CakeHive.openBox(Trade.boxName, encryptionKey: tradesBoxKey); - final orders = await CakeHive.openBox(Order.boxName, encryptionKey: ordersBoxKey); + final trades = + await CakeHive.openBox(Trade.boxName, encryptionKey: tradesBoxKey); + final orders = + await CakeHive.openBox(Order.boxName, encryptionKey: ordersBoxKey); final templates = await CakeHive.openBox