diff --git a/.gitignore b/.gitignore index 7e3f38be..5d71bc11 100644 --- a/.gitignore +++ b/.gitignore @@ -116,10 +116,15 @@ cw_shared_external/ios/External/ cw_haven/ios/External/ cw_haven/android/.externalNativeBuild/ cw_haven/android/.cxx/ +# cw_wownero/** +cw_wownero/ios/External/ +cw_wownero/android/.externalNativeBuild/ +cw_wownero/android/.cxx/ lib/bitcoin/bitcoin.dart lib/monero/monero.dart lib/haven/haven.dart +lib/haven/wownero.dart ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png diff --git a/android/app/src/main/java/com/cakewallet/wownero/Application.java b/android/app/src/main/java/com/cakewallet/wownero/Application.java new file mode 100644 index 00000000..887adb8d --- /dev/null +++ b/android/app/src/main/java/com/cakewallet/wownero/Application.java @@ -0,0 +1,11 @@ +package com.cakewallet.wownero; + +import io.flutter.app.FlutterApplication; +import io.flutter.plugin.common.PluginRegistry; +import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class Application extends FlutterApplication implements PluginRegistrantCallback { + @Override + public void registerWith(PluginRegistry registry) {} +} \ No newline at end of file diff --git a/android/app/src/main/java/com/cakewallet/wownero/MainActivity.java b/android/app/src/main/java/com/cakewallet/wownero/MainActivity.java new file mode 100644 index 00000000..ca3ab56d --- /dev/null +++ b/android/app/src/main/java/com/cakewallet/wownero/MainActivity.java @@ -0,0 +1,90 @@ +package com.cakewallet.wownero; + +import androidx.annotation.NonNull; + +import io.flutter.embedding.android.FlutterFragmentActivity; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.plugins.GeneratedPluginRegistrant; + +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; + +import android.os.AsyncTask; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.view.WindowManager; + +import com.unstoppabledomains.resolution.DomainResolution; +import com.unstoppabledomains.resolution.Resolution; + +import java.security.SecureRandom; + +public class MainActivity extends FlutterFragmentActivity { + final String UTILS_CHANNEL = "com.cake_wallet/native_utils"; + final int UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK = 24; + + @Override + public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { + GeneratedPluginRegistrant.registerWith(flutterEngine); + + MethodChannel utilsChannel = + new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), + UTILS_CHANNEL); + + utilsChannel.setMethodCallHandler(this::handle); + } + + private void handle(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + Handler handler = new Handler(Looper.getMainLooper()); + + try { + switch (call.method) { + case "enableWakeScreen": + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + handler.post(() -> result.success(true)); + break; + case "disableWakeScreen": + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + handler.post(() -> result.success(true)); + break; + case "sec_random": + int count = call.argument("count"); + SecureRandom random = new SecureRandom(); + byte bytes[] = new byte[count]; + random.nextBytes(bytes); + handler.post(() -> result.success(bytes)); + break; + case "getUnstoppableDomainAddress": + int version = Build.VERSION.SDK_INT; + if (version >= UNSTOPPABLE_DOMAIN_MIN_VERSION_SDK) { + getUnstoppableDomainAddress(call, result); + } else { + handler.post(() -> result.success("")); + } + break; + default: + handler.post(() -> result.notImplemented()); + } + } catch (Exception e) { + handler.post(() -> result.error("UNCAUGHT_ERROR", e.getMessage(), null)); + } + } + + private void getUnstoppableDomainAddress(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + DomainResolution resolution = new Resolution(); + Handler handler = new Handler(Looper.getMainLooper()); + String domain = call.argument("domain"); + String ticker = call.argument("ticker"); + + AsyncTask.execute(() -> { + try { + String address = resolution.getAddress(domain, ticker); + handler.post(() -> result.success(address)); + } catch (Exception e) { + System.out.println("Expected Address, but got " + e.getMessage()); + handler.post(() -> result.success("")); + } + }); + } +} \ No newline at end of file diff --git a/assets/images/wownero_logo.png b/assets/images/wownero_logo.png new file mode 100644 index 00000000..a3da77b9 Binary files /dev/null and b/assets/images/wownero_logo.png differ diff --git a/assets/images/wownero_menu.png b/assets/images/wownero_menu.png new file mode 100644 index 00000000..a3da77b9 Binary files /dev/null and b/assets/images/wownero_menu.png differ diff --git a/assets/node_list.yml b/assets/node_list.yml index 09fb7238..54747615 100644 --- a/assets/node_list.yml +++ b/assets/node_list.yml @@ -22,10 +22,9 @@ - uri: node.imonero.org:18081 is_default: false - +- uri: node.c3pool.com:18081 is_default: false - +- uri: xmr.prprpr.icu:18081 is_default: false - diff --git a/assets/wownero_node_list.yml b/assets/wownero_node_list.yml new file mode 100644 index 00000000..84f1db65 --- /dev/null +++ b/assets/wownero_node_list.yml @@ -0,0 +1,24 @@ +- + uri: eu-west-2.wow.xmr.pm:34568 + is_default: true + useSSL: false +- + uri: eu-west-1.wow.xmr.pm:34568 + is_default: false + useSSL: false +- + uri: eu-west-6.wow.xmr.pm:34568 + is_default: false + useSSL: false +- + uri: node.wownero.club:34568 + is_default: false + useSSL: false +- + uri: node.suchwow.xyz:34568 + is_default: false + useSSL: false +- + uri: idontwanttogototoronto.wow.fail:34568 + is_default: false + useSSL: false diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index ddcdb585..bf53eb7f 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -24,7 +24,8 @@ class CryptoCurrency extends EnumerableItem with Serializable { CryptoCurrency.usdterc20, CryptoCurrency.xlm, CryptoCurrency.xrp, - CryptoCurrency.xhv + CryptoCurrency.xhv, + CryptoCurrency.wow ]; static const xmr = CryptoCurrency(title: 'XMR', raw: 0); static const ada = CryptoCurrency(title: 'ADA', raw: 1); @@ -58,6 +59,8 @@ class CryptoCurrency extends EnumerableItem with Serializable { static const xnzd = CryptoCurrency(title: 'XNZD', raw: 28); static const xusd = CryptoCurrency(title: 'XUSD', raw: 29); + static const wow = CryptoCurrency(title: 'WOW', raw: 30); + static CryptoCurrency deserialize({int raw}) { switch (raw) { case 0: @@ -120,6 +123,8 @@ class CryptoCurrency extends EnumerableItem with Serializable { return CryptoCurrency.xnzd; case 29: return CryptoCurrency.xusd; + case 30: + return CryptoCurrency.wow; default: return null; } @@ -187,6 +192,8 @@ class CryptoCurrency extends EnumerableItem with Serializable { return CryptoCurrency.xnzd; case 'xusd': return CryptoCurrency.xusd; + case 'wow': + return CryptoCurrency.wow; default: return null; } diff --git a/cw_core/lib/currency_for_wallet_type.dart b/cw_core/lib/currency_for_wallet_type.dart index b6d1f18c..66d9b35e 100644 --- a/cw_core/lib/currency_for_wallet_type.dart +++ b/cw_core/lib/currency_for_wallet_type.dart @@ -11,6 +11,8 @@ CryptoCurrency currencyForWalletType(WalletType type) { return CryptoCurrency.ltc; case WalletType.haven: return CryptoCurrency.xhv; + case WalletType.wownero: + return CryptoCurrency.wow; default: return null; } diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index 2560a72d..07ef57a7 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -62,6 +62,8 @@ class Node extends HiveObject with Keyable { return createUriFromElectrumAddress(uriRaw); case WalletType.haven: return Uri.http(uriRaw, ''); + case WalletType.wownero: + return Uri.http(uriRaw, ''); default: return null; } @@ -83,6 +85,7 @@ class Node extends HiveObject with Keyable { try { switch (type) { case WalletType.monero: + case WalletType.wownero: return requestMoneroNode(); case WalletType.bitcoin: return requestElectrumServer(); diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart index 6a39fa63..9dbddc14 100644 --- a/cw_core/lib/wallet_type.dart +++ b/cw_core/lib/wallet_type.dart @@ -7,7 +7,8 @@ const walletTypes = [ WalletType.monero, WalletType.bitcoin, WalletType.litecoin, - WalletType.haven + WalletType.haven, + WalletType.wownero ]; const walletTypeTypeId = 5; @@ -26,7 +27,10 @@ enum WalletType { litecoin, @HiveField(4) - haven + haven, + + @HiveField(5) + wownero } int serializeToInt(WalletType type) { @@ -39,6 +43,8 @@ int serializeToInt(WalletType type) { return 2; case WalletType.haven: return 3; + case WalletType.wownero: + return 4; default: return -1; } @@ -54,6 +60,8 @@ WalletType deserializeFromInt(int raw) { return WalletType.litecoin; case 3: return WalletType.haven; + case 4: + return WalletType.wownero; default: return null; } @@ -69,6 +77,8 @@ String walletTypeToString(WalletType type) { return 'Litecoin'; case WalletType.haven: return 'Haven'; + case WalletType.wownero: + return 'Wownero'; default: return ''; } @@ -84,6 +94,8 @@ String walletTypeToDisplayName(WalletType type) { return 'Litecoin (Electrum)'; case WalletType.haven: return 'Haven'; + case WalletType.wownero: + return 'Wownero'; default: return ''; } @@ -99,6 +111,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) { return CryptoCurrency.ltc; case WalletType.haven: return CryptoCurrency.xhv; + case WalletType.wownero: + return CryptoCurrency.wow; default: return null; } diff --git a/cw_wownero/.gitignore b/cw_wownero/.gitignore new file mode 100644 index 00000000..c8bb7849 --- /dev/null +++ b/cw_wownero/.gitignore @@ -0,0 +1,11 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +build/ + +ios/External/ +android/.externalNativeBuild/ +android/.cxx/ \ No newline at end of file diff --git a/cw_wownero/.metadata b/cw_wownero/.metadata new file mode 100644 index 00000000..bd6953e1 --- /dev/null +++ b/cw_wownero/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 398e4272a2e43d7daab75f225a13422e384ee0cd + channel: dev + +project_type: plugin diff --git a/cw_wownero/CHANGELOG.md b/cw_wownero/CHANGELOG.md new file mode 100644 index 00000000..19b64793 --- /dev/null +++ b/cw_wownero/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: wow. diff --git a/cw_wownero/LICENSE b/cw_wownero/LICENSE new file mode 100644 index 00000000..ba75c69f --- /dev/null +++ b/cw_wownero/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/cw_wownero/README.md b/cw_wownero/README.md new file mode 100644 index 00000000..6d61f9bb --- /dev/null +++ b/cw_wownero/README.md @@ -0,0 +1,5 @@ +# cw_wownero + +This project is part of Cake Wallet app. + +Copyright (c) 2022 Cake Technologies LLC. \ No newline at end of file diff --git a/cw_wownero/android/.classpath b/cw_wownero/android/.classpath new file mode 100644 index 00000000..4a04201c --- /dev/null +++ b/cw_wownero/android/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/cw_wownero/android/.gitignore b/cw_wownero/android/.gitignore new file mode 100644 index 00000000..c6cbe562 --- /dev/null +++ b/cw_wownero/android/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/cw_wownero/android/.project b/cw_wownero/android/.project new file mode 100644 index 00000000..e1b61ff1 --- /dev/null +++ b/cw_wownero/android/.project @@ -0,0 +1,23 @@ + + + cw_wownero + Project android created by Buildship. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/cw_wownero/android/.settings/org.eclipse.buildship.core.prefs b/cw_wownero/android/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 00000000..a88c4d48 --- /dev/null +++ b/cw_wownero/android/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,13 @@ +arguments= +auto.sync=false +build.scans.enabled=false +connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(6.0-20191016123526+0000)) +connection.project.dir=../../android +eclipse.preferences.version=1 +gradle.user.home= +java.home= +jvm.arguments= +offline.mode=false +override.workspace.settings=true +show.console.view=true +show.executions.view=true diff --git a/cw_wownero/android/CMakeLists.txt b/cw_wownero/android/CMakeLists.txt new file mode 100644 index 00000000..fb5a8b74 --- /dev/null +++ b/cw_wownero/android/CMakeLists.txt @@ -0,0 +1,244 @@ +cmake_minimum_required(VERSION 3.4.1) + +add_library( cw_wownero + SHARED + ./jni/wownero_jni.cpp + ../ios/Classes/wownero_api.cpp) + + find_library( log-lib log ) + +set(EXTERNAL_LIBS_DIR ${CMAKE_SOURCE_DIR}/../../cw_shared_external/ios/External/android) + +############ +# libsodium +############ + +add_library(sodium STATIC IMPORTED) +set_target_properties(sodium PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libsodium.a) + +############ +# OpenSSL +############ + +add_library(crypto STATIC IMPORTED) +set_target_properties(crypto PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libcrypto.a) + +add_library(ssl STATIC IMPORTED) +set_target_properties(ssl PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libssl.a) + +############ +# wownero-seed +############ + +add_library(wownero-seed STATIC IMPORTED) +set_target_properties(wownero-seed PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libwownero-seed.a) + +############ +# Boost +############ + +add_library(boost_chrono STATIC IMPORTED) +set_target_properties(boost_chrono PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libboost_chrono.a) + +add_library(boost_date_time STATIC IMPORTED) +set_target_properties(boost_date_time PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libboost_date_time.a) + +add_library(boost_filesystem STATIC IMPORTED) +set_target_properties(boost_filesystem PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libboost_filesystem.a) + +add_library(boost_program_options STATIC IMPORTED) +set_target_properties(boost_program_options PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libboost_program_options.a) + +add_library(boost_regex STATIC IMPORTED) +set_target_properties(boost_regex PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libboost_regex.a) + +add_library(boost_serialization STATIC IMPORTED) +set_target_properties(boost_serialization PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libboost_serialization.a) + +add_library(boost_system STATIC IMPORTED) +set_target_properties(boost_system PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libboost_system.a) + +add_library(boost_thread STATIC IMPORTED) +set_target_properties(boost_thread PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libboost_thread.a) + +add_library(boost_wserialization STATIC IMPORTED) +set_target_properties(boost_wserialization PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/libboost_wserialization.a) + +############# +# Wownero +############# + +add_library(wallet_api STATIC IMPORTED) +set_target_properties(wallet_api PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libwallet_api.a) + +add_library(wallet STATIC IMPORTED) +set_target_properties(wallet PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libwallet.a) + +add_library(cryptonote_core STATIC IMPORTED) +set_target_properties(cryptonote_core PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libcryptonote_core.a) + +add_library(cryptonote_basic STATIC IMPORTED) +set_target_properties(cryptonote_basic PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libcryptonote_basic.a) + +add_library(cryptonote_format_utils_basic STATIC IMPORTED) +set_target_properties(cryptonote_format_utils_basic PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libcryptonote_format_utils_basic.a) + +add_library(mnemonics STATIC IMPORTED) +set_target_properties(mnemonics PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libmnemonics.a) + +add_library(common STATIC IMPORTED) +set_target_properties(common PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libcommon.a) + +add_library(cncrypto STATIC IMPORTED) +set_target_properties(cncrypto PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libcncrypto.a) + +add_library(ringct STATIC IMPORTED) +set_target_properties(ringct PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libringct.a) + +add_library(ringct_basic STATIC IMPORTED) +set_target_properties(ringct_basic PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libringct_basic.a) + +add_library(blockchain_db STATIC IMPORTED) +set_target_properties(blockchain_db PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libblockchain_db.a) + +add_library(lmdb STATIC IMPORTED) +set_target_properties(lmdb PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/liblmdb.a) + +add_library(easylogging STATIC IMPORTED) +set_target_properties(easylogging PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libeasylogging.a) + +add_library(unbound STATIC IMPORTED) +set_target_properties(unbound PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libunbound.a) + +add_library(epee STATIC IMPORTED) +set_target_properties(epee PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libepee.a) + +add_library(blocks STATIC IMPORTED) +set_target_properties(blocks PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libblocks.a) + +add_library(checkpoints STATIC IMPORTED) +set_target_properties(checkpoints PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libcheckpoints.a) + +add_library(device STATIC IMPORTED) +set_target_properties(device PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libdevice.a) + +add_library(device_trezor STATIC IMPORTED) +set_target_properties(device_trezor PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libdevice_trezor.a) + +add_library(multisig STATIC IMPORTED) +set_target_properties(multisig PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libmultisig.a) + +add_library(version STATIC IMPORTED) +set_target_properties(version PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libversion.a) + +add_library(net STATIC IMPORTED) +set_target_properties(net PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libnet.a) + +add_library(hardforks STATIC IMPORTED) +set_target_properties(hardforks PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libhardforks.a) + +add_library(randomx STATIC IMPORTED) +set_target_properties(randomx PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/librandomx.a) + +add_library(rpc_base STATIC IMPORTED) +set_target_properties(rpc_base PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/librpc_base.a) + +add_library(wallet-crypto STATIC IMPORTED) +set_target_properties(wallet-crypto PROPERTIES IMPORTED_LOCATION + ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/lib/wownero/libwallet-crypto.a) + +set(WALLET_CRYPTO "") + +if(${ANDROID_ABI} STREQUAL "x86_64") + set(WALLET_CRYPTO "wallet-crypto") +endif() + +include_directories( ${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/include ) + +target_link_libraries( cw_wownero + + wallet_api + wallet + cryptonote_core + cryptonote_basic + cryptonote_format_utils_basic + mnemonics + ringct + ringct_basic + net + common + cncrypto + blockchain_db + lmdb + easylogging + unbound + epee + blocks + checkpoints + device + device_trezor + multisig + version + randomx + hardforks + rpc_base + ${WALLET_CRYPTO} + + boost_chrono + boost_date_time + boost_filesystem + boost_program_options + boost_regex + boost_serialization + boost_system + boost_thread + boost_wserialization + + ssl + crypto + + sodium + + -Wl,--whole-archive + wownero-seed + -Wl,--no-whole-archive + + ${log-lib} ) \ No newline at end of file diff --git a/cw_wownero/android/build.gradle b/cw_wownero/android/build.gradle new file mode 100644 index 00000000..79cfdc7f --- /dev/null +++ b/cw_wownero/android/build.gradle @@ -0,0 +1,49 @@ +group 'com.cakewallet.wownero' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.3.50' + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.4' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +rootProject.allprojects { + repositories { + google() + jcenter() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion 28 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + defaultConfig { + minSdkVersion 21 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + lintOptions { + disable 'InvalidPackage' + } + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/cw_wownero/android/gradle.properties b/cw_wownero/android/gradle.properties new file mode 100644 index 00000000..38c8d454 --- /dev/null +++ b/cw_wownero/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/cw_wownero/android/gradle/wrapper/gradle-wrapper.properties b/cw_wownero/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..019065d1 --- /dev/null +++ b/cw_wownero/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip diff --git a/cw_wownero/android/jni/wownero_jni.cpp b/cw_wownero/android/jni/wownero_jni.cpp new file mode 100644 index 00000000..4e2deb6a --- /dev/null +++ b/cw_wownero/android/jni/wownero_jni.cpp @@ -0,0 +1,74 @@ +#include +#include +#include "../../ios/Classes/wownero_api.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT void JNICALL +Java_com_cakewallet_wownero_WowneroApi_setNodeAddressJNI( + JNIEnv *env, + jobject inst, + jstring uri, + jstring login, + jstring password, + jboolean use_ssl, + jboolean is_light_wallet) { + const char *_uri = env->GetStringUTFChars(uri, 0); + const char *_login = ""; + const char *_password = ""; + char *error; + + if (login != NULL) { + _login = env->GetStringUTFChars(login, 0); + } + + if (password != NULL) { + _password = env->GetStringUTFChars(password, 0); + } + char *__uri = (char*) _uri; + char *__login = (char*) _login; + char *__password = (char*) _password; + bool inited = setup_node(__uri, __login, __password, false, false, error); + + if (!inited) { + env->ThrowNew(env->FindClass("java/lang/Exception"), error); + } +} + +JNIEXPORT void JNICALL +Java_com_cakewallet_wownero_WowneroApi_connectToNodeJNI( + JNIEnv *env, + jobject inst) { + char *error; + bool is_connected = connect_to_node(error); + + if (!is_connected) { + env->ThrowNew(env->FindClass("java/lang/Exception"), error); + } +} + +JNIEXPORT void JNICALL +Java_com_cakewallet_wownero_WowneroApi_startSyncJNI( + JNIEnv *env, + jobject inst) { + start_refresh(); +} + +JNIEXPORT void JNICALL +Java_com_cakewallet_wownero_WowneroApi_loadWalletJNI( + JNIEnv *env, + jobject inst, + jstring path, + jstring password) { + char *_path = (char *) env->GetStringUTFChars(path, 0); + char *_password = (char *) env->GetStringUTFChars(password, 0); + + load_wallet(_path, _password, 0); +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/cw_wownero/android/settings.gradle b/cw_wownero/android/settings.gradle new file mode 100644 index 00000000..f6786df2 --- /dev/null +++ b/cw_wownero/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'cw_wownero' diff --git a/cw_wownero/android/src/main/AndroidManifest.xml b/cw_wownero/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..a1e6eef7 --- /dev/null +++ b/cw_wownero/android/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + diff --git a/cw_wownero/android/src/main/kotlin/com/cakewallet/cw_wownero/CwWowneroPlugin.kt b/cw_wownero/android/src/main/kotlin/com/cakewallet/cw_wownero/CwWowneroPlugin.kt new file mode 100644 index 00000000..3f79780a --- /dev/null +++ b/cw_wownero/android/src/main/kotlin/com/cakewallet/cw_wownero/CwWowneroPlugin.kt @@ -0,0 +1,74 @@ +package com.cakewallet.cw_wownero + +import android.app.Activity +import android.os.AsyncTask +import android.os.Looper +import android.os.Handler +import android.os.Process + +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result +import io.flutter.plugin.common.PluginRegistry.Registrar + +class doAsync(val handler: () -> Unit) : AsyncTask() { + override fun doInBackground(vararg params: Void?): Void? { + Process.setThreadPriority(Process.THREAD_PRIORITY_AUDIO); + handler() + return null + } +} + +class CwWowneroPlugin: MethodCallHandler { + companion object { +// val wowneroApi = WowneroApi() + val main = Handler(Looper.getMainLooper()); + + init { + System.loadLibrary("cw_wownero") + } + + @JvmStatic + fun registerWith(registrar: Registrar) { + val channel = MethodChannel(registrar.messenger(), "cw_wownero") + channel.setMethodCallHandler(CwWowneroPlugin()) + } + } + + override fun onMethodCall(call: MethodCall, result: Result) { + if (call.method == "setupNode") { + val uri = call.argument("address") ?: "" + val login = call.argument("login") ?: "" + val password = call.argument("password") ?: "" + val useSSL = false + val isLightWallet = false +// doAsync { +// try { +// wowneroApi.setNodeAddressJNI(uri, login, password, useSSL, isLightWallet) +// main.post({ +// result.success(true) +// }); +// } catch(e: Throwable) { +// main.post({ +// result.error("CONNECTION_ERROR", e.message, null) +// }); +// } +// }.execute() + } + if (call.method == "startSync") { +// doAsync { +// wowneroApi.startSyncJNI() +// main.post({ +// result.success(true) +// }); +// }.execute() + } + if (call.method == "loadWallet") { + val path = call.argument("path") ?: "" + val password = call.argument("password") ?: "" +// wowneroApi.loadWalletJNI(path, password) + result.success(true) + } + } +} diff --git a/cw_wownero/ios/.gitignore b/cw_wownero/ios/.gitignore new file mode 100644 index 00000000..aa479fd3 --- /dev/null +++ b/cw_wownero/ios/.gitignore @@ -0,0 +1,37 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/Generated.xcconfig +/Flutter/flutter_export_environment.sh \ No newline at end of file diff --git a/cw_wownero/ios/Assets/.gitkeep b/cw_wownero/ios/Assets/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/cw_wownero/ios/Classes/CwWalletListener.h b/cw_wownero/ios/Classes/CwWalletListener.h new file mode 100644 index 00000000..fecc2320 --- /dev/null +++ b/cw_wownero/ios/Classes/CwWalletListener.h @@ -0,0 +1,23 @@ +#include + +struct CWWowneroWalletListener; + +typedef int8_t (*on_new_block_callback)(uint64_t height); +typedef int8_t (*on_need_to_refresh_callback)(); + +typedef struct CWWowneroWalletListener +{ + // on_money_spent_callback *on_money_spent; + // on_money_received_callback *on_money_received; + // on_unconfirmed_money_received_callback *on_unconfirmed_money_received; + // on_new_block_callback *on_new_block; + // on_updated_callback *on_updated; + // on_refreshed_callback *on_refreshed; + + on_new_block_callback on_new_block; +} CWWowneroWalletListener; + +struct TestListener { + // int8_t x; + on_new_block_callback on_new_block; +}; \ No newline at end of file diff --git a/cw_wownero/ios/Classes/CwWowneroPlugin.h b/cw_wownero/ios/Classes/CwWowneroPlugin.h new file mode 100644 index 00000000..4b3002c8 --- /dev/null +++ b/cw_wownero/ios/Classes/CwWowneroPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface CwWowneroPlugin : NSObject +@end diff --git a/cw_wownero/ios/Classes/CwWowneroPlugin.m b/cw_wownero/ios/Classes/CwWowneroPlugin.m new file mode 100644 index 00000000..67d39529 --- /dev/null +++ b/cw_wownero/ios/Classes/CwWowneroPlugin.m @@ -0,0 +1,8 @@ +#import "CwWowneroPlugin.h" +#import + +@implementation CwWowneroPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + [SwiftCwWowneroPlugin registerWithRegistrar:registrar]; +} +@end diff --git a/cw_wownero/ios/Classes/SwiftCwWowneroPlugin.swift b/cw_wownero/ios/Classes/SwiftCwWowneroPlugin.swift new file mode 100644 index 00000000..975181dc --- /dev/null +++ b/cw_wownero/ios/Classes/SwiftCwWowneroPlugin.swift @@ -0,0 +1,14 @@ +import Flutter +import UIKit + +public class SwiftCwWowneroPlugin: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "cw_wownero", binaryMessenger: registrar.messenger()) + let instance = SwiftCwWowneroPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + result("iOS " + UIDevice.current.systemVersion) + } +} diff --git a/cw_wownero/ios/Classes/wownero_api.cpp b/cw_wownero/ios/Classes/wownero_api.cpp new file mode 100644 index 00000000..25ef35cb --- /dev/null +++ b/cw_wownero/ios/Classes/wownero_api.cpp @@ -0,0 +1,823 @@ +#include +#include "cstdlib" +#include +#include +#include +#include +#include +#include "thread" +#include "CwWalletListener.h" + +#include +#include + +#if __APPLE__ +// Fix for randomx on ios +void __clear_cache(void* start, void* end) { } +#include "../External/ios/include/wallet2_api.h" +#else +#include "../External/android/include/wallet2_api.h" +#endif + +using namespace std::chrono_literals; +#ifdef __cplusplus +extern "C" +{ +#endif + const uint64_t MONERO_BLOCK_SIZE = 1000; + + struct Utf8Box + { + char *value; + + Utf8Box(char *_value) + { + value = _value; + } + }; + + struct SubaddressRow + { + uint64_t id; + char *address; + char *label; + + SubaddressRow(std::size_t _id, char *_address, char *_label) + { + id = static_cast(_id); + address = _address; + label = _label; + } + }; + + struct AccountRow + { + uint64_t id; + char *label; + + AccountRow(std::size_t _id, char *_label) + { + id = static_cast(_id); + label = _label; + } + }; + + struct MoneroWalletListener : Monero::WalletListener + { + uint64_t m_height; + bool m_need_to_refresh; + bool m_new_transaction; + + MoneroWalletListener() + { + m_height = 0; + m_need_to_refresh = false; + m_new_transaction = false; + } + + void moneySpent(const std::string &txId, uint64_t amount) + { + m_new_transaction = true; + } + + void moneyReceived(const std::string &txId, uint64_t amount) + { + m_new_transaction = true; + } + + void unconfirmedMoneyReceived(const std::string &txId, uint64_t amount) + { + m_new_transaction = true; + } + + void newBlock(uint64_t height) + { + m_height = height; + } + + void updated() + { + m_new_transaction = true; + } + + void refreshed(bool success) + { + m_need_to_refresh = true; + } + + void resetNeedToRefresh() + { + m_need_to_refresh = false; + } + + bool isNeedToRefresh() + { + return m_need_to_refresh; + } + + bool isNewTransactionExist() + { + return m_new_transaction; + } + + void resetIsNewTransactionExist() + { + m_new_transaction = false; + } + + uint64_t height() + { + return m_height; + } + }; + + struct TransactionInfoRow + { + uint64_t amount; + uint64_t fee; + uint64_t blockHeight; + uint64_t confirmations; + uint32_t subaddrAccount; + int8_t direction; + int8_t isPending; + uint32_t subaddrIndex; + + char *hash; + char *paymentId; + + int64_t datetime; + + TransactionInfoRow(Monero::TransactionInfo *transaction) + { + amount = transaction->amount(); + fee = transaction->fee(); + blockHeight = transaction->blockHeight(); + subaddrAccount = transaction->subaddrAccount(); + std::set::iterator it = transaction->subaddrIndex().begin(); + subaddrIndex = *it; + confirmations = transaction->confirmations(); + datetime = static_cast(transaction->timestamp()); + direction = transaction->direction(); + isPending = static_cast(transaction->isPending()); + std::string *hash_str = new std::string(transaction->hash()); + hash = strdup(hash_str->c_str()); + paymentId = strdup(transaction->paymentId().c_str()); + } + }; + + struct PendingTransactionRaw + { + uint64_t amount; + uint64_t fee; + char *hash; + Monero::PendingTransaction *transaction; + + PendingTransactionRaw(Monero::PendingTransaction *_transaction) + { + transaction = _transaction; + amount = _transaction->amount(); + fee = _transaction->fee(); + hash = strdup(_transaction->txid()[0].c_str()); + } + }; + + Monero::Wallet *m_wallet; + Monero::TransactionHistory *m_transaction_history; + MoneroWalletListener *m_listener; + Monero::Subaddress *m_subaddress; + Monero::SubaddressAccount *m_account; + uint64_t m_last_known_wallet_height; + uint64_t m_cached_syncing_blockchain_height = 0; + std::mutex store_lock; + bool is_storing = false; + + void change_current_wallet(Monero::Wallet *wallet) + { + m_wallet = wallet; + m_listener = nullptr; + + + if (wallet != nullptr) + { + m_transaction_history = wallet->history(); + } + else + { + m_transaction_history = nullptr; + } + + if (wallet != nullptr) + { + m_account = wallet->subaddressAccount(); + } + else + { + m_account = nullptr; + } + + if (wallet != nullptr) + { + m_subaddress = wallet->subaddress(); + } + else + { + m_subaddress = nullptr; + } + } + + Monero::Wallet *get_current_wallet() + { + return m_wallet; + } + + bool create_wallet(char *path, char *password, char *language, int32_t networkType, char *error) + { + Monero::NetworkType _networkType = static_cast(networkType); + Monero::WalletManager *walletManager = Monero::WalletManagerFactory::getWalletManager(); + + time_t time = std::time(nullptr); + wownero_seed wow_seed(time, "wownero"); + + std::stringstream seed_stream; + seed_stream << wow_seed; + std::string seed = seed_stream.str(); + + std::stringstream key_stream; + key_stream << wow_seed.key(); + std::string spendKey = key_stream.str(); + + uint64_t restoreHeight = wow_seed.blockheight(); + + Monero::Wallet *wallet = walletManager->createDeterministicWalletFromSpendKey( + std::string(path), + std::string(password), + std::string(language), + static_cast(_networkType), + (uint64_t)restoreHeight, + spendKey, + 1); + wallet->setCacheAttribute("cake.seed", seed); + + int status; + std::string errorString; + + wallet->statusWithErrorString(status, errorString); + + if (wallet->status() != Monero::Wallet::Status_Ok) + { + error = strdup(wallet->errorString().c_str()); + return false; + } + + change_current_wallet(wallet); + return true; + } + + bool restore_wallet_from_seed(char *path, char *password, char *seed, int32_t networkType, char *error) + { + Monero::NetworkType _networkType = static_cast(networkType); + Monero::WalletManager *walletManager = Monero::WalletManagerFactory::getWalletManager(); + + wownero_seed wow_seed(seed, "wownero"); + + std::stringstream seed_stream; + seed_stream << wow_seed; + std::string seed_str = seed_stream.str(); + + std::stringstream key_stream; + key_stream << wow_seed.key(); + std::string spendKey = key_stream.str(); + + uint64_t restoreHeight = wow_seed.blockheight(); + + Monero::Wallet *wallet = walletManager->createDeterministicWalletFromSpendKey( + std::string(path), + std::string(password), + "English", + static_cast(_networkType), + (uint64_t)restoreHeight, + spendKey, + 1); + wallet->setCacheAttribute("cake.seed", seed_str); + + int status; + std::string errorString; + + wallet->statusWithErrorString(status, errorString); + + if (status != Monero::Wallet::Status_Ok || !errorString.empty()) + { + error = strdup(errorString.c_str()); + return false; + } + + change_current_wallet(wallet); + return true; + } + + bool restore_wallet_from_keys(char *path, char *password, char *language, char *address, char *viewKey, char *spendKey, int32_t networkType, uint64_t restoreHeight, char *error) + { + // this function is not used, restoring from keys is disabled for Wownero + Monero::NetworkType _networkType = static_cast(networkType); + Monero::Wallet *wallet = Monero::WalletManagerFactory::getWalletManager()->createWalletFromKeys( + std::string(path), + std::string(password), + std::string(language), + _networkType, + (uint64_t)restoreHeight, + std::string(address), + std::string(viewKey), + std::string(spendKey)); + + int status; + std::string errorString; + + wallet->statusWithErrorString(status, errorString); + + if (status != Monero::Wallet::Status_Ok || !errorString.empty()) + { + error = strdup(errorString.c_str()); + return false; + } + + change_current_wallet(wallet); + return true; + } + + bool load_wallet(char *path, char *password, int32_t nettype) + { + nice(19); + Monero::NetworkType networkType = static_cast(nettype); + Monero::WalletManager *walletManager = Monero::WalletManagerFactory::getWalletManager(); + Monero::Wallet *wallet = walletManager->openWallet(std::string(path), std::string(password), networkType); + int status; + std::string errorString; + + wallet->statusWithErrorString(status, errorString); + change_current_wallet(wallet); + + return !(status != Monero::Wallet::Status_Ok || !errorString.empty()); + } + + char *error_string() { + return strdup(get_current_wallet()->errorString().c_str()); + } + + + bool is_wallet_exist(char *path) + { + return Monero::WalletManagerFactory::getWalletManager()->walletExists(std::string(path)); + } + + void close_current_wallet() + { + Monero::WalletManagerFactory::getWalletManager()->closeWallet(get_current_wallet()); + change_current_wallet(nullptr); + } + + char *get_filename() + { + return strdup(get_current_wallet()->filename().c_str()); + } + + char *secret_view_key() + { + return strdup(get_current_wallet()->secretViewKey().c_str()); + } + + char *public_view_key() + { + return strdup(get_current_wallet()->publicViewKey().c_str()); + } + + char *secret_spend_key() + { + return strdup(get_current_wallet()->secretSpendKey().c_str()); + } + + char *public_spend_key() + { + return strdup(get_current_wallet()->publicSpendKey().c_str()); + } + + char *get_address(uint32_t account_index, uint32_t address_index) + { + return strdup(get_current_wallet()->address(account_index, address_index).c_str()); + } + + const char *seed() + { + return strdup(get_current_wallet()->getCacheAttribute("cake.seed").c_str()); + } + + uint64_t get_full_balance(uint32_t account_index) + { + return get_current_wallet()->balance(account_index); + } + + uint64_t get_unlocked_balance(uint32_t account_index) + { + return get_current_wallet()->unlockedBalance(account_index); + } + + uint64_t get_current_height() + { + return get_current_wallet()->blockChainHeight(); + } + + uint64_t get_node_height() + { + return get_current_wallet()->daemonBlockChainHeight(); + } + + uint64_t get_seed_height(char *seed) + { + wownero_seed wow_seed(seed, "wownero"); + return wow_seed.blockheight(); + } + + bool connect_to_node(char *error) + { + nice(19); + bool is_connected = get_current_wallet()->connectToDaemon(); + + if (!is_connected) + { + error = strdup(get_current_wallet()->errorString().c_str()); + } + + return is_connected; + } + + bool setup_node(char *address, char *login, char *password, bool use_ssl, bool is_light_wallet, char *error) + { + nice(19); + Monero::Wallet *wallet = get_current_wallet(); + + std::string _login = ""; + std::string _password = ""; + + if (login != nullptr) + { + _login = std::string(login); + } + + if (password != nullptr) + { + _password = std::string(password); + } + + bool inited = wallet->init(std::string(address), 0, _login, _password, true, is_light_wallet); + + if (!inited) + { + error = strdup(wallet->errorString().c_str()); + } else if (!wallet->connectToDaemon()) { + error = strdup(wallet->errorString().c_str()); + } + + wallet->setTrustedDaemon(true); + return inited; + } + + bool is_connected() + { + return get_current_wallet()->connected(); + } + + void start_refresh() + { + get_current_wallet()->refreshAsync(); + get_current_wallet()->startRefresh(); + } + + void set_refresh_from_block_height(uint64_t height) + { + get_current_wallet()->setRefreshFromBlockHeight(height); + } + + void set_recovering_from_seed(bool is_recovery) + { + get_current_wallet()->setRecoveringFromSeed(is_recovery); + } + + void store(char *path) + { + store_lock.lock(); + if (is_storing) { + return; + } + + is_storing = true; + get_current_wallet()->store(std::string(path)); + is_storing = false; + store_lock.unlock(); + } + + bool transaction_create(char *address, char *payment_id, char *amount, + uint8_t priority_raw, uint32_t subaddr_account, Utf8Box &error, PendingTransactionRaw &pendingTransaction) + { + nice(19); + + auto priority = static_cast(priority_raw); + std::string _payment_id; + Monero::PendingTransaction *transaction; + + if (payment_id != nullptr) + { + _payment_id = std::string(payment_id); + } + + if (amount != nullptr) + { + uint64_t _amount = Monero::Wallet::amountFromString(std::string(amount)); + transaction = m_wallet->createTransaction(std::string(address), _payment_id, _amount, m_wallet->defaultMixin(), priority, subaddr_account); + } + else + { + transaction = m_wallet->createTransaction(std::string(address), _payment_id, Monero::optional(), m_wallet->defaultMixin(), priority, subaddr_account); + } + + int status = transaction->status(); + + if (status == Monero::PendingTransaction::Status::Status_Error || status == Monero::PendingTransaction::Status::Status_Critical) + { + error = Utf8Box(strdup(transaction->errorString().c_str())); + return false; + } + + if (m_listener != nullptr) { + m_listener->m_new_transaction = true; + } + + pendingTransaction = PendingTransactionRaw(transaction); + return true; + } + + bool transaction_create_mult_dest(char **addresses, char *payment_id, char **amounts, uint32_t size, + uint8_t priority_raw, uint32_t subaddr_account, Utf8Box &error, PendingTransactionRaw &pendingTransaction) + { + nice(19); + + std::vector _addresses; + std::vector _amounts; + + for (int i = 0; i < size; i++) { + _addresses.push_back(std::string(*addresses)); + _amounts.push_back(Monero::Wallet::amountFromString(std::string(*amounts))); + addresses++; + amounts++; + } + + auto priority = static_cast(priority_raw); + std::string _payment_id; + Monero::PendingTransaction *transaction; + + if (payment_id != nullptr) + { + _payment_id = std::string(payment_id); + } + + transaction = m_wallet->createTransactionMultDest(_addresses, _payment_id, _amounts, m_wallet->defaultMixin(), priority, subaddr_account); + + int status = transaction->status(); + + if (status == Monero::PendingTransaction::Status::Status_Error || status == Monero::PendingTransaction::Status::Status_Critical) + { + error = Utf8Box(strdup(transaction->errorString().c_str())); + return false; + } + + if (m_listener != nullptr) { + m_listener->m_new_transaction = true; + } + + pendingTransaction = PendingTransactionRaw(transaction); + return true; + } + + bool transaction_commit(PendingTransactionRaw *transaction, Utf8Box &error) + { + bool committed = transaction->transaction->commit(); + + if (!committed) + { + error = Utf8Box(strdup(transaction->transaction->errorString().c_str())); + } else if (m_listener != nullptr) { + m_listener->m_new_transaction = true; + } + + return committed; + } + + uint64_t get_node_height_or_update(uint64_t base_eight) + { + if (m_cached_syncing_blockchain_height < base_eight) { + m_cached_syncing_blockchain_height = base_eight; + } + + return m_cached_syncing_blockchain_height; + } + + uint64_t get_syncing_height() + { + if (m_listener == nullptr) { + return 0; + } + + uint64_t height = m_listener->height(); + + if (height <= 1) { + return 0; + } + + if (height != m_last_known_wallet_height) + { + m_last_known_wallet_height = height; + } + + return height; + } + + uint64_t is_needed_to_refresh() + { + if (m_listener == nullptr) { + return false; + } + + bool should_refresh = m_listener->isNeedToRefresh(); + + if (should_refresh) { + m_listener->resetNeedToRefresh(); + } + + return should_refresh; + } + + uint8_t is_new_transaction_exist() + { + if (m_listener == nullptr) { + return false; + } + + bool is_new_transaction_exist = m_listener->isNewTransactionExist(); + + if (is_new_transaction_exist) + { + m_listener->resetIsNewTransactionExist(); + } + + return is_new_transaction_exist; + } + + void set_listener() + { + m_last_known_wallet_height = 0; + + if (m_listener != nullptr) + { + free(m_listener); + } + + m_listener = new MoneroWalletListener(); + get_current_wallet()->setListener(m_listener); + } + + int64_t *subaddrress_get_all() + { + std::vector _subaddresses = m_subaddress->getAll(); + size_t size = _subaddresses.size(); + int64_t *subaddresses = (int64_t *)malloc(size * sizeof(int64_t)); + + for (int i = 0; i < size; i++) + { + Monero::SubaddressRow *row = _subaddresses[i]; + SubaddressRow *_row = new SubaddressRow(row->getRowId(), strdup(row->getAddress().c_str()), strdup(row->getLabel().c_str())); + subaddresses[i] = reinterpret_cast(_row); + } + + return subaddresses; + } + + int32_t subaddrress_size() + { + std::vector _subaddresses = m_subaddress->getAll(); + return _subaddresses.size(); + } + + void subaddress_add_row(uint32_t accountIndex, char *label) + { + m_subaddress->addRow(accountIndex, std::string(label)); + } + + void subaddress_set_label(uint32_t accountIndex, uint32_t addressIndex, char *label) + { + m_subaddress->setLabel(accountIndex, addressIndex, std::string(label)); + } + + void subaddress_refresh(uint32_t accountIndex) + { + m_subaddress->refresh(accountIndex); + } + + int32_t account_size() + { + std::vector _accocunts = m_account->getAll(); + return _accocunts.size(); + } + + int64_t *account_get_all() + { + std::vector _accocunts = m_account->getAll(); + size_t size = _accocunts.size(); + int64_t *accocunts = (int64_t *)malloc(size * sizeof(int64_t)); + + for (int i = 0; i < size; i++) + { + Monero::SubaddressAccountRow *row = _accocunts[i]; + AccountRow *_row = new AccountRow(row->getRowId(), strdup(row->getLabel().c_str())); + accocunts[i] = reinterpret_cast(_row); + } + + return accocunts; + } + + void account_add_row(char *label) + { + m_account->addRow(std::string(label)); + } + + void account_set_label_row(uint32_t account_index, char *label) + { + m_account->setLabel(account_index, label); + } + + void account_refresh() + { + m_account->refresh(); + } + + int64_t *transactions_get_all() + { + std::vector transactions = m_transaction_history->getAll(); + size_t size = transactions.size(); + int64_t *transactionAddresses = (int64_t *)malloc(size * sizeof(int64_t)); + + for (int i = 0; i < size; i++) + { + Monero::TransactionInfo *row = transactions[i]; + TransactionInfoRow *tx = new TransactionInfoRow(row); + transactionAddresses[i] = reinterpret_cast(tx); + } + + return transactionAddresses; + } + + void transactions_refresh() + { + m_transaction_history->refresh(); + } + + int64_t transactions_count() + { + return m_transaction_history->count(); + } + + int LedgerExchange( + unsigned char *command, + unsigned int cmd_len, + unsigned char *response, + unsigned int max_resp_len) + { + return -1; + } + + int LedgerFind(char *buffer, size_t len) + { + return -1; + } + + void on_startup() + { + Monero::Utils::onStartup(); + Monero::WalletManagerFactory::setLogLevel(4); + } + + void rescan_blockchain() + { + m_wallet->rescanBlockchainAsync(); + } + + char * get_tx_key(char * txId) + { + return strdup(m_wallet->getTxKey(std::string(txId)).c_str()); + } + + char *get_subaddress_label(uint32_t accountIndex, uint32_t addressIndex) + { + return strdup(get_current_wallet()->getSubaddressLabel(accountIndex, addressIndex).c_str()); + } + +#ifdef __cplusplus +} +#endif diff --git a/cw_wownero/ios/Classes/wownero_api.h b/cw_wownero/ios/Classes/wownero_api.h new file mode 100644 index 00000000..fabb5c32 --- /dev/null +++ b/cw_wownero/ios/Classes/wownero_api.h @@ -0,0 +1,36 @@ +#include +#include +#include +#include "CwWalletListener.h" + +#ifdef __cplusplus +extern "C" { +#endif + +bool create_wallet(char *path, char *password, char *language, int32_t networkType, char *error); +bool restore_wallet_from_seed(char *path, char *password, char *seed, int32_t networkType, uint64_t restoreHeight, char *error); +bool restore_wallet_from_keys(char *path, char *password, char *language, char *address, char *viewKey, char *spendKey, int32_t networkType, uint64_t restoreHeight, char *error); +void load_wallet(char *path, char *password, int32_t nettype); +bool is_wallet_exist(char *path); + +char *get_filename(); +const char *seed(); +char *get_address(uint32_t account_index, uint32_t address_index); +uint64_t get_full_balance(uint32_t account_index); +uint64_t get_unlocked_balance(uint32_t account_index); +uint64_t get_current_height(); +uint64_t get_node_height(); +uint64_t get_seed_height(char *seed); + +bool is_connected(); + +bool setup_node(char *address, char *login, char *password, bool use_ssl, bool is_light_wallet, char *error); +bool connect_to_node(char *error); +void start_refresh(); +void set_refresh_from_block_height(uint64_t height); +void set_recovering_from_seed(bool is_recovery); +void store(char *path); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/cw_wownero/ios/cw_wownero.podspec b/cw_wownero/ios/cw_wownero.podspec new file mode 100644 index 00000000..e5100d30 --- /dev/null +++ b/cw_wownero/ios/cw_wownero.podspec @@ -0,0 +1,50 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint cw_wownero.podspec' to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'cw_wownero' + s.version = '0.0.2' + s.summary = 'Cake Wallet Wownero' + s.description = 'Cake Wallet wrapper over Wownero project' + s.homepage = 'http://cakewallet.com' + s.license = { :file => '../LICENSE' } + s.author = { 'CakeWallet' => 'support@cakewallet.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h, Classes/*.h, ../shared_external/ios/libs/monero/include/src/**/*.h, ../shared_external/ios/libs/monero/include/contrib/**/*.h, ../shared_external/ios/libs/monero/include/../shared_external/ios/**/*.h' + s.dependency 'Flutter' + s.dependency 'cw_shared_external' + s.platform = :ios, '10.0' + s.swift_version = '5.0' + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS' => 'arm64', 'ENABLE_BITCODE' => 'NO' } + s.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/Classes/*.h" } + + s.subspec 'OpenSSL' do |openssl| + openssl.preserve_paths = '../../../../../cw_shared_external/ios/External/ios/include/**/*.h' + openssl.vendored_libraries = '../../../../../cw_shared_external/ios/External/ios/lib/libcrypto.a', '../../../../../cw_shared_external/ios/External/ios/lib/libssl.a' + openssl.libraries = 'ssl', 'crypto' + openssl.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/ios/include/**" } + end + + s.subspec 'Sodium' do |sodium| + sodium.preserve_paths = '../../../../../cw_shared_external/ios/External/ios/include/**/*.h' + sodium.vendored_libraries = '../../../../../cw_shared_external/ios/External/ios/lib/libsodium.a' + sodium.libraries = 'sodium' + sodium.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/ios/include/**" } + end + + s.subspec 'Boost' do |boost| + boost.preserve_paths = '../../../../../cw_shared_external/ios/External/ios/include/**/*.h', + boost.vendored_libraries = '../../../../../cw_shared_external/ios/External/ios/lib/libboost.a', + boost.libraries = 'boost' + boost.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/ios/include/**" } + end + + s.subspec 'Wownero' do |wownero| + wownero.preserve_paths = 'External/ios/include/**/*.h' + wownero.vendored_libraries = 'External/ios/lib/libwownero.a' + wownero.libraries = 'wownero' + wownero.xcconfig = { 'HEADER_SEARCH_PATHS' => "${PODS_ROOT}/#{s.name}/External/ios/include" } + end +end diff --git a/cw_wownero/lib/api/account_list.dart b/cw_wownero/lib/api/account_list.dart new file mode 100644 index 00000000..ead68dd4 --- /dev/null +++ b/cw_wownero/lib/api/account_list.dart @@ -0,0 +1,83 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import 'package:cw_wownero/api/signatures.dart'; +import 'package:cw_wownero/api/types.dart'; +import 'package:cw_wownero/api/wownero_api.dart'; +import 'package:cw_wownero/api/structs/account_row.dart'; +import 'package:flutter/foundation.dart'; +import 'package:cw_wownero/api/wallet.dart'; + +final accountSizeNative = wowneroApi + .lookup>('account_size') + .asFunction(); + +final accountRefreshNative = wowneroApi + .lookup>('account_refresh') + .asFunction(); + +final accountGetAllNative = wowneroApi + .lookup>('account_get_all') + .asFunction(); + +final accountAddNewNative = wowneroApi + .lookup>('account_add_row') + .asFunction(); + +final accountSetLabelNative = wowneroApi + .lookup>('account_set_label_row') + .asFunction(); + +bool isUpdating = false; + +void refreshAccounts() { + try { + isUpdating = true; + accountRefreshNative(); + isUpdating = false; + } catch (e) { + isUpdating = false; + rethrow; + } +} + +List getAllAccount() { + final size = accountSizeNative(); + final accountAddressesPointer = accountGetAllNative(); + final accountAddresses = accountAddressesPointer.asTypedList(size); + + return accountAddresses + .map((addr) => Pointer.fromAddress(addr).ref) + .toList(); +} + +void addAccountSync({String label}) { + final labelPointer = Utf8.toUtf8(label); + accountAddNewNative(labelPointer); + free(labelPointer); +} + +void setLabelForAccountSync({int accountIndex, String label}) { + final labelPointer = Utf8.toUtf8(label); + accountSetLabelNative(accountIndex, labelPointer); + free(labelPointer); +} + +void _addAccount(String label) => addAccountSync(label: label); + +void _setLabelForAccount(Map args) { + final label = args['label'] as String; + final accountIndex = args['accountIndex'] as int; + + setLabelForAccountSync(label: label, accountIndex: accountIndex); +} + +Future addAccount({String label}) async { + await compute(_addAccount, label); + await store(); +} + +Future setLabelForAccount({int accountIndex, String label}) async { + await compute( + _setLabelForAccount, {'accountIndex': accountIndex, 'label': label}); + await store(); +} \ No newline at end of file diff --git a/cw_wownero/lib/api/convert_utf8_to_string.dart b/cw_wownero/lib/api/convert_utf8_to_string.dart new file mode 100644 index 00000000..7fa5a68d --- /dev/null +++ b/cw_wownero/lib/api/convert_utf8_to_string.dart @@ -0,0 +1,8 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +String convertUTF8ToString({Pointer pointer}) { + final str = Utf8.fromUtf8(pointer); + free(pointer); + return str; +} \ No newline at end of file diff --git a/cw_wownero/lib/api/exceptions/connection_to_node_exception.dart b/cw_wownero/lib/api/exceptions/connection_to_node_exception.dart new file mode 100644 index 00000000..6ee272b8 --- /dev/null +++ b/cw_wownero/lib/api/exceptions/connection_to_node_exception.dart @@ -0,0 +1,5 @@ +class ConnectionToNodeException implements Exception { + ConnectionToNodeException({this.message}); + + final String message; +} \ No newline at end of file diff --git a/cw_wownero/lib/api/exceptions/creation_transaction_exception.dart b/cw_wownero/lib/api/exceptions/creation_transaction_exception.dart new file mode 100644 index 00000000..bb477d67 --- /dev/null +++ b/cw_wownero/lib/api/exceptions/creation_transaction_exception.dart @@ -0,0 +1,8 @@ +class CreationTransactionException implements Exception { + CreationTransactionException({this.message}); + + final String message; + + @override + String toString() => message; +} \ No newline at end of file diff --git a/cw_wownero/lib/api/exceptions/setup_wallet_exception.dart b/cw_wownero/lib/api/exceptions/setup_wallet_exception.dart new file mode 100644 index 00000000..ce43c0ec --- /dev/null +++ b/cw_wownero/lib/api/exceptions/setup_wallet_exception.dart @@ -0,0 +1,5 @@ +class SetupWalletException implements Exception { + SetupWalletException({this.message}); + + final String message; +} \ No newline at end of file diff --git a/cw_wownero/lib/api/exceptions/wallet_creation_exception.dart b/cw_wownero/lib/api/exceptions/wallet_creation_exception.dart new file mode 100644 index 00000000..6b00445a --- /dev/null +++ b/cw_wownero/lib/api/exceptions/wallet_creation_exception.dart @@ -0,0 +1,8 @@ +class WalletCreationException implements Exception { + WalletCreationException({this.message}); + + final String message; + + @override + String toString() => message; +} \ No newline at end of file diff --git a/cw_wownero/lib/api/exceptions/wallet_opening_exception.dart b/cw_wownero/lib/api/exceptions/wallet_opening_exception.dart new file mode 100644 index 00000000..8d84b0f7 --- /dev/null +++ b/cw_wownero/lib/api/exceptions/wallet_opening_exception.dart @@ -0,0 +1,8 @@ +class WalletOpeningException implements Exception { + WalletOpeningException({this.message}); + + final String message; + + @override + String toString() => message; +} \ No newline at end of file diff --git a/cw_wownero/lib/api/exceptions/wallet_restore_from_keys_exception.dart b/cw_wownero/lib/api/exceptions/wallet_restore_from_keys_exception.dart new file mode 100644 index 00000000..5f08437d --- /dev/null +++ b/cw_wownero/lib/api/exceptions/wallet_restore_from_keys_exception.dart @@ -0,0 +1,5 @@ +class WalletRestoreFromKeysException implements Exception { + WalletRestoreFromKeysException({this.message}); + + final String message; +} \ No newline at end of file diff --git a/cw_wownero/lib/api/exceptions/wallet_restore_from_seed_exception.dart b/cw_wownero/lib/api/exceptions/wallet_restore_from_seed_exception.dart new file mode 100644 index 00000000..fd89e416 --- /dev/null +++ b/cw_wownero/lib/api/exceptions/wallet_restore_from_seed_exception.dart @@ -0,0 +1,5 @@ +class WalletRestoreFromSeedException implements Exception { + WalletRestoreFromSeedException({this.message}); + + final String message; +} \ No newline at end of file diff --git a/cw_wownero/lib/api/signatures.dart b/cw_wownero/lib/api/signatures.dart new file mode 100644 index 00000000..7a0ec07a --- /dev/null +++ b/cw_wownero/lib/api/signatures.dart @@ -0,0 +1,128 @@ +import 'dart:ffi'; +import 'package:cw_wownero/api/structs/pending_transaction.dart'; +import 'package:cw_wownero/api/structs/ut8_box.dart'; +import 'package:ffi/ffi.dart'; + +typedef create_wallet = Int8 Function( + Pointer, Pointer, Pointer, Int32, Pointer); + +typedef restore_wallet_from_seed = Int8 Function( + Pointer, Pointer, Pointer, Int32, Pointer); + +typedef restore_wallet_from_keys = Int8 Function(Pointer, Pointer, + Pointer, Pointer, Pointer, Pointer, Int32, Int64, Pointer); + +typedef is_wallet_exist = Int8 Function(Pointer); + +typedef load_wallet = Int8 Function(Pointer, Pointer, Int8); + +typedef error_string = Pointer Function(); + +typedef get_filename = Pointer Function(); + +typedef get_seed = Pointer Function(); + +typedef get_address = Pointer Function(Int32, Int32); + +typedef get_full_balanace = Int64 Function(Int32); + +typedef get_unlocked_balanace = Int64 Function(Int32); + +typedef get_current_height = Int64 Function(); + +typedef get_node_height = Int64 Function(); + +typedef get_seed_height = Int64 Function(Pointer); + +typedef is_connected = Int8 Function(); + +typedef setup_node = Int8 Function( + Pointer, Pointer, Pointer, Int8, Int8, Pointer); + +typedef start_refresh = Void Function(); + +typedef connect_to_node = Int8 Function(); + +typedef set_refresh_from_block_height = Void Function(Int64); + +typedef set_recovering_from_seed = Void Function(Int8); + +typedef store_c = Void Function(Pointer); + +typedef set_listener = Void Function(); + +typedef get_syncing_height = Int64 Function(); + +typedef is_needed_to_refresh = Int8 Function(); + +typedef is_new_transaction_exist = Int8 Function(); + +typedef subaddrress_size = Int32 Function(); + +typedef subaddrress_refresh = Void Function(Int32); + +typedef subaddress_get_all = Pointer Function(); + +typedef subaddress_add_new = Void Function( + Int32 accountIndex, Pointer label); + +typedef subaddress_set_label = Void Function( + Int32 accountIndex, Int32 addressIndex, Pointer label); + +typedef account_size = Int32 Function(); + +typedef account_refresh = Void Function(); + +typedef account_get_all = Pointer Function(); + +typedef account_add_new = Void Function(Pointer label); + +typedef account_set_label = Void Function( + Int32 accountIndex, Pointer label); + +typedef transactions_refresh = Void Function(); + +typedef get_tx_key = Pointer Function(Pointer txId); + +typedef transactions_count = Int64 Function(); + +typedef transactions_get_all = Pointer Function(); + +typedef transaction_create = Int8 Function( + Pointer address, + Pointer paymentId, + Pointer amount, + Int8 priorityRaw, + Int32 subaddrAccount, + Pointer error, + Pointer pendingTransaction); + +typedef transaction_create_mult_dest = Int8 Function( + Pointer> addresses, + Pointer paymentId, + Pointer> amounts, + Int32 size, + Int8 priorityRaw, + Int32 subaddrAccount, + Pointer error, + Pointer pendingTransaction); + +typedef transaction_commit = Int8 Function(Pointer, Pointer); + +typedef secret_view_key = Pointer Function(); + +typedef public_view_key = Pointer Function(); + +typedef secret_spend_key = Pointer Function(); + +typedef public_spend_key = Pointer Function(); + +typedef close_current_wallet = Void Function(); + +typedef on_startup = Void Function(); + +typedef rescan_blockchain = Void Function(); + +typedef get_subaddress_label = Pointer Function( + Int32 accountIndex, + Int32 addressIndex); \ No newline at end of file diff --git a/cw_wownero/lib/api/structs/account_row.dart b/cw_wownero/lib/api/structs/account_row.dart new file mode 100644 index 00000000..c3fc22de --- /dev/null +++ b/cw_wownero/lib/api/structs/account_row.dart @@ -0,0 +1,11 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +class AccountRow extends Struct { + @Int64() + int id; + Pointer label; + + String getLabel() => Utf8.fromUtf8(label); + int getId() => id; +} diff --git a/cw_wownero/lib/api/structs/pending_transaction.dart b/cw_wownero/lib/api/structs/pending_transaction.dart new file mode 100644 index 00000000..b492f28a --- /dev/null +++ b/cw_wownero/lib/api/structs/pending_transaction.dart @@ -0,0 +1,23 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +class PendingTransactionRaw extends Struct { + @Int64() + int amount; + + @Int64() + int fee; + + Pointer hash; + + String getHash() => Utf8.fromUtf8(hash); +} + +class PendingTransactionDescription { + PendingTransactionDescription({this.amount, this.fee, this.hash, this.pointerAddress}); + + final int amount; + final int fee; + final String hash; + final int pointerAddress; +} \ No newline at end of file diff --git a/cw_wownero/lib/api/structs/subaddress_row.dart b/cw_wownero/lib/api/structs/subaddress_row.dart new file mode 100644 index 00000000..1673e00c --- /dev/null +++ b/cw_wownero/lib/api/structs/subaddress_row.dart @@ -0,0 +1,13 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +class SubaddressRow extends Struct { + @Int64() + int id; + Pointer address; + Pointer label; + + String getLabel() => Utf8.fromUtf8(label); + String getAddress() => Utf8.fromUtf8(address); + int getId() => id; +} \ No newline at end of file diff --git a/cw_wownero/lib/api/structs/transaction_info_row.dart b/cw_wownero/lib/api/structs/transaction_info_row.dart new file mode 100644 index 00000000..37b0d02e --- /dev/null +++ b/cw_wownero/lib/api/structs/transaction_info_row.dart @@ -0,0 +1,41 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +class TransactionInfoRow extends Struct { + @Uint64() + int amount; + + @Uint64() + int fee; + + @Uint64() + int blockHeight; + + @Uint64() + int confirmations; + + @Uint32() + int subaddrAccount; + + @Int8() + int direction; + + @Int8() + int isPending; + + @Uint32() + int subaddrIndex; + + Pointer hash; + + Pointer paymentId; + + @Int64() + int datetime; + + int getDatetime() => datetime; + int getAmount() => amount >= 0 ? amount : amount * -1; + bool getIsPending() => isPending != 0; + String getHash() => Utf8.fromUtf8(hash); + String getPaymentId() => Utf8.fromUtf8(paymentId); +} diff --git a/cw_wownero/lib/api/structs/ut8_box.dart b/cw_wownero/lib/api/structs/ut8_box.dart new file mode 100644 index 00000000..a6f41bc7 --- /dev/null +++ b/cw_wownero/lib/api/structs/ut8_box.dart @@ -0,0 +1,8 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; + +class Utf8Box extends Struct { + Pointer value; + + String getValue() => Utf8.fromUtf8(value); +} diff --git a/cw_wownero/lib/api/subaddress_list.dart b/cw_wownero/lib/api/subaddress_list.dart new file mode 100644 index 00000000..1271d4ac --- /dev/null +++ b/cw_wownero/lib/api/subaddress_list.dart @@ -0,0 +1,97 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import 'package:flutter/foundation.dart'; +import 'package:cw_wownero/api/signatures.dart'; +import 'package:cw_wownero/api/types.dart'; +import 'package:cw_wownero/api/wownero_api.dart'; +import 'package:cw_wownero/api/structs/subaddress_row.dart'; +import 'package:cw_wownero/api/wallet.dart'; + +final subaddressSizeNative = wowneroApi + .lookup>('subaddrress_size') + .asFunction(); + +final subaddressRefreshNative = wowneroApi + .lookup>('subaddress_refresh') + .asFunction(); + +final subaddrressGetAllNative = wowneroApi + .lookup>('subaddrress_get_all') + .asFunction(); + +final subaddrressAddNewNative = wowneroApi + .lookup>('subaddress_add_row') + .asFunction(); + +final subaddrressSetLabelNative = wowneroApi + .lookup>('subaddress_set_label') + .asFunction(); + +bool isUpdating = false; + +void refreshSubaddresses({@required int accountIndex}) { + try { + isUpdating = true; + subaddressRefreshNative(accountIndex); + isUpdating = false; + } catch (e) { + isUpdating = false; + rethrow; + } +} + +List getAllSubaddresses() { + final size = subaddressSizeNative(); + final subaddressAddressesPointer = subaddrressGetAllNative(); + final subaddressAddresses = subaddressAddressesPointer.asTypedList(size); + + return subaddressAddresses + .map((addr) => Pointer.fromAddress(addr).ref) + .toList(); +} + +void addSubaddressSync({int accountIndex, String label}) { + final labelPointer = Utf8.toUtf8(label); + subaddrressAddNewNative(accountIndex, labelPointer); + free(labelPointer); +} + +void setLabelForSubaddressSync( + {int accountIndex, int addressIndex, String label}) { + final labelPointer = Utf8.toUtf8(label); + + subaddrressSetLabelNative(accountIndex, addressIndex, labelPointer); + free(labelPointer); +} + +void _addSubaddress(Map args) { + final label = args['label'] as String; + final accountIndex = args['accountIndex'] as int; + + addSubaddressSync(accountIndex: accountIndex, label: label); +} + +void _setLabelForSubaddress(Map args) { + final label = args['label'] as String; + final accountIndex = args['accountIndex'] as int; + final addressIndex = args['addressIndex'] as int; + + setLabelForSubaddressSync( + accountIndex: accountIndex, addressIndex: addressIndex, label: label); +} + +Future addSubaddress({int accountIndex, String label}) async { + await compute, void>( + _addSubaddress, {'accountIndex': accountIndex, 'label': label}); + await store(); +} + +Future setLabelForSubaddress( + {int accountIndex, int addressIndex, String label}) async { + await compute, void>(_setLabelForSubaddress, { + 'accountIndex': accountIndex, + 'addressIndex': addressIndex, + 'label': label + }); + await store(); +} diff --git a/cw_wownero/lib/api/transaction_history.dart b/cw_wownero/lib/api/transaction_history.dart new file mode 100644 index 00000000..db7cb608 --- /dev/null +++ b/cw_wownero/lib/api/transaction_history.dart @@ -0,0 +1,230 @@ +import 'dart:ffi'; +import 'package:cw_wownero/api/convert_utf8_to_string.dart'; +import 'package:cw_wownero/api/wownero_output.dart'; +import 'package:cw_wownero/api/structs/ut8_box.dart'; +import 'package:ffi/ffi.dart'; +import 'package:flutter/foundation.dart'; +import 'package:cw_wownero/api/signatures.dart'; +import 'package:cw_wownero/api/types.dart'; +import 'package:cw_wownero/api/wownero_api.dart'; +import 'package:cw_wownero/api/structs/transaction_info_row.dart'; +import 'package:cw_wownero/api/structs/pending_transaction.dart'; +import 'package:cw_wownero/api/exceptions/creation_transaction_exception.dart'; + +final transactionsRefreshNative = wowneroApi + .lookup>('transactions_refresh') + .asFunction(); + +final transactionsCountNative = wowneroApi + .lookup>('transactions_count') + .asFunction(); + +final transactionsGetAllNative = wowneroApi + .lookup>('transactions_get_all') + .asFunction(); + +final transactionCreateNative = wowneroApi + .lookup>('transaction_create') + .asFunction(); + +final transactionCreateMultDestNative = wowneroApi + .lookup>('transaction_create_mult_dest') + .asFunction(); + +final transactionCommitNative = wowneroApi + .lookup>('transaction_commit') + .asFunction(); + +final getTxKeyNative = wowneroApi + .lookup>('get_tx_key') + .asFunction(); + +String getTxKey(String txId) { + final txIdPointer = Utf8.toUtf8(txId); + final keyPointer = getTxKeyNative(txIdPointer); + + free(txIdPointer); + + if (keyPointer != null) { + return convertUTF8ToString(pointer: keyPointer); + } + + return null; +} + +void refreshTransactions() => transactionsRefreshNative(); + +int countOfTransactions() => transactionsCountNative(); + +List getAllTransations() { + final size = transactionsCountNative(); + final transactionsPointer = transactionsGetAllNative(); + final transactionsAddresses = transactionsPointer.asTypedList(size); + + return transactionsAddresses + .map((addr) => Pointer.fromAddress(addr).ref) + .toList(); +} + +PendingTransactionDescription createTransactionSync( + {String address, + String paymentId, + String amount, + int priorityRaw, + int accountIndex = 0}) { + final addressPointer = Utf8.toUtf8(address); + final paymentIdPointer = Utf8.toUtf8(paymentId); + final amountPointer = amount != null ? Utf8.toUtf8(amount) : nullptr; + final errorMessagePointer = allocate(); + final pendingTransactionRawPointer = allocate(); + final created = transactionCreateNative( + addressPointer, + paymentIdPointer, + amountPointer, + priorityRaw, + accountIndex, + errorMessagePointer, + pendingTransactionRawPointer) != + 0; + + free(addressPointer); + free(paymentIdPointer); + + if (amountPointer != nullptr) { + free(amountPointer); + } + + if (!created) { + final message = errorMessagePointer.ref.getValue(); + free(errorMessagePointer); + throw CreationTransactionException(message: message); + } + + return PendingTransactionDescription( + amount: pendingTransactionRawPointer.ref.amount, + fee: pendingTransactionRawPointer.ref.fee, + hash: pendingTransactionRawPointer.ref.getHash(), + pointerAddress: pendingTransactionRawPointer.address); +} + +PendingTransactionDescription createTransactionMultDestSync( + {List outputs, + String paymentId, + int priorityRaw, + int accountIndex = 0}) { + final int size = outputs.length; + final List> addressesPointers = outputs.map((output) => + Utf8.toUtf8(output.address)).toList(); + final Pointer> addressesPointerPointer = allocate(count: size); + final List> amountsPointers = outputs.map((output) => + Utf8.toUtf8(output.amount)).toList(); + final Pointer> amountsPointerPointer = allocate(count: size); + + for (int i = 0; i < size; i++) { + addressesPointerPointer[i] = addressesPointers[i]; + amountsPointerPointer[i] = amountsPointers[i]; + } + + final paymentIdPointer = Utf8.toUtf8(paymentId); + final errorMessagePointer = allocate(); + final pendingTransactionRawPointer = allocate(); + final created = transactionCreateMultDestNative( + addressesPointerPointer, + paymentIdPointer, + amountsPointerPointer, + size, + priorityRaw, + accountIndex, + errorMessagePointer, + pendingTransactionRawPointer) != + 0; + + free(addressesPointerPointer); + free(amountsPointerPointer); + + addressesPointers.forEach((element) => free(element)); + amountsPointers.forEach((element) => free(element)); + + free(paymentIdPointer); + + if (!created) { + final message = errorMessagePointer.ref.getValue(); + free(errorMessagePointer); + throw CreationTransactionException(message: message); + } + + return PendingTransactionDescription( + amount: pendingTransactionRawPointer.ref.amount, + fee: pendingTransactionRawPointer.ref.fee, + hash: pendingTransactionRawPointer.ref.getHash(), + pointerAddress: pendingTransactionRawPointer.address); +} + +void commitTransactionFromPointerAddress({int address}) => commitTransaction( + transactionPointer: Pointer.fromAddress(address)); + +void commitTransaction({Pointer transactionPointer}) { + final errorMessagePointer = allocate(); + final isCommited = + transactionCommitNative(transactionPointer, errorMessagePointer) != 0; + + if (!isCommited) { + final message = errorMessagePointer.ref.getValue(); + free(errorMessagePointer); + throw CreationTransactionException(message: message); + } +} + +PendingTransactionDescription _createTransactionSync(Map args) { + final address = args['address'] as String; + final paymentId = args['paymentId'] as String; + final amount = args['amount'] as String; + final priorityRaw = args['priorityRaw'] as int; + final accountIndex = args['accountIndex'] as int; + + return createTransactionSync( + address: address, + paymentId: paymentId, + amount: amount, + priorityRaw: priorityRaw, + accountIndex: accountIndex); +} + +PendingTransactionDescription _createTransactionMultDestSync(Map args) { + final outputs = args['outputs'] as List; + final paymentId = args['paymentId'] as String; + final priorityRaw = args['priorityRaw'] as int; + final accountIndex = args['accountIndex'] as int; + + return createTransactionMultDestSync( + outputs: outputs, + paymentId: paymentId, + priorityRaw: priorityRaw, + accountIndex: accountIndex); +} + +Future createTransaction( + {String address, + String paymentId = '', + String amount, + int priorityRaw, + int accountIndex = 0}) => + compute(_createTransactionSync, { + 'address': address, + 'paymentId': paymentId, + 'amount': amount, + 'priorityRaw': priorityRaw, + 'accountIndex': accountIndex + }); + +Future createTransactionMultDest( + {List outputs, + String paymentId = '', + int priorityRaw, + int accountIndex = 0}) => + compute(_createTransactionMultDestSync, { + 'outputs': outputs, + 'paymentId': paymentId, + 'priorityRaw': priorityRaw, + 'accountIndex': accountIndex + }); diff --git a/cw_wownero/lib/api/types.dart b/cw_wownero/lib/api/types.dart new file mode 100644 index 00000000..5a8a3b73 --- /dev/null +++ b/cw_wownero/lib/api/types.dart @@ -0,0 +1,126 @@ +import 'dart:ffi'; +import 'package:cw_wownero/api/structs/pending_transaction.dart'; +import 'package:cw_wownero/api/structs/ut8_box.dart'; +import 'package:ffi/ffi.dart'; + +typedef CreateWallet = int Function( + Pointer, Pointer, Pointer, int, Pointer); + +typedef RestoreWalletFromSeed = int Function( + Pointer, Pointer, Pointer, int, Pointer); + +typedef RestoreWalletFromKeys = int Function(Pointer, Pointer, + Pointer, Pointer, Pointer, Pointer, int, int, Pointer); + +typedef IsWalletExist = int Function(Pointer); + +typedef LoadWallet = int Function(Pointer, Pointer, int); + +typedef ErrorString = Pointer Function(); + +typedef GetFilename = Pointer Function(); + +typedef GetSeed = Pointer Function(); + +typedef GetAddress = Pointer Function(int, int); + +typedef GetFullBalance = int Function(int); + +typedef GetUnlockedBalance = int Function(int); + +typedef GetCurrentHeight = int Function(); + +typedef GetNodeHeight = int Function(); + +typedef GetSeedHeight = int Function(Pointer); + +typedef IsConnected = int Function(); + +typedef SetupNode = int Function( + Pointer, Pointer, Pointer, int, int, Pointer); + +typedef StartRefresh = void Function(); + +typedef ConnectToNode = int Function(); + +typedef SetRefreshFromBlockHeight = void Function(int); + +typedef SetRecoveringFromSeed = void Function(int); + +typedef Store = void Function(Pointer); + +typedef SetListener = void Function(); + +typedef GetSyncingHeight = int Function(); + +typedef IsNeededToRefresh = int Function(); + +typedef IsNewTransactionExist = int Function(); + +typedef SubaddressSize = int Function(); + +typedef SubaddressRefresh = void Function(int); + +typedef SubaddressGetAll = Pointer Function(); + +typedef SubaddressAddNew = void Function(int accountIndex, Pointer label); + +typedef SubaddressSetLabel = void Function( + int accountIndex, int addressIndex, Pointer label); + +typedef AccountSize = int Function(); + +typedef AccountRefresh = void Function(); + +typedef AccountGetAll = Pointer Function(); + +typedef AccountAddNew = void Function(Pointer label); + +typedef AccountSetLabel = void Function(int accountIndex, Pointer label); + +typedef TransactionsRefresh = void Function(); + +typedef GetTxKey = Pointer Function(Pointer txId); + +typedef TransactionsCount = int Function(); + +typedef TransactionsGetAll = Pointer Function(); + +typedef TransactionCreate = int Function( + Pointer address, + Pointer paymentId, + Pointer amount, + int priorityRaw, + int subaddrAccount, + Pointer error, + Pointer pendingTransaction); + +typedef TransactionCreateMultDest = int Function( + Pointer> addresses, + Pointer paymentId, + Pointer> amounts, + int size, + int priorityRaw, + int subaddrAccount, + Pointer error, + Pointer pendingTransaction); + +typedef TransactionCommit = int Function(Pointer, Pointer); + +typedef SecretViewKey = Pointer Function(); + +typedef PublicViewKey = Pointer Function(); + +typedef SecretSpendKey = Pointer Function(); + +typedef PublicSpendKey = Pointer Function(); + +typedef CloseCurrentWallet = void Function(); + +typedef OnStartup = void Function(); + +typedef RescanBlockchainAsync = void Function(); + +typedef GetSubaddressLabel = Pointer Function( + int accountIndex, + int addressIndex); \ No newline at end of file diff --git a/cw_wownero/lib/api/wallet.dart b/cw_wownero/lib/api/wallet.dart new file mode 100644 index 00000000..1e97470f --- /dev/null +++ b/cw_wownero/lib/api/wallet.dart @@ -0,0 +1,348 @@ +import 'dart:async'; +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import 'package:cw_wownero/api/convert_utf8_to_string.dart'; +import 'package:cw_wownero/api/signatures.dart'; +import 'package:cw_wownero/api/types.dart'; +import 'package:cw_wownero/api/wownero_api.dart'; +import 'package:cw_wownero/api/exceptions/setup_wallet_exception.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +int _boolToInt(bool value) => value ? 1 : 0; + +final getFileNameNative = wowneroApi + .lookup>('get_filename') + .asFunction(); + +final getSeedNative = + wowneroApi.lookup>('seed').asFunction(); + +final getAddressNative = wowneroApi + .lookup>('get_address') + .asFunction(); + +final getFullBalanceNative = wowneroApi + .lookup>('get_full_balance') + .asFunction(); + +final getUnlockedBalanceNative = wowneroApi + .lookup>('get_unlocked_balance') + .asFunction(); + +final getCurrentHeightNative = wowneroApi + .lookup>('get_current_height') + .asFunction(); + +final getNodeHeightNative = wowneroApi + .lookup>('get_node_height') + .asFunction(); + +final getSeedHeightNative = wowneroApi + .lookup>('get_seed_height') + .asFunction(); + +final isConnectedNative = wowneroApi + .lookup>('is_connected') + .asFunction(); + +final setupNodeNative = wowneroApi + .lookup>('setup_node') + .asFunction(); + +final startRefreshNative = wowneroApi + .lookup>('start_refresh') + .asFunction(); + +final connecToNodeNative = wowneroApi + .lookup>('connect_to_node') + .asFunction(); + +final setRefreshFromBlockHeightNative = wowneroApi + .lookup>( + 'set_refresh_from_block_height') + .asFunction(); + +final setRecoveringFromSeedNative = wowneroApi + .lookup>( + 'set_recovering_from_seed') + .asFunction(); + +final storeNative = + wowneroApi.lookup>('store').asFunction(); + +final setListenerNative = wowneroApi + .lookup>('set_listener') + .asFunction(); + +final getSyncingHeightNative = wowneroApi + .lookup>('get_syncing_height') + .asFunction(); + +final isNeededToRefreshNative = wowneroApi + .lookup>('is_needed_to_refresh') + .asFunction(); + +final isNewTransactionExistNative = wowneroApi + .lookup>( + 'is_new_transaction_exist') + .asFunction(); + +final getSecretViewKeyNative = wowneroApi + .lookup>('secret_view_key') + .asFunction(); + +final getPublicViewKeyNative = wowneroApi + .lookup>('public_view_key') + .asFunction(); + +final getSecretSpendKeyNative = wowneroApi + .lookup>('secret_spend_key') + .asFunction(); + +final getPublicSpendKeyNative = wowneroApi + .lookup>('public_spend_key') + .asFunction(); + +final closeCurrentWalletNative = wowneroApi + .lookup>('close_current_wallet') + .asFunction(); + +final onStartupNative = wowneroApi + .lookup>('on_startup') + .asFunction(); + +final rescanBlockchainAsyncNative = wowneroApi + .lookup>('rescan_blockchain') + .asFunction(); + +final getSubaddressLabelNative = wowneroApi + .lookup>('get_subaddress_label') + .asFunction(); + +int getSyncingHeight() => getSyncingHeightNative(); + +bool isNeededToRefresh() => isNeededToRefreshNative() != 0; + +bool isNewTransactionExist() => isNewTransactionExistNative() != 0; + +String getFilename() => convertUTF8ToString(pointer: getFileNameNative()); + +String getSeed() => convertUTF8ToString(pointer: getSeedNative()); + +String getAddress({int accountIndex = 0, int addressIndex = 0}) => + convertUTF8ToString(pointer: getAddressNative(accountIndex, addressIndex)); + +int getFullBalance({int accountIndex = 0}) => + getFullBalanceNative(accountIndex); + +int getUnlockedBalance({int accountIndex = 0}) => + getUnlockedBalanceNative(accountIndex); + +int getCurrentHeight() => getCurrentHeightNative(); + +int getNodeHeightSync() => getNodeHeightNative(); + +int getSeedHeightSync(String seed) { + final seedPointer = Utf8.toUtf8(seed); + final ret = getSeedHeightNative(seedPointer); + free(seedPointer); + return ret; +} + +bool isConnectedSync() => isConnectedNative() != 0; + +bool setupNodeSync( + {String address, + String login, + String password, + bool useSSL = false, + bool isLightWallet = false}) { + final addressPointer = Utf8.toUtf8(address); + Pointer loginPointer; + Pointer passwordPointer; + + if (login != null) { + loginPointer = Utf8.toUtf8(login); + } + + if (password != null) { + passwordPointer = Utf8.toUtf8(password); + } + + final errorMessagePointer = allocate(); + final isSetupNode = setupNodeNative( + addressPointer, + loginPointer, + passwordPointer, + _boolToInt(useSSL), + _boolToInt(isLightWallet), + errorMessagePointer) != + 0; + + free(addressPointer); + free(loginPointer); + free(passwordPointer); + + if (!isSetupNode) { + throw SetupWalletException( + message: convertUTF8ToString(pointer: errorMessagePointer)); + } + + return isSetupNode; +} + +void startRefreshSync() => startRefreshNative(); + +Future connectToNode() async => connecToNodeNative() != 0; + +void setRefreshFromBlockHeight({int height}) => + setRefreshFromBlockHeightNative(height); + +void setRecoveringFromSeed({bool isRecovery}) => + setRecoveringFromSeedNative(_boolToInt(isRecovery)); + +void storeSync() { + final pathPointer = Utf8.toUtf8(''); + storeNative(pathPointer); + free(pathPointer); +} + +void closeCurrentWallet() => closeCurrentWalletNative(); + +String getSecretViewKey() => + convertUTF8ToString(pointer: getSecretViewKeyNative()); + +String getPublicViewKey() => + convertUTF8ToString(pointer: getPublicViewKeyNative()); + +String getSecretSpendKey() => + convertUTF8ToString(pointer: getSecretSpendKeyNative()); + +String getPublicSpendKey() => + convertUTF8ToString(pointer: getPublicSpendKeyNative()); + +class SyncListener { + SyncListener(this.onNewBlock, this.onNewTransaction) { + _cachedBlockchainHeight = 0; + _lastKnownBlockHeight = 0; + _initialSyncHeight = 0; + } + + void Function(int, int, double) onNewBlock; + void Function() onNewTransaction; + + Timer _updateSyncInfoTimer; + int _cachedBlockchainHeight; + int _lastKnownBlockHeight; + int _initialSyncHeight; + + Future getNodeHeightOrUpdate(int baseHeight) async { + if (_cachedBlockchainHeight < baseHeight || _cachedBlockchainHeight == 0) { + _cachedBlockchainHeight = await getNodeHeight(); + } + + return _cachedBlockchainHeight; + } + + void start() { + _cachedBlockchainHeight = 0; + _lastKnownBlockHeight = 0; + _initialSyncHeight = 0; + _updateSyncInfoTimer ??= + Timer.periodic(Duration(milliseconds: 1200), (_) async { + if (isNewTransactionExist()) { + onNewTransaction?.call(); + } + + var syncHeight = getSyncingHeight(); + + if (syncHeight <= 0) { + syncHeight = getCurrentHeight(); + } + + if (_initialSyncHeight <= 0) { + _initialSyncHeight = syncHeight; + } + + final bchHeight = await getNodeHeightOrUpdate(syncHeight); + + if (_lastKnownBlockHeight == syncHeight || syncHeight == null) { + return; + } + + _lastKnownBlockHeight = syncHeight; + final track = bchHeight - _initialSyncHeight; + final diff = track - (bchHeight - syncHeight); + final ptc = diff <= 0 ? 0.0 : diff / track; + final left = bchHeight - syncHeight; + + if (syncHeight < 0 || left < 0) { + return; + } + + // 1. Actual new height; 2. Blocks left to finish; 3. Progress in percents; + onNewBlock?.call(syncHeight, left, ptc); + }); + } + + void stop() => _updateSyncInfoTimer?.cancel(); +} + +SyncListener setListeners(void Function(int, int, double) onNewBlock, + void Function() onNewTransaction) { + final listener = SyncListener(onNewBlock, onNewTransaction); + setListenerNative(); + return listener; +} + +void onStartup() => onStartupNative(); + +void _storeSync(Object _) => storeSync(); + +bool _setupNodeSync(Map args) { + final address = args['address'] as String; + final login = (args['login'] ?? '') as String; + final password = (args['password'] ?? '') as String; + final useSSL = args['useSSL'] as bool; + final isLightWallet = args['isLightWallet'] as bool; + + return setupNodeSync( + address: address, + login: login, + password: password, + useSSL: useSSL, + isLightWallet: isLightWallet); +} + +bool _isConnected(Object _) => isConnectedSync(); + +int _getNodeHeight(Object _) => getNodeHeightSync(); + +void startRefresh() => startRefreshSync(); + +Future setupNode( + {String address, + String login, + String password, + bool useSSL = false, + bool isLightWallet = false}) => + compute, void>(_setupNodeSync, { + 'address': address, + 'login': login, + 'password': password, + 'useSSL': useSSL, + 'isLightWallet': isLightWallet + }); + +Future store() => compute(_storeSync, 0); + +Future isConnected() => compute(_isConnected, 0); + +Future getNodeHeight() => compute(_getNodeHeight, 0); + +void rescanBlockchainAsync() => rescanBlockchainAsyncNative(); + +String getSubaddressLabel(int accountIndex, int addressIndex) { + return convertUTF8ToString(pointer: getSubaddressLabelNative(accountIndex, addressIndex)); +} \ No newline at end of file diff --git a/cw_wownero/lib/api/wallet_manager.dart b/cw_wownero/lib/api/wallet_manager.dart new file mode 100644 index 00000000..f6bef253 --- /dev/null +++ b/cw_wownero/lib/api/wallet_manager.dart @@ -0,0 +1,247 @@ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import 'package:flutter/foundation.dart'; +import 'package:cw_wownero/api/convert_utf8_to_string.dart'; +import 'package:cw_wownero/api/signatures.dart'; +import 'package:cw_wownero/api/types.dart'; +import 'package:cw_wownero/api/wownero_api.dart'; +import 'package:cw_wownero/api/wallet.dart'; +import 'package:cw_wownero/api/exceptions/wallet_opening_exception.dart'; +import 'package:cw_wownero/api/exceptions/wallet_creation_exception.dart'; +import 'package:cw_wownero/api/exceptions/wallet_restore_from_keys_exception.dart'; +import 'package:cw_wownero/api/exceptions/wallet_restore_from_seed_exception.dart'; + +final createWalletNative = wowneroApi + .lookup>('create_wallet') + .asFunction(); + +final restoreWalletFromSeedNative = wowneroApi + .lookup>( + 'restore_wallet_from_seed') + .asFunction(); + +final restoreWalletFromKeysNative = wowneroApi + .lookup>( + 'restore_wallet_from_keys') + .asFunction(); + +final isWalletExistNative = wowneroApi + .lookup>('is_wallet_exist') + .asFunction(); + +final loadWalletNative = wowneroApi + .lookup>('load_wallet') + .asFunction(); + +final errorStringNative = wowneroApi + .lookup>('error_string') + .asFunction(); + +void createWalletSync( + {String path, String password, String language, int nettype = 0}) { + final pathPointer = Utf8.toUtf8(path); + final passwordPointer = Utf8.toUtf8(password); + final languagePointer = Utf8.toUtf8(language); + final errorMessagePointer = allocate(); + final isWalletCreated = createWalletNative(pathPointer, passwordPointer, + languagePointer, nettype, errorMessagePointer) != + 0; + + free(pathPointer); + free(passwordPointer); + free(languagePointer); + + if (!isWalletCreated) { + throw WalletCreationException( + message: convertUTF8ToString(pointer: errorMessagePointer)); + } + + // setupNodeSync(address: "node.wowneroworld.com:18089"); +} + +bool isWalletExistSync({String path}) { + final pathPointer = Utf8.toUtf8(path); + final isExist = isWalletExistNative(pathPointer) != 0; + + free(pathPointer); + + return isExist; +} + +void restoreWalletFromSeedSync( + {String path, + String password, + String seed, + int nettype = 0, + int restoreHeight = 0}) { + final pathPointer = Utf8.toUtf8(path); + final passwordPointer = Utf8.toUtf8(password); + final seedPointer = Utf8.toUtf8(seed); + final errorMessagePointer = allocate(); + final isWalletRestored = restoreWalletFromSeedNative( + pathPointer, + passwordPointer, + seedPointer, + nettype, + errorMessagePointer) != + 0; + + free(pathPointer); + free(passwordPointer); + free(seedPointer); + + if (!isWalletRestored) { + throw WalletRestoreFromSeedException( + message: convertUTF8ToString(pointer: errorMessagePointer)); + } +} + +void restoreWalletFromKeysSync( + {String path, + String password, + String language, + String address, + String viewKey, + String spendKey, + int nettype = 0, + int restoreHeight = 0}) { + final pathPointer = Utf8.toUtf8(path); + final passwordPointer = Utf8.toUtf8(password); + final languagePointer = Utf8.toUtf8(language); + final addressPointer = Utf8.toUtf8(address); + final viewKeyPointer = Utf8.toUtf8(viewKey); + final spendKeyPointer = Utf8.toUtf8(spendKey); + final errorMessagePointer = allocate(); + final isWalletRestored = restoreWalletFromKeysNative( + pathPointer, + passwordPointer, + languagePointer, + addressPointer, + viewKeyPointer, + spendKeyPointer, + nettype, + restoreHeight, + errorMessagePointer) != + 0; + + free(pathPointer); + free(passwordPointer); + free(languagePointer); + free(addressPointer); + free(viewKeyPointer); + free(spendKeyPointer); + + if (!isWalletRestored) { + throw WalletRestoreFromKeysException( + message: convertUTF8ToString(pointer: errorMessagePointer)); + } +} + +void loadWallet({String path, String password, int nettype = 0}) { + final pathPointer = Utf8.toUtf8(path); + final passwordPointer = Utf8.toUtf8(password); + final loaded = loadWalletNative(pathPointer, passwordPointer, nettype) != 0; + free(pathPointer); + free(passwordPointer); + + if (!loaded) { + throw WalletOpeningException( + message: convertUTF8ToString(pointer: errorStringNative())); + } +} + +void _createWallet(Map args) { + final path = args['path'] as String; + final password = args['password'] as String; + final language = args['language'] as String; + + createWalletSync(path: path, password: password, language: language); +} + +void _restoreFromSeed(Map args) { + final path = args['path'] as String; + final password = args['password'] as String; + final seed = args['seed'] as String; + final restoreHeight = args['restoreHeight'] as int; + + restoreWalletFromSeedSync( + path: path, password: password, seed: seed, restoreHeight: restoreHeight); +} + +void _restoreFromKeys(Map args) { + final path = args['path'] as String; + final password = args['password'] as String; + final language = args['language'] as String; + final restoreHeight = args['restoreHeight'] as int; + final address = args['address'] as String; + final viewKey = args['viewKey'] as String; + final spendKey = args['spendKey'] as String; + + restoreWalletFromKeysSync( + path: path, + password: password, + language: language, + restoreHeight: restoreHeight, + address: address, + viewKey: viewKey, + spendKey: spendKey); +} + +Future _openWallet(Map args) async => + loadWallet(path: args['path'], password: args['password']); + +bool _isWalletExist(String path) => isWalletExistSync(path: path); + +void openWallet({String path, String password, int nettype = 0}) async => + loadWallet(path: path, password: password, nettype: nettype); + +Future openWalletAsync(Map args) async => + compute(_openWallet, args); + +Future createWallet( + {String path, + String password, + String language, + int nettype = 0}) async => + compute(_createWallet, { + 'path': path, + 'password': password, + 'language': language, + 'nettype': nettype + }); + +Future restoreFromSeed( + {String path, + String password, + String seed, + int nettype = 0, + int restoreHeight = 0}) async => + compute, void>(_restoreFromSeed, { + 'path': path, + 'password': password, + 'seed': seed, + 'nettype': nettype, + 'restoreHeight': restoreHeight + }); + +Future restoreFromKeys( + {String path, + String password, + String language, + String address, + String viewKey, + String spendKey, + int nettype = 0, + int restoreHeight = 0}) async => + compute, void>(_restoreFromKeys, { + 'path': path, + 'password': password, + 'language': language, + 'address': address, + 'viewKey': viewKey, + 'spendKey': spendKey, + 'nettype': nettype, + 'restoreHeight': restoreHeight + }); + +Future isWalletExist({String path}) => compute(_isWalletExist, path); diff --git a/cw_wownero/lib/api/wownero_api.dart b/cw_wownero/lib/api/wownero_api.dart new file mode 100644 index 00000000..35e21f39 --- /dev/null +++ b/cw_wownero/lib/api/wownero_api.dart @@ -0,0 +1,6 @@ +import 'dart:ffi'; +import 'dart:io'; + +final DynamicLibrary wowneroApi = Platform.isAndroid + ? DynamicLibrary.open("libcw_wownero.so") + : DynamicLibrary.open("cw_wownero.framework/cw_wownero"); \ No newline at end of file diff --git a/cw_wownero/lib/api/wownero_output.dart b/cw_wownero/lib/api/wownero_output.dart new file mode 100644 index 00000000..831ee1f2 --- /dev/null +++ b/cw_wownero/lib/api/wownero_output.dart @@ -0,0 +1,8 @@ +import 'package:flutter/foundation.dart'; + +class MoneroOutput { + MoneroOutput({@required this.address, @required this.amount}); + + final String address; + final String amount; +} \ No newline at end of file diff --git a/cw_wownero/lib/mnemonics/english.dart b/cw_wownero/lib/mnemonics/english.dart new file mode 100644 index 00000000..8c835f08 --- /dev/null +++ b/cw_wownero/lib/mnemonics/english.dart @@ -0,0 +1,2052 @@ +class EnglishMnemonics { + static const words = [ + "abandon", + "ability", + "able", + "about", + "above", + "absent", + "absorb", + "abstract", + "absurd", + "abuse", + "access", + "accident", + "account", + "accuse", + "achieve", + "acid", + "acoustic", + "acquire", + "across", + "act", + "action", + "actor", + "actress", + "actual", + "adapt", + "add", + "addict", + "address", + "adjust", + "admit", + "adult", + "advance", + "advice", + "aerobic", + "affair", + "afford", + "afraid", + "again", + "age", + "agent", + "agree", + "ahead", + "aim", + "air", + "airport", + "aisle", + "alarm", + "album", + "alcohol", + "alert", + "alien", + "all", + "alley", + "allow", + "almost", + "alone", + "alpha", + "already", + "also", + "alter", + "always", + "amateur", + "amazing", + "among", + "amount", + "amused", + "analyst", + "anchor", + "ancient", + "anger", + "angle", + "angry", + "animal", + "ankle", + "announce", + "annual", + "another", + "answer", + "antenna", + "antique", + "anxiety", + "any", + "apart", + "apology", + "appear", + "apple", + "approve", + "april", + "arch", + "arctic", + "area", + "arena", + "argue", + "arm", + "armed", + "armor", + "army", + "around", + "arrange", + "arrest", + "arrive", + "arrow", + "art", + "artefact", + "artist", + "artwork", + "ask", + "aspect", + "assault", + "asset", + "assist", + "assume", + "asthma", + "athlete", + "atom", + "attack", + "attend", + "attitude", + "attract", + "auction", + "audit", + "august", + "aunt", + "author", + "auto", + "autumn", + "average", + "avocado", + "avoid", + "awake", + "aware", + "away", + "awesome", + "awful", + "awkward", + "axis", + "baby", + "bachelor", + "bacon", + "badge", + "bag", + "balance", + "balcony", + "ball", + "bamboo", + "banana", + "banner", + "bar", + "barely", + "bargain", + "barrel", + "base", + "basic", + "basket", + "battle", + "beach", + "bean", + "beauty", + "because", + "become", + "beef", + "before", + "begin", + "behave", + "behind", + "believe", + "below", + "belt", + "bench", + "benefit", + "best", + "betray", + "better", + "between", + "beyond", + "bicycle", + "bid", + "bike", + "bind", + "biology", + "bird", + "birth", + "bitter", + "black", + "blade", + "blame", + "blanket", + "blast", + "bleak", + "bless", + "blind", + "blood", + "blossom", + "blouse", + "blue", + "blur", + "blush", + "board", + "boat", + "body", + "boil", + "bomb", + "bone", + "bonus", + "book", + "boost", + "border", + "boring", + "borrow", + "boss", + "bottom", + "bounce", + "box", + "boy", + "bracket", + "brain", + "brand", + "brass", + "brave", + "bread", + "breeze", + "brick", + "bridge", + "brief", + "bright", + "bring", + "brisk", + "broccoli", + "broken", + "bronze", + "broom", + "brother", + "brown", + "brush", + "bubble", + "buddy", + "budget", + "buffalo", + "build", + "bulb", + "bulk", + "bullet", + "bundle", + "bunker", + "burden", + "burger", + "burst", + "bus", + "business", + "busy", + "butter", + "buyer", + "buzz", + "cabbage", + "cabin", + "cable", + "cactus", + "cage", + "cake", + "call", + "calm", + "camera", + "camp", + "can", + "canal", + "cancel", + "candy", + "cannon", + "canoe", + "canvas", + "canyon", + "capable", + "capital", + "captain", + "car", + "carbon", + "card", + "cargo", + "carpet", + "carry", + "cart", + "case", + "cash", + "casino", + "castle", + "casual", + "cat", + "catalog", + "catch", + "category", + "cattle", + "caught", + "cause", + "caution", + "cave", + "ceiling", + "celery", + "cement", + "census", + "century", + "cereal", + "certain", + "chair", + "chalk", + "champion", + "change", + "chaos", + "chapter", + "charge", + "chase", + "chat", + "cheap", + "check", + "cheese", + "chef", + "cherry", + "chest", + "chicken", + "chief", + "child", + "chimney", + "choice", + "choose", + "chronic", + "chuckle", + "chunk", + "churn", + "cigar", + "cinnamon", + "circle", + "citizen", + "city", + "civil", + "claim", + "clap", + "clarify", + "claw", + "clay", + "clean", + "clerk", + "clever", + "click", + "client", + "cliff", + "climb", + "clinic", + "clip", + "clock", + "clog", + "close", + "cloth", + "cloud", + "clown", + "club", + "clump", + "cluster", + "clutch", + "coach", + "coast", + "coconut", + "code", + "coffee", + "coil", + "coin", + "collect", + "color", + "column", + "combine", + "come", + "comfort", + "comic", + "common", + "company", + "concert", + "conduct", + "confirm", + "congress", + "connect", + "consider", + "control", + "convince", + "cook", + "cool", + "copper", + "copy", + "coral", + "core", + "corn", + "correct", + "cost", + "cotton", + "couch", + "country", + "couple", + "course", + "cousin", + "cover", + "coyote", + "crack", + "cradle", + "craft", + "cram", + "crane", + "crash", + "crater", + "crawl", + "crazy", + "cream", + "credit", + "creek", + "crew", + "cricket", + "crime", + "crisp", + "critic", + "crop", + "cross", + "crouch", + "crowd", + "crucial", + "cruel", + "cruise", + "crumble", + "crunch", + "crush", + "cry", + "crystal", + "cube", + "culture", + "cup", + "cupboard", + "curious", + "current", + "curtain", + "curve", + "cushion", + "custom", + "cute", + "cycle", + "dad", + "damage", + "damp", + "dance", + "danger", + "daring", + "dash", + "daughter", + "dawn", + "day", + "deal", + "debate", + "debris", + "decade", + "december", + "decide", + "decline", + "decorate", + "decrease", + "deer", + "defense", + "define", + "defy", + "degree", + "delay", + "deliver", + "demand", + "demise", + "denial", + "dentist", + "deny", + "depart", + "depend", + "deposit", + "depth", + "deputy", + "derive", + "describe", + "desert", + "design", + "desk", + "despair", + "destroy", + "detail", + "detect", + "develop", + "device", + "devote", + "diagram", + "dial", + "diamond", + "diary", + "dice", + "diesel", + "diet", + "differ", + "digital", + "dignity", + "dilemma", + "dinner", + "dinosaur", + "direct", + "dirt", + "disagree", + "discover", + "disease", + "dish", + "dismiss", + "disorder", + "display", + "distance", + "divert", + "divide", + "divorce", + "dizzy", + "doctor", + "document", + "dog", + "doll", + "dolphin", + "domain", + "donate", + "donkey", + "donor", + "door", + "dose", + "double", + "dove", + "draft", + "dragon", + "drama", + "drastic", + "draw", + "dream", + "dress", + "drift", + "drill", + "drink", + "drip", + "drive", + "drop", + "drum", + "dry", + "duck", + "dumb", + "dune", + "during", + "dust", + "dutch", + "duty", + "dwarf", + "dynamic", + "eager", + "eagle", + "early", + "earn", + "earth", + "easily", + "east", + "easy", + "echo", + "ecology", + "economy", + "edge", + "edit", + "educate", + "effort", + "egg", + "eight", + "either", + "elbow", + "elder", + "electric", + "elegant", + "element", + "elephant", + "elevator", + "elite", + "else", + "embark", + "embody", + "embrace", + "emerge", + "emotion", + "employ", + "empower", + "empty", + "enable", + "enact", + "end", + "endless", + "endorse", + "enemy", + "energy", + "enforce", + "engage", + "engine", + "enhance", + "enjoy", + "enlist", + "enough", + "enrich", + "enroll", + "ensure", + "enter", + "entire", + "entry", + "envelope", + "episode", + "equal", + "equip", + "era", + "erase", + "erode", + "erosion", + "error", + "erupt", + "escape", + "essay", + "essence", + "estate", + "eternal", + "ethics", + "evidence", + "evil", + "evoke", + "evolve", + "exact", + "example", + "excess", + "exchange", + "excite", + "exclude", + "excuse", + "execute", + "exercise", + "exhaust", + "exhibit", + "exile", + "exist", + "exit", + "exotic", + "expand", + "expect", + "expire", + "explain", + "expose", + "express", + "extend", + "extra", + "eye", + "eyebrow", + "fabric", + "face", + "faculty", + "fade", + "faint", + "faith", + "fall", + "false", + "fame", + "family", + "famous", + "fan", + "fancy", + "fantasy", + "farm", + "fashion", + "fat", + "fatal", + "father", + "fatigue", + "fault", + "favorite", + "feature", + "february", + "federal", + "fee", + "feed", + "feel", + "female", + "fence", + "festival", + "fetch", + "fever", + "few", + "fiber", + "fiction", + "field", + "figure", + "file", + "film", + "filter", + "final", + "find", + "fine", + "finger", + "finish", + "fire", + "firm", + "first", + "fiscal", + "fish", + "fit", + "fitness", + "fix", + "flag", + "flame", + "flash", + "flat", + "flavor", + "flee", + "flight", + "flip", + "float", + "flock", + "floor", + "flower", + "fluid", + "flush", + "fly", + "foam", + "focus", + "fog", + "foil", + "fold", + "follow", + "food", + "foot", + "force", + "forest", + "forget", + "fork", + "fortune", + "forum", + "forward", + "fossil", + "foster", + "found", + "fox", + "fragile", + "frame", + "frequent", + "fresh", + "friend", + "fringe", + "frog", + "front", + "frost", + "frown", + "frozen", + "fruit", + "fuel", + "fun", + "funny", + "furnace", + "fury", + "future", + "gadget", + "gain", + "galaxy", + "gallery", + "game", + "gap", + "garage", + "garbage", + "garden", + "garlic", + "garment", + "gas", + "gasp", + "gate", + "gather", + "gauge", + "gaze", + "general", + "genius", + "genre", + "gentle", + "genuine", + "gesture", + "ghost", + "giant", + "gift", + "giggle", + "ginger", + "giraffe", + "girl", + "give", + "glad", + "glance", + "glare", + "glass", + "glide", + "glimpse", + "globe", + "gloom", + "glory", + "glove", + "glow", + "glue", + "goat", + "goddess", + "gold", + "good", + "goose", + "gorilla", + "gospel", + "gossip", + "govern", + "gown", + "grab", + "grace", + "grain", + "grant", + "grape", + "grass", + "gravity", + "great", + "green", + "grid", + "grief", + "grit", + "grocery", + "group", + "grow", + "grunt", + "guard", + "guess", + "guide", + "guilt", + "guitar", + "gun", + "gym", + "habit", + "hair", + "half", + "hammer", + "hamster", + "hand", + "happy", + "harbor", + "hard", + "harsh", + "harvest", + "hat", + "have", + "hawk", + "hazard", + "head", + "health", + "heart", + "heavy", + "hedgehog", + "height", + "hello", + "helmet", + "help", + "hen", + "hero", + "hidden", + "high", + "hill", + "hint", + "hip", + "hire", + "history", + "hobby", + "hockey", + "hold", + "hole", + "holiday", + "hollow", + "home", + "honey", + "hood", + "hope", + "horn", + "horror", + "horse", + "hospital", + "host", + "hotel", + "hour", + "hover", + "hub", + "huge", + "human", + "humble", + "humor", + "hundred", + "hungry", + "hunt", + "hurdle", + "hurry", + "hurt", + "husband", + "hybrid", + "ice", + "icon", + "idea", + "identify", + "idle", + "ignore", + "ill", + "illegal", + "illness", + "image", + "imitate", + "immense", + "immune", + "impact", + "impose", + "improve", + "impulse", + "inch", + "include", + "income", + "increase", + "index", + "indicate", + "indoor", + "industry", + "infant", + "inflict", + "inform", + "inhale", + "inherit", + "initial", + "inject", + "injury", + "inmate", + "inner", + "innocent", + "input", + "inquiry", + "insane", + "insect", + "inside", + "inspire", + "install", + "intact", + "interest", + "into", + "invest", + "invite", + "involve", + "iron", + "island", + "isolate", + "issue", + "item", + "ivory", + "jacket", + "jaguar", + "jar", + "jazz", + "jealous", + "jeans", + "jelly", + "jewel", + "job", + "join", + "joke", + "journey", + "joy", + "judge", + "juice", + "jump", + "jungle", + "junior", + "junk", + "just", + "kangaroo", + "keen", + "keep", + "ketchup", + "key", + "kick", + "kid", + "kidney", + "kind", + "kingdom", + "kiss", + "kit", + "kitchen", + "kite", + "kitten", + "kiwi", + "knee", + "knife", + "knock", + "know", + "lab", + "label", + "labor", + "ladder", + "lady", + "lake", + "lamp", + "language", + "laptop", + "large", + "later", + "latin", + "laugh", + "laundry", + "lava", + "law", + "lawn", + "lawsuit", + "layer", + "lazy", + "leader", + "leaf", + "learn", + "leave", + "lecture", + "left", + "leg", + "legal", + "legend", + "leisure", + "lemon", + "lend", + "length", + "lens", + "leopard", + "lesson", + "letter", + "level", + "liar", + "liberty", + "library", + "license", + "life", + "lift", + "light", + "like", + "limb", + "limit", + "link", + "lion", + "liquid", + "list", + "little", + "live", + "lizard", + "load", + "loan", + "lobster", + "local", + "lock", + "logic", + "lonely", + "long", + "loop", + "lottery", + "loud", + "lounge", + "love", + "loyal", + "lucky", + "luggage", + "lumber", + "lunar", + "lunch", + "luxury", + "lyrics", + "machine", + "mad", + "magic", + "magnet", + "maid", + "mail", + "main", + "major", + "make", + "mammal", + "man", + "manage", + "mandate", + "mango", + "mansion", + "manual", + "maple", + "marble", + "march", + "margin", + "marine", + "market", + "marriage", + "mask", + "mass", + "master", + "match", + "material", + "math", + "matrix", + "matter", + "maximum", + "maze", + "meadow", + "mean", + "measure", + "meat", + "mechanic", + "medal", + "media", + "melody", + "melt", + "member", + "memory", + "mention", + "menu", + "mercy", + "merge", + "merit", + "merry", + "mesh", + "message", + "metal", + "method", + "middle", + "midnight", + "milk", + "million", + "mimic", + "mind", + "minimum", + "minor", + "minute", + "miracle", + "mirror", + "misery", + "miss", + "mistake", + "mix", + "mixed", + "mixture", + "mobile", + "model", + "modify", + "mom", + "moment", + "monitor", + "monkey", + "monster", + "month", + "moon", + "moral", + "more", + "morning", + "mosquito", + "mother", + "motion", + "motor", + "mountain", + "mouse", + "move", + "movie", + "much", + "muffin", + "mule", + "multiply", + "muscle", + "museum", + "mushroom", + "music", + "must", + "mutual", + "myself", + "mystery", + "myth", + "naive", + "name", + "napkin", + "narrow", + "nasty", + "nation", + "nature", + "near", + "neck", + "need", + "negative", + "neglect", + "neither", + "nephew", + "nerve", + "nest", + "net", + "network", + "neutral", + "never", + "news", + "next", + "nice", + "night", + "noble", + "noise", + "nominee", + "noodle", + "normal", + "north", + "nose", + "notable", + "note", + "nothing", + "notice", + "novel", + "now", + "nuclear", + "number", + "nurse", + "nut", + "oak", + "obey", + "object", + "oblige", + "obscure", + "observe", + "obtain", + "obvious", + "occur", + "ocean", + "october", + "odor", + "off", + "offer", + "office", + "often", + "oil", + "okay", + "old", + "olive", + "olympic", + "omit", + "once", + "one", + "onion", + "online", + "only", + "open", + "opera", + "opinion", + "oppose", + "option", + "orange", + "orbit", + "orchard", + "order", + "ordinary", + "organ", + "orient", + "original", + "orphan", + "ostrich", + "other", + "outdoor", + "outer", + "output", + "outside", + "oval", + "oven", + "over", + "own", + "owner", + "oxygen", + "oyster", + "ozone", + "pact", + "paddle", + "page", + "pair", + "palace", + "palm", + "panda", + "panel", + "panic", + "panther", + "paper", + "parade", + "parent", + "park", + "parrot", + "party", + "pass", + "patch", + "path", + "patient", + "patrol", + "pattern", + "pause", + "pave", + "payment", + "peace", + "peanut", + "pear", + "peasant", + "pelican", + "pen", + "penalty", + "pencil", + "people", + "pepper", + "perfect", + "permit", + "person", + "pet", + "phone", + "photo", + "phrase", + "physical", + "piano", + "picnic", + "picture", + "piece", + "pig", + "pigeon", + "pill", + "pilot", + "pink", + "pioneer", + "pipe", + "pistol", + "pitch", + "pizza", + "place", + "planet", + "plastic", + "plate", + "play", + "please", + "pledge", + "pluck", + "plug", + "plunge", + "poem", + "poet", + "point", + "polar", + "pole", + "police", + "pond", + "pony", + "pool", + "popular", + "portion", + "position", + "possible", + "post", + "potato", + "pottery", + "poverty", + "powder", + "power", + "practice", + "praise", + "predict", + "prefer", + "prepare", + "present", + "pretty", + "prevent", + "price", + "pride", + "primary", + "print", + "priority", + "prison", + "private", + "prize", + "problem", + "process", + "produce", + "profit", + "program", + "project", + "promote", + "proof", + "property", + "prosper", + "protect", + "proud", + "provide", + "public", + "pudding", + "pull", + "pulp", + "pulse", + "pumpkin", + "punch", + "pupil", + "puppy", + "purchase", + "purity", + "purpose", + "purse", + "push", + "put", + "puzzle", + "pyramid", + "quality", + "quantum", + "quarter", + "question", + "quick", + "quit", + "quiz", + "quote", + "rabbit", + "raccoon", + "race", + "rack", + "radar", + "radio", + "rail", + "rain", + "raise", + "rally", + "ramp", + "ranch", + "random", + "range", + "rapid", + "rare", + "rate", + "rather", + "raven", + "raw", + "razor", + "ready", + "real", + "reason", + "rebel", + "rebuild", + "recall", + "receive", + "recipe", + "record", + "recycle", + "reduce", + "reflect", + "reform", + "refuse", + "region", + "regret", + "regular", + "reject", + "relax", + "release", + "relief", + "rely", + "remain", + "remember", + "remind", + "remove", + "render", + "renew", + "rent", + "reopen", + "repair", + "repeat", + "replace", + "report", + "require", + "rescue", + "resemble", + "resist", + "resource", + "response", + "result", + "retire", + "retreat", + "return", + "reunion", + "reveal", + "review", + "reward", + "rhythm", + "rib", + "ribbon", + "rice", + "rich", + "ride", + "ridge", + "rifle", + "right", + "rigid", + "ring", + "riot", + "ripple", + "risk", + "ritual", + "rival", + "river", + "road", + "roast", + "robot", + "robust", + "rocket", + "romance", + "roof", + "rookie", + "room", + "rose", + "rotate", + "rough", + "round", + "route", + "royal", + "rubber", + "rude", + "rug", + "rule", + "run", + "runway", + "rural", + "sad", + "saddle", + "sadness", + "safe", + "sail", + "salad", + "salmon", + "salon", + "salt", + "salute", + "same", + "sample", + "sand", + "satisfy", + "satoshi", + "sauce", + "sausage", + "save", + "say", + "scale", + "scan", + "scare", + "scatter", + "scene", + "scheme", + "school", + "science", + "scissors", + "scorpion", + "scout", + "scrap", + "screen", + "script", + "scrub", + "sea", + "search", + "season", + "seat", + "second", + "secret", + "section", + "security", + "seed", + "seek", + "segment", + "select", + "sell", + "seminar", + "senior", + "sense", + "sentence", + "series", + "service", + "session", + "settle", + "setup", + "seven", + "shadow", + "shaft", + "shallow", + "share", + "shed", + "shell", + "sheriff", + "shield", + "shift", + "shine", + "ship", + "shiver", + "shock", + "shoe", + "shoot", + "shop", + "short", + "shoulder", + "shove", + "shrimp", + "shrug", + "shuffle", + "shy", + "sibling", + "sick", + "side", + "siege", + "sight", + "sign", + "silent", + "silk", + "silly", + "silver", + "similar", + "simple", + "since", + "sing", + "siren", + "sister", + "situate", + "six", + "size", + "skate", + "sketch", + "ski", + "skill", + "skin", + "skirt", + "skull", + "slab", + "slam", + "sleep", + "slender", + "slice", + "slide", + "slight", + "slim", + "slogan", + "slot", + "slow", + "slush", + "small", + "smart", + "smile", + "smoke", + "smooth", + "snack", + "snake", + "snap", + "sniff", + "snow", + "soap", + "soccer", + "social", + "sock", + "soda", + "soft", + "solar", + "soldier", + "solid", + "solution", + "solve", + "someone", + "song", + "soon", + "sorry", + "sort", + "soul", + "sound", + "soup", + "source", + "south", + "space", + "spare", + "spatial", + "spawn", + "speak", + "special", + "speed", + "spell", + "spend", + "sphere", + "spice", + "spider", + "spike", + "spin", + "spirit", + "split", + "spoil", + "sponsor", + "spoon", + "sport", + "spot", + "spray", + "spread", + "spring", + "spy", + "square", + "squeeze", + "squirrel", + "stable", + "stadium", + "staff", + "stage", + "stairs", + "stamp", + "stand", + "start", + "state", + "stay", + "steak", + "steel", + "stem", + "step", + "stereo", + "stick", + "still", + "sting", + "stock", + "stomach", + "stone", + "stool", + "story", + "stove", + "strategy", + "street", + "strike", + "strong", + "struggle", + "student", + "stuff", + "stumble", + "style", + "subject", + "submit", + "subway", + "success", + "such", + "sudden", + "suffer", + "sugar", + "suggest", + "suit", + "summer", + "sun", + "sunny", + "sunset", + "super", + "supply", + "supreme", + "sure", + "surface", + "surge", + "surprise", + "surround", + "survey", + "suspect", + "sustain", + "swallow", + "swamp", + "swap", + "swarm", + "swear", + "sweet", + "swift", + "swim", + "swing", + "switch", + "sword", + "symbol", + "symptom", + "syrup", + "system", + "table", + "tackle", + "tag", + "tail", + "talent", + "talk", + "tank", + "tape", + "target", + "task", + "taste", + "tattoo", + "taxi", + "teach", + "team", + "tell", + "ten", + "tenant", + "tennis", + "tent", + "term", + "test", + "text", + "thank", + "that", + "theme", + "then", + "theory", + "there", + "they", + "thing", + "this", + "thought", + "three", + "thrive", + "throw", + "thumb", + "thunder", + "ticket", + "tide", + "tiger", + "tilt", + "timber", + "time", + "tiny", + "tip", + "tired", + "tissue", + "title", + "toast", + "tobacco", + "today", + "toddler", + "toe", + "together", + "toilet", + "token", + "tomato", + "tomorrow", + "tone", + "tongue", + "tonight", + "tool", + "tooth", + "top", + "topic", + "topple", + "torch", + "tornado", + "tortoise", + "toss", + "total", + "tourist", + "toward", + "tower", + "town", + "toy", + "track", + "trade", + "traffic", + "tragic", + "train", + "transfer", + "trap", + "trash", + "travel", + "tray", + "treat", + "tree", + "trend", + "trial", + "tribe", + "trick", + "trigger", + "trim", + "trip", + "trophy", + "trouble", + "truck", + "true", + "truly", + "trumpet", + "trust", + "truth", + "try", + "tube", + "tuition", + "tumble", + "tuna", + "tunnel", + "turkey", + "turn", + "turtle", + "twelve", + "twenty", + "twice", + "twin", + "twist", + "two", + "type", + "typical", + "ugly", + "umbrella", + "unable", + "unaware", + "uncle", + "uncover", + "under", + "undo", + "unfair", + "unfold", + "unhappy", + "uniform", + "unique", + "unit", + "universe", + "unknown", + "unlock", + "until", + "unusual", + "unveil", + "update", + "upgrade", + "uphold", + "upon", + "upper", + "upset", + "urban", + "urge", + "usage", + "use", + "used", + "useful", + "useless", + "usual", + "utility", + "vacant", + "vacuum", + "vague", + "valid", + "valley", + "valve", + "van", + "vanish", + "vapor", + "various", + "vast", + "vault", + "vehicle", + "velvet", + "vendor", + "venture", + "venue", + "verb", + "verify", + "version", + "very", + "vessel", + "veteran", + "viable", + "vibrant", + "vicious", + "victory", + "video", + "view", + "village", + "vintage", + "violin", + "virtual", + "virus", + "visa", + "visit", + "visual", + "vital", + "vivid", + "vocal", + "voice", + "void", + "volcano", + "volume", + "vote", + "voyage", + "wage", + "wagon", + "wait", + "walk", + "wall", + "walnut", + "want", + "warfare", + "warm", + "warrior", + "wash", + "wasp", + "waste", + "water", + "wave", + "way", + "wealth", + "weapon", + "wear", + "weasel", + "weather", + "web", + "wedding", + "weekend", + "weird", + "welcome", + "west", + "wet", + "whale", + "what", + "wheat", + "wheel", + "when", + "where", + "whip", + "whisper", + "wide", + "width", + "wife", + "wild", + "will", + "win", + "window", + "wine", + "wing", + "wink", + "winner", + "winter", + "wire", + "wisdom", + "wise", + "wish", + "witness", + "wolf", + "woman", + "wonder", + "wood", + "wool", + "word", + "work", + "world", + "worry", + "worth", + "wrap", + "wreck", + "wrestle", + "wrist", + "write", + "wrong", + "yard", + "year", + "yellow", + "you", + "young", + "youth", + "zebra", + "zero", + "zone", + "zoo" + ]; +} diff --git a/cw_wownero/lib/pending_wownero_transaction.dart b/cw_wownero/lib/pending_wownero_transaction.dart new file mode 100644 index 00000000..e14bfb06 --- /dev/null +++ b/cw_wownero/lib/pending_wownero_transaction.dart @@ -0,0 +1,48 @@ +import 'package:cw_wownero/api/structs/pending_transaction.dart'; +import 'package:cw_wownero/api/transaction_history.dart' + as wownero_transaction_history; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cake_wallet/core/amount_converter.dart'; + +import 'package:cw_core/pending_transaction.dart'; + +class DoubleSpendException implements Exception { + DoubleSpendException(); + + @override + String toString() => + 'This transaction cannot be committed. This can be due to many reasons including the wallet not being synced, there is not enough WOW in your available balance, or previous transactions are not yet fully processed.'; +} + +class PendingWowneroTransaction with PendingTransaction { + PendingWowneroTransaction(this.pendingTransactionDescription); + + final PendingTransactionDescription pendingTransactionDescription; + + @override + String get id => pendingTransactionDescription.hash; + + @override + String get amountFormatted => AmountConverter.amountIntToString( + CryptoCurrency.wow, pendingTransactionDescription.amount); + + @override + String get feeFormatted => AmountConverter.amountIntToString( + CryptoCurrency.wow, pendingTransactionDescription.fee); + + @override + Future commit() async { + try { + wownero_transaction_history.commitTransactionFromPointerAddress( + address: pendingTransactionDescription.pointerAddress); + } catch (e) { + final message = e.toString(); + + if (message.contains('Reason: double spend')) { + throw DoubleSpendException(); + } + + rethrow; + } + } +} diff --git a/cw_wownero/lib/wownero_account_list.dart b/cw_wownero/lib/wownero_account_list.dart new file mode 100644 index 00000000..88644145 --- /dev/null +++ b/cw_wownero/lib/wownero_account_list.dart @@ -0,0 +1,78 @@ +import 'package:mobx/mobx.dart'; +import 'package:cw_core/account.dart'; +import 'package:cw_wownero/api/account_list.dart' as account_list; + +part 'wownero_account_list.g.dart'; + +class WowneroAccountList = WowneroAccountListBase with _$WowneroAccountList; + +abstract class WowneroAccountListBase with Store { + WowneroAccountListBase() + : accounts = ObservableList(), + _isRefreshing = false, + _isUpdating = false { + refresh(); + print(account_list.accountSizeNative()); + } + + @observable + ObservableList accounts; + bool _isRefreshing; + bool _isUpdating; + + void update() async { + if (_isUpdating) { + return; + } + + try { + _isUpdating = true; + refresh(); + final accounts = getAll(); + + if (accounts.isNotEmpty) { + this.accounts.clear(); + this.accounts.addAll(accounts); + } + + _isUpdating = false; + } catch (e) { + _isUpdating = false; + rethrow; + } + } + + List getAll() => account_list + .getAllAccount() + .map((accountRow) => Account( + id: accountRow.getId(), + label: accountRow.getLabel())) + .toList(); + + Future addAccount({String label}) async { + await account_list.addAccount(label: label); + update(); + } + + Future setLabelAccount({int accountIndex, String label}) async { + await account_list.setLabelForAccount( + accountIndex: accountIndex, label: label); + update(); + } + + void refresh() { + if (_isRefreshing) { + return; + } + + try { + _isRefreshing = true; + account_list.refreshAccounts(); + _isRefreshing = false; + } catch (e) { + _isRefreshing = false; + print(e); + rethrow; + } + } +} diff --git a/cw_wownero/lib/wownero_amount_format.dart b/cw_wownero/lib/wownero_amount_format.dart new file mode 100644 index 00000000..450969e7 --- /dev/null +++ b/cw_wownero/lib/wownero_amount_format.dart @@ -0,0 +1,18 @@ +import 'package:intl/intl.dart'; +import 'package:cw_core/crypto_amount_format.dart'; + +const wowneroAmountLength = 11; +const wowneroAmountDivider = 100000000000; +final wowneroAmountFormat = NumberFormat() + ..maximumFractionDigits = wowneroAmountLength + ..minimumFractionDigits = 1; + +String wowneroAmountToString({int amount}) => wowneroAmountFormat + .format(cryptoAmountToDouble(amount: amount, divider: wowneroAmountDivider)) + .replaceAll(',', ''); + +double wowneroAmountToDouble({int amount}) => + cryptoAmountToDouble(amount: amount, divider: wowneroAmountDivider); + +int wowneroParseAmount({String amount}) => + (double.parse(amount) * wowneroAmountDivider).toInt(); diff --git a/cw_wownero/lib/wownero_balance.dart b/cw_wownero/lib/wownero_balance.dart new file mode 100644 index 00000000..e3a19466 --- /dev/null +++ b/cw_wownero/lib/wownero_balance.dart @@ -0,0 +1,30 @@ +import 'package:cw_core/balance.dart'; +import 'package:flutter/foundation.dart'; +import 'package:cw_wownero/wownero_amount_format.dart'; + +class WowneroBalance extends Balance { + WowneroBalance({@required this.fullBalance, @required this.unlockedBalance}) + : formattedFullBalance = wowneroAmountToString(amount: fullBalance), + formattedUnlockedBalance = + wowneroAmountToString(amount: unlockedBalance), + super(unlockedBalance, fullBalance); + + WowneroBalance.fromString( + {@required this.formattedFullBalance, + @required this.formattedUnlockedBalance}) + : fullBalance = wowneroParseAmount(amount: formattedFullBalance), + unlockedBalance = wowneroParseAmount(amount: formattedUnlockedBalance), + super(wowneroParseAmount(amount: formattedUnlockedBalance), + wowneroParseAmount(amount: formattedFullBalance)); + + final int fullBalance; + final int unlockedBalance; + final String formattedFullBalance; + final String formattedUnlockedBalance; + + @override + String get formattedAvailableBalance => formattedUnlockedBalance; + + @override + String get formattedAdditionalBalance => formattedFullBalance; +} diff --git a/cw_wownero/lib/wownero_subaddress_list.dart b/cw_wownero/lib/wownero_subaddress_list.dart new file mode 100644 index 00000000..b6f47034 --- /dev/null +++ b/cw_wownero/lib/wownero_subaddress_list.dart @@ -0,0 +1,90 @@ +import 'package:cw_wownero/api/structs/subaddress_row.dart'; +import 'package:flutter/services.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cw_wownero/api/subaddress_list.dart' as subaddress_list; +import 'package:cw_core/subaddress.dart'; + +part 'wownero_subaddress_list.g.dart'; + +class WowneroSubaddressList = WowneroSubaddressListBase + with _$WowneroSubaddressList; + +abstract class WowneroSubaddressListBase with Store { + WowneroSubaddressListBase() { + _isRefreshing = false; + _isUpdating = false; + subaddresses = ObservableList(); + } + + @observable + ObservableList subaddresses; + + bool _isRefreshing; + bool _isUpdating; + + void update({int accountIndex}) { + if (_isUpdating) { + return; + } + + try { + _isUpdating = true; + refresh(accountIndex: accountIndex); + subaddresses.clear(); + subaddresses.addAll(getAll()); + _isUpdating = false; + } catch (e) { + _isUpdating = false; + rethrow; + } + } + + List getAll() { + var subaddresses = subaddress_list.getAllSubaddresses(); + + if (subaddresses.length > 2) { + final primary = subaddresses.first; + final rest = subaddresses.sublist(1).reversed; + subaddresses = [primary] + rest.toList(); + } + + return subaddresses + .map((subaddressRow) => Subaddress( + id: subaddressRow.getId(), + address: subaddressRow.getAddress(), + label: subaddressRow.getId() == 0 && + subaddressRow.getLabel().toLowerCase() == 'Primary account'.toLowerCase() + ? 'Primary address' + : subaddressRow.getLabel())) + .toList(); + } + + Future addSubaddress({int accountIndex, String label}) async { + await subaddress_list.addSubaddress( + accountIndex: accountIndex, label: label); + update(accountIndex: accountIndex); + } + + Future setLabelSubaddress( + {int accountIndex, int addressIndex, String label}) async { + await subaddress_list.setLabelForSubaddress( + accountIndex: accountIndex, addressIndex: addressIndex, label: label); + update(accountIndex: accountIndex); + } + + void refresh({int accountIndex}) { + if (_isRefreshing) { + return; + } + + try { + _isRefreshing = true; + subaddress_list.refreshSubaddresses(accountIndex: accountIndex); + _isRefreshing = false; + } on PlatformException catch (e) { + _isRefreshing = false; + print(e); + rethrow; + } + } +} diff --git a/cw_wownero/lib/wownero_transaction_creation_credentials.dart b/cw_wownero/lib/wownero_transaction_creation_credentials.dart new file mode 100644 index 00000000..c6bd291e --- /dev/null +++ b/cw_wownero/lib/wownero_transaction_creation_credentials.dart @@ -0,0 +1,9 @@ +import 'package:cw_core/monero_transaction_priority.dart'; +import 'package:cw_core/output_info.dart'; + +class WowneroTransactionCreationCredentials { + WowneroTransactionCreationCredentials({this.outputs, this.priority}); + + final List outputs; + final MoneroTransactionPriority priority; +} diff --git a/cw_wownero/lib/wownero_transaction_creation_exception.dart b/cw_wownero/lib/wownero_transaction_creation_exception.dart new file mode 100644 index 00000000..3b24be87 --- /dev/null +++ b/cw_wownero/lib/wownero_transaction_creation_exception.dart @@ -0,0 +1,8 @@ +class WowneroTransactionCreationException implements Exception { + WowneroTransactionCreationException(this.message); + + final String message; + + @override + String toString() => message; +} \ No newline at end of file diff --git a/cw_wownero/lib/wownero_transaction_history.dart b/cw_wownero/lib/wownero_transaction_history.dart new file mode 100644 index 00000000..49e8c284 --- /dev/null +++ b/cw_wownero/lib/wownero_transaction_history.dart @@ -0,0 +1,27 @@ +import 'dart:core'; +import 'package:mobx/mobx.dart'; +import 'package:cw_core/transaction_history.dart'; +import 'package:cw_wownero/wownero_transaction_info.dart'; + +part 'wownero_transaction_history.g.dart'; + +class WowneroTransactionHistory = WowneroTransactionHistoryBase + with _$WowneroTransactionHistory; + +abstract class WowneroTransactionHistoryBase + extends TransactionHistoryBase with Store { + WowneroTransactionHistoryBase() { + transactions = ObservableMap(); + } + + @override + Future save() async {} + + @override + void addOne(WowneroTransactionInfo transaction) => + transactions[transaction.id] = transaction; + + @override + void addMany(Map transactions) => + this.transactions.addAll(transactions); +} diff --git a/cw_wownero/lib/wownero_transaction_info.dart b/cw_wownero/lib/wownero_transaction_info.dart new file mode 100644 index 00000000..040697c5 --- /dev/null +++ b/cw_wownero/lib/wownero_transaction_info.dart @@ -0,0 +1,80 @@ +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_wownero/wownero_amount_format.dart'; +import 'package:cw_wownero/api/structs/transaction_info_row.dart'; +import 'package:cw_core/parseBoolFromString.dart'; +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_core/format_amount.dart'; +import 'package:cw_wownero/api/transaction_history.dart'; + +class WowneroTransactionInfo extends TransactionInfo { + WowneroTransactionInfo(this.id, this.height, this.direction, this.date, + this.isPending, this.amount, this.accountIndex, this.addressIndex, this.fee); + + WowneroTransactionInfo.fromMap(Map map) + : id = (map['hash'] ?? '') as String, + height = (map['height'] ?? 0) as int, + direction = + parseTransactionDirectionFromNumber(map['direction'] as String) ?? + TransactionDirection.incoming, + date = DateTime.fromMillisecondsSinceEpoch( + (int.parse(map['timestamp'] as String) ?? 0) * 1000), + isPending = parseBoolFromString(map['isPending'] as String), + amount = map['amount'] as int, + accountIndex = int.parse(map['accountIndex'] as String), + addressIndex = map['addressIndex'] as int, + key = getTxKey((map['hash'] ?? '') as String), + fee = map['fee'] as int ?? 0 { + additionalInfo = { + 'key': key, + 'accountIndex': accountIndex, + 'addressIndex': addressIndex + }; + } + + WowneroTransactionInfo.fromRow(TransactionInfoRow row) + : id = row.getHash(), + height = row.blockHeight, + direction = parseTransactionDirectionFromInt(row.direction) ?? + TransactionDirection.incoming, + date = DateTime.fromMillisecondsSinceEpoch(row.getDatetime() * 1000), + isPending = row.isPending != 0, + amount = row.getAmount(), + accountIndex = row.subaddrAccount, + addressIndex = row.subaddrIndex, + key = getTxKey(row.getHash()), + fee = row.fee { + additionalInfo = { + 'key': key, + 'accountIndex': accountIndex, + 'addressIndex': addressIndex + }; + } + + final String id; + final int height; + final TransactionDirection direction; + final DateTime date; + final int accountIndex; + final bool isPending; + final int amount; + final int fee; + final int addressIndex; + String recipientAddress; + String key; + + String _fiatAmount; + + @override + String amountFormatted() => + '${formatAmount(wowneroAmountToString(amount: amount))} WOW'; + + @override + String fiatAmount() => _fiatAmount ?? ''; + + @override + void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); + + @override + String feeFormatted() => + '${formatAmount(wowneroAmountToString(amount: fee))} WOW'; +} diff --git a/cw_wownero/lib/wownero_wallet.dart b/cw_wownero/lib/wownero_wallet.dart new file mode 100644 index 00000000..2932cb42 --- /dev/null +++ b/cw_wownero/lib/wownero_wallet.dart @@ -0,0 +1,428 @@ +import 'dart:async'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_wownero/wownero_transaction_creation_exception.dart'; +import 'package:cw_wownero/wownero_transaction_info.dart'; +import 'package:cw_wownero/wownero_wallet_addresses.dart'; +import 'package:cw_core/monero_wallet_utils.dart'; +import 'package:cw_wownero/api/structs/pending_transaction.dart'; +import 'package:flutter/foundation.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cw_wownero/api/transaction_history.dart' + as wownero_transaction_history; +import 'package:cw_wownero/api/wallet.dart'; +import 'package:cw_wownero/api/wallet.dart' as wownero_wallet; +import 'package:cw_wownero/api/transaction_history.dart' as transaction_history; +import 'package:cw_wownero/wownero_amount_format.dart'; +import 'package:cw_wownero/api/wownero_output.dart'; +import 'package:cw_wownero/wownero_transaction_creation_credentials.dart'; +import 'package:cw_wownero/pending_wownero_transaction.dart'; +import 'package:cw_wownero/wownero_balance.dart'; +import 'package:cw_wownero/wownero_transaction_history.dart'; +import 'package:cw_core/monero_wallet_keys.dart'; +import 'package:cw_core/account.dart'; +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/sync_status.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/node.dart'; +import 'package:cw_core/monero_transaction_priority.dart'; +import 'package:cw_core/crypto_currency.dart'; + +part 'wownero_wallet.g.dart'; + +const wowneroBlockSize = 1000; + +class WowneroWallet = WowneroWalletBase with _$WowneroWallet; + +abstract class WowneroWalletBase extends WalletBase with Store { + WowneroWalletBase({WalletInfo walletInfo}) + : super(walletInfo) { + transactionHistory = WowneroTransactionHistory(); + balance = ObservableMap.of({ + CryptoCurrency.wow: WowneroBalance( + fullBalance: wownero_wallet.getFullBalance(accountIndex: 0), + unlockedBalance: wownero_wallet.getFullBalance(accountIndex: 0)) + }); + _isTransactionUpdating = false; + _hasSyncAfterStartup = false; + walletAddresses = WowneroWalletAddresses(walletInfo); + _onAccountChangeReaction = reaction((_) => walletAddresses.account, + (Account account) { + balance = ObservableMap.of( + { + currency: WowneroBalance( + fullBalance: wownero_wallet.getFullBalance(accountIndex: account.id), + unlockedBalance: + wownero_wallet.getUnlockedBalance(accountIndex: account.id)) + }); + walletAddresses.updateSubaddressList(accountIndex: account.id); + }); + } + + static const int _autoSaveInterval = 30; + + @override + WowneroWalletAddresses walletAddresses; + + @override + @observable + SyncStatus syncStatus; + + @override + @observable + ObservableMap balance; + + @override + String get seed => wownero_wallet.getSeed(); + + @override + MoneroWalletKeys get keys => MoneroWalletKeys( + privateSpendKey: wownero_wallet.getSecretSpendKey(), + privateViewKey: wownero_wallet.getSecretViewKey(), + publicSpendKey: wownero_wallet.getPublicSpendKey(), + publicViewKey: wownero_wallet.getPublicViewKey()); + + SyncListener _listener; + ReactionDisposer _onAccountChangeReaction; + bool _isTransactionUpdating; + bool _hasSyncAfterStartup; + Timer _autoSaveTimer; + + Future init() async { + await walletAddresses.init(); + balance = ObservableMap.of( + { + currency: WowneroBalance( + fullBalance: wownero_wallet.getFullBalance(accountIndex: walletAddresses.account.id), + unlockedBalance: wownero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account.id)) + }); + _setListeners(); + await updateTransactions(); + + if (walletInfo.isRecovery) { + wownero_wallet.setRecoveringFromSeed(isRecovery: walletInfo.isRecovery); + + if (wownero_wallet.getCurrentHeight() <= 1) { + wownero_wallet.setRefreshFromBlockHeight( + height: walletInfo.restoreHeight); + } + } + + _autoSaveTimer = Timer.periodic( + Duration(seconds: _autoSaveInterval), + (_) async => await save()); + } + + @override + void close() { + _listener?.stop(); + _onAccountChangeReaction?.reaction?.dispose(); + _autoSaveTimer?.cancel(); + } + + @override + Future connectToNode({@required Node node}) async { + try { + syncStatus = ConnectingSyncStatus(); + await wownero_wallet.setupNode( + address: node.uri.toString(), + login: node.login, + password: node.password, + useSSL: node.isSSL, + isLightWallet: false); // FIXME: hardcoded value + syncStatus = ConnectedSyncStatus(); + } catch (e) { + syncStatus = FailedSyncStatus(); + print(e); + } + } + + @override + Future startSync() async { + try { + _setInitialHeight(); + } catch (_) {} + + try { + syncStatus = StartingSyncStatus(); + wownero_wallet.startRefresh(); + _setListeners(); + _listener?.start(); + } catch (e) { + syncStatus = FailedSyncStatus(); + print(e); + rethrow; + } + } + + @override + Future createTransaction(Object credentials) async { + final _credentials = credentials as WowneroTransactionCreationCredentials; + final outputs = _credentials.outputs; + final hasMultiDestination = outputs.length > 1; + final unlockedBalance = + wownero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account.id); + + PendingTransactionDescription pendingTransactionDescription; + + if (!(syncStatus is SyncedSyncStatus)) { + throw WowneroTransactionCreationException('The wallet is not synced.'); + } + + if (hasMultiDestination) { + if (outputs.any((item) => item.sendAll + || item.formattedCryptoAmount <= 0)) { + throw WowneroTransactionCreationException('Wrong balance. Not enough WOW on your balance.'); + } + + final int totalAmount = outputs.fold(0, (acc, value) => + acc + value.formattedCryptoAmount); + + if (unlockedBalance < totalAmount) { + throw WowneroTransactionCreationException('Wrong balance. Not enough WOW on your balance.'); + } + + final wowneroOutputs = outputs.map((output) { + final outputAddress = output.isParsedAddress + ? output.extractedAddress + : output.address; + + return MoneroOutput( + address: outputAddress, + amount: output.cryptoAmount.replaceAll(',', '.')); + }).toList(); + + pendingTransactionDescription = + await transaction_history.createTransactionMultDest( + outputs: wowneroOutputs, + priorityRaw: _credentials.priority.serialize(), + accountIndex: walletAddresses.account.id); + } else { + final output = outputs.first; + final address = output.isParsedAddress + ? output.extractedAddress + : output.address; + final amount = output.sendAll + ? null + : output.cryptoAmount.replaceAll(',', '.'); + final formattedAmount = output.sendAll + ? null + : output.formattedCryptoAmount; + + if ((formattedAmount != null && unlockedBalance < formattedAmount) || + (formattedAmount == null && unlockedBalance <= 0)) { + final formattedBalance = wowneroAmountToString(amount: unlockedBalance); + + throw WowneroTransactionCreationException( + 'Incorrect unlocked balance. Unlocked: $formattedBalance. Transaction amount: ${output.cryptoAmount}.'); + } + + pendingTransactionDescription = + await transaction_history.createTransaction( + address: address, + amount: amount, + priorityRaw: _credentials.priority.serialize(), + accountIndex: walletAddresses.account.id); + } + + return PendingWowneroTransaction(pendingTransactionDescription); + } + + @override + int calculateEstimatedFee(TransactionPriority priority, int amount) { + // FIXME: hardcoded value; + + if (priority is MoneroTransactionPriority) { + switch (priority) { + case MoneroTransactionPriority.slow: + return 24590000; + case MoneroTransactionPriority.regular: + return 123050000; + case MoneroTransactionPriority.medium: + return 245029999; + case MoneroTransactionPriority.fast: + return 614530000; + case MoneroTransactionPriority.fastest: + return 26021600000; + } + } + + return 0; + } + + @override + Future save() async { + await walletAddresses.updateAddressesInBox(); + await backupWalletFiles(name); + await wownero_wallet.store(); + } + + Future getNodeHeight() async => wownero_wallet.getNodeHeight(); + + int getSeedHeight(String seed) => wownero_wallet.getSeedHeightSync(seed); + + Future isConnected() async => wownero_wallet.isConnected(); + + Future setAsRecovered() async { + walletInfo.isRecovery = false; + await walletInfo.save(); + } + + @override + Future rescan({int height}) async { + walletInfo.restoreHeight = height; + walletInfo.isRecovery = true; + wownero_wallet.setRefreshFromBlockHeight(height: height); + wownero_wallet.rescanBlockchainAsync(); + await startSync(); + _askForUpdateBalance(); + walletAddresses.accountList.update(); + await _askForUpdateTransactionHistory(); + await save(); + await walletInfo.save(); + } + + String getTransactionAddress(int accountIndex, int addressIndex) => + wownero_wallet.getAddress( + accountIndex: accountIndex, + addressIndex: addressIndex); + + @override + Future> fetchTransactions() async { + wownero_transaction_history.refreshTransactions(); + return _getAllTransactions(null).fold>( + {}, + (Map acc, WowneroTransactionInfo tx) { + acc[tx.id] = tx; + return acc; + }); + } + + Future updateTransactions() async { + try { + if (_isTransactionUpdating) { + return; + } + + _isTransactionUpdating = true; + final transactions = await fetchTransactions(); + transactionHistory.addMany(transactions); + await transactionHistory.save(); + _isTransactionUpdating = false; + } catch (e) { + print(e); + _isTransactionUpdating = false; + } + } + + String getSubaddressLabel(int accountIndex, int addressIndex) { + return wownero_wallet.getSubaddressLabel(accountIndex, addressIndex); + } + + List _getAllTransactions(dynamic _) => + wownero_transaction_history + .getAllTransations() + .map((row) => WowneroTransactionInfo.fromRow(row)) + .toList(); + + void _setListeners() { + _listener?.stop(); + _listener = wownero_wallet.setListeners(_onNewBlock, _onNewTransaction); + } + + void _setInitialHeight() { + if (walletInfo.isRecovery) { + return; + } + + final currentHeight = getCurrentHeight(); + + if (currentHeight <= 1) { + final height = _getHeightByDate(walletInfo.date); + wownero_wallet.setRecoveringFromSeed(isRecovery: true); + wownero_wallet.setRefreshFromBlockHeight(height: height); + } + } + + int _getHeightDistance(DateTime date) { + final distance = + DateTime.now().millisecondsSinceEpoch - date.millisecondsSinceEpoch; + final distance_sec = distance / 1000; + final daysTmp = (distance_sec / 86400).round(); + final days = daysTmp < 1 ? 1 : daysTmp; + + return days * 2000; + } + + int _getHeightByDate(DateTime date) { + final nodeHeight = wownero_wallet.getNodeHeightSync(); + final heightDistance = _getHeightDistance(date); + + if (nodeHeight <= 0) { + return 0; + } + + return nodeHeight - heightDistance; + } + + void _askForUpdateBalance() { + final unlockedBalance = _getUnlockedBalance(); + final fullBalance = _getFullBalance(); + + if (balance[currency].fullBalance != fullBalance || + balance[currency].unlockedBalance != unlockedBalance) { + balance[currency] = WowneroBalance( + fullBalance: fullBalance, unlockedBalance: unlockedBalance); + } + } + + Future _askForUpdateTransactionHistory() async => + await updateTransactions(); + + int _getFullBalance() => + wownero_wallet.getFullBalance(accountIndex: walletAddresses.account.id); + + int _getUnlockedBalance() => + wownero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account.id); + + void _onNewBlock(int height, int blocksLeft, double ptc) async { + try { + if (walletInfo.isRecovery) { + await _askForUpdateTransactionHistory(); + _askForUpdateBalance(); + walletAddresses.accountList.update(); + } + + if (blocksLeft < 100) { + await _askForUpdateTransactionHistory(); + _askForUpdateBalance(); + walletAddresses.accountList.update(); + syncStatus = SyncedSyncStatus(); + + if (!_hasSyncAfterStartup) { + _hasSyncAfterStartup = true; + await save(); + } + + if (walletInfo.isRecovery) { + await setAsRecovered(); + } + } else { + syncStatus = SyncingSyncStatus(blocksLeft, ptc); + } + } catch (e) { + print(e.toString()); + } + } + + void _onNewTransaction() async { + try { + await _askForUpdateTransactionHistory(); + _askForUpdateBalance(); + await Future.delayed(Duration(seconds: 1)); + } catch (e) { + print(e.toString()); + } + } +} diff --git a/cw_wownero/lib/wownero_wallet_addresses.dart b/cw_wownero/lib/wownero_wallet_addresses.dart new file mode 100644 index 00000000..964b7ffd --- /dev/null +++ b/cw_wownero/lib/wownero_wallet_addresses.dart @@ -0,0 +1,85 @@ +import 'package:cw_core/wallet_addresses.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/account.dart'; +import 'package:cw_wownero/wownero_account_list.dart'; +import 'package:cw_wownero/wownero_subaddress_list.dart'; +import 'package:cw_core/subaddress.dart'; +import 'package:mobx/mobx.dart'; + +part 'wownero_wallet_addresses.g.dart'; + +class WowneroWalletAddresses = WowneroWalletAddressesBase + with _$WowneroWalletAddresses; + +abstract class WowneroWalletAddressesBase extends WalletAddresses with Store { + WowneroWalletAddressesBase(WalletInfo walletInfo) : super(walletInfo) { + accountList = WowneroAccountList(); + subaddressList = WowneroSubaddressList(); + } + + @override + @observable + String address; + + @observable + Account account; + + @observable + Subaddress subaddress; + + WowneroSubaddressList subaddressList; + + WowneroAccountList accountList; + + @override + Future init() async { + accountList.update(); + account = accountList.accounts.first; + updateSubaddressList(accountIndex: account.id ?? 0); + await updateAddressesInBox(); + } + + @override + Future updateAddressesInBox() async { + try { + final _subaddressList = WowneroSubaddressList(); + + addressesMap.clear(); + + accountList.accounts.forEach((account) { + _subaddressList.update(accountIndex: account.id); + _subaddressList.subaddresses.forEach((subaddress) { + addressesMap[subaddress.address] = subaddress.label; + }); + }); + + await saveAddressesInBox(); + } catch (e) { + print(e.toString()); + } + } + + bool validate() { + accountList.update(); + final accountListLength = accountList.accounts?.length ?? 0; + + if (accountListLength <= 0) { + return false; + } + + subaddressList.update(accountIndex: accountList.accounts.first.id); + final subaddressListLength = subaddressList.subaddresses?.length ?? 0; + + if (subaddressListLength <= 0) { + return false; + } + + return true; + } + + void updateSubaddressList({int accountIndex}) { + subaddressList.update(accountIndex: accountIndex); + subaddress = subaddressList.subaddresses.first; + address = subaddress.address; + } +} \ No newline at end of file diff --git a/cw_wownero/lib/wownero_wallet_service.dart b/cw_wownero/lib/wownero_wallet_service.dart new file mode 100644 index 00000000..7f03df66 --- /dev/null +++ b/cw_wownero/lib/wownero_wallet_service.dart @@ -0,0 +1,232 @@ +import 'dart:io'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/monero_wallet_utils.dart'; +import 'package:hive/hive.dart'; +import 'package:cw_wownero/api/wallet_manager.dart' as wownero_wallet_manager; +import 'package:cw_wownero/api/wallet.dart' as wownero_wallet; +import 'package:cw_wownero/api/exceptions/wallet_opening_exception.dart'; +import 'package:cw_wownero/wownero_wallet.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_type.dart'; + +class WowneroNewWalletCredentials extends WalletCredentials { + WowneroNewWalletCredentials({String name, String password, this.language}) + : super(name: name, password: password); + + final String language; +} + +class WowneroRestoreWalletFromSeedCredentials extends WalletCredentials { + WowneroRestoreWalletFromSeedCredentials( + {String name, String password, int height, this.mnemonic}) + : super(name: name, password: password, height: height); + + final String mnemonic; +} + +class WowneroWalletLoadingException implements Exception { + @override + String toString() => 'Failure to load the wallet.'; +} + +class WowneroRestoreWalletFromKeysCredentials extends WalletCredentials { + WowneroRestoreWalletFromKeysCredentials( + {String name, + String password, + this.language, + this.address, + this.viewKey, + this.spendKey, + int height}) + : super(name: name, password: password, height: height); + + final String language; + final String address; + final String viewKey; + final String spendKey; +} + +class WowneroWalletService extends WalletService< + WowneroNewWalletCredentials, + WowneroRestoreWalletFromSeedCredentials, + WowneroRestoreWalletFromKeysCredentials> { + WowneroWalletService(this.walletInfoSource); + + final Box walletInfoSource; + + static bool walletFilesExist(String path) => + !File(path).existsSync() && !File('$path.keys').existsSync(); + + @override + WalletType getType() => WalletType.wownero; + + @override + Future create(WowneroNewWalletCredentials credentials) async { + try { + final path = await pathForWallet(name: credentials.name, type: getType()); + await wownero_wallet_manager.createWallet( + path: path, + password: credentials.password, + language: credentials.language); + final wallet = WowneroWallet(walletInfo: credentials.walletInfo); + await wallet.init(); + + return wallet; + } catch (e) { + // TODO: Implement Exception for wallet list service. + print('WowneroWalletsManager Error: ${e.toString()}'); + rethrow; + } + } + + @override + Future isWalletExit(String name) async { + try { + final path = await pathForWallet(name: name, type: getType()); + return wownero_wallet_manager.isWalletExist(path: path); + } catch (e) { + // TODO: Implement Exception for wallet list service. + print('WowneroWalletsManager Error: $e'); + rethrow; + } + } + + @override + Future openWallet(String name, String password) async { + try { + final path = await pathForWallet(name: name, type: getType()); + + if (walletFilesExist(path)) { + await repairOldAndroidWallet(name); + } + + await wownero_wallet_manager + .openWalletAsync({'path': path, 'password': password}); + final walletInfo = walletInfoSource.values.firstWhere( + (info) => info.id == WalletBase.idFor(name, getType()), + orElse: () => null); + final wallet = WowneroWallet(walletInfo: walletInfo); + final isValid = wallet.walletAddresses.validate(); + + if (!isValid) { + await restoreOrResetWalletFiles(name); + wallet.close(); + return openWallet(name, password); + } + + await wallet.init(); + + return wallet; + } catch (e) { + // TODO: Implement Exception for wallet list service. + + if ((e.toString().contains('bad_alloc') || + (e is WalletOpeningException && + (e.message == 'std::bad_alloc' || + e.message.contains('bad_alloc')))) || + (e.toString().contains('does not correspond') || + (e is WalletOpeningException && + e.message.contains('does not correspond')))) { + await restoreOrResetWalletFiles(name); + return openWallet(name, password); + } + + rethrow; + } + } + + @override + Future remove(String wallet) async { + final path = await pathForWalletDir(name: wallet, type: getType()); + final file = Directory(path); + final isExist = file.existsSync(); + + if (isExist) { + await file.delete(recursive: true); + } + } + + @override + Future restoreFromKeys( + WowneroRestoreWalletFromKeysCredentials credentials) async { + try { + final path = await pathForWallet(name: credentials.name, type: getType()); + await wownero_wallet_manager.restoreFromKeys( + path: path, + password: credentials.password, + language: credentials.language, + restoreHeight: credentials.height, + address: credentials.address, + viewKey: credentials.viewKey, + spendKey: credentials.spendKey); + final wallet = WowneroWallet(walletInfo: credentials.walletInfo); + wallet.walletInfo.isRecovery = true; + await wallet.init(); + + return wallet; + } catch (e) { + // TODO: Implement Exception for wallet list service. + print('WowneroWalletsManager Error: $e'); + rethrow; + } + } + + @override + Future restoreFromSeed( + WowneroRestoreWalletFromSeedCredentials credentials) async { + try { + final path = await pathForWallet(name: credentials.name, type: getType()); + await wownero_wallet_manager.restoreFromSeed( + path: path, + password: credentials.password, + seed: credentials.mnemonic, + restoreHeight: credentials.height); + final wallet = WowneroWallet(walletInfo: credentials.walletInfo); + wallet.walletInfo.isRecovery = true; + wallet.walletInfo.restoreHeight = wallet.getSeedHeight(credentials.mnemonic); + await wallet.init(); + + return wallet; + } catch (e) { + // TODO: Implement Exception for wallet list service. + print('WowneroWalletsManager Error: $e'); + rethrow; + } + } + + Future repairOldAndroidWallet(String name) async { + try { + if (!Platform.isAndroid) { + return; + } + + final oldAndroidWalletDirPath = + await outdatedAndroidPathForWalletDir(name: name); + final dir = Directory(oldAndroidWalletDirPath); + + if (!dir.existsSync()) { + return; + } + + final newWalletDirPath = + await pathForWalletDir(name: name, type: getType()); + + dir.listSync().forEach((f) { + final file = File(f.path); + final name = f.path.split('/').last; + final newPath = newWalletDirPath + '/$name'; + final newFile = File(newPath); + + if (!newFile.existsSync()) { + newFile.createSync(); + } + newFile.writeAsBytesSync(file.readAsBytesSync()); + }); + } catch (e) { + print(e.toString()); + } + } +} diff --git a/cw_wownero/pubspec.lock b/cw_wownero/pubspec.lock new file mode 100644 index 00000000..05f5e319 --- /dev/null +++ b/cw_wownero/pubspec.lock @@ -0,0 +1,546 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "14.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "0.41.2" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.0" + asn1lib: + dependency: transitive + description: + name: asn1lib + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.1" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.5.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + build: + dependency: transitive + description: + name: build + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.2" + build_config: + dependency: transitive + description: + name: build_config + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.6" + build_daemon: + dependency: transitive + description: + name: build_daemon + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.10" + build_resolvers: + dependency: "direct dev" + description: + name: build_resolvers + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.3" + build_runner: + dependency: "direct dev" + description: + name: build_runner + url: "https://pub.dartlang.org" + source: hosted + version: "1.11.5" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.10" + built_collection: + dependency: transitive + description: + name: built_collection + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + url: "https://pub.dartlang.org" + source: hosted + version: "8.1.3" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + cli_util: + dependency: transitive + description: + name: cli_util + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.5" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + code_builder: + dependency: transitive + description: + name: code_builder + url: "https://pub.dartlang.org" + source: hosted + version: "3.7.0" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.15.0" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.5" + 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 + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.12" + dartx: + dependency: transitive + description: + name: dartx + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.0" + encrypt: + dependency: transitive + description: + name: encrypt + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.0" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + ffi: + dependency: "direct main" + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.2" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_mobx: + dependency: "direct main" + description: + name: flutter_mobx + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0+2" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + graphs: + dependency: transitive + description: + name: graphs + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" + hive: + dependency: transitive + description: + name: hive + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.4+1" + hive_generator: + dependency: "direct dev" + description: + name: hive_generator + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.2" + http: + dependency: "direct main" + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.4" + intl: + dependency: "direct main" + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.0" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.5" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.3" + json_annotation: + dependency: transitive + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.1" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.10" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + mobx: + dependency: "direct main" + description: + name: mobx + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1+4" + mobx_codegen: + dependency: "direct dev" + description: + name: mobx_codegen + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.3" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.11.1" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" + pointycastle: + dependency: transitive + description: + name: pointycastle + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.8" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.9" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.4+1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.10+3" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.19" + time: + dependency: transitive + description: + name: time + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.1" + timing: + dependency: transitive + description: + name: timing + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.1+3" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" +sdks: + dart: ">=2.12.0 <3.0.0" + flutter: ">=1.20.0" diff --git a/cw_wownero/pubspec.yaml b/cw_wownero/pubspec.yaml new file mode 100644 index 00000000..6cdf81b7 --- /dev/null +++ b/cw_wownero/pubspec.yaml @@ -0,0 +1,78 @@ +name: cw_wownero +description: A new flutter plugin project. +version: 0.0.1 +publish_to: none +author: Cake Wallet +homepage: https://cakewallet.com + +environment: + sdk: ">=2.6.0 <3.0.0" + flutter: ">=1.20.0" + +dependencies: + flutter: + sdk: flutter + ffi: ^0.1.3 + path_provider: ^1.4.0 + http: ^0.12.0+2 + mobx: ^1.2.1+2 + flutter_mobx: ^1.1.0+2 + intl: ^0.17.0 + cw_core: + path: ../cw_core + +dev_dependencies: + flutter_test: + sdk: flutter + build_runner: ^1.10.3 + build_resolvers: ^1.3.10 + mobx_codegen: ^1.1.0+1 + hive_generator: ^0.8.1 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + # This section identifies this Flutter project as a plugin project. + # The androidPackage and pluginClass identifiers should not ordinarily + # be modified. They are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + android: + package: com.cakewallet.cw_wownero + pluginClass: CwWowneroPlugin + ios: + pluginClass: CwWowneroPlugin + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index 8e87c993..22f9389e 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -61,6 +61,7 @@ class AddressValidator extends TextValidator { case CryptoCurrency.xnok: case CryptoCurrency.xnzd: case CryptoCurrency.xusd: + case CryptoCurrency.wow: return '[0-9a-zA-Z]'; default: return '[0-9a-zA-Z]'; @@ -117,6 +118,8 @@ class AddressValidator extends TextValidator { case CryptoCurrency.xnzd: case CryptoCurrency.xusd: return [98, 99, 106]; + case CryptoCurrency.wow: + return [97]; default: return []; } diff --git a/lib/core/amount_converter.dart b/lib/core/amount_converter.dart index b3c976e2..15cf8524 100644 --- a/lib/core/amount_converter.dart +++ b/lib/core/amount_converter.dart @@ -4,6 +4,8 @@ import 'package:cw_core/crypto_currency.dart'; class AmountConverter { static const _moneroAmountLength = 12; static const _moneroAmountDivider = 1000000000000; + static const _wowneroAmountLength = 11; + static const _wowneroAmountDivider = 100000000000; static const _litecoinAmountDivider = 100000000; static const _ethereumAmountDivider = 1000000000000000000; static const _dashAmountDivider = 100000000; @@ -16,6 +18,9 @@ class AmountConverter { static final _moneroAmountFormat = NumberFormat() ..maximumFractionDigits = _moneroAmountLength ..minimumFractionDigits = 1; + static final _wowneroAmountFormat = NumberFormat() + ..maximumFractionDigits = _wowneroAmountLength + ..minimumFractionDigits = 1; static double amountIntToDouble(CryptoCurrency cryptoCurrency, int amount) { switch (cryptoCurrency) { @@ -23,6 +28,8 @@ class AmountConverter { return _moneroAmountToDouble(amount); case CryptoCurrency.btc: return _bitcoinAmountToDouble(amount); + case CryptoCurrency.wow: + return _wowneroAmountToDouble(amount); case CryptoCurrency.bch: return _bitcoinCashAmountToDouble(amount); case CryptoCurrency.dash: @@ -55,6 +62,8 @@ class AmountConverter { switch (cryptoCurrency) { case CryptoCurrency.xmr: return _moneroParseAmount(amount); + case CryptoCurrency.wow: + return _wowneroParseAmount(amount); case CryptoCurrency.xhv: case CryptoCurrency.xag: case CryptoCurrency.xau: @@ -81,6 +90,8 @@ class AmountConverter { return _moneroAmountToString(amount); case CryptoCurrency.btc: return _bitcoinAmountToString(amount); + case CryptoCurrency.wow: + return _wowneroAmountToString(amount); case CryptoCurrency.xhv: case CryptoCurrency.xag: case CryptoCurrency.xau: @@ -113,6 +124,15 @@ class AmountConverter { static int _moneroParseAmount(String amount) => _moneroAmountFormat.parse(amount).toInt(); + static String _wowneroAmountToString(int amount) => _wowneroAmountFormat.format( + cryptoAmountToDouble(amount: amount, divider: _wowneroAmountDivider)); + + static double _wowneroAmountToDouble(int amount) => + cryptoAmountToDouble(amount: amount, divider: _wowneroAmountDivider); + + static int _wowneroParseAmount(String amount) => + _wowneroAmountFormat.parse(amount).toInt(); + static String _bitcoinAmountToString(int amount) => _bitcoinAmountFormat.format( cryptoAmountToDouble(amount: amount, divider: _bitcoinAmountDivider)); diff --git a/lib/core/seed_validator.dart b/lib/core/seed_validator.dart index e1e5920f..73203be8 100644 --- a/lib/core/seed_validator.dart +++ b/lib/core/seed_validator.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/haven/haven.dart'; +import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cake_wallet/core/validator.dart'; import 'package:cake_wallet/entities/mnemonic_item.dart'; import 'package:cw_core/wallet_type.dart'; @@ -22,6 +23,8 @@ class SeedValidator extends Validator { return getBitcoinWordList(language); case WalletType.monero: return monero.getMoneroWordList(language); + case WalletType.wownero: + return wownero.getWowneroWordList(language); case WalletType.haven: return haven.getMoneroWordList(language); default: diff --git a/lib/di.dart b/lib/di.dart index 45760f1d..41463ff6 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -3,7 +3,7 @@ import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/entities/wake_lock.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/haven/haven.dart'; -import 'package:cake_wallet/haven/haven.dart'; +import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart'; import 'package:cw_core/unspent_coins_info.dart'; @@ -360,7 +360,7 @@ Future setup( getIt.registerFactory(() { final wallet = getIt.get().wallet; - if (wallet.type == WalletType.monero || wallet.type == WalletType.haven) { + if (wallet.type == WalletType.monero || wallet.type == WalletType.haven || wallet.type == WalletType.wownero) { return MoneroAccountListViewModel(wallet); } @@ -391,6 +391,7 @@ Future setup( (AccountListItem account, _) => MoneroAccountEditOrCreateViewModel( monero.getAccountList(getIt.get().wallet), haven?.getAccountList(getIt.get().wallet), + wownero.getAccountList(getIt.get().wallet), wallet: getIt.get().wallet, accountListItem: account)); @@ -481,6 +482,8 @@ Future setup( return haven.createHavenWalletService(_walletInfoSource); case WalletType.monero: return monero.createMoneroWalletService(_walletInfoSource); + case WalletType.wownero: + return wownero.createWowneroWalletService(_walletInfoSource); case WalletType.bitcoin: return bitcoin.createBitcoinWalletService( _walletInfoSource, _unspentCoinsInfoSource); diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index ad6d0801..5abf789f 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -23,6 +23,7 @@ const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081'; const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002'; const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002'; const havenDefaultNodeUri = 'nodes.havenprotocol.org:443'; +const wowneroDefaultNodeUri = 'http://eu-west-2.wow.xmr.pm:34568'; Future defaultSettingsMigration( {@required int version, @@ -131,6 +132,12 @@ Future defaultSettingsMigration( case 17: await changeDefaultHavenNode(nodes); break; + case 18: + await addWowneroNodeList(nodes: nodes); + await changeWowneroCurrentNodeToDefault( + sharedPreferences: sharedPreferences, nodes: nodes); + await checkCurrentNodes(nodes, sharedPreferences); + break; default: break; @@ -202,6 +209,14 @@ Node getHavenDefaultNode({@required Box nodes}) { orElse: () => null); } +Node getWowneroDefaultNode({@required Box nodes}) { + return nodes.values.firstWhere( + (Node node) => node.uriRaw == wowneroDefaultNodeUri, + orElse: () => null) ?? + nodes.values.firstWhere((node) => node.type == WalletType.wownero, + orElse: () => null); +} + Node getMoneroDefaultNode({@required Box nodes}) { final timeZone = DateTime.now().timeZoneOffset.inHours; var nodeUri = ''; @@ -246,6 +261,15 @@ Future changeHavenCurrentNodeToDefault( await sharedPreferences.setInt(PreferencesKey.currentHavenNodeIdKey, nodeId); } +Future changeWowneroCurrentNodeToDefault( + {@required SharedPreferences sharedPreferences, + @required Box nodes}) async { + final node = getWowneroDefaultNode(nodes: nodes); + final nodeId = node?.key as int ?? 0; + + await sharedPreferences.setInt(PreferencesKey.currentWowneroNodeIdKey, nodeId); +} + Future replaceDefaultNode( {@required SharedPreferences sharedPreferences, @required Box nodes}) async { @@ -292,6 +316,11 @@ Future addHavenNodeList({@required Box nodes}) async { await nodes.addAll(nodeList); } +Future addWowneroNodeList({@required Box nodes}) async { + final nodeList = await loadDefaultWowneroNodes(); + await nodes.addAll(nodeList); +} + Future addAddressesForMoneroWallets( Box walletInfoSource) async { final moneroWalletsInfo = @@ -383,6 +412,8 @@ Future checkCurrentNodes( .getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); final currentHavenNodeId = sharedPreferences .getInt(PreferencesKey.currentHavenNodeIdKey); + final currentWowneroNodeId = sharedPreferences + .getInt(PreferencesKey.currentWowneroNodeIdKey); final currentMoneroNode = nodeSource.values.firstWhere( (node) => node.key == currentMoneroNodeId, orElse: () => null); @@ -395,6 +426,9 @@ Future checkCurrentNodes( final currentHavenNodeServer = nodeSource.values.firstWhere( (node) => node.key == currentHavenNodeId, orElse: () => null); + final currentWowneroNodeServer = nodeSource.values.firstWhere( + (node) => node.key == currentWowneroNodeId, + orElse: () => null); if (currentMoneroNode == null) { final newCakeWalletNode = @@ -429,6 +463,14 @@ Future checkCurrentNodes( await sharedPreferences.setInt( PreferencesKey.currentHavenNodeIdKey, node.key as int); } + + if (currentWowneroNodeServer == null) { + final nodes = await loadDefaultWowneroNodes(); + final node = nodes.first; + await nodeSource.add(node); + await sharedPreferences.setInt( + PreferencesKey.currentWowneroNodeIdKey, node.key as int); + } } Future resetBitcoinElectrumServer( diff --git a/lib/entities/node_list.dart b/lib/entities/node_list.dart index 66b41c20..d499330e 100644 --- a/lib/entities/node_list.dart +++ b/lib/entities/node_list.dart @@ -70,16 +70,34 @@ Future> loadDefaultHavenNodes() async { }).toList(); } +Future> loadDefaultWowneroNodes() async { + final nodesRaw = await rootBundle.loadString('assets/wownero_node_list.yml'); + final nodes = loadYaml(nodesRaw) as YamlList; + + return nodes.map((dynamic raw) { + if (raw is Map) { + final node = Node.fromMap(raw); + node?.type = WalletType.wownero; + + return node; + } + + return null; + }).toList(); +} + Future resetToDefault(Box nodeSource) async { final moneroNodes = await loadDefaultNodes(); final bitcoinElectrumServerList = await loadBitcoinElectrumServerList(); final litecoinElectrumServerList = await loadLitecoinElectrumServerList(); final havenNodes = await loadDefaultHavenNodes(); + final wowneroNodes = await loadDefaultWowneroNodes(); final nodes = moneroNodes + bitcoinElectrumServerList + litecoinElectrumServerList + - havenNodes; + havenNodes + + wowneroNodes; await nodeSource.clear(); await nodeSource.addAll(nodes); diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index 82e100c4..6b427c6b 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -5,6 +5,7 @@ class PreferencesKey { static const currentBitcoinElectrumSererIdKey = 'current_node_id_btc'; static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc'; static const currentHavenNodeIdKey = 'current_node_id_xhv'; + static const currentWowneroNodeIdKey = 'current_node_id_wow'; static const currentFiatCurrencyKey = 'current_fiat_currency'; static const currentTransactionPriorityKeyLegacy = 'current_fee_priority'; static const currentBalanceDisplayModeKey = 'current_balance_display_mode'; diff --git a/lib/src/screens/contact/contact_list_page.dart b/lib/src/screens/contact/contact_list_page.dart index 7a46e99c..c111fdd5 100644 --- a/lib/src/screens/contact/contact_list_page.dart +++ b/lib/src/screens/contact/contact_list_page.dart @@ -229,6 +229,9 @@ class ContactListPage extends BasePage { case CryptoCurrency.xhv: image = Image.asset('assets/images/haven_logo.png', height: 24, width: 24); break; + case CryptoCurrency.wow: + image = Image.asset('assets/images/wownero_logo.png', height: 24, width: 24); + break; default: image = null; } diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart index eb8c9ab1..b0fb6fb8 100644 --- a/lib/src/screens/dashboard/dashboard_page.dart +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -322,6 +322,7 @@ class DashboardPage extends BasePage { switch (walletType) { case WalletType.haven: + case WalletType.wownero: await showPopUp( context: context, builder: (BuildContext context) { diff --git a/lib/src/screens/dashboard/widgets/menu_widget.dart b/lib/src/screens/dashboard/widgets/menu_widget.dart index 075ee1cc..e106f301 100644 --- a/lib/src/screens/dashboard/widgets/menu_widget.dart +++ b/lib/src/screens/dashboard/widgets/menu_widget.dart @@ -23,6 +23,7 @@ class MenuWidgetState extends State { Image bitcoinIcon; Image litecoinIcon; Image havenIcon; + Image wowneroIcon; final largeScreen = 731; double menuWidth; @@ -80,6 +81,7 @@ class MenuWidgetState extends State { color: Theme.of(context).accentTextTheme.overline.decorationColor); litecoinIcon = Image.asset('assets/images/litecoin_menu.png'); havenIcon = Image.asset('assets/images/haven_menu.png'); + wowneroIcon = Image.asset('assets/images/wownero_menu.png'); return Row( mainAxisSize: MainAxisSize.max, @@ -245,6 +247,8 @@ class MenuWidgetState extends State { return litecoinIcon; case WalletType.haven: return havenIcon; + case WalletType.wownero: + return wowneroIcon; default: return null; } diff --git a/lib/src/screens/new_wallet/new_wallet_type_page.dart b/lib/src/screens/new_wallet/new_wallet_type_page.dart index c4220cef..434fcbab 100644 --- a/lib/src/screens/new_wallet/new_wallet_type_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_type_page.dart @@ -67,7 +67,8 @@ class WalletTypeFormState extends State { Image.asset('assets/images/wallet_type_light.png'); final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24); - + final wowneroIcon = + Image.asset('assets/images/wownero_logo.png', height: 24, width: 24); WalletType selected; List types; Flushbar _progressBar; @@ -133,6 +134,8 @@ class WalletTypeFormState extends State { return litecoinIcon; case WalletType.haven: return havenIcon; + case WalletType.wownero: + return wowneroIcon; default: return null; } diff --git a/lib/src/screens/receive/receive_page.dart b/lib/src/screens/receive/receive_page.dart index 9e86f2b1..a632a8bf 100644 --- a/lib/src/screens/receive/receive_page.dart +++ b/lib/src/screens/receive/receive_page.dart @@ -109,7 +109,7 @@ class ReceivePage extends BasePage { @override Widget body(BuildContext context) { - return (addressListViewModel.type == WalletType.monero || addressListViewModel.type == WalletType.haven) + return (addressListViewModel.type == WalletType.monero || addressListViewModel.type == WalletType.haven || addressListViewModel.type == WalletType.wownero) ? KeyboardActions( config: KeyboardActionsConfig( keyboardActionsPlatform: KeyboardActionsPlatform.IOS, diff --git a/lib/src/screens/restore/wallet_restore_from_seed_form.dart b/lib/src/screens/restore/wallet_restore_from_seed_form.dart index a54d48ee..4e952c55 100644 --- a/lib/src/screens/restore/wallet_restore_from_seed_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_seed_form.dart @@ -131,7 +131,7 @@ class WalletRestoreFromSeedFormState extends State { focusNode: widget.blockHeightFocusNode, key: blockchainHeightKey, onHeightOrDateEntered: widget.onHeightOrDateEntered, - hasDatePicker: widget.type == WalletType.monero) + hasDatePicker: widget.type == WalletType.monero || widget.type == WalletType.wownero) ])); } diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index 65b6c03e..fe4ca498 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -215,9 +215,13 @@ class WalletRestorePage extends BasePage { .text .split(' '); - if ((walletRestoreViewModel.type == WalletType.monero || walletRestoreViewModel.type == WalletType.haven) && + if ((walletRestoreViewModel.type == WalletType.monero || + walletRestoreViewModel.type == WalletType.haven) && seedWords.length != WalletRestoreViewModelBase.moneroSeedMnemonicLength) { return false; + } else if(walletRestoreViewModel.type == WalletType.wownero && + seedWords.length != WalletRestoreViewModelBase.wowneroSeedMnemonicLength) { + return false; } if ((walletRestoreViewModel.type == WalletType.bitcoin || diff --git a/lib/src/screens/seed/pre_seed_page.dart b/lib/src/screens/seed/pre_seed_page.dart index ba2ce520..3eaf6e14 100644 --- a/lib/src/screens/seed/pre_seed_page.dart +++ b/lib/src/screens/seed/pre_seed_page.dart @@ -10,15 +10,23 @@ import 'package:cake_wallet/src/screens/base_page.dart'; class PreSeedPage extends BasePage { PreSeedPage(this.type) : imageLight = Image.asset('assets/images/pre_seed_light.png'), - imageDark = Image.asset('assets/images/pre_seed_dark.png'), - wordsCount = type == WalletType.monero - ? 25 - : 24; // FIXME: Stupid fast implementation + imageDark = Image.asset('assets/images/pre_seed_dark.png'); final Image imageDark; final Image imageLight; final WalletType type; - final int wordsCount; + + @override + int wordsCount() { + switch(this.type) { + case WalletType.monero: + return 25; + case WalletType.wownero: + return 14; + default: + return 24; + } + } @override Widget leading(BuildContext context) => null; @@ -51,7 +59,7 @@ class PreSeedPage extends BasePage { child: Text( S .of(context) - .pre_seed_description(wordsCount.toString()), + .pre_seed_description(wordsCount().toString()), textAlign: TextAlign.center, style: TextStyle( fontSize: 14, diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index d583f4a7..39e536a0 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -47,6 +47,8 @@ class WalletListBodyState extends State { Image.asset('assets/images/close.png', height: 24, width: 24); final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24); + final wowneroIcon = + Image.asset('assets/images/wownero_logo.png', height: 24, width: 24); final scrollController = ScrollController(); final double tileHeight = 60; Flushbar _progressBar; @@ -220,6 +222,8 @@ class WalletListBodyState extends State { return litecoinIcon; case WalletType.haven: return havenIcon; + case WalletType.wownero: + return wowneroIcon; default: return nonWalletTypeIcon; } diff --git a/lib/src/screens/welcome/welcome_page.dart b/lib/src/screens/welcome/welcome_page.dart index 394b6fea..0d2b4b8e 100644 --- a/lib/src/screens/welcome/welcome_page.dart +++ b/lib/src/screens/welcome/welcome_page.dart @@ -19,7 +19,11 @@ class WelcomePage extends BasePage { if (isHaven) { return S.of(context).haven_app; } - + + if (isWownero) { + return S.of(context).wownero_app; + } + return S.of(context).cake_wallet; } @@ -31,6 +35,10 @@ class WelcomePage extends BasePage { if (isHaven) { return S.of(context).haven_app_wallet_text; } + + if (isWownero) { + return S.of(context).wownero_app_wallet_text; + } return S.of(context).first_wallet_text; } diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 373ba0ac..9d720668 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -38,6 +38,7 @@ abstract class SettingsStoreBase with Store { @required Map nodes, @required TransactionPriority initialBitcoinTransactionPriority, @required TransactionPriority initialMoneroTransactionPriority, + @required TransactionPriority initialWowneroTransactionPriority, @required this.shouldShowYatPopup, @required this.isBitcoinBuyEnabled, this.actionlistDisplayMode}) { @@ -50,7 +51,8 @@ abstract class SettingsStoreBase with Store { languageCode = initialLanguageCode; priority = ObservableMap.of({ WalletType.monero: initialMoneroTransactionPriority, - WalletType.bitcoin: initialBitcoinTransactionPriority + WalletType.bitcoin: initialBitcoinTransactionPriority, + WalletType.wownero: initialWowneroTransactionPriority }); this.nodes = ObservableMap.of(nodes); _sharedPreferences = sharedPreferences; @@ -229,10 +231,13 @@ abstract class SettingsStoreBase with Store { .getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); final havenNodeId = sharedPreferences .getInt(PreferencesKey.currentHavenNodeIdKey); + final wowneroNodeId = sharedPreferences + .getInt(PreferencesKey.currentWowneroNodeIdKey); final moneroNode = nodeSource.get(nodeId); final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId); final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId); final havenNode = nodeSource.get(havenNodeId); + final wowneroNode = nodeSource.get(wowneroNodeId); final packageInfo = await PackageInfo.fromPlatform(); final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true; @@ -243,7 +248,8 @@ abstract class SettingsStoreBase with Store { WalletType.monero: moneroNode, WalletType.bitcoin: bitcoinElectrumServer, WalletType.litecoin: litecoinElectrumServer, - WalletType.haven: havenNode + WalletType.haven: havenNode, + WalletType.wownero: wowneroNode }, appVersion: packageInfo.version, isBitcoinBuyEnabled: isBitcoinBuyEnabled, @@ -314,6 +320,10 @@ abstract class SettingsStoreBase with Store { await _sharedPreferences.setInt( PreferencesKey.currentNodeIdKey, node.key as int); break; + case WalletType.wownero: + await _sharedPreferences.setInt( + PreferencesKey.currentWowneroNodeIdKey, node.key as int); + break; default: break; } diff --git a/lib/view_model/dashboard/balance_view_model.dart b/lib/view_model/dashboard/balance_view_model.dart index 7bd8a928..ac6121da 100644 --- a/lib/view_model/dashboard/balance_view_model.dart +++ b/lib/view_model/dashboard/balance_view_model.dart @@ -91,6 +91,7 @@ abstract class BalanceViewModelBase with Store { switch(wallet.type) { case WalletType.monero: case WalletType.haven: + case WalletType.wownero: return S.current.xmr_available_balance; default: return S.current.confirmed; @@ -102,6 +103,7 @@ abstract class BalanceViewModelBase with Store { switch(wallet.type) { case WalletType.monero: case WalletType.haven: + case WalletType.wownero: return S.current.xmr_full_balance; default: return S.current.unconfirmed; diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 1a7a4440..53adea38 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -337,14 +337,16 @@ abstract class DashboardViewModelBase with Store { } void updateActions() { - isEnabledExchangeAction = wallet.type != WalletType.haven; - hasExchangeAction = !isHaven; + isEnabledExchangeAction = wallet.type != WalletType.haven && wallet.type != WalletType.wownero; + hasExchangeAction = !isHaven && !isWownero; isEnabledBuyAction = wallet.type != WalletType.haven - && wallet.type != WalletType.monero; - hasBuyAction = !isMoneroOnly && !isHaven; + && wallet.type != WalletType.monero + && wallet.type != WalletType.wownero; + hasBuyAction = !isMoneroOnly && !isHaven && !isWownero; isEnabledSellAction = wallet.type != WalletType.haven && wallet.type != WalletType.monero + && wallet.type != WalletType.wownero && wallet.type != WalletType.litecoin; - hasSellAction = !isMoneroOnly && !isHaven; + hasSellAction = !isMoneroOnly && !isHaven && !isWownero; } } diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart index 99c30641..1b42448d 100644 --- a/lib/view_model/dashboard/transaction_list_item.dart +++ b/lib/view_model/dashboard/transaction_list_item.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/view_model/dashboard/action_list_item.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/haven/haven.dart'; +import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart'; import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; @@ -56,6 +57,11 @@ class TransactionListItem extends ActionListItem with Keyable { cryptoAmount: haven.formatterMoneroAmountToDouble(amount: transaction.amount), price: price); break; + case WalletType.wownero: + amount = calculateFiatAmountRaw( + cryptoAmount: wownero.formatterWowneroAmountToDouble(amount: transaction.amount), + price: price); + break; default: break; } diff --git a/lib/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart b/lib/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart index 63415cd3..a864c2f9 100644 --- a/lib/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart +++ b/lib/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart @@ -5,6 +5,7 @@ import 'package:mobx/mobx.dart'; import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/haven/haven.dart'; +import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart'; part 'monero_account_edit_or_create_view_model.g.dart'; @@ -13,7 +14,7 @@ class MoneroAccountEditOrCreateViewModel = MoneroAccountEditOrCreateViewModelBas with _$MoneroAccountEditOrCreateViewModel; abstract class MoneroAccountEditOrCreateViewModelBase with Store { - MoneroAccountEditOrCreateViewModelBase(this._moneroAccountList, this._havenAccountList, + MoneroAccountEditOrCreateViewModelBase(this._moneroAccountList, this._havenAccountList, this._wowneroAccountList, {@required WalletBase wallet, AccountListItem accountListItem}) : state = InitialExecutionState(), isEdit = accountListItem != null, @@ -31,6 +32,7 @@ abstract class MoneroAccountEditOrCreateViewModelBase with Store { final MoneroAccountList _moneroAccountList; final HavenAccountList _havenAccountList; + final WowneroAccountList _wowneroAccountList; final AccountListItem _accountListItem; final WalletBase _wallet; @@ -42,6 +44,10 @@ abstract class MoneroAccountEditOrCreateViewModelBase with Store { if (_wallet.type == WalletType.haven) { await saveHaven(); } + + if (_wallet.type == WalletType.wownero) { + await saveWownero(); + } } Future saveMonero() async { @@ -91,4 +97,26 @@ abstract class MoneroAccountEditOrCreateViewModelBase with Store { state = FailureState(e.toString()); } } + + Future saveWownero() async { + try { + state = IsExecutingState(); + + if (_accountListItem != null) { + await _wowneroAccountList.setLabelAccount( + _wallet, + accountIndex: _accountListItem.id, + label: label); + } else { + await _wowneroAccountList.addAccount( + _wallet, + label: label); + } + + await _wallet.save(); + state = ExecutedSuccessfullyState(); + } catch (e) { + state = FailureState(e.toString()); + } + } } diff --git a/lib/view_model/monero_account_list/monero_account_list_view_model.dart b/lib/view_model/monero_account_list/monero_account_list_view_model.dart index b7735ff6..9d39f6df 100644 --- a/lib/view_model/monero_account_list/monero_account_list_view_model.dart +++ b/lib/view_model/monero_account_list/monero_account_list_view_model.dart @@ -4,6 +4,7 @@ import 'package:cw_core/wallet_base.dart'; import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/haven/haven.dart'; +import 'package:cake_wallet/wownero/wownero.dart'; part 'monero_account_list_view_model.g.dart'; diff --git a/lib/view_model/node_list/node_create_or_edit_view_model.dart b/lib/view_model/node_list/node_create_or_edit_view_model.dart index ef7e0318..7c849ef7 100644 --- a/lib/view_model/node_list/node_create_or_edit_view_model.dart +++ b/lib/view_model/node_list/node_create_or_edit_view_model.dart @@ -42,7 +42,7 @@ abstract class NodeCreateOrEditViewModelBase with Store { (address?.isNotEmpty ?? false) && (port?.isNotEmpty ?? false); bool get hasAuthCredentials => _wallet.type == WalletType.monero || - _wallet.type == WalletType.haven; + _wallet.type == WalletType.haven || _wallet.type == WalletType.wownero; String get uri { var uri = address; diff --git a/lib/view_model/node_list/node_list_view_model.dart b/lib/view_model/node_list/node_list_view_model.dart index 30e5cb9c..05b2dced 100644 --- a/lib/view_model/node_list/node_list_view_model.dart +++ b/lib/view_model/node_list/node_list_view_model.dart @@ -39,6 +39,9 @@ abstract class NodeListViewModelBase with Store { case WalletType.monero: node = getMoneroDefaultNode(nodes: _nodeSource); break; + case WalletType.wownero: + node = getWowneroDefaultNode(nodes: _nodeSource); + break; case WalletType.litecoin: node = getLitecoinDefaultElectrumServer(nodes: _nodeSource); break; diff --git a/lib/view_model/send/output.dart b/lib/view_model/send/output.dart index 39c2fa83..18185bea 100644 --- a/lib/view_model/send/output.dart +++ b/lib/view_model/send/output.dart @@ -3,6 +3,7 @@ import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/entities/parsed_address.dart'; import 'package:cake_wallet/haven/haven.dart'; +import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:flutter/material.dart'; @@ -82,6 +83,9 @@ abstract class OutputBase with Store { case WalletType.haven: _amount = haven.formatterMoneroParseAmount(amount: _cryptoAmount); break; + case WalletType.wownero: + _amount = wownero.formatterWowneroParseAmount(amount: _cryptoAmount); + break; default: break; } @@ -115,6 +119,10 @@ abstract class OutputBase with Store { if (_wallet.type == WalletType.haven) { return haven.formatterMoneroAmountToDouble(amount: fee); } + + if (_wallet.type == WalletType.wownero) { + return wownero.formatterWowneroAmountToDouble(amount: fee); + } } catch (e) { print(e.toString()); } @@ -220,6 +228,9 @@ abstract class OutputBase with Store { case WalletType.haven: maximumFractionDigits = 12; break; + case WalletType.wownero: + maximumFractionDigits = 12; + break; default: break; } diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index d82dd979..39a8bd06 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -26,6 +26,7 @@ import 'package:cake_wallet/view_model/send/send_view_model_state.dart'; import 'package:cake_wallet/entities/parsed_address.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/haven/haven.dart'; +import 'package:cake_wallet/wownero/wownero.dart'; part 'send_view_model.g.dart'; @@ -149,7 +150,7 @@ abstract class SendViewModelBase with Store { List currencies; - bool get hasMultiRecipient => _wallet.type != WalletType.haven; + bool get hasMultiRecipient => _wallet.type != WalletType.haven && _wallet.type != WalletType.wownero; bool get hasYat => outputs.any((out) => out.isParsedAddress && @@ -237,6 +238,11 @@ abstract class SendViewModelBase with Store { return haven.createHavenTransactionCreationCredentials( outputs: outputs, priority: priority, assetType: selectedCryptoCurrency.title); + case WalletType.wownero: + final priority = _settingsStore.priority[_wallet.type]; + + return wownero.createWowneroTransactionCreationCredentials( + outputs: outputs, priority: priority); default: return null; } diff --git a/lib/view_model/settings/settings_view_model.dart b/lib/view_model/settings/settings_view_model.dart index 555b0d2a..4e095b4d 100644 --- a/lib/view_model/settings/settings_view_model.dart +++ b/lib/view_model/settings/settings_view_model.dart @@ -15,6 +15,7 @@ import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cw_core/node.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/haven/haven.dart'; +import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cake_wallet/entities/action_list_display_mode.dart'; import 'package:cake_wallet/view_model/settings/version_list_item.dart'; import 'package:cake_wallet/view_model/settings/picker_list_item.dart'; @@ -46,6 +47,8 @@ List priorityForWalletType(WalletType type) { return bitcoin.getLitecoinTransactionPriorities(); case WalletType.haven: return haven.getTransactionPriorities(); + case WalletType.wownero: + return wownero.getTransactionPriorities(); default: return []; } @@ -105,7 +108,7 @@ abstract class SettingsViewModelBase with Store { selectedItem: () => balanceDisplayMode, onItemSelected: (BalanceDisplayMode mode) => _settingsStore.balanceDisplayMode = mode), - if (!isHaven) + if (!isHaven && !isWownero) PickerListItem( title: S.current.settings_currency, items: FiatCurrency.all, diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index 64c76481..2fb840a0 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -15,6 +15,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/haven/haven.dart'; +import 'package:cake_wallet/wownero/wownero.dart'; part 'transaction_details_view_model.g.dart'; @@ -110,7 +111,7 @@ abstract class TransactionDetailsViewModelBase with Store { items.addAll(_items); } - if (wallet.type == WalletType.haven) { + if (wallet.type == WalletType.haven || wallet.type == WalletType.wownero) { items.addAll([ StandartListItem( title: S.current.transaction_details_transaction_id, value: tx.id), @@ -183,6 +184,8 @@ abstract class TransactionDetailsViewModelBase with Store { return 'https://blockchair.com/litecoin/transaction/${txId}'; case WalletType.haven: return 'https://explorer.havenprotocol.org/search?value=${txId}'; + case WalletType.wownero: + return 'https://explore.wownero.com/tx/${txId}'; default: return ''; } @@ -198,6 +201,8 @@ abstract class TransactionDetailsViewModelBase with Store { return S.current.view_transaction_on + 'Blockchair.com'; case WalletType.haven: return S.current.view_transaction_on + 'explorer.havenprotocol.org'; + case WalletType.haven: + return S.current.view_transaction_on + 'explore.wownero.com'; default: return ''; } diff --git a/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart index 4e7572bc..78e86164 100644 --- a/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart @@ -4,6 +4,7 @@ import 'package:cw_core/wallet_base.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/haven/haven.dart'; +import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cw_core/wallet_type.dart'; part 'wallet_address_edit_or_create_view_model.g.dart'; @@ -89,6 +90,16 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store { label: label); await wallet.save(); } + + if (wallet.type == WalletType.wownero) { + await wownero + .getSubaddressList(wallet) + .addSubaddress( + wallet, + accountIndex: wownero.getCurrentAccount(wallet).id, + label: label); + await wallet.save(); + } } Future _update() async { @@ -120,5 +131,16 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store { label: label); await wallet.save(); } + + if (wallet.type == WalletType.wownero) { + await wownero + .getSubaddressList(wallet) + .setLabelSubaddress( + wallet, + accountIndex: wownero.getCurrentAccount(wallet).id, + addressIndex: _item.id as int, + label: label); + await wallet.save(); + } } } diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index 30675926..bf1efa0e 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -16,6 +16,7 @@ import 'package:cake_wallet/store/app_store.dart'; import 'dart:async'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/haven/haven.dart'; +import 'package:cake_wallet/wownero/wownero.dart'; part 'wallet_address_list_view_model.g.dart'; @@ -61,6 +62,22 @@ class HavenURI extends PaymentURI { } } +class WowneroURI extends PaymentURI { + WowneroURI({String amount, String address}) + : super(amount: amount, address: address); + + @override + String toString() { + var base = 'wownero:' + address; + + if (amount?.isNotEmpty ?? false) { + base += '?tx_amount=${amount.replaceAll(',', '.')}'; + } + + return base; + } +} + class BitcoinURI extends PaymentURI { BitcoinURI({String amount, String address}) : super(amount: amount, address: address); @@ -131,6 +148,10 @@ abstract class WalletAddressListViewModelBase with Store { return HavenURI(amount: amount, address: address.address); } + if (_wallet.type == WalletType.wownero) { + return WowneroURI(amount: amount, address: address.address); + } + if (_wallet.type == WalletType.bitcoin) { return BitcoinURI(amount: amount, address: address.address); } @@ -185,6 +206,23 @@ abstract class WalletAddressListViewModelBase with Store { addressList.addAll(addressItems); } + if (wallet.type == WalletType.wownero) { + final primaryAddress = wownero.getSubaddressList(wallet).subaddresses.first; + final addressItems = wownero + .getSubaddressList(wallet) + .subaddresses + .map((subaddress) { + final isPrimary = subaddress == primaryAddress; + + return WalletAddressListItem( + id: subaddress.id, + isPrimary: isPrimary, + name: subaddress.label, + address: subaddress.address); + }); + addressList.addAll(addressItems); + } + if (wallet.type == WalletType.bitcoin) { final primaryAddress = bitcoin.getAddress(wallet); final bitcoinAddresses = bitcoin.getAddresses(wallet).map((addr) { @@ -214,11 +252,15 @@ abstract class WalletAddressListViewModelBase with Store { return haven.getCurrentAccount(wallet).label; } + if (wallet.type == WalletType.wownero) { + return wownero.getCurrentAccount(wallet).label; + } + return null; } @computed - bool get hasAddressList => _wallet.type == WalletType.monero || _wallet.type == WalletType.haven; + bool get hasAddressList => _wallet.type == WalletType.monero || _wallet.type == WalletType.haven || _wallet.type == WalletType.wownero; @observable WalletBase, TransactionInfo> diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index 34f4ea2b..723431df 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/haven/haven.dart'; +import 'package:cake_wallet/wownero/wownero.dart'; part 'wallet_keys_view_model.g.dart'; @@ -46,6 +47,22 @@ abstract class WalletKeysViewModelBase with Store { ]); } + if (wallet.type == WalletType.wownero) { + final keys = wownero.getKeys(wallet); + + items.addAll([ + StandartListItem( + title: S.current.spend_key_public, value: keys['publicSpendKey']), + StandartListItem( + title: S.current.spend_key_private, value: keys['privateSpendKey']), + StandartListItem( + title: S.current.view_key_public, value: keys['publicViewKey']), + StandartListItem( + title: S.current.view_key_private, value: keys['privateViewKey']), + StandartListItem(title: S.current.wallet_seed, value: wallet.seed), + ]); + } + if (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin) { final keys = bitcoin.getWalletKeys(wallet); diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index b044ea36..619e570f 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -11,6 +11,7 @@ import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/view_model/wallet_creation_vm.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/haven/haven.dart'; +import 'package:cake_wallet/wownero/wownero.dart'; part 'wallet_new_vm.g.dart'; @@ -43,6 +44,9 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { case WalletType.haven: return haven.createHavenNewWalletCredentials( name: name, language: options as String); + case WalletType.wownero: + return wownero.createWowneroNewWalletCredentials( + name: name, language: 'English'); default: return null; } diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index 1943f76b..c5913e61 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -13,6 +13,7 @@ import 'package:cw_core/wallet_info.dart'; import 'package:cake_wallet/view_model/wallet_creation_vm.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/haven/haven.dart'; +import 'package:cake_wallet/wownero/wownero.dart'; part 'wallet_restore_view_model.g.dart'; @@ -37,6 +38,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { _walletCreationService.changeWalletType(type: type); } + static const wowneroSeedMnemonicLength = 14; static const moneroSeedMnemonicLength = 25; static const electrumSeedMnemonicLength = 24; static const electrumShortSeedMnemonicLength = 12; @@ -84,6 +86,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { height: height ?? 0, mnemonic: seed, password: password); + case WalletType.wownero: + return wownero.createWowneroRestoreWalletFromSeedCredentials( + name: name, + height: height ?? 0, + mnemonic: seed, + password: password); default: break; } @@ -115,6 +123,17 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { password: password, language: 'English'); } + + if (type == WalletType.wownero) { + return wownero.createWowneroRestoreWalletFromKeysCredentials( + name: name, + height: height, + spendKey: spendKey, + viewKey: viewKey, + address: address, + password: password, + language: 'English'); + } } return null; diff --git a/lib/wallet_type_utils.dart b/lib/wallet_type_utils.dart index 5ed78dc6..bd20c080 100644 --- a/lib/wallet_type_utils.dart +++ b/lib/wallet_type_utils.dart @@ -11,6 +11,10 @@ bool get isHaven { && availableWalletTypes.first == WalletType.haven; } +bool get isWownero { + return availableWalletTypes.length == 1 + && availableWalletTypes.first == WalletType.wownero; +} bool get isSingleCoin { return availableWalletTypes.length == 1; @@ -24,6 +28,10 @@ String get approximatedAppName { if (isHaven) { return 'Haven'; } - + + if (isWownero) { + return 'Wownero'; + } + return 'Cake Wallet'; } \ No newline at end of file diff --git a/lib/wownero/cw_wownero.dart b/lib/wownero/cw_wownero.dart new file mode 100644 index 00000000..9940b8d9 --- /dev/null +++ b/lib/wownero/cw_wownero.dart @@ -0,0 +1,280 @@ +part of 'wownero.dart'; + +class CWWowneroAccountList extends WowneroAccountList { + CWWowneroAccountList(this._wallet); + Object _wallet; + + @override + @computed + ObservableList get accounts { + final wowneroWallet = _wallet as WowneroWallet; + final accounts = wowneroWallet.walletAddresses.accountList + .accounts + .map((acc) => Account(id: acc.id, label: acc.label)) + .toList(); + return ObservableList.of(accounts); + } + + @override + void update(Object wallet) { + final wowneroWallet = wallet as WowneroWallet; + wowneroWallet.walletAddresses.accountList.update(); + } + + @override + void refresh(Object wallet) { + final wowneroWallet = wallet as WowneroWallet; + wowneroWallet.walletAddresses.accountList.refresh(); + } + + @override + List getAll(Object wallet) { + final wowneroWallet = wallet as WowneroWallet; + return wowneroWallet.walletAddresses.accountList + .getAll() + .map((acc) => Account(id: acc.id, label: acc.label)) + .toList(); + } + + @override + Future addAccount(Object wallet, {String label}) async { + final wowneroWallet = wallet as WowneroWallet; + wowneroWallet.walletAddresses.accountList.addAccount(label: label); + } + + @override + Future setLabelAccount(Object wallet, {int accountIndex, String label}) async { + final wowneroWallet = wallet as WowneroWallet; + wowneroWallet.walletAddresses.accountList + .setLabelAccount( + accountIndex: accountIndex, + label: label); + } +} + +class CWWowneroSubaddressList extends WowneroSubaddressList { + CWWowneroSubaddressList(this._wallet); + Object _wallet; + + @override + @computed + ObservableList get subaddresses { + final wowneroWallet = _wallet as WowneroWallet; + final subAddresses = wowneroWallet.walletAddresses.subaddressList + .subaddresses + .map((sub) => Subaddress( + id: sub.id, + address: sub.address, + label: sub.label)) + .toList(); + return ObservableList.of(subAddresses); + } + + @override + void update(Object wallet, {int accountIndex}) { + final wowneroWallet = wallet as WowneroWallet; + wowneroWallet.walletAddresses.subaddressList.update(accountIndex: accountIndex); + } + + @override + void refresh(Object wallet, {int accountIndex}) { + final wowneroWallet = wallet as WowneroWallet; + wowneroWallet.walletAddresses.subaddressList.refresh(accountIndex: accountIndex); + } + + @override + List getAll(Object wallet) { + final wowneroWallet = wallet as WowneroWallet; + return wowneroWallet.walletAddresses + .subaddressList + .getAll() + .map((sub) => Subaddress(id: sub.id, label: sub.label, address: sub.address)) + .toList(); + } + + @override + Future addSubaddress(Object wallet, {int accountIndex, String label}) async { + final wowneroWallet = wallet as WowneroWallet; + wowneroWallet.walletAddresses.subaddressList + .addSubaddress( + accountIndex: accountIndex, + label: label); + } + + @override + Future setLabelSubaddress(Object wallet, + {int accountIndex, int addressIndex, String label}) async { + final wowneroWallet = wallet as WowneroWallet; + wowneroWallet.walletAddresses.subaddressList + .setLabelSubaddress( + accountIndex: accountIndex, + addressIndex: addressIndex, + label: label); + } +} + +class CWWowneroWalletDetails extends WowneroWalletDetails { + CWWowneroWalletDetails(this._wallet); + Object _wallet; + + @computed + Account get account { + final wowneroWallet = _wallet as WowneroWallet; + final acc = wowneroWallet.walletAddresses.account; + return Account(id: acc.id, label: acc.label); + } + + @computed + WowneroBalance get balance { + final wowneroWallet = _wallet as WowneroWallet; + final balance = wowneroWallet.balance; + return WowneroBalance(); + //return WowneroBalance( + // fullBalance: balance.fullBalance, + // unlockedBalance: balance.unlockedBalance); + } +} + +class CWWownero extends Wownero { + WowneroAccountList getAccountList(Object wallet) { + return CWWowneroAccountList(wallet); + } + + WowneroSubaddressList getSubaddressList(Object wallet) { + return CWWowneroSubaddressList(wallet); + } + + TransactionHistoryBase getTransactionHistory(Object wallet) { + final wowneroWallet = wallet as WowneroWallet; + return wowneroWallet.transactionHistory; + } + + WowneroWalletDetails getWowneroWalletDetails(Object wallet) { + return CWWowneroWalletDetails(wallet); + } + + TransactionPriority getDefaultTransactionPriority() { + return MoneroTransactionPriority.slow; + } + + TransactionPriority deserializeMoneroTransactionPriority({int raw}) { + return MoneroTransactionPriority.deserialize(raw: raw); + } + + List getTransactionPriorities() { + return MoneroTransactionPriority.all; + } + + List getWowneroWordList(String language) { + switch (language.toLowerCase()) { + case 'english': + return EnglishMnemonics.words; + default: + return EnglishMnemonics.words; + } + } + + WalletCredentials createWowneroRestoreWalletFromKeysCredentials({ + String name, + String spendKey, + String viewKey, + String address, + String password, + String language, + int height}) { + return WowneroRestoreWalletFromKeysCredentials( + name: name, + spendKey: spendKey, + viewKey: viewKey, + address: address, + password: password, + language: language, + height: height); + } + + WalletCredentials createWowneroRestoreWalletFromSeedCredentials({String name, String password, int height, String mnemonic}) { + return WowneroRestoreWalletFromSeedCredentials( + name: name, + password: password, + height: height, + mnemonic: mnemonic); + } + + WalletCredentials createWowneroNewWalletCredentials({String name, String password, String language}) { + return WowneroNewWalletCredentials( + name: name, + password: password, + language: language); + } + + Map getKeys(Object wallet) { + final wowneroWallet = wallet as WowneroWallet; + final keys = wowneroWallet.keys; + return { + 'privateSpendKey': keys.privateSpendKey, + 'privateViewKey': keys.privateViewKey, + 'publicSpendKey': keys.publicSpendKey, + 'publicViewKey': keys.publicViewKey}; + } + + Object createWowneroTransactionCreationCredentials({List outputs, TransactionPriority priority}) { + return WowneroTransactionCreationCredentials( + outputs: outputs.map((out) => OutputInfo( + fiatAmount: out.fiatAmount, + cryptoAmount: out.cryptoAmount, + address: out.address, + note: out.note, + sendAll: out.sendAll, + extractedAddress: out.extractedAddress, + isParsedAddress: out.isParsedAddress, + formattedCryptoAmount: out.formattedCryptoAmount)) + .toList(), + priority: priority as MoneroTransactionPriority); + } + + String formatterWowneroAmountToString({int amount}) { + return wowneroAmountToString(amount: amount); + } + + double formatterWowneroAmountToDouble({int amount}) { + return wowneroAmountToDouble(amount: amount); + } + + int formatterWowneroParseAmount({String amount}) { + return wowneroParseAmount(amount: amount); + } + + Account getCurrentAccount(Object wallet) { + final wowneroWallet = wallet as WowneroWallet; + final acc = wowneroWallet.walletAddresses.account; + return Account(id: acc.id, label: acc.label); + } + + void setCurrentAccount(Object wallet, int id, String label) { + final wowneroWallet = wallet as WowneroWallet; + wowneroWallet.walletAddresses.account = wownero_account.Account(id: id, label: label); + } + + void onStartup() { + wownero_wallet_api.onStartup(); + } + + int getTransactionInfoAccountId(TransactionInfo tx) { + final wowneroTransactionInfo = tx as WowneroTransactionInfo; + return wowneroTransactionInfo.accountIndex; + } + + WalletService createWowneroWalletService(Box walletInfoSource) { + return WowneroWalletService(walletInfoSource); + } + + String getTransactionAddress(Object wallet, int accountIndex, int addressIndex) { + final wowneroWallet = wallet as WowneroWallet; + return wowneroWallet.getTransactionAddress(accountIndex, addressIndex); + } + + String getSubaddressLabel(Object wallet, int accountIndex, int addressIndex) { + final wowneroWallet = wallet as WowneroWallet; + return wowneroWallet.getSubaddressLabel(accountIndex, addressIndex); + } +} diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 62f12924..15892bd2 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -77,6 +77,7 @@ flutter: - assets/images/ - assets/node_list.yml - assets/haven_node_list.yml + - assets/wownero_node_list.yml - assets/bitcoin_electrum_server_list.yml - assets/litecoin_electrum_server_list.yml - assets/text/ diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index a501cd72..8d9f36a6 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -11,6 +11,9 @@ "haven_app": "Haven by Cake Wallet", "haven_app_wallet_text": "Awesome wallet for Haven", + + "wownero_app": "Wownero by Cake Wallet", + "wownero_app_wallet_text": "Awesome wallet for Wownero", "accounts" : "Konten", "edit" : "Bearbeiten", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index dfe3b84f..08691951 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -12,6 +12,9 @@ "haven_app": "Haven by Cake Wallet", "haven_app_wallet_text": "Awesome wallet for Haven", + "wownero_app": "Wownero by Cake Wallet", + "wownero_app_wallet_text": "Awesome wallet for Wownero", + "accounts" : "Accounts", "edit" : "Edit", "account" : "Account", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index a033025f..466b82a6 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -12,6 +12,9 @@ "haven_app": "Haven by Cake Wallet", "haven_app_wallet_text": "Awesome wallet for Haven", + "wownero_app": "Wownero by Cake Wallet", + "wownero_app_wallet_text": "Awesome wallet for Wownero", + "accounts" : "Cuentas", "edit" : "Editar", "account" : "Cuenta", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 4efe457c..054765d5 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -11,6 +11,9 @@ "haven_app": "Haven by Cake Wallet", "haven_app_wallet_text": "Awesome wallet for Haven", + + "wownero_app": "Wownero by Cake Wallet", + "wownero_app_wallet_text": "Awesome wallet for Wownero", "accounts" : "हिसाब किताब", "edit" : "संपादित करें", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 95004d68..193ca7fa 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -11,6 +11,9 @@ "haven_app": "Haven by Cake Wallet", "haven_app_wallet_text": "Awesome wallet for Haven", + + "wownero_app": "Wownero by Cake Wallet", + "wownero_app_wallet_text": "Awesome wallet for Wownero", "accounts" : "Računi", "edit" : "Uredi", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index a65078b3..fcc21ecb 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -11,6 +11,9 @@ "haven_app": "Haven by Cake Wallet", "haven_app_wallet_text": "Awesome wallet for Haven", + + "wownero_app": "Wownero by Cake Wallet", + "wownero_app_wallet_text": "Awesome wallet for Wownero", "accounts" : "Accounts", "edit" : "Modifica", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 5cb3039c..f55b26a8 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -11,6 +11,9 @@ "haven_app": "Haven by Cake Wallet", "haven_app_wallet_text": "Awesome wallet for Haven", + + "wownero_app": "Wownero by Cake Wallet", + "wownero_app_wallet_text": "Awesome wallet for Wownero", "accounts" : "アカウント", "edit" : "編集", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index db099be6..75a0c85e 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -11,6 +11,9 @@ "haven_app": "Haven by Cake Wallet", "haven_app_wallet_text": "Awesome wallet for Haven", + + "wownero_app": "Wownero by Cake Wallet", + "wownero_app_wallet_text": "Awesome wallet for Wownero", "accounts" : "계정", "edit" : "편집하다", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 20797946..d448fa83 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -11,6 +11,9 @@ "haven_app": "Haven by Cake Wallet", "haven_app_wallet_text": "Awesome wallet for Haven", + + "wownero_app": "Wownero by Cake Wallet", + "wownero_app_wallet_text": "Awesome wallet for Wownero", "accounts" : "Accounts", "edit" : "Bewerk", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 3da2cc94..5d6786e1 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -12,8 +12,14 @@ "haven_app": "Haven by Cake Wallet", "haven_app_wallet_text": "Awesome wallet for Haven", + "wownero_app": "Wownero by Cake Wallet", + "wownero_app_wallet_text": "Awesome wallet for Wownero", + "haven_app": "Haven by Cake Wallet", "haven_app_wallet_text": "Awesome wallet for Haven", + + "wownero_app": "Wownero by Cake Wallet", + "wownero_app_wallet_text": "Awesome wallet for Wownero", "accounts" : "Konta", "edit" : "Edytować", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index d32e47bd..549b8af9 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -11,6 +11,9 @@ "haven_app": "Haven by Cake Wallet", "haven_app_wallet_text": "Awesome wallet for Haven", + + "wownero_app": "Wownero by Cake Wallet", + "wownero_app_wallet_text": "Awesome wallet for Wownero", "accounts" : "Contas", "edit" : "Editar", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 78c67943..f78ec741 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -11,6 +11,9 @@ "haven_app": "Haven by Cake Wallet", "haven_app_wallet_text": "Awesome wallet for Haven", + + "wownero_app": "Wownero by Cake Wallet", + "wownero_app_wallet_text": "Awesome wallet for Wownero", "accounts" : "Аккаунты", "edit" : "Редактировать", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index c2a96ab4..18db5396 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -11,6 +11,9 @@ "haven_app": "Haven by Cake Wallet", "haven_app_wallet_text": "Awesome wallet for Haven", + + "wownero_app": "Wownero by Cake Wallet", + "wownero_app_wallet_text": "Awesome wallet for Wownero", "accounts" : "Акаунти", "edit" : "Редагувати", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 43132041..c32e228a 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -11,6 +11,9 @@ "haven_app": "Haven by Cake Wallet", "haven_app_wallet_text": "Awesome wallet for Haven", + + "wownero_app": "Wownero by Cake Wallet", + "wownero_app_wallet_text": "Awesome wallet for Wownero", "accounts" : "账户", "edit" : "编辑", diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index 689b866e..a41092ad 100755 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -9,8 +9,9 @@ APP_ANDROID_PACKAGE="" MONERO_COM="monero.com" CAKEWALLET="cakewallet" HAVEN="haven" +WOWNERO="wownero" -TYPES=($MONERO_COM $CAKEWALLET $HAVEN) +TYPES=($MONERO_COM $CAKEWALLET $HAVEN $WOWNERO) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" @@ -31,6 +32,12 @@ HAVEN_BUILD_NUMBER=1 HAVEN_BUNDLE_ID="com.cakewallet.haven" HAVEN_PACKAGE="com.cakewallet.haven" +WOWNERO_NAME="Wownero" +WOWNERO_VERSION="1.0.0" +WOWNERO_BUILD_NUMBER=1 +WOWNERO_BUNDLE_ID="com.cakewallet.wownero" +WOWNERO_PACKAGE="com.cakewallet.wownero" + if ! [[ " ${TYPES[*]} " =~ " ${APP_ANDROID_TYPE} " ]]; then echo "Wrong app type." return 1 2>/dev/null @@ -59,6 +66,13 @@ case $APP_ANDROID_TYPE in APP_ANDROID_BUNDLE_ID=$HAVEN_BUNDLE_ID APP_ANDROID_PACKAGE=$HAVEN_PACKAGE ;; + $WOWNERO) + APP_ANDROID_NAME=$WOWNERO_NAME + APP_ANDROID_VERSION=$WOWNERO_VERSION + APP_ANDROID_BUILD_NUMBER=$WOWNERO_BUILD_NUMBER + APP_ANDROID_BUNDLE_ID=$WOWNERO_BUNDLE_ID + APP_ANDROID_PACKAGE=$WOWNERO_PACKAGE + ;; esac export APP_ANDROID_TYPE diff --git a/scripts/android/app_icon.sh b/scripts/android/app_icon.sh index 9db01f22..d56df113 100755 --- a/scripts/android/app_icon.sh +++ b/scripts/android/app_icon.sh @@ -28,6 +28,11 @@ case $APP_ANDROID_TYPE in ANDROID_ICON=$CAKEWALLET_PATH ANDROID_ICON_SET=$CAKEWALLET_ICON_SET_PATH ;; + "wownero") + APP_LOGO=$ASSETS_DIR/images/cakewallet_logo.png + ANDROID_ICON=$CAKEWALLET_PATH + ANDROID_ICON_SET=$CAKEWALLET_ICON_SET_PATH + ;; esac rm $APP_LOGO_DEST_PATH diff --git a/scripts/android/build_all.sh b/scripts/android/build_all.sh index 5093d231..08052cc0 100755 --- a/scripts/android/build_all.sh +++ b/scripts/android/build_all.sh @@ -8,8 +8,13 @@ fi DIR=$(dirname "$0") case $APP_ANDROID_TYPE in - "monero.com") $DIR/build_monero_all.sh ;; - "cakewallet") $DIR/build_monero_all.sh - $DIR/build_haven.sh ;; - "haven") $DIR/build_haven_all.sh ;; -esac +"monero.com") $DIR/build_monero_all.sh ;; +"cakewallet") + $DIR/build_monero_all.sh + $DIR/build_haven.sh + $DIR/build_wownero.sh + $DIR/build_wownero_seed.sh + ;; +"haven") $DIR/build_haven_all.sh ;; +"wownero") $DIR/build_wownero_all.sh ;; +esac \ No newline at end of file diff --git a/scripts/android/build_wownero.sh b/scripts/android/build_wownero.sh new file mode 100755 index 00000000..55b6941e --- /dev/null +++ b/scripts/android/build_wownero.sh @@ -0,0 +1,72 @@ +#!/bin/sh + +. ./config.sh + +WOWNERO_VERSION=fix-armv7a-compile +WOWNERO_SRC_DIR=${WORKDIR}/wownero +WOWNERO_SHA_HEAD="373b8842c6075c54cc4904b147f1c86daf7cb60d" + +git clone https://git.wownero.com/wownero/wownero.git ${WOWNERO_SRC_DIR} --branch ${WOWNERO_VERSION} +cd $WOWNERO_SRC_DIR +git reset --hard $WOWNERO_SHA_HEAD +git submodule init +git submodule update + +for arch in "aarch" "aarch64" "i686" "x86_64" +do +FLAGS="" +PREFIX=${WORKDIR}/prefix_${arch} +DEST_LIB_DIR=${PREFIX}/lib/wownero +DEST_INCLUDE_DIR=${PREFIX}/include/wownero +export CMAKE_INCLUDE_PATH="${PREFIX}/include" +export CMAKE_LIBRARY_PATH="${PREFIX}/lib" +ANDROID_STANDALONE_TOOLCHAIN_PATH="${TOOLCHAIN_BASE_DIR}_${arch}" +PATH="${ANDROID_STANDALONE_TOOLCHAIN_PATH}/bin:${ORIGINAL_PATH}" + +mkdir -p $DEST_LIB_DIR +mkdir -p $DEST_INCLUDE_DIR + +case $arch in + "aarch" ) + CLANG=arm-linux-androideabi-clang + CXXLANG=arm-linux-androideabi-clang++ + BUILD_64=OFF + TAG="android-armv7" + ARCH="armv7-a" + ARCH_ABI="armeabi-v7a" + FLAGS="-D CMAKE_ANDROID_ARM_MODE=ON -D NO_AES=true";; + "aarch64" ) + CLANG=aarch64-linux-androideabi-clang + CXXLANG=aarch64-linux-androideabi-clang++ + BUILD_64=ON + TAG="android-armv8" + ARCH="armv8-a" + ARCH_ABI="arm64-v8a";; + "i686" ) + CLANG=i686-linux-androideabi-clang + CXXLANG=i686-linux-androideabi-clang++ + BUILD_64=OFF + TAG="android-x86" + ARCH="i686" + ARCH_ABI="x86";; + "x86_64" ) + CLANG=x86_64-linux-androideabi-clang + CXXLANG=x86_64-linux-androideabi-clang++ + BUILD_64=ON + TAG="android-x86_64" + ARCH="x86-64" + ARCH_ABI="x86_64";; +esac + +cd $WOWNERO_SRC_DIR +rm -rf ./build/release +mkdir -p ./build/release +cd ./build/release +CC=${CLANG} CXX=${CXXLANG} cmake -D USE_DEVICE_TREZOR=OFF -D BUILD_GUI_DEPS=1 -D BUILD_TESTS=OFF -D ARCH=${ARCH} -D STATIC=ON -D BUILD_64=${BUILD_64} -D CMAKE_BUILD_TYPE=release -D ANDROID=true -D INSTALL_VENDORED_LIBUNBOUND=ON -D BUILD_TAG=${TAG} -D CMAKE_SYSTEM_NAME="Android" -D CMAKE_ANDROID_STANDALONE_TOOLCHAIN="${ANDROID_STANDALONE_TOOLCHAIN_PATH}" -D CMAKE_ANDROID_ARCH_ABI=${ARCH_ABI} $FLAGS ../.. + +make wallet_api -j$THREADS +find . -path ./lib -prune -o -name '*.a' -exec cp '{}' lib \; + +cp -r ./lib/* $DEST_LIB_DIR +cp ../../src/wallet/api/wallet2_api.h $DEST_INCLUDE_DIR +done diff --git a/scripts/android/build_wownero_all.sh b/scripts/android/build_wownero_all.sh new file mode 100755 index 00000000..7763697f --- /dev/null +++ b/scripts/android/build_wownero_all.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +./build_iconv.sh +./build_boost.sh +./build_openssl.sh +./build_sodium.sh +./build_zmq.sh +./build_wownero.sh +./build_wownero_seed.sh \ No newline at end of file diff --git a/scripts/android/build_wownero_seed.sh b/scripts/android/build_wownero_seed.sh new file mode 100755 index 00000000..19c570fd --- /dev/null +++ b/scripts/android/build_wownero_seed.sh @@ -0,0 +1,65 @@ +#!/bin/sh + +. ./config.sh + +SEED_DIR=$WORKDIR/seed +SEED_TAG=0.3.0 +SEED_COMMIT_HASH="ef6910b6bb3b61757c36e2e5db0927d75f1731c8" + +for arch in "aarch" "aarch64" "i686" "x86_64" +do + +FLAGS="" +PREFIX=$WORKDIR/prefix_${arch} +DEST_LIB_DIR=${PREFIX}/lib/ +DEST_INCLUDE_DIR=${PREFIX}/include/ +export CMAKE_INCLUDE_PATH="${PREFIX}/include" +export CMAKE_LIBRARY_PATH="${PREFIX}/lib" +ANDROID_STANDALONE_TOOLCHAIN_PATH="${TOOLCHAIN_BASE_DIR}_${arch}" +PATH="${ANDROID_STANDALONE_TOOLCHAIN_PATH}/bin:${ORIGINAL_PATH}" + +case $arch in + "aarch" ) + CLANG=arm-linux-androideabi-clang + CXXLANG=arm-linux-androideabi-clang++ + BUILD_64=OFF + TAG="android-armv7" + ARCH="armv7-a" + ARCH_ABI="armeabi-v7a" + FLAGS="-D CMAKE_ANDROID_ARM_MODE=ON";; + "aarch64" ) + CLANG=aarch64-linux-androideabi-clang + CXXLANG=aarch64-linux-androideabi-clang++ + BUILD_64=ON + TAG="android-armv8" + ARCH="armv8-a" + ARCH_ABI="arm64-v8a";; + "i686" ) + CLANG=i686-linux-androideabi-clang + CXXLANG=i686-linux-androideabi-clang++ + BUILD_64=OFF + TAG="android-x86" + ARCH="i686" + ARCH_ABI="x86";; + "x86_64" ) + CLANG=x86_64-linux-androideabi-clang + CXXLANG=x86_64-linux-androideabi-clang++ + BUILD_64=ON + TAG="android-x86_64" + ARCH="x86-64" + ARCH_ABI="x86_64";; +esac + +cd $WORKDIR + +rm -rf $SEED_DIR +git clone -b $SEED_TAG --depth 1 https://git.wownero.com/wowlet/wownero-seed.git $SEED_DIR +cd $SEED_DIR +git reset --hard $SEED_COMMIT_HASH + +CC={$CLANG} CXX={$CXXLANG} cmake -Bbuild -DCMAKE_INSTALL_PREFIX=${PREFIX} ARCH=${ARCH} -D CMAKE_BUILD_TYPE=Release -D CMAKE_SYSTEM_NAME="Android" -D CMAKE_ANDROID_STANDALONE_TOOLCHAIN="${ANDROID_STANDALONE_TOOLCHAIN_PATH}" -D CMAKE_ANDROID_ARCH_ABI=${ARCH_ABI} $FLAGS . +make -Cbuild -j$THREADS +make -Cbuild install + +done + diff --git a/scripts/android/copy_monero_deps.sh b/scripts/android/copy_monero_deps.sh index 55360697..c20aad6c 100755 --- a/scripts/android/copy_monero_deps.sh +++ b/scripts/android/copy_monero_deps.sh @@ -3,6 +3,7 @@ WORKDIR=/opt/android CW_DIR=${WORKDIR}/cake_wallet CW_EXRTERNAL_DIR=${CW_DIR}/cw_shared_external/ios/External/android +CW_WOWNERO_EXTERNAL_DIR=${CW_DIR}/cw_wownero/ios/External/android CW_HAVEN_EXTERNAL_DIR=${CW_DIR}/cw_haven/ios/External/android CW_MONERO_EXTERNAL_DIR=${CW_DIR}/cw_monero/ios/External/android for arch in "aarch" "aarch64" "i686" "x86_64" @@ -36,6 +37,9 @@ done mkdir -p ${CW_HAVEN_EXTERNAL_DIR}/include mkdir -p ${CW_MONERO_EXTERNAL_DIR}/include +mkdir -p ${CW_WOWNERO_EXTERNAL_DIR}/include cp $CW_EXRTERNAL_DIR/x86/include/monero/wallet2_api.h ${CW_MONERO_EXTERNAL_DIR}/include cp $CW_EXRTERNAL_DIR/x86/include/haven/wallet2_api.h ${CW_HAVEN_EXTERNAL_DIR}/include +cp $CW_EXRTERNAL_DIR/x86/include/wownero/wallet2_api.h ${CW_WOWNERO_EXTERNAL_DIR}/include +cp -R $CW_EXRTERNAL_DIR/x86/include/wownero_seed ${CW_WOWNERO_EXTERNAL_DIR}/include diff --git a/scripts/android/pubspec_gen.sh b/scripts/android/pubspec_gen.sh index c69ac390..72d67970 100755 --- a/scripts/android/pubspec_gen.sh +++ b/scripts/android/pubspec_gen.sh @@ -3,6 +3,7 @@ MONERO_COM=monero.com CAKEWALLET=cakewallet HAVEN=haven +WOWNERO=wownero CONFIG_ARGS="" case $APP_ANDROID_TYPE in @@ -10,11 +11,14 @@ case $APP_ANDROID_TYPE in CONFIG_ARGS="--monero" ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --haven" + CONFIG_ARGS="--monero --bitcoin --haven --wownero" ;; $HAVEN) CONFIG_ARGS="--haven" ;; + $WOWNERO) + CONFIG_ARGS="--wownero" + ;; esac cd ../.. diff --git a/scripts/ios/app_config.sh b/scripts/ios/app_config.sh index fdc0072e..ad53b6cd 100755 --- a/scripts/ios/app_config.sh +++ b/scripts/ios/app_config.sh @@ -3,6 +3,7 @@ MONERO_COM="monero.com" CAKEWALLET="cakewallet" HAVEN="haven" +WOWNERO="wownero" DIR=`pwd` if [ -z "$APP_IOS_TYPE" ]; then @@ -23,11 +24,14 @@ case $APP_IOS_TYPE in CONFIG_ARGS="--monero" ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --haven" + CONFIG_ARGS="--monero --bitcoin --haven --wownero" ;; $HAVEN) CONFIG_ARGS="--haven" ;; + $WOWNERO) + CONFIG_ARGS="--wownero" + ;; esac cp -rf pubspec_description.yaml pubspec.yaml diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index ca7e5de7..f832ddee 100755 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -8,8 +8,9 @@ APP_IOS_BUNDLE_ID="" MONERO_COM="monero.com" CAKEWALLET="cakewallet" HAVEN="haven" +WOWNERO="wownero" -TYPES=($MONERO_COM $CAKEWALLET $HAVEN) +TYPES=($MONERO_COM $CAKEWALLET $HAVEN $WOWNERO) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" @@ -27,6 +28,11 @@ HAVEN_VERSION="1.0.0" HAVEN_BUILD_NUMBER=3 HAVEN_BUNDLE_ID="com.cakewallet.haven" +WOWNERO_NAME="Wownero" +WOWNERO_VERSION="1.0.0" +WOWNERO_BUILD_NUMBER=3 +WOWNERO_BUNDLE_ID="com.cakewallet.wownero" + if ! [[ " ${TYPES[*]} " =~ " ${APP_IOS_TYPE} " ]]; then echo "Wrong app type." exit 1 @@ -51,6 +57,12 @@ case $APP_IOS_TYPE in APP_IOS_BUILD_NUMBER=$HAVEN_BUILD_NUMBER APP_IOS_BUNDLE_ID=$HAVEN_BUNDLE_ID ;; + $WOWNERO) + APP_IOS_NAME=$WOWNERO_NAME + APP_IOS_VERSION=$WOWNERO_VERSION + APP_IOS_BUILD_NUMBER=$WOWNERO_BUILD_NUMBER + APP_IOS_BUNDLE_ID=$WOWNERO_BUNDLE_ID + ;; esac export APP_IOS_TYPE diff --git a/scripts/ios/app_icon.sh b/scripts/ios/app_icon.sh index 3914c375..832258d5 100755 --- a/scripts/ios/app_icon.sh +++ b/scripts/ios/app_icon.sh @@ -18,6 +18,10 @@ case $APP_IOS_TYPE in ICON_120_PATH=`pwd`/../../assets/images/cakewallet_icon_120.png ICON_180_PATH=`pwd`/../../assets/images/cakewallet_icon_180.png ICON_1024_PATH=`pwd`/../../assets/images/cakewallet_icon_1024.png;; + "wownero") + ICON_120_PATH=`pwd`/../../assets/images/cakewallet_icon_120.png + ICON_180_PATH=`pwd`/../../assets/images/cakewallet_icon_180.png + ICON_1024_PATH=`pwd`/../../assets/images/cakewallet_icon_1024.png;; esac rm $DEST_DIR_PATH/app_icon_120.png diff --git a/scripts/ios/build_all.sh b/scripts/ios/build_all.sh index 565679e2..01c3900b 100755 --- a/scripts/ios/build_all.sh +++ b/scripts/ios/build_all.sh @@ -9,6 +9,7 @@ DIR=$(dirname "$0") case $APP_IOS_TYPE in "monero.com") $DIR/build_monero_all.sh ;; - "cakewallet") $DIR/build_monero_all.sh && $DIR/build_haven.sh ;; + "cakewallet") $DIR/build_monero_all.sh && $DIR/build_haven.sh && $DIR/build_wownero.sh && $DIR/build_wownero_seed.sh ;; "haven") $DIR/build_haven_all.sh ;; + "wownero") $DIR/build_wownero_all.sh ;; esac diff --git a/scripts/ios/build_wownero.sh b/scripts/ios/build_wownero.sh new file mode 100755 index 00000000..9cefa42a --- /dev/null +++ b/scripts/ios/build_wownero.sh @@ -0,0 +1,67 @@ +#!/bin/sh + +. ./config.sh + +WOWNERO_URL="https://git.wownero.com/wownero/wownero.git" +WOWNERO_DIR_PATH="${EXTERNAL_IOS_SOURCE_DIR}/wownero" +WOWNERO_VERSION=fix-armv7a-compile +WOWNERO_SHA_HEAD="373b8842c6075c54cc4904b147f1c86daf7cb60d" + +BUILD_TYPE=release +PREFIX=${EXTERNAL_IOS_DIR} +DEST_LIB_DIR=${EXTERNAL_IOS_LIB_DIR}/wownero +DEST_INCLUDE_DIR=${EXTERNAL_IOS_INCLUDE_DIR}/wownero + +echo "Cloning wownero from - $WOWNERO_URL to - $WOWNERO_DIR_PATH" +git clone $WOWNERO_URL $WOWNERO_DIR_PATH +cd $WOWNERO_DIR_PATH +git reset --hard $WOWNERO_SHA_HEAD +git checkout $WOWNERO_VERSION +git submodule update --init --force +mkdir -p build +cd .. + +ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +if [ -z $INSTALL_PREFIX ]; then + INSTALL_PREFIX=${ROOT_DIR}/wownero +fi + +for arch in "arm64" #"armv7" "arm64" +do + +echo "Building IOS ${arch}" +export CMAKE_INCLUDE_PATH="${PREFIX}/include" +export CMAKE_LIBRARY_PATH="${PREFIX}/lib" + +case $arch in + "armv7" ) + DEST_LIB=../../lib-armv7;; + "arm64" ) + DEST_LIB=../../lib-armv8-a;; +esac + +rm -rf wownero/build > /dev/null + +mkdir -p wownero/build/${BUILD_TYPE} +pushd wownero/build/${BUILD_TYPE} +cmake -D IOS=ON \ + -DARCH=${arch} \ + -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \ + -DSTATIC=ON \ + -DBUILD_GUI_DEPS=ON \ + -DINSTALL_VENDORED_LIBUNBOUND=ON \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} \ + -DUSE_DEVICE_TREZOR=OFF \ + ../.. +make -j4 && make install +cp src/cryptonote_basic/libcryptonote_basic.a ${DEST_LIB} +cp src/offshore/liboffshore.a ${DEST_LIB} +popd + +done + +#only for arm64 +mkdir -p $DEST_LIB_DIR +mkdir -p $DEST_INCLUDE_DIR +cp ${WOWNERO_DIR_PATH}/lib-armv8-a/* $DEST_LIB_DIR +cp ${WOWNERO_DIR_PATH}/include/wallet/api/* $DEST_INCLUDE_DIR \ No newline at end of file diff --git a/scripts/ios/build_wownero_all.sh b/scripts/ios/build_wownero_all.sh new file mode 100755 index 00000000..60a202fa --- /dev/null +++ b/scripts/ios/build_wownero_all.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +. ./config.sh +./install_missing_headers.sh +./build_openssl.sh +./build_boost.sh +./build_sodium.sh +./build_zmq.sh +./build_wownero.sh +./build_wownero_seed.sh \ No newline at end of file diff --git a/scripts/ios/build_wownero_seed.sh b/scripts/ios/build_wownero_seed.sh new file mode 100644 index 00000000..9501e01c --- /dev/null +++ b/scripts/ios/build_wownero_seed.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +. ./config.sh + +SEED_VERSION=0.3.0 +SEED_SRC_DIR="${EXTERNAL_IOS_SOURCE_DIR}/seed" +SEED_URL="https://git.wownero.com/wowlet/wownero-seed.git" +SEED_SHA_HEAD="ef6910b6bb3b61757c36e2e5db0927d75f1731c8" + +rm -rf "$SEED_SRC_DIR" > /dev/null + +echo "[*] cloning $SEED_URL" +git clone --branch ${SEED_VERSION} ${SEED_URL} ${SEED_SRC_DIR} +cd $SEED_SRC_DIR +git reset --hard $SEED_SHA_HEAD + +BUILD_TYPE=release +PREFIX=${EXTERNAL_IOS_DIR} +DEST_LIB_DIR=${EXTERNAL_IOS_LIB_DIR}/wownero-seed +DEST_INCLUDE_DIR=${EXTERNAL_IOS_INCLUDE_DIR}/wownero-seed + +ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +if [ -z $INSTALL_PREFIX ]; then + INSTALL_PREFIX=${ROOT_DIR}/wownero-seed +fi + +for arch in "arm64" #"armv7" "arm64" +do + +echo "Building wownero-seed IOS ${arch}" +export CMAKE_INCLUDE_PATH="${PREFIX}/include" +export CMAKE_LIBRARY_PATH="${PREFIX}/lib" + +case $arch in + "armv7" ) + DEST_LIB=../../lib-armv7;; + "arm64" ) + DEST_LIB=../../lib-armv8-a;; +esac + +cmake -Bbuild -DCMAKE_INSTALL_PREFIX="${PREFIX}" -DCMAKE_SYSTEM_NAME="iOS" -DCMAKE_OSX_ARCHITECTURES="${arch}" . +make -Cbuild -j$THREADS +make -Cbuild install + +done diff --git a/scripts/ios/setup.sh b/scripts/ios/setup.sh index bddfa21e..94625503 100755 --- a/scripts/ios/setup.sh +++ b/scripts/ios/setup.sh @@ -8,20 +8,26 @@ LIBRANDOMX_PATH=${EXTERNAL_IOS_LIB_DIR}/monero/librandomx.a if [ -f "$LIBRANDOMX_PATH" ]; then cp $LIBRANDOMX_PATH ./haven + cp $LIBRANDOMX_PATH ./wownero fi libtool -static -o libboost.a ./libboost_*.a libtool -static -o libhaven.a ./haven/*.a +libtool -static -o libwownero.a ./wownero/*.a libtool -static -o libmonero.a ./monero/*.a CW_HAVEN_EXTERNAL_LIB=../../../../../cw_haven/ios/External/ios/lib CW_HAVEN_EXTERNAL_INCLUDE=../../../../../cw_haven/ios/External/ios/include +CW_WOWNERO_EXTERNAL_LIB=../../../../../cw_wownero/ios/External/ios/lib +CW_WOWNERO_EXTERNAL_INCLUDE=../../../../../cw_wownero/ios/External/ios/include CW_MONERO_EXTERNAL_LIB=../../../../../cw_monero/ios/External/ios/lib CW_MONERO_EXTERNAL_INCLUDE=../../../../../cw_monero/ios/External/ios/include mkdir -p $CW_HAVEN_EXTERNAL_INCLUDE +mkdir -p $CW_WOWNERO_EXTERNAL_INCLUDE mkdir -p $CW_MONERO_EXTERNAL_INCLUDE mkdir -p $CW_HAVEN_EXTERNAL_LIB +mkdir -p $CW_WOWNERO_EXTERNAL_LIB mkdir -p $CW_MONERO_EXTERNAL_LIB ln ./libboost.a ${CW_HAVEN_EXTERNAL_LIB}/libboost.a @@ -31,6 +37,13 @@ ln ./libsodium.a ${CW_HAVEN_EXTERNAL_LIB}/libsodium.a cp ./libhaven.a $CW_HAVEN_EXTERNAL_LIB cp ../include/haven/* $CW_HAVEN_EXTERNAL_INCLUDE +ln ./libboost.a ${CW_WOWNERO_EXTERNAL_LIB}/libboost.a +ln ./libcrypto.a ${CW_WOWNERO_EXTERNAL_LIB}/libcrypto.a +ln ./libssl.a ${CW_WOWNERO_EXTERNAL_LIB}/libssl.a +ln ./libsodium.a ${CW_WOWNERO_EXTERNAL_LIB}/libsodium.a +cp ./libwownero.a $CW_WOWNERO_EXTERNAL_LIB +cp ../include/wownero/* $CW_WOWNERO_EXTERNAL_INCLUDE + ln ./libboost.a ${CW_MONERO_EXTERNAL_LIB}/libboost.a ln ./libcrypto.a ${CW_MONERO_EXTERNAL_LIB}/libcrypto.a ln ./libssl.a ${CW_MONERO_EXTERNAL_LIB}/libssl.a diff --git a/tool/configure.dart b/tool/configure.dart index dcbac59c..7e25db7d 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -4,6 +4,7 @@ import 'dart:io'; const bitcoinOutputPath = 'lib/bitcoin/bitcoin.dart'; const moneroOutputPath = 'lib/monero/monero.dart'; const havenOutputPath = 'lib/haven/haven.dart'; +const wowneroOutputPath = 'lib/wownero/wownero.dart'; const walletTypesPath = 'lib/wallet_types.g.dart'; const pubspecDefaultPath = 'pubspec_default.yaml'; const pubspecOutputPath = 'pubspec.yaml'; @@ -13,11 +14,13 @@ Future main(List args) async { final hasBitcoin = args.contains('${prefix}bitcoin'); final hasMonero = args.contains('${prefix}monero'); final hasHaven = args.contains('${prefix}haven'); + final hasWownero = args.contains('${prefix}wownero'); await generateBitcoin(hasBitcoin); await generateMonero(hasMonero); await generateHaven(hasHaven); - await generatePubspec(hasMonero: hasMonero, hasBitcoin: hasBitcoin, hasHaven: hasHaven); - await generateWalletTypes(hasMonero: hasMonero, hasBitcoin: hasBitcoin, hasHaven: hasHaven); + await generateWownero(hasWownero); + await generatePubspec(hasMonero: hasMonero, hasBitcoin: hasBitcoin, hasHaven: hasHaven, hasWownero: hasWownero); + await generateWalletTypes(hasMonero: hasMonero, hasBitcoin: hasBitcoin, hasHaven: hasHaven, hasWownero: hasWownero); } Future generateBitcoin(bool hasImplementation) async { @@ -452,7 +455,163 @@ abstract class HavenAccountList { await outputFile.writeAsString(output); } -Future generatePubspec({bool hasMonero, bool hasBitcoin, bool hasHaven}) async { +Future generateWownero(bool hasImplementation) async { + final outputFile = File(wowneroOutputPath); + const wowneroCommonHeaders = """ +import 'package:mobx/mobx.dart'; +import 'package:flutter/foundation.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/transaction_history.dart'; +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/balance.dart'; +import 'package:cw_core/output_info.dart'; +import 'package:cake_wallet/view_model/send/output.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:hive/hive.dart';"""; + const wowneroCWHeaders = """ +import 'package:cw_core/monero_amount_format.dart'; +import 'package:cw_core/monero_transaction_priority.dart'; +import 'package:cw_wownero/wownero_amount_format.dart'; +import 'package:cw_wownero/wownero_wallet_service.dart'; +import 'package:cw_wownero/wownero_wallet.dart'; +import 'package:cw_wownero/wownero_transaction_info.dart'; +import 'package:cw_wownero/wownero_transaction_history.dart'; +import 'package:cw_wownero/wownero_transaction_creation_credentials.dart'; +import 'package:cw_core/account.dart' as wownero_account; +import 'package:cw_wownero/api/wallet.dart' as wownero_wallet_api; +import 'package:cw_wownero/mnemonics/english.dart'; +"""; + const wowneroCwPart = "part 'cw_wownero.dart';"; + const wowneroContent = """ +class Account { + Account({this.id, this.label}); + final int id; + final String label; +} + +class Subaddress { + Subaddress({this.id, this.accountId, this.label, this.address}); + final int id; + final int accountId; + final String label; + final String address; +} + +class WowneroBalance extends Balance { + WowneroBalance({@required this.fullBalance, @required this.unlockedBalance}) + : formattedFullBalance = wownero.formatterWowneroAmountToString(amount: fullBalance), + formattedUnlockedBalance = + wownero.formatterWowneroAmountToString(amount: unlockedBalance), + super(unlockedBalance, fullBalance); + + WowneroBalance.fromString( + {@required this.formattedFullBalance, + @required this.formattedUnlockedBalance}) + : fullBalance = wownero.formatterWowneroParseAmount(amount: formattedFullBalance), + unlockedBalance = wownero.formatterWowneroParseAmount(amount: formattedUnlockedBalance), + super(wownero.formatterWowneroParseAmount(amount: formattedUnlockedBalance), + wownero.formatterWowneroParseAmount(amount: formattedFullBalance)); + + final int fullBalance; + final int unlockedBalance; + final String formattedFullBalance; + final String formattedUnlockedBalance; + + @override + String get formattedAvailableBalance => formattedUnlockedBalance; + + @override + String get formattedAdditionalBalance => formattedFullBalance; +} + +abstract class WowneroWalletDetails { + @observable + Account account; + + @observable + WowneroBalance balance; +} + +abstract class Wownero { + WowneroAccountList getAccountList(Object wallet); + + WowneroSubaddressList getSubaddressList(Object wallet); + + TransactionHistoryBase getTransactionHistory(Object wallet); + + WowneroWalletDetails getWowneroWalletDetails(Object wallet); + + String getTransactionAddress(Object wallet, int accountIndex, int addressIndex); + + String getSubaddressLabel(Object wallet, int accountIndex, int addressIndex); + + TransactionPriority getDefaultTransactionPriority(); + TransactionPriority deserializeMoneroTransactionPriority({int raw}); + List getTransactionPriorities(); + List getWowneroWordList(String language); + + WalletCredentials createWowneroRestoreWalletFromKeysCredentials({ + String name, + String spendKey, + String viewKey, + String address, + String password, + String language, + int height}); + WalletCredentials createWowneroRestoreWalletFromSeedCredentials({String name, String password, int height, String mnemonic}); + WalletCredentials createWowneroNewWalletCredentials({String name, String password, String language}); + Map getKeys(Object wallet); + Object createWowneroTransactionCreationCredentials({List outputs, TransactionPriority priority}); + String formatterWowneroAmountToString({int amount}); + double formatterWowneroAmountToDouble({int amount}); + int formatterWowneroParseAmount({String amount}); + Account getCurrentAccount(Object wallet); + void setCurrentAccount(Object wallet, int id, String label); + void onStartup(); + int getTransactionInfoAccountId(TransactionInfo tx); + WalletService createWowneroWalletService(Box walletInfoSource); +} + +abstract class WowneroSubaddressList { + ObservableList get subaddresses; + void update(Object wallet, {int accountIndex}); + void refresh(Object wallet, {int accountIndex}); + List getAll(Object wallet); + Future addSubaddress(Object wallet, {int accountIndex, String label}); + Future setLabelSubaddress(Object wallet, + {int accountIndex, int addressIndex, String label}); +} + +abstract class WowneroAccountList { + ObservableList get accounts; + void update(Object wallet); + void refresh(Object wallet); + List getAll(Object wallet); + Future addAccount(Object wallet, {String label}); + Future setLabelAccount(Object wallet, {int accountIndex, String label}); +} + """; + + const wowneroEmptyDefinition = 'Wownero wownero;\n'; + const wowneroCWDefinition = 'Wownero wownero = CWWownero();\n'; + + final output = '$wowneroCommonHeaders\n' + + (hasImplementation ? '$wowneroCWHeaders\n' : '\n') + + (hasImplementation ? '$wowneroCwPart\n\n' : '\n') + + (hasImplementation ? wowneroCWDefinition : wowneroEmptyDefinition) + + '\n' + + wowneroContent; + + if (outputFile.existsSync()) { + await outputFile.delete(); + } + + await outputFile.writeAsString(output); +} + +Future generatePubspec({bool hasMonero, bool hasBitcoin, bool hasHaven, bool hasWownero}) async { const cwCore = """ cw_core: path: ./cw_core @@ -469,6 +628,10 @@ Future generatePubspec({bool hasMonero, bool hasBitcoin, bool hasHaven}) a cw_haven: path: ./cw_haven """; + const cwWownero = """ + cw_wownero: + path: ./cw_wownero + """; const cwSharedExternal = """ cw_shared_external: path: ./cw_shared_external @@ -493,6 +656,12 @@ Future generatePubspec({bool hasMonero, bool hasBitcoin, bool hasHaven}) a output += '\n$cwHaven'; } + if (hasWownero && !hasMonero) { + output += '\n$cwSharedExternal\n$cwWownero'; + } else if (hasWownero) { + output += '\n$cwWownero'; + } + final outputLines = output.split('\n'); inputLines.insertAll(dependenciesIndex + 1, outputLines); final outputContent = inputLines.join('\n'); @@ -505,7 +674,7 @@ Future generatePubspec({bool hasMonero, bool hasBitcoin, bool hasHaven}) a await outputFile.writeAsString(outputContent); } -Future generateWalletTypes({bool hasMonero, bool hasBitcoin, bool hasHaven}) async { +Future generateWalletTypes({bool hasMonero, bool hasBitcoin, bool hasHaven, bool hasWownero}) async { final walletTypesFile = File(walletTypesPath); if (walletTypesFile.existsSync()) { @@ -528,6 +697,10 @@ Future generateWalletTypes({bool hasMonero, bool hasBitcoin, bool hasHaven outputContent += '\tWalletType.haven,\n'; } + if (hasWownero) { + outputContent += '\tWalletType.wownero,\n'; + } + outputContent += '];\n'; await walletTypesFile.writeAsString(outputContent); } \ No newline at end of file