From 376632e22dcfb8ec05f9c489934f880f9d28bae7 Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Sat, 20 Apr 2024 10:07:50 +0100
Subject: [PATCH 01/31] Allow the app to run without building monero

---
 cw_haven/android/build.gradle                                    | 1 -
 cw_monero/android/build.gradle                                   | 1 -
 .../src/main/kotlin/com/cakewallet/monero/CwMoneroPlugin.kt      | 1 -
 lib/main.dart                                                    | 1 -
 4 files changed, 4 deletions(-)

diff --git a/cw_haven/android/build.gradle b/cw_haven/android/build.gradle
index fb941f657..1319e4ad4 100644
--- a/cw_haven/android/build.gradle
+++ b/cw_haven/android/build.gradle
@@ -35,7 +35,6 @@ android {
     }
     externalNativeBuild {
         cmake {
-          path "CMakeLists.txt"
         }
     }
 }
diff --git a/cw_monero/android/build.gradle b/cw_monero/android/build.gradle
index fc4835e81..46b1b4315 100644
--- a/cw_monero/android/build.gradle
+++ b/cw_monero/android/build.gradle
@@ -39,7 +39,6 @@ android {
     }
     externalNativeBuild {
         cmake {
-          path "CMakeLists.txt"
         }
     }
 }
diff --git a/cw_monero/android/src/main/kotlin/com/cakewallet/monero/CwMoneroPlugin.kt b/cw_monero/android/src/main/kotlin/com/cakewallet/monero/CwMoneroPlugin.kt
index 37684a16a..57eec7d00 100644
--- a/cw_monero/android/src/main/kotlin/com/cakewallet/monero/CwMoneroPlugin.kt
+++ b/cw_monero/android/src/main/kotlin/com/cakewallet/monero/CwMoneroPlugin.kt
@@ -26,7 +26,6 @@ class CwMoneroPlugin: MethodCallHandler {
     val main = Handler(Looper.getMainLooper());
 
     init {
-      System.loadLibrary("cw_monero")
     }
 
     @JvmStatic
diff --git a/lib/main.dart b/lib/main.dart
index b80c9eb85..cf9a766a4 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -207,7 +207,6 @@ Future<void> initialSetup(
       unspentCoinsInfoSource: unspentCoinsInfoSource,
       secureStorage: secureStorage);
   await bootstrap(navigatorKey);
-  monero?.onStartup();
 }
 
 class App extends StatefulWidget {

From 9e9ff7095e91b500114cd47196033fb84d96d55a Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Sun, 21 Apr 2024 00:20:52 +0100
Subject: [PATCH 02/31] Add mwebd

---
 cw_bitcoin/lib/litecoin_wallet.dart           |  15 +
 cw_bitcoin/lib/litecoin_wallet_service.dart   |   6 +
 cw_bitcoin/pubspec.lock                       |  47 ++
 cw_bitcoin/pubspec.yaml                       |   3 +
 cw_mweb/.gitignore                            |  31 +
 cw_mweb/.metadata                             |  36 +
 cw_mweb/CHANGELOG.md                          |   3 +
 cw_mweb/LICENSE                               |   1 +
 cw_mweb/README.md                             |  15 +
 cw_mweb/analysis_options.yaml                 |   4 +
 cw_mweb/android/.gitignore                    |   9 +
 cw_mweb/android/build.gradle                  |  76 ++
 cw_mweb/android/settings.gradle               |   1 +
 cw_mweb/android/src/main/AndroidManifest.xml  |   3 +
 .../com/cakewallet/mweb/CwMwebPlugin.kt       |  43 +
 .../cakewallet/cw_mweb/CwMwebPluginTest.kt    |  27 +
 cw_mweb/ios/.gitignore                        |  38 +
 cw_mweb/ios/Assets/.gitkeep                   |   0
 cw_mweb/ios/Classes/CwMwebPlugin.swift        |  19 +
 cw_mweb/ios/cw_mweb.podspec                   |  23 +
 cw_mweb/lib/cw_mweb.dart                      |  17 +
 cw_mweb/lib/cw_mweb_method_channel.dart       |  17 +
 cw_mweb/lib/cw_mweb_platform_interface.dart   |  29 +
 cw_mweb/lib/mwebd.pb.dart                     | 773 ++++++++++++++++++
 cw_mweb/lib/mwebd.pbgrpc.dart                 | 159 ++++
 cw_mweb/macos/Classes/CwMwebPlugin.swift      |  19 +
 cw_mweb/macos/cw_mweb.podspec                 |  23 +
 cw_mweb/pubspec.yaml                          |  74 ++
 cw_mweb/test/cw_mweb_method_channel_test.dart |  27 +
 cw_mweb/test/cw_mweb_test.dart                |  29 +
 macos/Flutter/GeneratedPluginRegistrant.swift |   2 +
 31 files changed, 1569 insertions(+)
 create mode 100644 cw_mweb/.gitignore
 create mode 100644 cw_mweb/.metadata
 create mode 100644 cw_mweb/CHANGELOG.md
 create mode 100644 cw_mweb/LICENSE
 create mode 100644 cw_mweb/README.md
 create mode 100644 cw_mweb/analysis_options.yaml
 create mode 100644 cw_mweb/android/.gitignore
 create mode 100644 cw_mweb/android/build.gradle
 create mode 100644 cw_mweb/android/settings.gradle
 create mode 100644 cw_mweb/android/src/main/AndroidManifest.xml
 create mode 100644 cw_mweb/android/src/main/kotlin/com/cakewallet/mweb/CwMwebPlugin.kt
 create mode 100644 cw_mweb/android/src/test/kotlin/com/cakewallet/cw_mweb/CwMwebPluginTest.kt
 create mode 100644 cw_mweb/ios/.gitignore
 create mode 100644 cw_mweb/ios/Assets/.gitkeep
 create mode 100644 cw_mweb/ios/Classes/CwMwebPlugin.swift
 create mode 100644 cw_mweb/ios/cw_mweb.podspec
 create mode 100644 cw_mweb/lib/cw_mweb.dart
 create mode 100644 cw_mweb/lib/cw_mweb_method_channel.dart
 create mode 100644 cw_mweb/lib/cw_mweb_platform_interface.dart
 create mode 100644 cw_mweb/lib/mwebd.pb.dart
 create mode 100644 cw_mweb/lib/mwebd.pbgrpc.dart
 create mode 100644 cw_mweb/macos/Classes/CwMwebPlugin.swift
 create mode 100644 cw_mweb/macos/cw_mweb.podspec
 create mode 100644 cw_mweb/pubspec.yaml
 create mode 100644 cw_mweb/test/cw_mweb_method_channel_test.dart
 create mode 100644 cw_mweb/test/cw_mweb_test.dart

diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index d2379d5a5..f218ddd5d 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -1,3 +1,4 @@
+import 'dart:async';
 import 'package:bitcoin_base/bitcoin_base.dart';
 import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
 import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
@@ -14,6 +15,8 @@ import 'package:cw_bitcoin/electrum_wallet.dart';
 import 'package:cw_bitcoin/bitcoin_address_record.dart';
 import 'package:cw_bitcoin/electrum_balance.dart';
 import 'package:cw_bitcoin/litecoin_network.dart';
+import 'package:cw_mweb/cw_mweb.dart';
+import 'package:cw_mweb/mwebd.pb.dart';
 import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
 
 part 'litecoin_wallet.g.dart';
@@ -103,6 +106,18 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
     );
   }
 
+  @action
+  @override
+  Future<void> startSync() async {
+    super.startSync();
+    final stub = CwMweb.stub();
+    Timer.periodic(
+      const Duration(seconds: 1), (timer) async {
+        final resp = await stub.status(StatusRequest());
+        print(resp.blockHeaderHeight);
+      });
+  }
+
   @override
   int feeRate(TransactionPriority priority) {
     if (priority is LitecoinTransactionPriority) {
diff --git a/cw_bitcoin/lib/litecoin_wallet_service.dart b/cw_bitcoin/lib/litecoin_wallet_service.dart
index ee3b0e628..7f2066c30 100644
--- a/cw_bitcoin/lib/litecoin_wallet_service.dart
+++ b/cw_bitcoin/lib/litecoin_wallet_service.dart
@@ -1,4 +1,5 @@
 import 'dart:io';
+import 'package:path_provider/path_provider.dart';
 import 'package:cw_core/unspent_coins_info.dart';
 import 'package:hive/hive.dart';
 import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
@@ -10,6 +11,7 @@ import 'package:cw_core/pathForWallet.dart';
 import 'package:cw_core/wallet_type.dart';
 import 'package:cw_core/wallet_info.dart';
 import 'package:cw_core/wallet_base.dart';
+import 'package:cw_mweb/cw_mweb.dart';
 import 'package:collection/collection.dart';
 
 class LitecoinWalletService extends WalletService<
@@ -26,6 +28,8 @@ class LitecoinWalletService extends WalletService<
 
   @override
   Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
+    final appDir = await getApplicationSupportDirectory();
+    await CwMweb.start(appDir.path);
     final wallet = await LitecoinWalletBase.create(
         mnemonic: await generateMnemonic(),
         password: credentials.password!,
@@ -43,6 +47,8 @@ class LitecoinWalletService extends WalletService<
 
   @override
   Future<LitecoinWallet> openWallet(String name, String password) async {
+    final appDir = await getApplicationSupportDirectory();
+    await CwMweb.start(appDir.path);
     final walletInfo = walletInfoSource.values.firstWhereOrNull(
         (info) => info.id == WalletBase.idFor(name, getType()))!;
 
diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock
index 50cd432c0..20851ea86 100644
--- a/cw_bitcoin/pubspec.lock
+++ b/cw_bitcoin/pubspec.lock
@@ -17,6 +17,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "4.7.0"
+  archive:
+    dependency: transitive
+    description:
+      name: archive
+      sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.4.10"
   args:
     dependency: transitive
     description:
@@ -252,6 +260,13 @@ packages:
       relative: true
     source: path
     version: "0.0.1"
+  cw_mweb:
+    dependency: "direct main"
+    description:
+      path: "../cw_mweb"
+      relative: true
+    source: path
+    version: "0.0.1"
   dart_style:
     dependency: transitive
     description:
@@ -334,6 +349,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.1.2"
+  googleapis_auth:
+    dependency: transitive
+    description:
+      name: googleapis_auth
+      sha256: af7c3a3edf9d0de2e1e0a77e994fae0a581c525fa7012af4fa0d4a52ed9484da
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.4.1"
   graphs:
     dependency: transitive
     description:
@@ -342,6 +365,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.3.1"
+  grpc:
+    dependency: "direct main"
+    description:
+      name: grpc
+      sha256: e93ee3bce45c134bf44e9728119102358c7cd69de7832d9a874e2e74eb8cab40
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.2.4"
   hex:
     dependency: transitive
     description:
@@ -374,6 +405,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.1.0"
+  http2:
+    dependency: transitive
+    description:
+      name: http2
+      sha256: "9ced024a160b77aba8fb8674e38f70875e321d319e6f303ec18e87bd5a4b0c1d"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.3.0"
   http_multi_server:
     dependency: transitive
     description:
@@ -582,6 +621,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.5.1"
+  protobuf:
+    dependency: transitive
+    description:
+      name: protobuf
+      sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.1.0"
   provider:
     dependency: transitive
     description:
diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml
index 632a3140a..033d1f84e 100644
--- a/cw_bitcoin/pubspec.yaml
+++ b/cw_bitcoin/pubspec.yaml
@@ -35,6 +35,9 @@ dependencies:
       url: https://github.com/cake-tech/bitcoin_base.git
       ref: cake-update-v2
   blockchain_utils: ^2.1.1
+  cw_mweb:
+    path: ../cw_mweb
+  grpc: ^3.2.4
 
 dev_dependencies:
   flutter_test:
diff --git a/cw_mweb/.gitignore b/cw_mweb/.gitignore
new file mode 100644
index 000000000..8959f5d70
--- /dev/null
+++ b/cw_mweb/.gitignore
@@ -0,0 +1,31 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
+/pubspec.lock
+**/doc/api/
+.dart_tool/
+.packages
+build/
+libs/
diff --git a/cw_mweb/.metadata b/cw_mweb/.metadata
new file mode 100644
index 000000000..606303914
--- /dev/null
+++ b/cw_mweb/.metadata
@@ -0,0 +1,36 @@
+# 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.
+
+version:
+  revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+  channel: stable
+
+project_type: plugin
+
+# Tracks metadata for the flutter migrate command
+migration:
+  platforms:
+    - platform: root
+      create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+      base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+    - platform: android
+      create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+      base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+    - platform: ios
+      create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+      base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+    - platform: macos
+      create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+      base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
+
+  # User provided section
+
+  # List of Local paths (relative to this file) that should be
+  # ignored by the migrate tool.
+  #
+  # Files that are not part of the templates will be ignored by default.
+  unmanaged_files:
+    - 'lib/main.dart'
+    - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/cw_mweb/CHANGELOG.md b/cw_mweb/CHANGELOG.md
new file mode 100644
index 000000000..41cc7d819
--- /dev/null
+++ b/cw_mweb/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.0.1
+
+* TODO: Describe initial release.
diff --git a/cw_mweb/LICENSE b/cw_mweb/LICENSE
new file mode 100644
index 000000000..ba75c69f7
--- /dev/null
+++ b/cw_mweb/LICENSE
@@ -0,0 +1 @@
+TODO: Add your license here.
diff --git a/cw_mweb/README.md b/cw_mweb/README.md
new file mode 100644
index 000000000..8a839b1ec
--- /dev/null
+++ b/cw_mweb/README.md
@@ -0,0 +1,15 @@
+# cw_mweb
+
+A new Flutter plugin project.
+
+## Getting Started
+
+This project is a starting point for a Flutter
+[plug-in package](https://flutter.dev/developing-packages/),
+a specialized package that includes platform-specific implementation code for
+Android and/or iOS.
+
+For help getting started with Flutter development, view the
+[online documentation](https://flutter.dev/docs), which offers tutorials,
+samples, guidance on mobile development, and a full API reference.
+
diff --git a/cw_mweb/analysis_options.yaml b/cw_mweb/analysis_options.yaml
new file mode 100644
index 000000000..a5744c1cf
--- /dev/null
+++ b/cw_mweb/analysis_options.yaml
@@ -0,0 +1,4 @@
+include: package:flutter_lints/flutter.yaml
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/cw_mweb/android/.gitignore b/cw_mweb/android/.gitignore
new file mode 100644
index 000000000..161bdcdaf
--- /dev/null
+++ b/cw_mweb/android/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.cxx
diff --git a/cw_mweb/android/build.gradle b/cw_mweb/android/build.gradle
new file mode 100644
index 000000000..7e67b98ad
--- /dev/null
+++ b/cw_mweb/android/build.gradle
@@ -0,0 +1,76 @@
+group 'com.cakewallet.mweb'
+version '1.0-SNAPSHOT'
+
+buildscript {
+    ext.kotlin_version = '1.7.10'
+    repositories {
+        google()
+        mavenCentral()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:7.3.0'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+rootProject.allprojects {
+    repositories {
+        flatDir {
+            dirs project(':cw_mweb').file('libs')
+        }
+    }
+}
+
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+android {
+    compileSdkVersion 31
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+
+    sourceSets {
+        main.java.srcDirs += 'src/main/kotlin'
+        test.java.srcDirs += 'src/test/kotlin'
+    }
+
+    defaultConfig {
+        minSdkVersion 16
+    }
+
+    dependencies {
+        testImplementation 'org.jetbrains.kotlin:kotlin-test'
+        testImplementation 'org.mockito:mockito-core:5.0.0'
+    }
+
+    testOptions {
+        unitTests.all {
+            useJUnitPlatform()
+
+            testLogging {
+               events "passed", "skipped", "failed", "standardOut", "standardError"
+               outputs.upToDateWhen {false}
+               showStandardStreams = true
+            }
+        }
+    }
+}
+
+dependencies {
+    implementation (name: 'mwebd', ext: 'aar')
+}
diff --git a/cw_mweb/android/settings.gradle b/cw_mweb/android/settings.gradle
new file mode 100644
index 000000000..88fbd66fb
--- /dev/null
+++ b/cw_mweb/android/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'cw_mweb'
diff --git a/cw_mweb/android/src/main/AndroidManifest.xml b/cw_mweb/android/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..fd3746a8c
--- /dev/null
+++ b/cw_mweb/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="com.cakewallet.mweb">
+</manifest>
diff --git a/cw_mweb/android/src/main/kotlin/com/cakewallet/mweb/CwMwebPlugin.kt b/cw_mweb/android/src/main/kotlin/com/cakewallet/mweb/CwMwebPlugin.kt
new file mode 100644
index 000000000..394b607ab
--- /dev/null
+++ b/cw_mweb/android/src/main/kotlin/com/cakewallet/mweb/CwMwebPlugin.kt
@@ -0,0 +1,43 @@
+package com.cakewallet.mweb
+
+import androidx.annotation.NonNull
+
+import io.flutter.embedding.engine.plugins.FlutterPlugin
+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 mwebd.Mwebd
+
+/** CwMwebPlugin */
+class CwMwebPlugin: FlutterPlugin, MethodCallHandler {
+  /// The MethodChannel that will the communication between Flutter and native Android
+  ///
+  /// This local reference serves to register the plugin with the Flutter Engine and unregister it
+  /// when the Flutter Engine is detached from the Activity
+  private lateinit var channel : MethodChannel
+  private var started = false
+
+  override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
+    channel = MethodChannel(flutterPluginBinding.binaryMessenger, "cw_mweb")
+    channel.setMethodCallHandler(this)
+  }
+
+  override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
+    if (call.method == "start") {
+      if (started) return
+      val dataDir = call.argument("dataDir") ?: ""
+      val server = Mwebd.newServer("mainnet", dataDir, "")
+      server.start(12345, true)
+      started = true
+      result.success(true)
+    } else {
+      result.notImplemented()
+    }
+  }
+
+  override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
+    channel.setMethodCallHandler(null)
+  }
+}
diff --git a/cw_mweb/android/src/test/kotlin/com/cakewallet/cw_mweb/CwMwebPluginTest.kt b/cw_mweb/android/src/test/kotlin/com/cakewallet/cw_mweb/CwMwebPluginTest.kt
new file mode 100644
index 000000000..baa81332f
--- /dev/null
+++ b/cw_mweb/android/src/test/kotlin/com/cakewallet/cw_mweb/CwMwebPluginTest.kt
@@ -0,0 +1,27 @@
+package com.cakewallet.mweb
+
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.MethodChannel
+import kotlin.test.Test
+import org.mockito.Mockito
+
+/*
+ * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation.
+ *
+ * Once you have built the plugin's example app, you can run these tests from the command
+ * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
+ * you can run them directly from IDEs that support JUnit such as Android Studio.
+ */
+
+internal class CwMwebPluginTest {
+  @Test
+  fun onMethodCall_getPlatformVersion_returnsExpectedValue() {
+    val plugin = CwMwebPlugin()
+
+    val call = MethodCall("getPlatformVersion", null)
+    val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
+    plugin.onMethodCall(call, mockResult)
+
+    Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE)
+  }
+}
diff --git a/cw_mweb/ios/.gitignore b/cw_mweb/ios/.gitignore
new file mode 100644
index 000000000..0c885071e
--- /dev/null
+++ b/cw_mweb/ios/.gitignore
@@ -0,0 +1,38 @@
+.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/ephemeral/
+/Flutter/flutter_export_environment.sh
\ No newline at end of file
diff --git a/cw_mweb/ios/Assets/.gitkeep b/cw_mweb/ios/Assets/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/cw_mweb/ios/Classes/CwMwebPlugin.swift b/cw_mweb/ios/Classes/CwMwebPlugin.swift
new file mode 100644
index 000000000..d742aab61
--- /dev/null
+++ b/cw_mweb/ios/Classes/CwMwebPlugin.swift
@@ -0,0 +1,19 @@
+import Flutter
+import UIKit
+
+public class CwMwebPlugin: NSObject, FlutterPlugin {
+  public static func register(with registrar: FlutterPluginRegistrar) {
+    let channel = FlutterMethodChannel(name: "cw_mweb", binaryMessenger: registrar.messenger())
+    let instance = CwMwebPlugin()
+    registrar.addMethodCallDelegate(instance, channel: channel)
+  }
+
+  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
+    switch call.method {
+    case "getPlatformVersion":
+      result("iOS " + UIDevice.current.systemVersion)
+    default:
+      result(FlutterMethodNotImplemented)
+    }
+  }
+}
diff --git a/cw_mweb/ios/cw_mweb.podspec b/cw_mweb/ios/cw_mweb.podspec
new file mode 100644
index 000000000..cd0600fa1
--- /dev/null
+++ b/cw_mweb/ios/cw_mweb.podspec
@@ -0,0 +1,23 @@
+#
+# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
+# Run `pod lib lint cw_mweb.podspec` to validate before publishing.
+#
+Pod::Spec.new do |s|
+  s.name             = 'cw_mweb'
+  s.version          = '0.0.1'
+  s.summary          = 'A new Flutter plugin project.'
+  s.description      = <<-DESC
+A new Flutter plugin project.
+                       DESC
+  s.homepage         = 'http://example.com'
+  s.license          = { :file => '../LICENSE' }
+  s.author           = { 'Your Company' => 'email@example.com' }
+  s.source           = { :path => '.' }
+  s.source_files = 'Classes/**/*'
+  s.dependency 'Flutter'
+  s.platform = :ios, '11.0'
+
+  # Flutter.framework does not contain a i386 slice.
+  s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
+  s.swift_version = '5.0'
+end
diff --git a/cw_mweb/lib/cw_mweb.dart b/cw_mweb/lib/cw_mweb.dart
new file mode 100644
index 000000000..e73887aaf
--- /dev/null
+++ b/cw_mweb/lib/cw_mweb.dart
@@ -0,0 +1,17 @@
+import 'package:grpc/grpc.dart';
+import 'cw_mweb_platform_interface.dart';
+import 'mwebd.pbgrpc.dart';
+
+class CwMweb {
+  static Future<bool?> start(String dataDir) {
+    return CwMwebPlatform.instance.start(dataDir);
+  }
+
+  static stub() {
+    final channel = ClientChannel('127.0.0.1',
+      port: 12345,
+      options: const ChannelOptions(
+        credentials: ChannelCredentials.insecure()));
+    return RpcClient(channel);
+  }
+}
diff --git a/cw_mweb/lib/cw_mweb_method_channel.dart b/cw_mweb/lib/cw_mweb_method_channel.dart
new file mode 100644
index 000000000..112dcfaa7
--- /dev/null
+++ b/cw_mweb/lib/cw_mweb_method_channel.dart
@@ -0,0 +1,17 @@
+import 'package:flutter/foundation.dart';
+import 'package:flutter/services.dart';
+
+import 'cw_mweb_platform_interface.dart';
+
+/// An implementation of [CwMwebPlatform] that uses method channels.
+class MethodChannelCwMweb extends CwMwebPlatform {
+  /// The method channel used to interact with the native platform.
+  @visibleForTesting
+  final methodChannel = const MethodChannel('cw_mweb');
+
+  @override
+  Future<bool?> start(String dataDir) async {
+    final result = await methodChannel.invokeMethod<bool>('start', {'dataDir': dataDir});
+    return result;
+  }
+}
diff --git a/cw_mweb/lib/cw_mweb_platform_interface.dart b/cw_mweb/lib/cw_mweb_platform_interface.dart
new file mode 100644
index 000000000..ce518402f
--- /dev/null
+++ b/cw_mweb/lib/cw_mweb_platform_interface.dart
@@ -0,0 +1,29 @@
+import 'package:plugin_platform_interface/plugin_platform_interface.dart';
+
+import 'cw_mweb_method_channel.dart';
+
+abstract class CwMwebPlatform extends PlatformInterface {
+  /// Constructs a CwMwebPlatform.
+  CwMwebPlatform() : super(token: _token);
+
+  static final Object _token = Object();
+
+  static CwMwebPlatform _instance = MethodChannelCwMweb();
+
+  /// The default instance of [CwMwebPlatform] to use.
+  ///
+  /// Defaults to [MethodChannelCwMweb].
+  static CwMwebPlatform get instance => _instance;
+
+  /// Platform-specific implementations should set this with their own
+  /// platform-specific class that extends [CwMwebPlatform] when
+  /// they register themselves.
+  static set instance(CwMwebPlatform instance) {
+    PlatformInterface.verifyToken(instance, _token);
+    _instance = instance;
+  }
+
+  Future<bool?> start(String dataDir) {
+    throw UnimplementedError('start() has not been implemented.');
+  }
+}
diff --git a/cw_mweb/lib/mwebd.pb.dart b/cw_mweb/lib/mwebd.pb.dart
new file mode 100644
index 000000000..8d139d7eb
--- /dev/null
+++ b/cw_mweb/lib/mwebd.pb.dart
@@ -0,0 +1,773 @@
+//
+//  Generated code. Do not modify.
+//  source: mwebd.proto
+//
+// @dart = 2.12
+
+// ignore_for_file: annotate_overrides, camel_case_types, comment_references
+// ignore_for_file: constant_identifier_names, library_prefixes
+// ignore_for_file: non_constant_identifier_names, prefer_final_fields
+// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+
+import 'dart:core' as $core;
+
+import 'package:fixnum/fixnum.dart' as $fixnum;
+import 'package:protobuf/protobuf.dart' as $pb;
+
+class StatusRequest extends $pb.GeneratedMessage {
+  factory StatusRequest() => create();
+  StatusRequest._() : super();
+  factory StatusRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory StatusRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'StatusRequest', createEmptyInstance: create)
+    ..hasRequiredFields = false
+  ;
+
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  StatusRequest clone() => StatusRequest()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  StatusRequest copyWith(void Function(StatusRequest) updates) => super.copyWith((message) => updates(message as StatusRequest)) as StatusRequest;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static StatusRequest create() => StatusRequest._();
+  StatusRequest createEmptyInstance() => create();
+  static $pb.PbList<StatusRequest> createRepeated() => $pb.PbList<StatusRequest>();
+  @$core.pragma('dart2js:noInline')
+  static StatusRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<StatusRequest>(create);
+  static StatusRequest? _defaultInstance;
+}
+
+class StatusResponse extends $pb.GeneratedMessage {
+  factory StatusResponse({
+    $core.int? blockHeaderHeight,
+    $core.int? mwebHeaderHeight,
+    $core.int? mwebUtxosHeight,
+  }) {
+    final $result = create();
+    if (blockHeaderHeight != null) {
+      $result.blockHeaderHeight = blockHeaderHeight;
+    }
+    if (mwebHeaderHeight != null) {
+      $result.mwebHeaderHeight = mwebHeaderHeight;
+    }
+    if (mwebUtxosHeight != null) {
+      $result.mwebUtxosHeight = mwebUtxosHeight;
+    }
+    return $result;
+  }
+  StatusResponse._() : super();
+  factory StatusResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory StatusResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'StatusResponse', createEmptyInstance: create)
+    ..a<$core.int>(1, _omitFieldNames ? '' : 'blockHeaderHeight', $pb.PbFieldType.O3)
+    ..a<$core.int>(2, _omitFieldNames ? '' : 'mwebHeaderHeight', $pb.PbFieldType.O3)
+    ..a<$core.int>(3, _omitFieldNames ? '' : 'mwebUtxosHeight', $pb.PbFieldType.O3)
+    ..hasRequiredFields = false
+  ;
+
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  StatusResponse clone() => StatusResponse()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  StatusResponse copyWith(void Function(StatusResponse) updates) => super.copyWith((message) => updates(message as StatusResponse)) as StatusResponse;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static StatusResponse create() => StatusResponse._();
+  StatusResponse createEmptyInstance() => create();
+  static $pb.PbList<StatusResponse> createRepeated() => $pb.PbList<StatusResponse>();
+  @$core.pragma('dart2js:noInline')
+  static StatusResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<StatusResponse>(create);
+  static StatusResponse? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.int get blockHeaderHeight => $_getIZ(0);
+  @$pb.TagNumber(1)
+  set blockHeaderHeight($core.int v) { $_setSignedInt32(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasBlockHeaderHeight() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearBlockHeaderHeight() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.int get mwebHeaderHeight => $_getIZ(1);
+  @$pb.TagNumber(2)
+  set mwebHeaderHeight($core.int v) { $_setSignedInt32(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasMwebHeaderHeight() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearMwebHeaderHeight() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.int get mwebUtxosHeight => $_getIZ(2);
+  @$pb.TagNumber(3)
+  set mwebUtxosHeight($core.int v) { $_setSignedInt32(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasMwebUtxosHeight() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearMwebUtxosHeight() => clearField(3);
+}
+
+class UtxosRequest extends $pb.GeneratedMessage {
+  factory UtxosRequest({
+    $core.int? fromHeight,
+    $core.List<$core.int>? scanSecret,
+  }) {
+    final $result = create();
+    if (fromHeight != null) {
+      $result.fromHeight = fromHeight;
+    }
+    if (scanSecret != null) {
+      $result.scanSecret = scanSecret;
+    }
+    return $result;
+  }
+  UtxosRequest._() : super();
+  factory UtxosRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory UtxosRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'UtxosRequest', createEmptyInstance: create)
+    ..a<$core.int>(1, _omitFieldNames ? '' : 'fromHeight', $pb.PbFieldType.O3)
+    ..a<$core.List<$core.int>>(2, _omitFieldNames ? '' : 'scanSecret', $pb.PbFieldType.OY)
+    ..hasRequiredFields = false
+  ;
+
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  UtxosRequest clone() => UtxosRequest()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  UtxosRequest copyWith(void Function(UtxosRequest) updates) => super.copyWith((message) => updates(message as UtxosRequest)) as UtxosRequest;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static UtxosRequest create() => UtxosRequest._();
+  UtxosRequest createEmptyInstance() => create();
+  static $pb.PbList<UtxosRequest> createRepeated() => $pb.PbList<UtxosRequest>();
+  @$core.pragma('dart2js:noInline')
+  static UtxosRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<UtxosRequest>(create);
+  static UtxosRequest? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.int get fromHeight => $_getIZ(0);
+  @$pb.TagNumber(1)
+  set fromHeight($core.int v) { $_setSignedInt32(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasFromHeight() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearFromHeight() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.List<$core.int> get scanSecret => $_getN(1);
+  @$pb.TagNumber(2)
+  set scanSecret($core.List<$core.int> v) { $_setBytes(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasScanSecret() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearScanSecret() => clearField(2);
+}
+
+class Utxo extends $pb.GeneratedMessage {
+  factory Utxo({
+    $core.int? height,
+    $fixnum.Int64? value,
+    $core.String? address,
+    $core.String? outputId,
+  }) {
+    final $result = create();
+    if (height != null) {
+      $result.height = height;
+    }
+    if (value != null) {
+      $result.value = value;
+    }
+    if (address != null) {
+      $result.address = address;
+    }
+    if (outputId != null) {
+      $result.outputId = outputId;
+    }
+    return $result;
+  }
+  Utxo._() : super();
+  factory Utxo.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory Utxo.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Utxo', createEmptyInstance: create)
+    ..a<$core.int>(1, _omitFieldNames ? '' : 'height', $pb.PbFieldType.O3)
+    ..a<$fixnum.Int64>(2, _omitFieldNames ? '' : 'value', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO)
+    ..aOS(3, _omitFieldNames ? '' : 'address')
+    ..aOS(4, _omitFieldNames ? '' : 'outputId')
+    ..hasRequiredFields = false
+  ;
+
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  Utxo clone() => Utxo()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  Utxo copyWith(void Function(Utxo) updates) => super.copyWith((message) => updates(message as Utxo)) as Utxo;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static Utxo create() => Utxo._();
+  Utxo createEmptyInstance() => create();
+  static $pb.PbList<Utxo> createRepeated() => $pb.PbList<Utxo>();
+  @$core.pragma('dart2js:noInline')
+  static Utxo getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Utxo>(create);
+  static Utxo? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.int get height => $_getIZ(0);
+  @$pb.TagNumber(1)
+  set height($core.int v) { $_setSignedInt32(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasHeight() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearHeight() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $fixnum.Int64 get value => $_getI64(1);
+  @$pb.TagNumber(2)
+  set value($fixnum.Int64 v) { $_setInt64(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasValue() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearValue() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.String get address => $_getSZ(2);
+  @$pb.TagNumber(3)
+  set address($core.String v) { $_setString(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasAddress() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearAddress() => clearField(3);
+
+  @$pb.TagNumber(4)
+  $core.String get outputId => $_getSZ(3);
+  @$pb.TagNumber(4)
+  set outputId($core.String v) { $_setString(3, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasOutputId() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearOutputId() => clearField(4);
+}
+
+class AddressRequest extends $pb.GeneratedMessage {
+  factory AddressRequest({
+    $core.int? fromIndex,
+    $core.int? toIndex,
+    $core.List<$core.int>? scanSecret,
+    $core.List<$core.int>? spendPubkey,
+  }) {
+    final $result = create();
+    if (fromIndex != null) {
+      $result.fromIndex = fromIndex;
+    }
+    if (toIndex != null) {
+      $result.toIndex = toIndex;
+    }
+    if (scanSecret != null) {
+      $result.scanSecret = scanSecret;
+    }
+    if (spendPubkey != null) {
+      $result.spendPubkey = spendPubkey;
+    }
+    return $result;
+  }
+  AddressRequest._() : super();
+  factory AddressRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory AddressRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'AddressRequest', createEmptyInstance: create)
+    ..a<$core.int>(1, _omitFieldNames ? '' : 'fromIndex', $pb.PbFieldType.OU3)
+    ..a<$core.int>(2, _omitFieldNames ? '' : 'toIndex', $pb.PbFieldType.OU3)
+    ..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'scanSecret', $pb.PbFieldType.OY)
+    ..a<$core.List<$core.int>>(4, _omitFieldNames ? '' : 'spendPubkey', $pb.PbFieldType.OY)
+    ..hasRequiredFields = false
+  ;
+
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  AddressRequest clone() => AddressRequest()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  AddressRequest copyWith(void Function(AddressRequest) updates) => super.copyWith((message) => updates(message as AddressRequest)) as AddressRequest;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static AddressRequest create() => AddressRequest._();
+  AddressRequest createEmptyInstance() => create();
+  static $pb.PbList<AddressRequest> createRepeated() => $pb.PbList<AddressRequest>();
+  @$core.pragma('dart2js:noInline')
+  static AddressRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<AddressRequest>(create);
+  static AddressRequest? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.int get fromIndex => $_getIZ(0);
+  @$pb.TagNumber(1)
+  set fromIndex($core.int v) { $_setUnsignedInt32(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasFromIndex() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearFromIndex() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.int get toIndex => $_getIZ(1);
+  @$pb.TagNumber(2)
+  set toIndex($core.int v) { $_setUnsignedInt32(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasToIndex() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearToIndex() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.List<$core.int> get scanSecret => $_getN(2);
+  @$pb.TagNumber(3)
+  set scanSecret($core.List<$core.int> v) { $_setBytes(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasScanSecret() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearScanSecret() => clearField(3);
+
+  @$pb.TagNumber(4)
+  $core.List<$core.int> get spendPubkey => $_getN(3);
+  @$pb.TagNumber(4)
+  set spendPubkey($core.List<$core.int> v) { $_setBytes(3, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasSpendPubkey() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearSpendPubkey() => clearField(4);
+}
+
+class AddressResponse extends $pb.GeneratedMessage {
+  factory AddressResponse({
+    $core.Iterable<$core.String>? address,
+  }) {
+    final $result = create();
+    if (address != null) {
+      $result.address.addAll(address);
+    }
+    return $result;
+  }
+  AddressResponse._() : super();
+  factory AddressResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory AddressResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'AddressResponse', createEmptyInstance: create)
+    ..pPS(1, _omitFieldNames ? '' : 'address')
+    ..hasRequiredFields = false
+  ;
+
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  AddressResponse clone() => AddressResponse()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  AddressResponse copyWith(void Function(AddressResponse) updates) => super.copyWith((message) => updates(message as AddressResponse)) as AddressResponse;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static AddressResponse create() => AddressResponse._();
+  AddressResponse createEmptyInstance() => create();
+  static $pb.PbList<AddressResponse> createRepeated() => $pb.PbList<AddressResponse>();
+  @$core.pragma('dart2js:noInline')
+  static AddressResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<AddressResponse>(create);
+  static AddressResponse? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<$core.String> get address => $_getList(0);
+}
+
+class SpentRequest extends $pb.GeneratedMessage {
+  factory SpentRequest({
+    $core.Iterable<$core.String>? outputId,
+  }) {
+    final $result = create();
+    if (outputId != null) {
+      $result.outputId.addAll(outputId);
+    }
+    return $result;
+  }
+  SpentRequest._() : super();
+  factory SpentRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory SpentRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'SpentRequest', createEmptyInstance: create)
+    ..pPS(1, _omitFieldNames ? '' : 'outputId')
+    ..hasRequiredFields = false
+  ;
+
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  SpentRequest clone() => SpentRequest()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  SpentRequest copyWith(void Function(SpentRequest) updates) => super.copyWith((message) => updates(message as SpentRequest)) as SpentRequest;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static SpentRequest create() => SpentRequest._();
+  SpentRequest createEmptyInstance() => create();
+  static $pb.PbList<SpentRequest> createRepeated() => $pb.PbList<SpentRequest>();
+  @$core.pragma('dart2js:noInline')
+  static SpentRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SpentRequest>(create);
+  static SpentRequest? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<$core.String> get outputId => $_getList(0);
+}
+
+class SpentResponse extends $pb.GeneratedMessage {
+  factory SpentResponse({
+    $core.Iterable<$core.String>? outputId,
+  }) {
+    final $result = create();
+    if (outputId != null) {
+      $result.outputId.addAll(outputId);
+    }
+    return $result;
+  }
+  SpentResponse._() : super();
+  factory SpentResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory SpentResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'SpentResponse', createEmptyInstance: create)
+    ..pPS(1, _omitFieldNames ? '' : 'outputId')
+    ..hasRequiredFields = false
+  ;
+
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  SpentResponse clone() => SpentResponse()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  SpentResponse copyWith(void Function(SpentResponse) updates) => super.copyWith((message) => updates(message as SpentResponse)) as SpentResponse;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static SpentResponse create() => SpentResponse._();
+  SpentResponse createEmptyInstance() => create();
+  static $pb.PbList<SpentResponse> createRepeated() => $pb.PbList<SpentResponse>();
+  @$core.pragma('dart2js:noInline')
+  static SpentResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SpentResponse>(create);
+  static SpentResponse? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<$core.String> get outputId => $_getList(0);
+}
+
+class CreateRequest extends $pb.GeneratedMessage {
+  factory CreateRequest({
+    $core.List<$core.int>? rawTx,
+    $core.List<$core.int>? scanSecret,
+    $core.List<$core.int>? spendSecret,
+    $fixnum.Int64? feeRatePerKb,
+    $core.bool? dryRun,
+  }) {
+    final $result = create();
+    if (rawTx != null) {
+      $result.rawTx = rawTx;
+    }
+    if (scanSecret != null) {
+      $result.scanSecret = scanSecret;
+    }
+    if (spendSecret != null) {
+      $result.spendSecret = spendSecret;
+    }
+    if (feeRatePerKb != null) {
+      $result.feeRatePerKb = feeRatePerKb;
+    }
+    if (dryRun != null) {
+      $result.dryRun = dryRun;
+    }
+    return $result;
+  }
+  CreateRequest._() : super();
+  factory CreateRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory CreateRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'CreateRequest', createEmptyInstance: create)
+    ..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'rawTx', $pb.PbFieldType.OY)
+    ..a<$core.List<$core.int>>(2, _omitFieldNames ? '' : 'scanSecret', $pb.PbFieldType.OY)
+    ..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'spendSecret', $pb.PbFieldType.OY)
+    ..a<$fixnum.Int64>(4, _omitFieldNames ? '' : 'feeRatePerKb', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO)
+    ..aOB(5, _omitFieldNames ? '' : 'dryRun')
+    ..hasRequiredFields = false
+  ;
+
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  CreateRequest clone() => CreateRequest()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  CreateRequest copyWith(void Function(CreateRequest) updates) => super.copyWith((message) => updates(message as CreateRequest)) as CreateRequest;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static CreateRequest create() => CreateRequest._();
+  CreateRequest createEmptyInstance() => create();
+  static $pb.PbList<CreateRequest> createRepeated() => $pb.PbList<CreateRequest>();
+  @$core.pragma('dart2js:noInline')
+  static CreateRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CreateRequest>(create);
+  static CreateRequest? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<$core.int> get rawTx => $_getN(0);
+  @$pb.TagNumber(1)
+  set rawTx($core.List<$core.int> v) { $_setBytes(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasRawTx() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearRawTx() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.List<$core.int> get scanSecret => $_getN(1);
+  @$pb.TagNumber(2)
+  set scanSecret($core.List<$core.int> v) { $_setBytes(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasScanSecret() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearScanSecret() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.List<$core.int> get spendSecret => $_getN(2);
+  @$pb.TagNumber(3)
+  set spendSecret($core.List<$core.int> v) { $_setBytes(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasSpendSecret() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearSpendSecret() => clearField(3);
+
+  @$pb.TagNumber(4)
+  $fixnum.Int64 get feeRatePerKb => $_getI64(3);
+  @$pb.TagNumber(4)
+  set feeRatePerKb($fixnum.Int64 v) { $_setInt64(3, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasFeeRatePerKb() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearFeeRatePerKb() => clearField(4);
+
+  @$pb.TagNumber(5)
+  $core.bool get dryRun => $_getBF(4);
+  @$pb.TagNumber(5)
+  set dryRun($core.bool v) { $_setBool(4, v); }
+  @$pb.TagNumber(5)
+  $core.bool hasDryRun() => $_has(4);
+  @$pb.TagNumber(5)
+  void clearDryRun() => clearField(5);
+}
+
+class CreateResponse extends $pb.GeneratedMessage {
+  factory CreateResponse({
+    $core.List<$core.int>? rawTx,
+    $core.Iterable<$core.String>? outputId,
+  }) {
+    final $result = create();
+    if (rawTx != null) {
+      $result.rawTx = rawTx;
+    }
+    if (outputId != null) {
+      $result.outputId.addAll(outputId);
+    }
+    return $result;
+  }
+  CreateResponse._() : super();
+  factory CreateResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory CreateResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'CreateResponse', createEmptyInstance: create)
+    ..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'rawTx', $pb.PbFieldType.OY)
+    ..pPS(2, _omitFieldNames ? '' : 'outputId')
+    ..hasRequiredFields = false
+  ;
+
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  CreateResponse clone() => CreateResponse()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  CreateResponse copyWith(void Function(CreateResponse) updates) => super.copyWith((message) => updates(message as CreateResponse)) as CreateResponse;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static CreateResponse create() => CreateResponse._();
+  CreateResponse createEmptyInstance() => create();
+  static $pb.PbList<CreateResponse> createRepeated() => $pb.PbList<CreateResponse>();
+  @$core.pragma('dart2js:noInline')
+  static CreateResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CreateResponse>(create);
+  static CreateResponse? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<$core.int> get rawTx => $_getN(0);
+  @$pb.TagNumber(1)
+  set rawTx($core.List<$core.int> v) { $_setBytes(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasRawTx() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearRawTx() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.List<$core.String> get outputId => $_getList(1);
+}
+
+class BroadcastRequest extends $pb.GeneratedMessage {
+  factory BroadcastRequest({
+    $core.List<$core.int>? rawTx,
+  }) {
+    final $result = create();
+    if (rawTx != null) {
+      $result.rawTx = rawTx;
+    }
+    return $result;
+  }
+  BroadcastRequest._() : super();
+  factory BroadcastRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory BroadcastRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'BroadcastRequest', createEmptyInstance: create)
+    ..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'rawTx', $pb.PbFieldType.OY)
+    ..hasRequiredFields = false
+  ;
+
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  BroadcastRequest clone() => BroadcastRequest()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  BroadcastRequest copyWith(void Function(BroadcastRequest) updates) => super.copyWith((message) => updates(message as BroadcastRequest)) as BroadcastRequest;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static BroadcastRequest create() => BroadcastRequest._();
+  BroadcastRequest createEmptyInstance() => create();
+  static $pb.PbList<BroadcastRequest> createRepeated() => $pb.PbList<BroadcastRequest>();
+  @$core.pragma('dart2js:noInline')
+  static BroadcastRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<BroadcastRequest>(create);
+  static BroadcastRequest? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<$core.int> get rawTx => $_getN(0);
+  @$pb.TagNumber(1)
+  set rawTx($core.List<$core.int> v) { $_setBytes(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasRawTx() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearRawTx() => clearField(1);
+}
+
+class BroadcastResponse extends $pb.GeneratedMessage {
+  factory BroadcastResponse({
+    $core.String? txid,
+  }) {
+    final $result = create();
+    if (txid != null) {
+      $result.txid = txid;
+    }
+    return $result;
+  }
+  BroadcastResponse._() : super();
+  factory BroadcastResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory BroadcastResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'BroadcastResponse', createEmptyInstance: create)
+    ..aOS(1, _omitFieldNames ? '' : 'txid')
+    ..hasRequiredFields = false
+  ;
+
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  BroadcastResponse clone() => BroadcastResponse()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  BroadcastResponse copyWith(void Function(BroadcastResponse) updates) => super.copyWith((message) => updates(message as BroadcastResponse)) as BroadcastResponse;
+
+  $pb.BuilderInfo get info_ => _i;
+
+  @$core.pragma('dart2js:noInline')
+  static BroadcastResponse create() => BroadcastResponse._();
+  BroadcastResponse createEmptyInstance() => create();
+  static $pb.PbList<BroadcastResponse> createRepeated() => $pb.PbList<BroadcastResponse>();
+  @$core.pragma('dart2js:noInline')
+  static BroadcastResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<BroadcastResponse>(create);
+  static BroadcastResponse? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get txid => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set txid($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasTxid() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearTxid() => clearField(1);
+}
+
+
+const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
+const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');
diff --git a/cw_mweb/lib/mwebd.pbgrpc.dart b/cw_mweb/lib/mwebd.pbgrpc.dart
new file mode 100644
index 000000000..6bc48cfdf
--- /dev/null
+++ b/cw_mweb/lib/mwebd.pbgrpc.dart
@@ -0,0 +1,159 @@
+//
+//  Generated code. Do not modify.
+//  source: mwebd.proto
+//
+// @dart = 2.12
+
+// ignore_for_file: annotate_overrides, camel_case_types, comment_references
+// ignore_for_file: constant_identifier_names, library_prefixes
+// ignore_for_file: non_constant_identifier_names, prefer_final_fields
+// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
+
+import 'dart:async' as $async;
+import 'dart:core' as $core;
+
+import 'package:grpc/service_api.dart' as $grpc;
+import 'package:protobuf/protobuf.dart' as $pb;
+
+import 'mwebd.pb.dart' as $0;
+
+export 'mwebd.pb.dart';
+
+@$pb.GrpcServiceName('Rpc')
+class RpcClient extends $grpc.Client {
+  static final _$status = $grpc.ClientMethod<$0.StatusRequest, $0.StatusResponse>(
+      '/Rpc/Status',
+      ($0.StatusRequest value) => value.writeToBuffer(),
+      ($core.List<$core.int> value) => $0.StatusResponse.fromBuffer(value));
+  static final _$utxos = $grpc.ClientMethod<$0.UtxosRequest, $0.Utxo>(
+      '/Rpc/Utxos',
+      ($0.UtxosRequest value) => value.writeToBuffer(),
+      ($core.List<$core.int> value) => $0.Utxo.fromBuffer(value));
+  static final _$addresses = $grpc.ClientMethod<$0.AddressRequest, $0.AddressResponse>(
+      '/Rpc/Addresses',
+      ($0.AddressRequest value) => value.writeToBuffer(),
+      ($core.List<$core.int> value) => $0.AddressResponse.fromBuffer(value));
+  static final _$spent = $grpc.ClientMethod<$0.SpentRequest, $0.SpentResponse>(
+      '/Rpc/Spent',
+      ($0.SpentRequest value) => value.writeToBuffer(),
+      ($core.List<$core.int> value) => $0.SpentResponse.fromBuffer(value));
+  static final _$create = $grpc.ClientMethod<$0.CreateRequest, $0.CreateResponse>(
+      '/Rpc/Create',
+      ($0.CreateRequest value) => value.writeToBuffer(),
+      ($core.List<$core.int> value) => $0.CreateResponse.fromBuffer(value));
+  static final _$broadcast = $grpc.ClientMethod<$0.BroadcastRequest, $0.BroadcastResponse>(
+      '/Rpc/Broadcast',
+      ($0.BroadcastRequest value) => value.writeToBuffer(),
+      ($core.List<$core.int> value) => $0.BroadcastResponse.fromBuffer(value));
+
+  RpcClient($grpc.ClientChannel channel,
+      {$grpc.CallOptions? options,
+      $core.Iterable<$grpc.ClientInterceptor>? interceptors})
+      : super(channel, options: options,
+        interceptors: interceptors);
+
+  $grpc.ResponseFuture<$0.StatusResponse> status($0.StatusRequest request, {$grpc.CallOptions? options}) {
+    return $createUnaryCall(_$status, request, options: options);
+  }
+
+  $grpc.ResponseStream<$0.Utxo> utxos($0.UtxosRequest request, {$grpc.CallOptions? options}) {
+    return $createStreamingCall(_$utxos, $async.Stream.fromIterable([request]), options: options);
+  }
+
+  $grpc.ResponseFuture<$0.AddressResponse> addresses($0.AddressRequest request, {$grpc.CallOptions? options}) {
+    return $createUnaryCall(_$addresses, request, options: options);
+  }
+
+  $grpc.ResponseFuture<$0.SpentResponse> spent($0.SpentRequest request, {$grpc.CallOptions? options}) {
+    return $createUnaryCall(_$spent, request, options: options);
+  }
+
+  $grpc.ResponseFuture<$0.CreateResponse> create($0.CreateRequest request, {$grpc.CallOptions? options}) {
+    return $createUnaryCall(_$create, request, options: options);
+  }
+
+  $grpc.ResponseFuture<$0.BroadcastResponse> broadcast($0.BroadcastRequest request, {$grpc.CallOptions? options}) {
+    return $createUnaryCall(_$broadcast, request, options: options);
+  }
+}
+
+@$pb.GrpcServiceName('Rpc')
+abstract class RpcServiceBase extends $grpc.Service {
+  $core.String get $name => 'Rpc';
+
+  RpcServiceBase() {
+    $addMethod($grpc.ServiceMethod<$0.StatusRequest, $0.StatusResponse>(
+        'Status',
+        status_Pre,
+        false,
+        false,
+        ($core.List<$core.int> value) => $0.StatusRequest.fromBuffer(value),
+        ($0.StatusResponse value) => value.writeToBuffer()));
+    $addMethod($grpc.ServiceMethod<$0.UtxosRequest, $0.Utxo>(
+        'Utxos',
+        utxos_Pre,
+        false,
+        true,
+        ($core.List<$core.int> value) => $0.UtxosRequest.fromBuffer(value),
+        ($0.Utxo value) => value.writeToBuffer()));
+    $addMethod($grpc.ServiceMethod<$0.AddressRequest, $0.AddressResponse>(
+        'Addresses',
+        addresses_Pre,
+        false,
+        false,
+        ($core.List<$core.int> value) => $0.AddressRequest.fromBuffer(value),
+        ($0.AddressResponse value) => value.writeToBuffer()));
+    $addMethod($grpc.ServiceMethod<$0.SpentRequest, $0.SpentResponse>(
+        'Spent',
+        spent_Pre,
+        false,
+        false,
+        ($core.List<$core.int> value) => $0.SpentRequest.fromBuffer(value),
+        ($0.SpentResponse value) => value.writeToBuffer()));
+    $addMethod($grpc.ServiceMethod<$0.CreateRequest, $0.CreateResponse>(
+        'Create',
+        create_Pre,
+        false,
+        false,
+        ($core.List<$core.int> value) => $0.CreateRequest.fromBuffer(value),
+        ($0.CreateResponse value) => value.writeToBuffer()));
+    $addMethod($grpc.ServiceMethod<$0.BroadcastRequest, $0.BroadcastResponse>(
+        'Broadcast',
+        broadcast_Pre,
+        false,
+        false,
+        ($core.List<$core.int> value) => $0.BroadcastRequest.fromBuffer(value),
+        ($0.BroadcastResponse value) => value.writeToBuffer()));
+  }
+
+  $async.Future<$0.StatusResponse> status_Pre($grpc.ServiceCall call, $async.Future<$0.StatusRequest> request) async {
+    return status(call, await request);
+  }
+
+  $async.Stream<$0.Utxo> utxos_Pre($grpc.ServiceCall call, $async.Future<$0.UtxosRequest> request) async* {
+    yield* utxos(call, await request);
+  }
+
+  $async.Future<$0.AddressResponse> addresses_Pre($grpc.ServiceCall call, $async.Future<$0.AddressRequest> request) async {
+    return addresses(call, await request);
+  }
+
+  $async.Future<$0.SpentResponse> spent_Pre($grpc.ServiceCall call, $async.Future<$0.SpentRequest> request) async {
+    return spent(call, await request);
+  }
+
+  $async.Future<$0.CreateResponse> create_Pre($grpc.ServiceCall call, $async.Future<$0.CreateRequest> request) async {
+    return create(call, await request);
+  }
+
+  $async.Future<$0.BroadcastResponse> broadcast_Pre($grpc.ServiceCall call, $async.Future<$0.BroadcastRequest> request) async {
+    return broadcast(call, await request);
+  }
+
+  $async.Future<$0.StatusResponse> status($grpc.ServiceCall call, $0.StatusRequest request);
+  $async.Stream<$0.Utxo> utxos($grpc.ServiceCall call, $0.UtxosRequest request);
+  $async.Future<$0.AddressResponse> addresses($grpc.ServiceCall call, $0.AddressRequest request);
+  $async.Future<$0.SpentResponse> spent($grpc.ServiceCall call, $0.SpentRequest request);
+  $async.Future<$0.CreateResponse> create($grpc.ServiceCall call, $0.CreateRequest request);
+  $async.Future<$0.BroadcastResponse> broadcast($grpc.ServiceCall call, $0.BroadcastRequest request);
+}
diff --git a/cw_mweb/macos/Classes/CwMwebPlugin.swift b/cw_mweb/macos/Classes/CwMwebPlugin.swift
new file mode 100644
index 000000000..9c0dabd40
--- /dev/null
+++ b/cw_mweb/macos/Classes/CwMwebPlugin.swift
@@ -0,0 +1,19 @@
+import Cocoa
+import FlutterMacOS
+
+public class CwMwebPlugin: NSObject, FlutterPlugin {
+  public static func register(with registrar: FlutterPluginRegistrar) {
+    let channel = FlutterMethodChannel(name: "cw_mweb", binaryMessenger: registrar.messenger)
+    let instance = CwMwebPlugin()
+    registrar.addMethodCallDelegate(instance, channel: channel)
+  }
+
+  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
+    switch call.method {
+    case "getPlatformVersion":
+      result("macOS " + ProcessInfo.processInfo.operatingSystemVersionString)
+    default:
+      result(FlutterMethodNotImplemented)
+    }
+  }
+}
diff --git a/cw_mweb/macos/cw_mweb.podspec b/cw_mweb/macos/cw_mweb.podspec
new file mode 100644
index 000000000..8fadcced9
--- /dev/null
+++ b/cw_mweb/macos/cw_mweb.podspec
@@ -0,0 +1,23 @@
+#
+# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
+# Run `pod lib lint cw_mweb.podspec` to validate before publishing.
+#
+Pod::Spec.new do |s|
+  s.name             = 'cw_mweb'
+  s.version          = '0.0.1'
+  s.summary          = 'A new Flutter plugin project.'
+  s.description      = <<-DESC
+A new Flutter plugin project.
+                       DESC
+  s.homepage         = 'http://example.com'
+  s.license          = { :file => '../LICENSE' }
+  s.author           = { 'Your Company' => 'email@example.com' }
+
+  s.source           = { :path => '.' }
+  s.source_files     = 'Classes/**/*'
+  s.dependency 'FlutterMacOS'
+
+  s.platform = :osx, '10.11'
+  s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
+  s.swift_version = '5.0'
+end
diff --git a/cw_mweb/pubspec.yaml b/cw_mweb/pubspec.yaml
new file mode 100644
index 000000000..5416963d8
--- /dev/null
+++ b/cw_mweb/pubspec.yaml
@@ -0,0 +1,74 @@
+name: cw_mweb
+description: A new Flutter plugin project.
+version: 0.0.1
+homepage:
+
+environment:
+  sdk: '>=3.0.6 <4.0.0'
+  flutter: ">=3.3.0"
+
+dependencies:
+  flutter:
+    sdk: flutter
+  plugin_platform_interface: ^2.0.2
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+  flutter_lints: ^2.0.0
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter packages.
+flutter:
+  # This section identifies this Flutter project as a plugin project.
+  # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
+  # which should be registered in the plugin registry. This is required for
+  # using method channels.
+  # The Android 'package' specifies package in which the registered class is.
+  # This is required for using method channels on Android.
+  # The 'ffiPlugin' specifies that native code should be built and bundled.
+  # This is required for using `dart:ffi`.
+  # All these are used by the tooling to maintain consistency when
+  # adding or updating assets for this project.
+  plugin:
+    platforms:
+      android:
+        package: com.cakewallet.mweb
+        pluginClass: CwMwebPlugin
+      ios:
+        pluginClass: CwMwebPlugin
+      macos:
+        pluginClass: CwMwebPlugin
+
+  # 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/cw_mweb/test/cw_mweb_method_channel_test.dart b/cw_mweb/test/cw_mweb_method_channel_test.dart
new file mode 100644
index 000000000..bb0523758
--- /dev/null
+++ b/cw_mweb/test/cw_mweb_method_channel_test.dart
@@ -0,0 +1,27 @@
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:cw_mweb/cw_mweb_method_channel.dart';
+
+void main() {
+  TestWidgetsFlutterBinding.ensureInitialized();
+
+  MethodChannelCwMweb platform = MethodChannelCwMweb();
+  const MethodChannel channel = MethodChannel('cw_mweb');
+
+  setUp(() {
+    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
+      channel,
+      (MethodCall methodCall) async {
+        return '42';
+      },
+    );
+  });
+
+  tearDown(() {
+    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null);
+  });
+
+  test('getPlatformVersion', () async {
+    expect(await platform.getPlatformVersion(), '42');
+  });
+}
diff --git a/cw_mweb/test/cw_mweb_test.dart b/cw_mweb/test/cw_mweb_test.dart
new file mode 100644
index 000000000..3677659a9
--- /dev/null
+++ b/cw_mweb/test/cw_mweb_test.dart
@@ -0,0 +1,29 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:cw_mweb/cw_mweb.dart';
+import 'package:cw_mweb/cw_mweb_platform_interface.dart';
+import 'package:cw_mweb/cw_mweb_method_channel.dart';
+import 'package:plugin_platform_interface/plugin_platform_interface.dart';
+
+class MockCwMwebPlatform
+    with MockPlatformInterfaceMixin
+    implements CwMwebPlatform {
+
+  @override
+  Future<String?> getPlatformVersion() => Future.value('42');
+}
+
+void main() {
+  final CwMwebPlatform initialPlatform = CwMwebPlatform.instance;
+
+  test('$MethodChannelCwMweb is the default instance', () {
+    expect(initialPlatform, isInstanceOf<MethodChannelCwMweb>());
+  });
+
+  test('getPlatformVersion', () async {
+    CwMweb cwMwebPlugin = CwMweb();
+    MockCwMwebPlatform fakePlatform = MockCwMwebPlatform();
+    CwMwebPlatform.instance = fakePlatform;
+
+    expect(await cwMwebPlugin.getPlatformVersion(), '42');
+  });
+}
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index 75a78404f..e309e3142 100644
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -7,6 +7,7 @@ import Foundation
 
 import connectivity_plus
 import cw_monero
+import cw_mweb
 import device_info_plus
 import devicelocale
 import flutter_inappwebview_macos
@@ -23,6 +24,7 @@ import wakelock_plus
 func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
   ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
   CwMoneroPlugin.register(with: registry.registrar(forPlugin: "CwMoneroPlugin"))
+  CwMwebPlugin.register(with: registry.registrar(forPlugin: "CwMwebPlugin"))
   DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
   DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin"))
   InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))

From a3aebbdb78efed727b7e254b3311ef7231767855 Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Sun, 21 Apr 2024 12:19:44 +0100
Subject: [PATCH 03/31] Sync status

---
 cw_bitcoin/lib/litecoin_wallet.dart            | 18 +++++++++++++++---
 cw_mweb/.gitignore                             |  1 -
 cw_mweb/android/.gitignore                     |  1 +
 .../kotlin/com/cakewallet/mweb/CwMwebPlugin.kt |  7 +------
 cw_mweb/lib/cw_mweb.dart                       | 13 +++++++------
 cw_mweb/lib/cw_mweb_method_channel.dart        |  4 ++--
 cw_mweb/lib/cw_mweb_platform_interface.dart    |  2 +-
 7 files changed, 27 insertions(+), 19 deletions(-)

diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index f218ddd5d..628391eed 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -3,6 +3,7 @@ import 'package:bitcoin_base/bitcoin_base.dart';
 import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
 import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
 import 'package:cw_core/crypto_currency.dart';
+import 'package:cw_core/sync_status.dart';
 import 'package:cw_core/unspent_coins_info.dart';
 import 'package:cw_bitcoin/litecoin_wallet_addresses.dart';
 import 'package:cw_core/transaction_priority.dart';
@@ -109,12 +110,23 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
   @action
   @override
   Future<void> startSync() async {
-    super.startSync();
+    await super.startSync();
     final stub = CwMweb.stub();
     Timer.periodic(
-      const Duration(seconds: 1), (timer) async {
+      const Duration(milliseconds: 1500), (timer) async {
+        final height = await electrumClient.getCurrentBlockChainTip() ?? 0;
         final resp = await stub.status(StatusRequest());
-        print(resp.blockHeaderHeight);
+        if (resp.blockHeaderHeight < height) {
+          int h = resp.blockHeaderHeight;
+          syncStatus = SyncingSyncStatus(height - h, h / height);
+        } else if (resp.mwebHeaderHeight < height) {
+          int h = resp.mwebHeaderHeight;
+          syncStatus = SyncingSyncStatus(height - h, h / height);
+        } else if (resp.mwebUtxosHeight < height) {
+          syncStatus = SyncingSyncStatus(1, 0.999);
+        } else {
+          syncStatus = SyncedSyncStatus();
+        }
       });
   }
 
diff --git a/cw_mweb/.gitignore b/cw_mweb/.gitignore
index 8959f5d70..96486fd93 100644
--- a/cw_mweb/.gitignore
+++ b/cw_mweb/.gitignore
@@ -28,4 +28,3 @@ migrate_working_dir/
 .dart_tool/
 .packages
 build/
-libs/
diff --git a/cw_mweb/android/.gitignore b/cw_mweb/android/.gitignore
index 161bdcdaf..881f3d95c 100644
--- a/cw_mweb/android/.gitignore
+++ b/cw_mweb/android/.gitignore
@@ -6,4 +6,5 @@
 .DS_Store
 /build
 /captures
+/libs
 .cxx
diff --git a/cw_mweb/android/src/main/kotlin/com/cakewallet/mweb/CwMwebPlugin.kt b/cw_mweb/android/src/main/kotlin/com/cakewallet/mweb/CwMwebPlugin.kt
index 394b607ab..cf194417b 100644
--- a/cw_mweb/android/src/main/kotlin/com/cakewallet/mweb/CwMwebPlugin.kt
+++ b/cw_mweb/android/src/main/kotlin/com/cakewallet/mweb/CwMwebPlugin.kt
@@ -17,7 +17,6 @@ class CwMwebPlugin: FlutterPlugin, MethodCallHandler {
   /// This local reference serves to register the plugin with the Flutter Engine and unregister it
   /// when the Flutter Engine is detached from the Activity
   private lateinit var channel : MethodChannel
-  private var started = false
 
   override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
     channel = MethodChannel(flutterPluginBinding.binaryMessenger, "cw_mweb")
@@ -26,12 +25,8 @@ class CwMwebPlugin: FlutterPlugin, MethodCallHandler {
 
   override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
     if (call.method == "start") {
-      if (started) return
       val dataDir = call.argument("dataDir") ?: ""
-      val server = Mwebd.newServer("mainnet", dataDir, "")
-      server.start(12345, true)
-      started = true
-      result.success(true)
+      result.success(Mwebd.newServer("", dataDir, "").start(0))
     } else {
       result.notImplemented()
     }
diff --git a/cw_mweb/lib/cw_mweb.dart b/cw_mweb/lib/cw_mweb.dart
index e73887aaf..fb3960ab0 100644
--- a/cw_mweb/lib/cw_mweb.dart
+++ b/cw_mweb/lib/cw_mweb.dart
@@ -3,15 +3,16 @@ import 'cw_mweb_platform_interface.dart';
 import 'mwebd.pbgrpc.dart';
 
 class CwMweb {
-  static Future<bool?> start(String dataDir) {
-    return CwMwebPlatform.instance.start(dataDir);
+  static var port;
+
+  static start(String dataDir) async {
+    port = port ?? await CwMwebPlatform.instance.start(dataDir);
   }
 
   static stub() {
-    final channel = ClientChannel('127.0.0.1',
-      port: 12345,
+    return RpcClient(ClientChannel('127.0.0.1',
+      port: port,
       options: const ChannelOptions(
-        credentials: ChannelCredentials.insecure()));
-    return RpcClient(channel);
+        credentials: ChannelCredentials.insecure())));
   }
 }
diff --git a/cw_mweb/lib/cw_mweb_method_channel.dart b/cw_mweb/lib/cw_mweb_method_channel.dart
index 112dcfaa7..cc880c6df 100644
--- a/cw_mweb/lib/cw_mweb_method_channel.dart
+++ b/cw_mweb/lib/cw_mweb_method_channel.dart
@@ -10,8 +10,8 @@ class MethodChannelCwMweb extends CwMwebPlatform {
   final methodChannel = const MethodChannel('cw_mweb');
 
   @override
-  Future<bool?> start(String dataDir) async {
-    final result = await methodChannel.invokeMethod<bool>('start', {'dataDir': dataDir});
+  Future<int?> start(String dataDir) async {
+    final result = await methodChannel.invokeMethod<int>('start', {'dataDir': dataDir});
     return result;
   }
 }
diff --git a/cw_mweb/lib/cw_mweb_platform_interface.dart b/cw_mweb/lib/cw_mweb_platform_interface.dart
index ce518402f..974e07284 100644
--- a/cw_mweb/lib/cw_mweb_platform_interface.dart
+++ b/cw_mweb/lib/cw_mweb_platform_interface.dart
@@ -23,7 +23,7 @@ abstract class CwMwebPlatform extends PlatformInterface {
     _instance = instance;
   }
 
-  Future<bool?> start(String dataDir) {
+  Future<int?> start(String dataDir) {
     throw UnimplementedError('start() has not been implemented.');
   }
 }

From dbc005dcf3a704b922d77af7c42c1530d773c4ba Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Sun, 21 Apr 2024 19:58:45 +0100
Subject: [PATCH 04/31] Fix stub creation

---
 cw_bitcoin/lib/litecoin_wallet.dart         |  2 +-
 cw_bitcoin/lib/litecoin_wallet_service.dart |  6 ------
 cw_mweb/lib/cw_mweb.dart                    | 13 ++++++-------
 cw_mweb/pubspec.yaml                        |  2 ++
 4 files changed, 9 insertions(+), 14 deletions(-)

diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index 628391eed..b6b996b27 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -111,7 +111,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
   @override
   Future<void> startSync() async {
     await super.startSync();
-    final stub = CwMweb.stub();
+    final stub = await CwMweb.stub();
     Timer.periodic(
       const Duration(milliseconds: 1500), (timer) async {
         final height = await electrumClient.getCurrentBlockChainTip() ?? 0;
diff --git a/cw_bitcoin/lib/litecoin_wallet_service.dart b/cw_bitcoin/lib/litecoin_wallet_service.dart
index 7f2066c30..ee3b0e628 100644
--- a/cw_bitcoin/lib/litecoin_wallet_service.dart
+++ b/cw_bitcoin/lib/litecoin_wallet_service.dart
@@ -1,5 +1,4 @@
 import 'dart:io';
-import 'package:path_provider/path_provider.dart';
 import 'package:cw_core/unspent_coins_info.dart';
 import 'package:hive/hive.dart';
 import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
@@ -11,7 +10,6 @@ import 'package:cw_core/pathForWallet.dart';
 import 'package:cw_core/wallet_type.dart';
 import 'package:cw_core/wallet_info.dart';
 import 'package:cw_core/wallet_base.dart';
-import 'package:cw_mweb/cw_mweb.dart';
 import 'package:collection/collection.dart';
 
 class LitecoinWalletService extends WalletService<
@@ -28,8 +26,6 @@ class LitecoinWalletService extends WalletService<
 
   @override
   Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
-    final appDir = await getApplicationSupportDirectory();
-    await CwMweb.start(appDir.path);
     final wallet = await LitecoinWalletBase.create(
         mnemonic: await generateMnemonic(),
         password: credentials.password!,
@@ -47,8 +43,6 @@ class LitecoinWalletService extends WalletService<
 
   @override
   Future<LitecoinWallet> openWallet(String name, String password) async {
-    final appDir = await getApplicationSupportDirectory();
-    await CwMweb.start(appDir.path);
     final walletInfo = walletInfoSource.values.firstWhereOrNull(
         (info) => info.id == WalletBase.idFor(name, getType()))!;
 
diff --git a/cw_mweb/lib/cw_mweb.dart b/cw_mweb/lib/cw_mweb.dart
index fb3960ab0..a54295673 100644
--- a/cw_mweb/lib/cw_mweb.dart
+++ b/cw_mweb/lib/cw_mweb.dart
@@ -1,17 +1,16 @@
 import 'package:grpc/grpc.dart';
+import 'package:path_provider/path_provider.dart';
 import 'cw_mweb_platform_interface.dart';
 import 'mwebd.pbgrpc.dart';
 
 class CwMweb {
-  static var port;
+  static Future<int?>? port;
 
-  static start(String dataDir) async {
-    port = port ?? await CwMwebPlatform.instance.start(dataDir);
-  }
-
-  static stub() {
+  static Future<RpcClient> stub() async {
+    final appDir = await getApplicationSupportDirectory();
+    port ??= CwMwebPlatform.instance.start(appDir.path);
     return RpcClient(ClientChannel('127.0.0.1',
-      port: port,
+      port: await port ?? 0,
       options: const ChannelOptions(
         credentials: ChannelCredentials.insecure())));
   }
diff --git a/cw_mweb/pubspec.yaml b/cw_mweb/pubspec.yaml
index 5416963d8..cfe43c70b 100644
--- a/cw_mweb/pubspec.yaml
+++ b/cw_mweb/pubspec.yaml
@@ -10,6 +10,8 @@ environment:
 dependencies:
   flutter:
     sdk: flutter
+  grpc: ^3.2.4
+  path_provider: ^2.1.2
   plugin_platform_interface: ^2.0.2
 
 dev_dependencies:

From 29238effdf17fa5047e8f30c232cab913b1f2482 Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Mon, 22 Apr 2024 11:45:16 +0100
Subject: [PATCH 05/31] Generate MWEB addresses

---
 .../lib/bitcoin_receive_page_option.dart      |  8 ++++
 cw_bitcoin/lib/electrum_wallet.dart           |  4 ++
 cw_bitcoin/lib/electrum_wallet_addresses.dart |  8 +++-
 cw_bitcoin/lib/litecoin_wallet_addresses.dart | 43 +++++++++++++++++--
 cw_bitcoin/pubspec.lock                       |  8 ++--
 cw_bitcoin/pubspec.yaml                       |  4 +-
 cw_bitcoin_cash/pubspec.yaml                  |  4 +-
 lib/bitcoin/cw_bitcoin.dart                   |  2 +
 .../screens/dashboard/pages/address_page.dart |  3 +-
 .../dashboard/receive_option_view_model.dart  | 32 +++++++++-----
 .../wallet_address_list_view_model.dart       |  3 +-
 11 files changed, 91 insertions(+), 28 deletions(-)

diff --git a/cw_bitcoin/lib/bitcoin_receive_page_option.dart b/cw_bitcoin/lib/bitcoin_receive_page_option.dart
index 2d2339a41..714f1da90 100644
--- a/cw_bitcoin/lib/bitcoin_receive_page_option.dart
+++ b/cw_bitcoin/lib/bitcoin_receive_page_option.dart
@@ -7,6 +7,7 @@ class BitcoinReceivePageOption implements ReceivePageOption {
   static const p2tr = BitcoinReceivePageOption._('Taproot (P2TR)');
   static const p2wsh = BitcoinReceivePageOption._('Segwit (P2WSH)');
   static const p2pkh = BitcoinReceivePageOption._('Legacy (P2PKH)');
+  static const mweb = BitcoinReceivePageOption._('MWEB');
 
   const BitcoinReceivePageOption._(this.value);
 
@@ -24,12 +25,19 @@ class BitcoinReceivePageOption implements ReceivePageOption {
     BitcoinReceivePageOption.p2pkh
   ];
 
+  static const allLitecoin = [
+    BitcoinReceivePageOption.p2wpkh,
+    BitcoinReceivePageOption.mweb
+  ];
+
   factory BitcoinReceivePageOption.fromType(BitcoinAddressType type) {
     switch (type) {
       case SegwitAddresType.p2tr:
         return BitcoinReceivePageOption.p2tr;
       case SegwitAddresType.p2wsh:
         return BitcoinReceivePageOption.p2wsh;
+      case SegwitAddresType.mweb:
+        return BitcoinReceivePageOption.mweb;
       case P2pkhAddressType.p2pkh:
         return BitcoinReceivePageOption.p2pkh;
       case P2shAddressType.p2wpkhInP2sh:
diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index 4a76ee5dd..17e9880e9 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -1337,6 +1337,8 @@ BitcoinBaseAddress addressTypeFromStr(String address, BasedUtxoNetwork network)
     return P2wshAddress.fromAddress(address: address, network: network);
   } else if (P2trAddress.regex.hasMatch(address)) {
     return P2trAddress.fromAddress(address: address, network: network);
+  } else if (MwebAddress.regex.hasMatch(address)) {
+    return MwebAddress.fromAddress(address: address, network: network);
   } else {
     return P2wpkhAddress.fromAddress(address: address, network: network);
   }
@@ -1351,6 +1353,8 @@ BitcoinAddressType _getScriptType(BitcoinBaseAddress type) {
     return SegwitAddresType.p2wsh;
   } else if (type is P2trAddress) {
     return SegwitAddresType.p2tr;
+  } else if (type is MwebAddress) {
+    return SegwitAddresType.mweb;
   } else {
     return SegwitAddresType.p2wpkh;
   }
diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart
index c43d4988a..6a7a9fd18 100644
--- a/cw_bitcoin/lib/electrum_wallet_addresses.dart
+++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart
@@ -16,6 +16,7 @@ const List<BitcoinAddressType> ADDRESS_TYPES = [
   P2pkhAddressType.p2pkh,
   SegwitAddresType.p2tr,
   SegwitAddresType.p2wsh,
+  SegwitAddresType.mweb,
   P2shAddressType.p2wpkhInP2sh,
 ];
 
@@ -154,6 +155,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
       await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
     } else if (walletInfo.type == WalletType.litecoin) {
       await _generateInitialAddresses();
+      await _generateInitialAddresses(type: SegwitAddresType.mweb);
     } else if (walletInfo.type == WalletType.bitcoin) {
       await _generateInitialAddresses();
       await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
@@ -217,6 +219,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
           {required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) =>
       '';
 
+  Future<String> getAddressAsync(
+          {required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) async =>
+      getAddress(index: index, hd: hd, addressType: addressType);
+
   @override
   Future<void> updateAddressesInBox() async {
     try {
@@ -328,7 +334,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
 
     for (var i = startIndex; i < count + startIndex; i++) {
       final address = BitcoinAddressRecord(
-        getAddress(index: i, hd: _getHd(isHidden), addressType: type ?? addressPageType),
+        await getAddressAsync(index: i, hd: _getHd(isHidden), addressType: type ?? addressPageType),
         index: i,
         isHidden: isHidden,
         type: type ?? addressPageType,
diff --git a/cw_bitcoin/lib/litecoin_wallet_addresses.dart b/cw_bitcoin/lib/litecoin_wallet_addresses.dart
index 993d17933..fead1de52 100644
--- a/cw_bitcoin/lib/litecoin_wallet_addresses.dart
+++ b/cw_bitcoin/lib/litecoin_wallet_addresses.dart
@@ -1,8 +1,11 @@
+import 'package:convert/convert.dart';
 import 'package:bitcoin_base/bitcoin_base.dart';
-import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
+import 'package:bitcoin_flutter/bitcoin_flutter.dart';
 import 'package:cw_bitcoin/utils.dart';
 import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
 import 'package:cw_core/wallet_info.dart';
+import 'package:cw_mweb/cw_mweb.dart';
+import 'package:cw_mweb/mwebd.pb.dart';
 import 'package:mobx/mobx.dart';
 
 part 'litecoin_wallet_addresses.g.dart';
@@ -21,8 +24,40 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
     super.initialChangeAddressIndex,
   }) : super(walletInfo);
 
+  List<String> mweb_addrs = [];
+
+  Future<void> topUpMweb(int index) async {
+    while (mweb_addrs.length - index < 1000) {
+      final length = mweb_addrs.length;
+      final scanSecret = mainHd.derive(0).privKey!;
+      final spendPubkey = mainHd.derive(1).pubKey!;
+      final stub = await CwMweb.stub();
+      final resp = await stub.addresses(AddressRequest(
+        fromIndex: length,
+        toIndex: index + 1000,
+        scanSecret: hex.decode(scanSecret),
+        spendPubkey: hex.decode(spendPubkey),
+      ));
+      if (mweb_addrs.length == length) {
+        mweb_addrs.addAll(resp.address);
+      }
+    }
+  }
+
   @override
-  String getAddress(
-          {required int index, required bitcoin.HDWallet hd, BitcoinAddressType? addressType}) =>
-      generateP2WPKHAddress(hd: hd, index: index, network: network);
+  String getAddress({required int index, required HDWallet hd, BitcoinAddressType? addressType}) {
+    if (addressType == SegwitAddresType.mweb) {
+      topUpMweb(index);
+      return hd == sideHd ? mweb_addrs[0] : mweb_addrs[index+1];
+    }
+    return generateP2WPKHAddress(hd: hd, index: index, network: network);
+  }
+
+  @override
+  Future<String> getAddressAsync({required int index, required HDWallet hd, BitcoinAddressType? addressType}) async {
+    if (addressType == SegwitAddresType.mweb) {
+      await topUpMweb(index);
+    }
+    return getAddress(index: index, hd: hd, addressType: addressType);
+  }
 }
diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock
index 20851ea86..f93365d89 100644
--- a/cw_bitcoin/pubspec.lock
+++ b/cw_bitcoin/pubspec.lock
@@ -86,11 +86,9 @@ packages:
   bitcoin_base:
     dependency: "direct main"
     description:
-      path: "."
-      ref: cake-update-v2
-      resolved-ref: "01d844a5f5a520a31df5254e34169af4664aa769"
-      url: "https://github.com/cake-tech/bitcoin_base.git"
-    source: git
+      path: "../../bitcoin_base"
+      relative: true
+    source: path
     version: "4.2.0"
   bitcoin_flutter:
     dependency: "direct main"
diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml
index 033d1f84e..9c3c5d751 100644
--- a/cw_bitcoin/pubspec.yaml
+++ b/cw_bitcoin/pubspec.yaml
@@ -31,9 +31,7 @@ dependencies:
   unorm_dart: ^0.2.0
   cryptography: ^2.0.5
   bitcoin_base:
-    git:
-      url: https://github.com/cake-tech/bitcoin_base.git
-      ref: cake-update-v2
+    path: ../../bitcoin_base
   blockchain_utils: ^2.1.1
   cw_mweb:
     path: ../cw_mweb
diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml
index 37827f1ba..e491cea32 100644
--- a/cw_bitcoin_cash/pubspec.yaml
+++ b/cw_bitcoin_cash/pubspec.yaml
@@ -30,9 +30,7 @@ dependencies:
       url: https://github.com/cake-tech/bitbox-flutter.git
       ref: Add-Support-For-OP-Return-data
   bitcoin_base:
-    git:
-      url: https://github.com/cake-tech/bitcoin_base.git
-      ref: cake-update-v2
+    path: ../../bitcoin_base
 
 
 
diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart
index 707f1157b..77c940ac4 100644
--- a/lib/bitcoin/cw_bitcoin.dart
+++ b/lib/bitcoin/cw_bitcoin.dart
@@ -242,6 +242,8 @@ class CWBitcoin extends Bitcoin {
         return SegwitAddresType.p2tr;
       case BitcoinReceivePageOption.p2wsh:
         return SegwitAddresType.p2wsh;
+      case BitcoinReceivePageOption.mweb:
+        return SegwitAddresType.mweb;
       case BitcoinReceivePageOption.p2wpkh:
       default:
         return SegwitAddresType.p2wpkh;
diff --git a/lib/src/screens/dashboard/pages/address_page.dart b/lib/src/screens/dashboard/pages/address_page.dart
index 3c77cad48..0e51f759d 100644
--- a/lib/src/screens/dashboard/pages/address_page.dart
+++ b/lib/src/screens/dashboard/pages/address_page.dart
@@ -219,7 +219,8 @@ class AddressPage extends BasePage {
           }
           break;
         default:
-          if (addressListViewModel.type == WalletType.bitcoin) {
+          if (addressListViewModel.type == WalletType.bitcoin ||
+              addressListViewModel.type == WalletType.litecoin) {
             addressListViewModel.setAddressType(bitcoin!.getBitcoinAddressType(option));
           }
       }
diff --git a/lib/view_model/dashboard/receive_option_view_model.dart b/lib/view_model/dashboard/receive_option_view_model.dart
index 1e4726eee..17e17842c 100644
--- a/lib/view_model/dashboard/receive_option_view_model.dart
+++ b/lib/view_model/dashboard/receive_option_view_model.dart
@@ -1,4 +1,5 @@
 import 'package:cake_wallet/bitcoin/bitcoin.dart';
+import 'package:cw_bitcoin/bitcoin_receive_page_option.dart';
 import 'package:cw_core/receive_page_option.dart';
 import 'package:cw_core/wallet_base.dart';
 import 'package:cw_core/wallet_type.dart';
@@ -11,19 +12,30 @@ class ReceiveOptionViewModel = ReceiveOptionViewModelBase with _$ReceiveOptionVi
 abstract class ReceiveOptionViewModelBase with Store {
   ReceiveOptionViewModelBase(this._wallet, this.initialPageOption)
       : selectedReceiveOption = initialPageOption ??
-            (_wallet.type == WalletType.bitcoin
+            (_wallet.type == WalletType.bitcoin ||
+             _wallet.type == WalletType.litecoin
                 ? bitcoin!.getSelectedAddressType(_wallet)
                 : ReceivePageOption.mainnet),
         _options = [] {
-    final walletType = _wallet.type;
-    _options = walletType == WalletType.haven
-        ? [ReceivePageOption.mainnet]
-        : walletType == WalletType.bitcoin
-            ? [
-                ...bitcoin!.getBitcoinReceivePageOptions(),
-                ...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet)
-              ]
-            : ReceivePageOptions;
+    switch (_wallet.type) {
+      case WalletType.bitcoin:
+        _options = [
+          ...bitcoin!.getBitcoinReceivePageOptions(),
+          ...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet)
+        ];
+        break;
+      case WalletType.litecoin:
+        _options = [
+          ...BitcoinReceivePageOption.allLitecoin,
+          ...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet)
+        ];
+        break;
+      case WalletType.haven:
+        _options = [ReceivePageOption.mainnet];
+        break;
+      default:
+        _options = ReceivePageOptions;
+    };
   }
 
   final WalletBase _wallet;
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 20980f5f0..5f3b2b9c2 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
@@ -404,7 +404,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
 
   @action
   Future<void> setAddressType(dynamic option) async {
-    if (wallet.type == WalletType.bitcoin) {
+    if (wallet.type == WalletType.bitcoin ||
+        wallet.type == WalletType.litecoin) {
       await bitcoin!.setAddressType(wallet, option);
     }
   }

From 407b73171e96b7ba74e92435ad2d5c60c25014d2 Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Mon, 22 Apr 2024 12:48:11 +0100
Subject: [PATCH 06/31] Fix mweb address derivation

---
 cw_bitcoin/lib/litecoin_wallet.dart                     | 1 +
 cw_bitcoin/lib/litecoin_wallet_addresses.dart           | 6 ++++--
 lib/view_model/dashboard/receive_option_view_model.dart | 2 +-
 3 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index b6b996b27..1608a3dba 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -54,6 +54,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
       initialChangeAddressIndex: initialChangeAddressIndex,
       mainHd: hd,
       sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"),
+      mwebHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/1000'"),
       network: network,
     );
     autorun((_) {
diff --git a/cw_bitcoin/lib/litecoin_wallet_addresses.dart b/cw_bitcoin/lib/litecoin_wallet_addresses.dart
index fead1de52..8875efa18 100644
--- a/cw_bitcoin/lib/litecoin_wallet_addresses.dart
+++ b/cw_bitcoin/lib/litecoin_wallet_addresses.dart
@@ -17,6 +17,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
     WalletInfo walletInfo, {
     required super.mainHd,
     required super.sideHd,
+    required this.mwebHd,
     required super.network,
     required super.electrumClient,
     super.initialAddresses,
@@ -24,13 +25,14 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
     super.initialChangeAddressIndex,
   }) : super(walletInfo);
 
+  final HDWallet mwebHd;
   List<String> mweb_addrs = [];
 
   Future<void> topUpMweb(int index) async {
     while (mweb_addrs.length - index < 1000) {
       final length = mweb_addrs.length;
-      final scanSecret = mainHd.derive(0).privKey!;
-      final spendPubkey = mainHd.derive(1).pubKey!;
+      final scanSecret = mwebHd.derive(0x80000000).privKey!;
+      final spendPubkey = mwebHd.derive(0x80000001).pubKey!;
       final stub = await CwMweb.stub();
       final resp = await stub.addresses(AddressRequest(
         fromIndex: length,
diff --git a/lib/view_model/dashboard/receive_option_view_model.dart b/lib/view_model/dashboard/receive_option_view_model.dart
index 17e17842c..472918f4a 100644
--- a/lib/view_model/dashboard/receive_option_view_model.dart
+++ b/lib/view_model/dashboard/receive_option_view_model.dart
@@ -35,7 +35,7 @@ abstract class ReceiveOptionViewModelBase with Store {
         break;
       default:
         _options = ReceivePageOptions;
-    };
+    }
   }
 
   final WalletBase _wallet;

From 90588ee88a01c0bb542b62038e8245653dbb07dc Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Mon, 22 Apr 2024 13:11:31 +0100
Subject: [PATCH 07/31] Use camel-case

---
 cw_bitcoin/lib/litecoin_wallet_addresses.dart | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/cw_bitcoin/lib/litecoin_wallet_addresses.dart b/cw_bitcoin/lib/litecoin_wallet_addresses.dart
index 8875efa18..e1ee81b31 100644
--- a/cw_bitcoin/lib/litecoin_wallet_addresses.dart
+++ b/cw_bitcoin/lib/litecoin_wallet_addresses.dart
@@ -26,11 +26,11 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
   }) : super(walletInfo);
 
   final HDWallet mwebHd;
-  List<String> mweb_addrs = [];
+  List<String> mwebAddrs = [];
 
   Future<void> topUpMweb(int index) async {
-    while (mweb_addrs.length - index < 1000) {
-      final length = mweb_addrs.length;
+    while (mwebAddrs.length - index < 1000) {
+      final length = mwebAddrs.length;
       final scanSecret = mwebHd.derive(0x80000000).privKey!;
       final spendPubkey = mwebHd.derive(0x80000001).pubKey!;
       final stub = await CwMweb.stub();
@@ -40,8 +40,8 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
         scanSecret: hex.decode(scanSecret),
         spendPubkey: hex.decode(spendPubkey),
       ));
-      if (mweb_addrs.length == length) {
-        mweb_addrs.addAll(resp.address);
+      if (mwebAddrs.length == length) {
+        mwebAddrs.addAll(resp.address);
       }
     }
   }
@@ -50,7 +50,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
   String getAddress({required int index, required HDWallet hd, BitcoinAddressType? addressType}) {
     if (addressType == SegwitAddresType.mweb) {
       topUpMweb(index);
-      return hd == sideHd ? mweb_addrs[0] : mweb_addrs[index+1];
+      return hd == sideHd ? mwebAddrs[0] : mwebAddrs[index+1];
     }
     return generateP2WPKHAddress(hd: hd, index: index, network: network);
   }

From 9aa5ef83316d20f51479b3f66e947490e98a84a0 Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Mon, 22 Apr 2024 17:18:18 +0100
Subject: [PATCH 08/31] Show utxos in tx list

---
 cw_bitcoin/lib/electrum.dart        |  9 +++++++--
 cw_bitcoin/lib/litecoin_wallet.dart | 31 +++++++++++++++++++++++++++--
 2 files changed, 36 insertions(+), 4 deletions(-)

diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart
index 0553170cc..359f9b36d 100644
--- a/cw_bitcoin/lib/electrum.dart
+++ b/cw_bitcoin/lib/electrum.dart
@@ -2,6 +2,7 @@ import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
 import 'dart:typed_data';
+import 'package:convert/convert.dart';
 import 'package:bitcoin_base/bitcoin_base.dart';
 import 'package:cw_bitcoin/bitcoin_amount_format.dart';
 import 'package:cw_bitcoin/script_hash.dart';
@@ -263,8 +264,12 @@ class ElectrumClient {
       await call(method: 'blockchain.transaction.get_merkle', params: [hash, height])
           as Map<String, dynamic>;
 
-  Future<Map<String, dynamic>> getHeader({required int height}) async =>
-      await call(method: 'blockchain.block.get_header', params: [height]) as Map<String, dynamic>;
+  Future<DateTime> getBlockTime({required int height}) async {
+    final header = await call(method: 'blockchain.block.header', params: [height]) as String;
+    final bd = ByteData.sublistView(Uint8List.fromList(hex.decode(header)));
+    final timestamp = bd.getUint32(68, Endian.little) * 1000;
+    return DateTime.fromMillisecondsSinceEpoch(timestamp, isUtc: true);
+  }
 
   Future<double> estimatefee({required int p}) =>
       call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) {
diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index 1608a3dba..e71a68dee 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -1,9 +1,12 @@
 import 'dart:async';
+import 'package:convert/convert.dart';
 import 'package:bitcoin_base/bitcoin_base.dart';
 import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
 import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
+import 'package:cw_bitcoin/electrum_transaction_info.dart';
 import 'package:cw_core/crypto_currency.dart';
 import 'package:cw_core/sync_status.dart';
+import 'package:cw_core/transaction_direction.dart';
 import 'package:cw_core/unspent_coins_info.dart';
 import 'package:cw_bitcoin/litecoin_wallet_addresses.dart';
 import 'package:cw_core/transaction_priority.dart';
@@ -11,6 +14,7 @@ import 'package:flutter/foundation.dart';
 import 'package:hive/hive.dart';
 import 'package:mobx/mobx.dart';
 import 'package:cw_core/wallet_info.dart';
+import 'package:cw_core/wallet_type.dart';
 import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
 import 'package:cw_bitcoin/electrum_wallet.dart';
 import 'package:cw_bitcoin/bitcoin_address_record.dart';
@@ -36,7 +40,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
     ElectrumBalance? initialBalance,
     Map<String, int>? initialRegularAddressIndex,
     Map<String, int>? initialChangeAddressIndex,
-  }) : super(
+  }) : mwebHd = bitcoin.HDWallet.fromSeed(seedBytes,
+            network: litecoinNetwork).derivePath("m/1000'"),
+       super(
             mnemonic: mnemonic,
             password: password,
             walletInfo: walletInfo,
@@ -54,7 +60,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
       initialChangeAddressIndex: initialChangeAddressIndex,
       mainHd: hd,
       sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"),
-      mwebHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/1000'"),
+      mwebHd: mwebHd,
       network: network,
     );
     autorun((_) {
@@ -62,6 +68,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
     });
   }
 
+  final bitcoin.HDWallet mwebHd;
+
   static Future<LitecoinWallet> create(
       {required String mnemonic,
       required String password,
@@ -129,6 +137,25 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
           syncStatus = SyncedSyncStatus();
         }
       });
+    final scanSecret = mwebHd.derive(0x80000000).privKey!;
+    final req = UtxosRequest(scanSecret: hex.decode(scanSecret));
+    await for (var utxo in stub.utxos(req)) {
+      final status = await stub.status(StatusRequest());
+      var date = DateTime.now();
+      var confirmations = 0;
+      if (utxo.height > 0) {
+        date = await electrumClient.getBlockTime(height: utxo.height);
+        confirmations = status.blockHeaderHeight - utxo.height + 1;
+      }
+      final tx = ElectrumTransactionInfo(WalletType.litecoin,
+        id: utxo.outputId, height: utxo.height,
+        amount: utxo.value.toInt(),
+        direction: TransactionDirection.incoming,
+        isPending: utxo.height == 0,
+        date: date, confirmations: confirmations);
+      transactionHistory.addOne(tx);
+      await transactionHistory.save();
+    }
   }
 
   @override

From b07c3d8a54a76dec713a06164086aba615e2846b Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Mon, 22 Apr 2024 18:07:59 +0100
Subject: [PATCH 09/31] A few fixes

---
 cw_bitcoin/lib/electrum.dart                  | 2 +-
 cw_bitcoin/lib/electrum_transaction_info.dart | 4 ++--
 cw_bitcoin/lib/litecoin_wallet.dart           | 6 ++++--
 3 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart
index 359f9b36d..d94dccbba 100644
--- a/cw_bitcoin/lib/electrum.dart
+++ b/cw_bitcoin/lib/electrum.dart
@@ -268,7 +268,7 @@ class ElectrumClient {
     final header = await call(method: 'blockchain.block.header', params: [height]) as String;
     final bd = ByteData.sublistView(Uint8List.fromList(hex.decode(header)));
     final timestamp = bd.getUint32(68, Endian.little) * 1000;
-    return DateTime.fromMillisecondsSinceEpoch(timestamp, isUtc: true);
+    return DateTime.fromMillisecondsSinceEpoch(timestamp);
   }
 
   Future<double> estimatefee({required int p}) =>
diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart
index f980bd884..00315319c 100644
--- a/cw_bitcoin/lib/electrum_transaction_info.dart
+++ b/cw_bitcoin/lib/electrum_transaction_info.dart
@@ -196,8 +196,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
         direction: parseTransactionDirectionFromInt(data['direction'] as int),
         date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
         isPending: data['isPending'] as bool,
-        inputAddresses: data['inputAddresses'] as List<String>,
-        outputAddresses: data['outputAddresses'] as List<String>,
+        inputAddresses: List<String>.from(data['inputAddresses'] as List),
+        outputAddresses: List<String>.from(data['outputAddresses'] as List),
         confirmations: data['confirmations'] as int);
   }
 
diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index e71a68dee..8fa5af63b 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -149,10 +149,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
       }
       final tx = ElectrumTransactionInfo(WalletType.litecoin,
         id: utxo.outputId, height: utxo.height,
-        amount: utxo.value.toInt(),
+        amount: utxo.value.toInt(), fee: 0,
         direction: TransactionDirection.incoming,
         isPending: utxo.height == 0,
-        date: date, confirmations: confirmations);
+        date: date, confirmations: confirmations,
+        inputAddresses: [],
+        outputAddresses: [utxo.address]);
       transactionHistory.addOne(tx);
       await transactionHistory.save();
     }

From 4fc1de8cb6b217ba051f62a4065f00c2dda13772 Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Tue, 23 Apr 2024 00:26:34 +0100
Subject: [PATCH 10/31] Add spent processing

---
 cw_bitcoin/lib/litecoin_wallet.dart | 50 +++++++++++++++++++++++++++++
 1 file changed, 50 insertions(+)

diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index 8fa5af63b..547a2dda5 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -1,5 +1,6 @@
 import 'dart:async';
 import 'package:convert/convert.dart';
+import 'package:crypto/crypto.dart';
 import 'package:bitcoin_base/bitcoin_base.dart';
 import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
 import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
@@ -137,9 +138,20 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
           syncStatus = SyncedSyncStatus();
         }
       });
+    processMwebUtxos();
+  }
+
+  final Map<String, Utxo> mwebUtxos = {};
+
+  Future<void> processMwebUtxos() async {
+    final stub = await CwMweb.stub();
     final scanSecret = mwebHd.derive(0x80000000).privKey!;
     final req = UtxosRequest(scanSecret: hex.decode(scanSecret));
     await for (var utxo in stub.utxos(req)) {
+      final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs;
+      if (!mwebAddrs.contains(utxo.address)) continue;
+      mwebUtxos[utxo.outputId] = utxo;
+
       final status = await stub.status(StatusRequest());
       var date = DateTime.now();
       var confirmations = 0;
@@ -160,6 +172,44 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
     }
   }
 
+  Future<void> checkMwebUtxosSpent() async {
+    final List<String> outputIds = [];
+    mwebUtxos.forEach((outputId, utxo) {
+      if (utxo.height > 0)
+        outputIds.add(outputId);
+    });
+    final stub = await CwMweb.stub();
+    final resp = await stub.spent(SpentRequest(outputId: outputIds));
+    final spent = resp.outputId;
+    if (spent.isEmpty) return;
+    final status = await stub.status(StatusRequest());
+    final height = await electrumClient.getCurrentBlockChainTip();
+    if (height == null || status.mwebUtxosHeight != height) return;
+    final date = await electrumClient.getBlockTime(height: height);
+    int amount = 0;
+    Set<String> inputAddresses = {};
+    var output = AccumulatorSink<Digest>();
+    var input = sha256.startChunkedConversion(output);
+    for (final outputId in spent) {
+      input.add(hex.decode(outputId));
+      amount += mwebUtxos[outputId]!.value.toInt();
+      inputAddresses.add(mwebUtxos[outputId]!.address);
+      mwebUtxos.remove(outputId);
+    }
+    input.close();
+    var digest = output.events.single;
+    final tx = ElectrumTransactionInfo(WalletType.litecoin,
+      id: digest.toString(), height: height,
+      amount: amount, fee: 0,
+      direction: TransactionDirection.outgoing,
+      isPending: false,
+      date: date, confirmations: 1,
+      inputAddresses: inputAddresses.toList(),
+      outputAddresses: []);
+    transactionHistory.addOne(tx);
+    await transactionHistory.save();
+  }
+
   @override
   int feeRate(TransactionPriority priority) {
     if (priority is LitecoinTransactionPriority) {

From 50ef9185f53a2f8e9d30c083aadd834eefe7b5ba Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Tue, 23 Apr 2024 13:37:44 +0100
Subject: [PATCH 11/31] Update balance

---
 cw_bitcoin/lib/electrum_wallet.dart |  6 +++++-
 cw_bitcoin/lib/litecoin_wallet.dart | 33 +++++++++++++++++++++++++++++
 2 files changed, 38 insertions(+), 1 deletion(-)

diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index 17e9880e9..6794d93c5 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -740,7 +740,7 @@ abstract class ElectrumWalletBase
 
   Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
 
-  Future<void> updateUnspent() async {
+  Future<void> updateUnspentCoins() async {
     List<BitcoinUnspent> updatedUnspentCoins = [];
 
     final addressesSet = walletAddresses.allAddresses.map((addr) => addr.address).toSet();
@@ -759,6 +759,10 @@ abstract class ElectrumWalletBase
             }))));
 
     unspentCoins = updatedUnspentCoins;
+  }
+
+  Future<void> updateUnspent() async {
+    await updateUnspentCoins();
 
     if (unspentCoinsInfo.isEmpty) {
       unspentCoins.forEach((coin) => _addCoinInfo(coin));
diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index 547a2dda5..1ff5b5ac9 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -4,6 +4,7 @@ import 'package:crypto/crypto.dart';
 import 'package:bitcoin_base/bitcoin_base.dart';
 import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
 import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
+import 'package:cw_bitcoin/bitcoin_unspent.dart';
 import 'package:cw_bitcoin/electrum_transaction_info.dart';
 import 'package:cw_core/crypto_currency.dart';
 import 'package:cw_core/sync_status.dart';
@@ -169,6 +170,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
         outputAddresses: [utxo.address]);
       transactionHistory.addOne(tx);
       await transactionHistory.save();
+      await updateUnspent();
+      await updateBalance();
     }
   }
 
@@ -210,6 +213,36 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
     await transactionHistory.save();
   }
 
+  @override
+  Future<void> updateUnspentCoins() async {
+    await super.updateUnspentCoins();
+    await checkMwebUtxosSpent();
+    final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs;
+    mwebUtxos.forEach((outputId, utxo) {
+      final addressRecord = walletAddresses.allAddresses.firstWhere(
+          (addressRecord) => addressRecord.address == utxo.address);
+      unspentCoins.add(BitcoinUnspent(addressRecord, outputId,
+          utxo.value.toInt(), mwebAddrs.indexOf(utxo.address)));
+    });
+  }
+
+  @override
+  Future<void> updateBalance() async {
+    await super.updateBalance();
+    var confirmed = balance[currency]!.confirmed;
+    var unconfirmed = balance[currency]!.unconfirmed;
+    mwebUtxos.values.forEach((utxo) {
+      if (utxo.height > 0)
+        confirmed += utxo.value.toInt();
+      else
+        unconfirmed += utxo.value.toInt();
+    });
+    balance[currency] = ElectrumBalance(
+        confirmed: confirmed, unconfirmed: unconfirmed,
+        frozen: balance[currency]!.frozen);
+    await save();
+  }
+
   @override
   int feeRate(TransactionPriority priority) {
     if (priority is LitecoinTransactionPriority) {

From 05b71a372451e8b10a3329ccf245faba97dbc04d Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Tue, 23 Apr 2024 17:16:47 +0100
Subject: [PATCH 12/31] Balance fixes

---
 cw_bitcoin/lib/electrum_wallet.dart |  8 +++---
 cw_bitcoin/lib/litecoin_wallet.dart | 43 +++++++++++++++++++++--------
 cw_bitcoin/pubspec.lock             |  8 ++++++
 cw_bitcoin/pubspec.yaml             |  1 +
 4 files changed, 44 insertions(+), 16 deletions(-)

diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index 6794d93c5..a2839b1e3 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -157,7 +157,7 @@ abstract class ElectrumWalletBase
       await updateTransactions();
       _subscribeForUpdates();
       await updateUnspent();
-      await updateBalance();
+      await updateBalance(delay: 5);
       _feeRates = await electrumClient.feeRates(network: network);
 
       Timer.periodic(
@@ -1196,7 +1196,7 @@ abstract class ElectrumWalletBase
     });
   }
 
-  Future<ElectrumBalance> _fetchBalances() async {
+  Future<ElectrumBalance> fetchBalances() async {
     final addresses = walletAddresses.allAddresses.toList();
     final balanceFutures = <Future<Map<String, dynamic>>>[];
     for (var i = 0; i < addresses.length; i++) {
@@ -1240,8 +1240,8 @@ abstract class ElectrumWalletBase
         confirmed: totalConfirmed, unconfirmed: totalUnconfirmed, frozen: totalFrozen);
   }
 
-  Future<void> updateBalance() async {
-    balance[currency] = await _fetchBalances();
+  Future<void> updateBalance({int delay = 1}) async {
+    balance[currency] = await fetchBalances();
     await save();
   }
 
diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index 1ff5b5ac9..35bb143f3 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -25,6 +25,7 @@ import 'package:cw_bitcoin/litecoin_network.dart';
 import 'package:cw_mweb/cw_mweb.dart';
 import 'package:cw_mweb/mwebd.pb.dart';
 import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
+import 'package:queue/queue.dart';
 
 part 'litecoin_wallet.g.dart';
 
@@ -157,7 +158,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
       var date = DateTime.now();
       var confirmations = 0;
       if (utxo.height > 0) {
-        date = await electrumClient.getBlockTime(height: utxo.height);
+        while (true) try {
+          date = await electrumClient.getBlockTime(height: utxo.height);
+          break;
+        } catch (err) {
+          await Future.delayed(Duration(seconds: 1));
+        }
         confirmations = status.blockHeaderHeight - utxo.height + 1;
       }
       final tx = ElectrumTransactionInfo(WalletType.litecoin,
@@ -169,9 +175,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
         inputAddresses: [],
         outputAddresses: [utxo.address]);
       transactionHistory.addOne(tx);
-      await transactionHistory.save();
-      await updateUnspent();
-      await updateBalance();
+      queueUpdate(1);
     }
   }
 
@@ -227,20 +231,35 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
   }
 
   @override
-  Future<void> updateBalance() async {
-    await super.updateBalance();
-    var confirmed = balance[currency]!.confirmed;
-    var unconfirmed = balance[currency]!.unconfirmed;
+  Future<ElectrumBalance> fetchBalances() async {
+    final balance = await super.fetchBalances();
+    var confirmed = balance.confirmed;
+    var unconfirmed = balance.unconfirmed;
     mwebUtxos.values.forEach((utxo) {
       if (utxo.height > 0)
         confirmed += utxo.value.toInt();
       else
         unconfirmed += utxo.value.toInt();
     });
-    balance[currency] = ElectrumBalance(
-        confirmed: confirmed, unconfirmed: unconfirmed,
-        frozen: balance[currency]!.frozen);
-    await save();
+    return ElectrumBalance(confirmed: confirmed,
+        unconfirmed: unconfirmed, frozen: balance.frozen);
+  }
+
+  @override
+  Future<void> updateBalance({int delay = 1}) async {
+    queueUpdate(delay);
+  }
+
+  Timer? _debounceTimer;
+  final _debounceQueue = Queue();
+
+  void queueUpdate(int delay) {
+    if (_debounceTimer?.isActive ?? false) _debounceTimer?.cancel();
+    _debounceTimer = Timer(Duration(seconds: delay), () =>
+      _debounceQueue.add(() async {
+        await updateUnspent();
+        await super.updateBalance();
+      }));
   }
 
   @override
diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock
index f93365d89..f081d7e87 100644
--- a/cw_bitcoin/pubspec.lock
+++ b/cw_bitcoin/pubspec.lock
@@ -651,6 +651,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.2.3"
+  queue:
+    dependency: "direct main"
+    description:
+      name: queue
+      sha256: "9a41ecadc15db79010108c06eae229a45c56b18db699760f34e8c9ac9b831ff9"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.1.0+2"
   rxdart:
     dependency: "direct main"
     description:
diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml
index 9c3c5d751..4615f1219 100644
--- a/cw_bitcoin/pubspec.yaml
+++ b/cw_bitcoin/pubspec.yaml
@@ -36,6 +36,7 @@ dependencies:
   cw_mweb:
     path: ../cw_mweb
   grpc: ^3.2.4
+  queue: ^3.1.0+2
 
 dev_dependencies:
   flutter_test:

From 3e5a5f18faed191245dd99a02df5c3e2d817a661 Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Tue, 23 Apr 2024 18:31:30 +0100
Subject: [PATCH 13/31] Update address records

---
 cw_bitcoin/lib/litecoin_wallet.dart | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index 35bb143f3..2da4c407c 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -174,6 +174,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
         date: date, confirmations: confirmations,
         inputAddresses: [],
         outputAddresses: [utxo.address]);
+      if (transactionHistory.transactions[utxo.outputId] == null) {
+        final addressRecord = walletAddresses.allAddresses.firstWhere(
+            (addressRecord) => addressRecord.address == utxo.address);
+        addressRecord.txCount++;
+        addressRecord.balance += utxo.value.toInt();
+      }
       transactionHistory.addOne(tx);
       queueUpdate(1);
     }
@@ -199,8 +205,14 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
     var input = sha256.startChunkedConversion(output);
     for (final outputId in spent) {
       input.add(hex.decode(outputId));
-      amount += mwebUtxos[outputId]!.value.toInt();
-      inputAddresses.add(mwebUtxos[outputId]!.address);
+      final utxo = mwebUtxos[outputId]!;
+      final addressRecord = walletAddresses.allAddresses.firstWhere(
+          (addressRecord) => addressRecord.address == utxo.address);
+      if (!inputAddresses.contains(utxo.address))
+        addressRecord.txCount++;
+      addressRecord.balance -= utxo.value.toInt();
+      amount += utxo.value.toInt();
+      inputAddresses.add(utxo.address);
       mwebUtxos.remove(outputId);
     }
     input.close();

From 37ff38f0dfbc7656be1e6dd1f2d1f8755532641a Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Wed, 24 Apr 2024 11:53:43 +0100
Subject: [PATCH 14/31] Get rid of debounce hack

---
 cw_bitcoin/lib/electrum.dart        |  9 ++----
 cw_bitcoin/lib/electrum_wallet.dart |  9 ++++--
 cw_bitcoin/lib/litecoin_wallet.dart | 44 +++++++++++------------------
 cw_bitcoin/pubspec.lock             |  8 ------
 cw_bitcoin/pubspec.yaml             |  1 -
 cw_mweb/lib/mwebd.pb.dart           | 28 ++++++++++++++++++
 6 files changed, 52 insertions(+), 47 deletions(-)

diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart
index d94dccbba..0553170cc 100644
--- a/cw_bitcoin/lib/electrum.dart
+++ b/cw_bitcoin/lib/electrum.dart
@@ -2,7 +2,6 @@ import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
 import 'dart:typed_data';
-import 'package:convert/convert.dart';
 import 'package:bitcoin_base/bitcoin_base.dart';
 import 'package:cw_bitcoin/bitcoin_amount_format.dart';
 import 'package:cw_bitcoin/script_hash.dart';
@@ -264,12 +263,8 @@ class ElectrumClient {
       await call(method: 'blockchain.transaction.get_merkle', params: [hash, height])
           as Map<String, dynamic>;
 
-  Future<DateTime> getBlockTime({required int height}) async {
-    final header = await call(method: 'blockchain.block.header', params: [height]) as String;
-    final bd = ByteData.sublistView(Uint8List.fromList(hex.decode(header)));
-    final timestamp = bd.getUint32(68, Endian.little) * 1000;
-    return DateTime.fromMillisecondsSinceEpoch(timestamp);
-  }
+  Future<Map<String, dynamic>> getHeader({required int height}) async =>
+      await call(method: 'blockchain.block.get_header', params: [height]) as Map<String, dynamic>;
 
   Future<double> estimatefee({required int p}) =>
       call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) {
diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index a2839b1e3..b25069863 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -21,6 +21,7 @@ import 'package:cw_bitcoin/electrum_transaction_info.dart';
 import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
 import 'package:cw_bitcoin/exceptions.dart';
 import 'package:cw_bitcoin/litecoin_network.dart';
+import 'package:cw_bitcoin/litecoin_wallet.dart';
 import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
 import 'package:cw_bitcoin/script_hash.dart';
 import 'package:cw_bitcoin/utils.dart';
@@ -156,8 +157,10 @@ abstract class ElectrumWalletBase
       syncStatus = AttemptingSyncStatus();
       await updateTransactions();
       _subscribeForUpdates();
-      await updateUnspent();
-      await updateBalance(delay: 5);
+      if (!(this is LitecoinWallet)) {
+        await updateUnspent();
+        await updateBalance();
+      }
       _feeRates = await electrumClient.feeRates(network: network);
 
       Timer.periodic(
@@ -1240,7 +1243,7 @@ abstract class ElectrumWalletBase
         confirmed: totalConfirmed, unconfirmed: totalUnconfirmed, frozen: totalFrozen);
   }
 
-  Future<void> updateBalance({int delay = 1}) async {
+  Future<void> updateBalance() async {
     balance[currency] = await fetchBalances();
     await save();
   }
diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index 2da4c407c..7506e368a 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -25,7 +25,6 @@ import 'package:cw_bitcoin/litecoin_network.dart';
 import 'package:cw_mweb/cw_mweb.dart';
 import 'package:cw_mweb/mwebd.pb.dart';
 import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
-import 'package:queue/queue.dart';
 
 part 'litecoin_wallet.g.dart';
 
@@ -149,7 +148,13 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
     final stub = await CwMweb.stub();
     final scanSecret = mwebHd.derive(0x80000000).privKey!;
     final req = UtxosRequest(scanSecret: hex.decode(scanSecret));
+    var initDone = false;
     await for (var utxo in stub.utxos(req)) {
+      if (utxo.address.isEmpty) {
+        await updateUnspent();
+        await updateBalance();
+        initDone = true;
+      }
       final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs;
       if (!mwebAddrs.contains(utxo.address)) continue;
       mwebUtxos[utxo.outputId] = utxo;
@@ -158,12 +163,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
       var date = DateTime.now();
       var confirmations = 0;
       if (utxo.height > 0) {
-        while (true) try {
-          date = await electrumClient.getBlockTime(height: utxo.height);
-          break;
-        } catch (err) {
-          await Future.delayed(Duration(seconds: 1));
-        }
+        date = DateTime.fromMillisecondsSinceEpoch(utxo.blockTime * 1000);
         confirmations = status.blockHeaderHeight - utxo.height + 1;
       }
       final tx = ElectrumTransactionInfo(WalletType.litecoin,
@@ -179,9 +179,13 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
             (addressRecord) => addressRecord.address == utxo.address);
         addressRecord.txCount++;
         addressRecord.balance += utxo.value.toInt();
+        addressRecord.setAsUsed();
       }
       transactionHistory.addOne(tx);
-      queueUpdate(1);
+      if (initDone) {
+        await updateUnspent();
+        await updateBalance();
+      }
     }
   }
 
@@ -197,8 +201,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
     if (spent.isEmpty) return;
     final status = await stub.status(StatusRequest());
     final height = await electrumClient.getCurrentBlockChainTip();
-    if (height == null || status.mwebUtxosHeight != height) return;
-    final date = await electrumClient.getBlockTime(height: height);
+    if (height == null || status.blockHeaderHeight != height) return;
+    if (status.mwebUtxosHeight != height) return;
     int amount = 0;
     Set<String> inputAddresses = {};
     var output = AccumulatorSink<Digest>();
@@ -222,7 +226,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
       amount: amount, fee: 0,
       direction: TransactionDirection.outgoing,
       isPending: false,
-      date: date, confirmations: 1,
+      date: DateTime.fromMillisecondsSinceEpoch(status.blockTime * 1000),
+      confirmations: 1,
       inputAddresses: inputAddresses.toList(),
       outputAddresses: []);
     transactionHistory.addOne(tx);
@@ -257,23 +262,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
         unconfirmed: unconfirmed, frozen: balance.frozen);
   }
 
-  @override
-  Future<void> updateBalance({int delay = 1}) async {
-    queueUpdate(delay);
-  }
-
-  Timer? _debounceTimer;
-  final _debounceQueue = Queue();
-
-  void queueUpdate(int delay) {
-    if (_debounceTimer?.isActive ?? false) _debounceTimer?.cancel();
-    _debounceTimer = Timer(Duration(seconds: delay), () =>
-      _debounceQueue.add(() async {
-        await updateUnspent();
-        await super.updateBalance();
-      }));
-  }
-
   @override
   int feeRate(TransactionPriority priority) {
     if (priority is LitecoinTransactionPriority) {
diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock
index f081d7e87..f93365d89 100644
--- a/cw_bitcoin/pubspec.lock
+++ b/cw_bitcoin/pubspec.lock
@@ -651,14 +651,6 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.2.3"
-  queue:
-    dependency: "direct main"
-    description:
-      name: queue
-      sha256: "9a41ecadc15db79010108c06eae229a45c56b18db699760f34e8c9ac9b831ff9"
-      url: "https://pub.dev"
-    source: hosted
-    version: "3.1.0+2"
   rxdart:
     dependency: "direct main"
     description:
diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml
index 4615f1219..9c3c5d751 100644
--- a/cw_bitcoin/pubspec.yaml
+++ b/cw_bitcoin/pubspec.yaml
@@ -36,7 +36,6 @@ dependencies:
   cw_mweb:
     path: ../cw_mweb
   grpc: ^3.2.4
-  queue: ^3.1.0+2
 
 dev_dependencies:
   flutter_test:
diff --git a/cw_mweb/lib/mwebd.pb.dart b/cw_mweb/lib/mwebd.pb.dart
index 8d139d7eb..d0dd486c0 100644
--- a/cw_mweb/lib/mwebd.pb.dart
+++ b/cw_mweb/lib/mwebd.pb.dart
@@ -51,6 +51,7 @@ class StatusResponse extends $pb.GeneratedMessage {
     $core.int? blockHeaderHeight,
     $core.int? mwebHeaderHeight,
     $core.int? mwebUtxosHeight,
+    $core.int? blockTime,
   }) {
     final $result = create();
     if (blockHeaderHeight != null) {
@@ -62,6 +63,9 @@ class StatusResponse extends $pb.GeneratedMessage {
     if (mwebUtxosHeight != null) {
       $result.mwebUtxosHeight = mwebUtxosHeight;
     }
+    if (blockTime != null) {
+      $result.blockTime = blockTime;
+    }
     return $result;
   }
   StatusResponse._() : super();
@@ -72,6 +76,7 @@ class StatusResponse extends $pb.GeneratedMessage {
     ..a<$core.int>(1, _omitFieldNames ? '' : 'blockHeaderHeight', $pb.PbFieldType.O3)
     ..a<$core.int>(2, _omitFieldNames ? '' : 'mwebHeaderHeight', $pb.PbFieldType.O3)
     ..a<$core.int>(3, _omitFieldNames ? '' : 'mwebUtxosHeight', $pb.PbFieldType.O3)
+    ..a<$core.int>(4, _omitFieldNames ? '' : 'blockTime', $pb.PbFieldType.OU3)
     ..hasRequiredFields = false
   ;
 
@@ -122,6 +127,15 @@ class StatusResponse extends $pb.GeneratedMessage {
   $core.bool hasMwebUtxosHeight() => $_has(2);
   @$pb.TagNumber(3)
   void clearMwebUtxosHeight() => clearField(3);
+
+  @$pb.TagNumber(4)
+  $core.int get blockTime => $_getIZ(3);
+  @$pb.TagNumber(4)
+  set blockTime($core.int v) { $_setUnsignedInt32(3, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasBlockTime() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearBlockTime() => clearField(4);
 }
 
 class UtxosRequest extends $pb.GeneratedMessage {
@@ -194,6 +208,7 @@ class Utxo extends $pb.GeneratedMessage {
     $fixnum.Int64? value,
     $core.String? address,
     $core.String? outputId,
+    $core.int? blockTime,
   }) {
     final $result = create();
     if (height != null) {
@@ -208,6 +223,9 @@ class Utxo extends $pb.GeneratedMessage {
     if (outputId != null) {
       $result.outputId = outputId;
     }
+    if (blockTime != null) {
+      $result.blockTime = blockTime;
+    }
     return $result;
   }
   Utxo._() : super();
@@ -219,6 +237,7 @@ class Utxo extends $pb.GeneratedMessage {
     ..a<$fixnum.Int64>(2, _omitFieldNames ? '' : 'value', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO)
     ..aOS(3, _omitFieldNames ? '' : 'address')
     ..aOS(4, _omitFieldNames ? '' : 'outputId')
+    ..a<$core.int>(5, _omitFieldNames ? '' : 'blockTime', $pb.PbFieldType.OU3)
     ..hasRequiredFields = false
   ;
 
@@ -278,6 +297,15 @@ class Utxo extends $pb.GeneratedMessage {
   $core.bool hasOutputId() => $_has(3);
   @$pb.TagNumber(4)
   void clearOutputId() => clearField(4);
+
+  @$pb.TagNumber(5)
+  $core.int get blockTime => $_getIZ(4);
+  @$pb.TagNumber(5)
+  set blockTime($core.int v) { $_setUnsignedInt32(4, v); }
+  @$pb.TagNumber(5)
+  $core.bool hasBlockTime() => $_has(4);
+  @$pb.TagNumber(5)
+  void clearBlockTime() => clearField(5);
 }
 
 class AddressRequest extends $pb.GeneratedMessage {

From 404672f10fdbc5bc780822af69a434d525df01cc Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Wed, 24 Apr 2024 14:49:51 +0100
Subject: [PATCH 15/31] Get sending up to the confirmation box

---
 cw_bitcoin/lib/electrum_wallet.dart           |  2 +-
 cw_bitcoin/lib/litecoin_wallet.dart           |  6 ++++--
 cw_bitcoin/lib/litecoin_wallet_addresses.dart |  7 +++++++
 lib/core/address_validator.dart               | 16 +++++++++-------
 4 files changed, 21 insertions(+), 10 deletions(-)

diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index b25069863..37937f45c 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -157,7 +157,7 @@ abstract class ElectrumWalletBase
       syncStatus = AttemptingSyncStatus();
       await updateTransactions();
       _subscribeForUpdates();
-      if (!(this is LitecoinWallet)) {
+      if (this is! LitecoinWallet) {
         await updateUnspent();
         await updateBalance();
       }
diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index 7506e368a..adce6e6b7 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -242,8 +242,10 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
     mwebUtxos.forEach((outputId, utxo) {
       final addressRecord = walletAddresses.allAddresses.firstWhere(
           (addressRecord) => addressRecord.address == utxo.address);
-      unspentCoins.add(BitcoinUnspent(addressRecord, outputId,
-          utxo.value.toInt(), mwebAddrs.indexOf(utxo.address)));
+      final unspent = BitcoinUnspent(addressRecord, outputId,
+          utxo.value.toInt(), mwebAddrs.indexOf(utxo.address));
+      if (unspent.vout == 0) unspent.isChange = true;
+      unspentCoins.add(unspent);
     });
   }
 
diff --git a/cw_bitcoin/lib/litecoin_wallet_addresses.dart b/cw_bitcoin/lib/litecoin_wallet_addresses.dart
index e1ee81b31..226bd1fc6 100644
--- a/cw_bitcoin/lib/litecoin_wallet_addresses.dart
+++ b/cw_bitcoin/lib/litecoin_wallet_addresses.dart
@@ -62,4 +62,11 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
     }
     return getAddress(index: index, hd: hd, addressType: addressType);
   }
+
+  @action
+  @override
+  Future<String> getChangeAddress() async {
+    await topUpMweb(0);
+    return mwebAddrs[0];
+  }
 }
diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart
index 967cf9bf0..bb69d4ecf 100644
--- a/lib/core/address_validator.dart
+++ b/lib/core/address_validator.dart
@@ -9,8 +9,9 @@ class AddressValidator extends TextValidator {
   AddressValidator({required CryptoCurrency type})
       : super(
             errorMessage: S.current.error_text_address,
-            useAdditionalValidation: type == CryptoCurrency.btc
-                ? (String txt) => validateAddress(address: txt, network: BitcoinNetwork.mainnet)
+            useAdditionalValidation: type == CryptoCurrency.btc || type == CryptoCurrency.ltc
+                ? (String txt) => validateAddress(address: txt, network:
+                    type == CryptoCurrency.btc ? BitcoinNetwork.mainnet : LitecoinNetwork.mainnet)
                 : null,
             pattern: getPattern(type),
             length: getLength(type));
@@ -27,6 +28,8 @@ class AddressValidator extends TextValidator {
             '|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$';
       case CryptoCurrency.btc:
         return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$';
+      case CryptoCurrency.ltc:
+        return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$|^${MwebAddress.regex.pattern}\$';
       case CryptoCurrency.nano:
         return '[0-9a-zA-Z_]';
       case CryptoCurrency.banano:
@@ -96,8 +99,6 @@ class AddressValidator extends TextValidator {
         return '^(?!bitcoincash:)[0-9a-zA-Z]*\$|^(?!bitcoincash:)q|p[0-9a-zA-Z]{41}\$|^(?!bitcoincash:)q|p[0-9a-zA-Z]{42}\$|^bitcoincash:q|p[0-9a-zA-Z]{41}\$|^bitcoincash:q|p[0-9a-zA-Z]{42}\$';
       case CryptoCurrency.bnb:
         return '[0-9a-zA-Z]';
-      case CryptoCurrency.ltc:
-        return '^(?!(ltc|LTC)1)[0-9a-zA-Z]*\$|(^LTC1[A-Z0-9]*\$)|(^ltc1[a-z0-9]*\$)';
       case CryptoCurrency.hbar:
         return '[0-9a-zA-Z.]';
       case CryptoCurrency.zaddr:
@@ -144,6 +145,8 @@ class AddressValidator extends TextValidator {
         return null;
       case CryptoCurrency.btc:
         return null;
+      case CryptoCurrency.ltc:
+        return null;
       case CryptoCurrency.dash:
         return [34];
       case CryptoCurrency.eos:
@@ -190,8 +193,6 @@ class AddressValidator extends TextValidator {
         return [42, 43, 44, 54, 55];
       case CryptoCurrency.bnb:
         return [42];
-      case CryptoCurrency.ltc:
-        return [34, 43, 63];
       case CryptoCurrency.nano:
         return [64, 65];
       case CryptoCurrency.banano:
@@ -278,7 +279,8 @@ class AddressValidator extends TextValidator {
       case CryptoCurrency.ltc:
         return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)'
             '|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)'
-            '|([^0-9a-zA-Z]|^)ltc[a-zA-Z0-9]{26,45}([^0-9a-zA-Z]|\$)';
+            '|([^0-9a-zA-Z]|^)ltc[a-zA-Z0-9]{26,45}([^0-9a-zA-Z]|\$)'
+            '|([^0-9a-zA-Z]|^)((ltc|t)mweb1q[ac-hj-np-z02-9]{90,120})([^0-9a-zA-Z]|\$)';
       case CryptoCurrency.eth:
         return '0x[0-9a-zA-Z]{42}';
       case CryptoCurrency.maticpoly:

From 4abe70062fbfa7f1896c3d63235a49bafabf4240 Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Thu, 25 Apr 2024 15:23:01 +0100
Subject: [PATCH 16/31] Fee estimation

---
 cw_bitcoin/lib/electrum.dart                  | 25 ++++++---
 cw_bitcoin/lib/electrum_wallet.dart           | 52 ++++++++++++-------
 cw_bitcoin/lib/litecoin_wallet.dart           | 44 ++++++++++++++++
 .../com/cakewallet/mweb/CwMwebPlugin.kt       |  4 +-
 cw_mweb/lib/cw_mweb.dart                      |  5 +-
 5 files changed, 100 insertions(+), 30 deletions(-)

diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart
index 0553170cc..2b3f490a3 100644
--- a/cw_bitcoin/lib/electrum.dart
+++ b/cw_bitcoin/lib/electrum.dart
@@ -331,14 +331,19 @@ class ElectrumClient {
   //   "height": 520481,
   //   "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4"
   // }
-  Future<int?> getCurrentBlockChainTip() =>
-      call(method: 'blockchain.headers.subscribe').then((result) {
-        if (result is Map<String, dynamic>) {
-          return result["height"] as int;
-        }
+  BehaviorSubject<Map<String, dynamic>>? tipListener;
+  int? currentTip;
 
-        return null;
-      });
+  Future<int?> getCurrentBlockChainTip() async {
+    final method = 'blockchain.headers.subscribe';
+    final cb = (result) => currentTip = result['height'] as int;
+    if (tipListener == null) {
+      tipListener = subscribe(id: method, method: method);
+      tipListener?.listen(cb);
+      cb(await call(method: method));
+    }
+    return currentTip;
+  }
 
   BehaviorSubject<Object>? scripthashUpdate(String scripthash) {
     _id += 1;
@@ -424,6 +429,12 @@ class ElectrumClient {
 
   void _methodHandler({required String method, required Map<String, dynamic> request}) {
     switch (method) {
+      case 'blockchain.headers.subscribe':
+        final params = request['params'] as List<dynamic>;
+        final id = 'blockchain.headers.subscribe';
+
+        _tasks[id]?.subject?.add(params.last);
+        break;
       case 'blockchain.scripthash.subscribe':
         final params = request['params'] as List<dynamic>;
         final scripthash = params.first as String?;
diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index 37937f45c..1f5c14319 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -392,24 +392,13 @@ abstract class ElectrumWalletBase
       value: BigInt.from(amountLeftForChangeAndFee),
     ));
 
-    int estimatedSize;
-    if (network is BitcoinCashNetwork) {
-      estimatedSize = ForkedTransactionBuilder.estimateTransactionSize(
-        utxos: utxos,
-        outputs: outputs,
-        network: network as BitcoinCashNetwork,
-        memo: memo,
-      );
-    } else {
-      estimatedSize = BitcoinTransactionBuilder.estimateTransactionSize(
-        utxos: utxos,
-        outputs: outputs,
-        network: network,
-        memo: memo,
-      );
-    }
-
-    int fee = feeAmountWithFeeRate(feeRate, 0, 0, size: estimatedSize);
+    int fee = await calcFee(
+      utxos: utxos,
+      outputs: outputs,
+      network: network,
+      memo: memo,
+      feeRate: feeRate,
+    );
 
     if (fee == 0) {
       throw BitcoinTransactionNoFeeException();
@@ -499,6 +488,33 @@ abstract class ElectrumWalletBase
     );
   }
 
+  Future<int> calcFee({
+      required List<UtxoWithAddress> utxos,
+      required List<BitcoinBaseOutput> outputs,
+      required BasedUtxoNetwork network,
+      String? memo,
+      required int feeRate}) async {
+
+    int estimatedSize;
+    if (network is BitcoinCashNetwork) {
+      estimatedSize = ForkedTransactionBuilder.estimateTransactionSize(
+        utxos: utxos,
+        outputs: outputs,
+        network: network,
+        memo: memo,
+      );
+    } else {
+      estimatedSize = BitcoinTransactionBuilder.estimateTransactionSize(
+        utxos: utxos,
+        outputs: outputs,
+        network: network,
+        memo: memo,
+      );
+    }
+
+    return feeAmountWithFeeRate(feeRate, 0, 0, size: estimatedSize);
+  }
+
   @override
   Future<PendingTransaction> createTransaction(Object credentials) async {
     try {
diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index adce6e6b7..b84675669 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -1,12 +1,15 @@
 import 'dart:async';
+import 'dart:math';
 import 'package:convert/convert.dart';
 import 'package:crypto/crypto.dart';
+import 'package:fixnum/fixnum.dart';
 import 'package:bitcoin_base/bitcoin_base.dart';
 import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
 import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
 import 'package:cw_bitcoin/bitcoin_unspent.dart';
 import 'package:cw_bitcoin/electrum_transaction_info.dart';
 import 'package:cw_core/crypto_currency.dart';
+import 'package:cw_core/pending_transaction.dart';
 import 'package:cw_core/sync_status.dart';
 import 'package:cw_core/transaction_direction.dart';
 import 'package:cw_core/unspent_coins_info.dart';
@@ -279,4 +282,45 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
 
     return 0;
   }
+
+  @override
+  Future<int> calcFee({
+      required List<UtxoWithAddress> utxos,
+      required List<BitcoinBaseOutput> outputs,
+      required BasedUtxoNetwork network,
+      String? memo,
+      required int feeRate}) async {
+
+    final txb = BitcoinTransactionBuilder(utxos: utxos,
+        outputs: outputs, fee: BigInt.zero, network: network);
+    final scanSecret = mwebHd.derive(0x80000000).privKey!;
+    final spendSecret = mwebHd.derive(0x80000001).privKey!;
+    final stub = await CwMweb.stub();
+    final resp = await stub.create(CreateRequest(
+        rawTx: txb.buildTransaction((a, b, c, d) => '').toBytes(),
+        scanSecret: hex.decode(scanSecret),
+        spendSecret: hex.decode(spendSecret),
+        feeRatePerKb: Int64(feeRate * 1000),
+        dryRun: true));
+    final tx = BtcTransaction.fromRaw(hex.encode(resp.rawTx));
+    final posUtxos = utxos.where((utxo) => tx.inputs.any((input) =>
+        input.txId == utxo.utxo.txHash && input.txIndex == utxo.utxo.vout)).toList();
+    final preOutputSum = outputs.fold<int>(0, (acc, output) => acc + output.toOutput.amount.toInt());
+    final posOutputSum = tx.outputs.fold<int>(0, (acc, output) => acc + output.amount.toInt());
+    final mwebInputSum = utxos.sumOfUtxosValue() - posUtxos.sumOfUtxosValue();
+    final expectedPegin = max(0, preOutputSum - mwebInputSum.toInt());
+    var fee = posOutputSum - expectedPegin;
+    if (expectedPegin > 0) {
+      fee += await super.calcFee(utxos: posUtxos, outputs: tx.outputs.map((output) =>
+          BitcoinScriptOutput(script: output.scriptPubKey, value: output.amount)).toList(),
+          network: network, memo: memo, feeRate: feeRate) + feeRate * 41;
+    }
+    return fee;
+  }
+
+  @override
+  Future<PendingTransaction> createTransaction(Object credentials) async {
+    final tx = await super.createTransaction(credentials);
+    return tx;
+  }
 }
diff --git a/cw_mweb/android/src/main/kotlin/com/cakewallet/mweb/CwMwebPlugin.kt b/cw_mweb/android/src/main/kotlin/com/cakewallet/mweb/CwMwebPlugin.kt
index cf194417b..1dfbe12e5 100644
--- a/cw_mweb/android/src/main/kotlin/com/cakewallet/mweb/CwMwebPlugin.kt
+++ b/cw_mweb/android/src/main/kotlin/com/cakewallet/mweb/CwMwebPlugin.kt
@@ -17,6 +17,7 @@ class CwMwebPlugin: FlutterPlugin, MethodCallHandler {
   /// This local reference serves to register the plugin with the Flutter Engine and unregister it
   /// when the Flutter Engine is detached from the Activity
   private lateinit var channel : MethodChannel
+  private var port: Long? = null
 
   override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
     channel = MethodChannel(flutterPluginBinding.binaryMessenger, "cw_mweb")
@@ -26,7 +27,8 @@ class CwMwebPlugin: FlutterPlugin, MethodCallHandler {
   override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
     if (call.method == "start") {
       val dataDir = call.argument("dataDir") ?: ""
-      result.success(Mwebd.newServer("", dataDir, "").start(0))
+      port = port ?: Mwebd.newServer("", dataDir, "").start(0)
+      result.success(port)
     } else {
       result.notImplemented()
     }
diff --git a/cw_mweb/lib/cw_mweb.dart b/cw_mweb/lib/cw_mweb.dart
index a54295673..f16d451d9 100644
--- a/cw_mweb/lib/cw_mweb.dart
+++ b/cw_mweb/lib/cw_mweb.dart
@@ -4,13 +4,10 @@ import 'cw_mweb_platform_interface.dart';
 import 'mwebd.pbgrpc.dart';
 
 class CwMweb {
-  static Future<int?>? port;
-
   static Future<RpcClient> stub() async {
     final appDir = await getApplicationSupportDirectory();
-    port ??= CwMwebPlatform.instance.start(appDir.path);
     return RpcClient(ClientChannel('127.0.0.1',
-      port: await port ?? 0,
+      port: await CwMwebPlatform.instance.start(appDir.path) ?? 0,
       options: const ChannelOptions(
         credentials: ChannelCredentials.insecure())));
   }

From c96424256e7dcb6d56c13a188989c96363222dea Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Thu, 25 Apr 2024 19:02:49 +0100
Subject: [PATCH 17/31] Stop the daemon if plugin is unloaded

---
 .../src/main/kotlin/com/cakewallet/mweb/CwMwebPlugin.kt     | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/cw_mweb/android/src/main/kotlin/com/cakewallet/mweb/CwMwebPlugin.kt b/cw_mweb/android/src/main/kotlin/com/cakewallet/mweb/CwMwebPlugin.kt
index 1dfbe12e5..d3d4140ef 100644
--- a/cw_mweb/android/src/main/kotlin/com/cakewallet/mweb/CwMwebPlugin.kt
+++ b/cw_mweb/android/src/main/kotlin/com/cakewallet/mweb/CwMwebPlugin.kt
@@ -9,6 +9,7 @@ import io.flutter.plugin.common.MethodChannel.MethodCallHandler
 import io.flutter.plugin.common.MethodChannel.Result
 
 import mwebd.Mwebd
+import mwebd.Server
 
 /** CwMwebPlugin */
 class CwMwebPlugin: FlutterPlugin, MethodCallHandler {
@@ -17,6 +18,7 @@ class CwMwebPlugin: FlutterPlugin, MethodCallHandler {
   /// This local reference serves to register the plugin with the Flutter Engine and unregister it
   /// when the Flutter Engine is detached from the Activity
   private lateinit var channel : MethodChannel
+  private var server: Server? = null
   private var port: Long? = null
 
   override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
@@ -27,7 +29,8 @@ class CwMwebPlugin: FlutterPlugin, MethodCallHandler {
   override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
     if (call.method == "start") {
       val dataDir = call.argument("dataDir") ?: ""
-      port = port ?: Mwebd.newServer("", dataDir, "").start(0)
+      server = server ?: Mwebd.newServer("", dataDir, "")
+      port = port ?: server?.start(0)
       result.success(port)
     } else {
       result.notImplemented()
@@ -36,5 +39,6 @@ class CwMwebPlugin: FlutterPlugin, MethodCallHandler {
 
   override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
     channel.setMethodCallHandler(null)
+    server?.stop()
   }
 }

From b1caf79c1b4f4a3bb124d7eac24b80e38abf1914 Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Thu, 25 Apr 2024 23:13:41 +0100
Subject: [PATCH 18/31] Normal fee for non-mweb txns

---
 cw_bitcoin/lib/electrum_wallet.dart | 1 +
 cw_bitcoin/lib/litecoin_wallet.dart | 7 +++++++
 2 files changed, 8 insertions(+)

diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index 1f5c14319..586c73ed8 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -1250,6 +1250,7 @@ abstract class ElectrumWalletBase
       totalConfirmed += confirmed;
       totalUnconfirmed += unconfirmed;
 
+      addressRecord.balance = confirmed + unconfirmed;
       if (confirmed > 0 || unconfirmed > 0) {
         addressRecord.setAsUsed();
       }
diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index b84675669..c9bee05f1 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -291,6 +291,13 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
       String? memo,
       required int feeRate}) async {
 
+    final spendsMweb = utxos.any((utxo) => utxo.utxo.scriptType == SegwitAddresType.mweb);
+    final paysToMweb = outputs.any((output) =>
+        output.toOutput.scriptPubKey.getAddressType() == SegwitAddresType.mweb);
+    if (!spendsMweb && !paysToMweb) {
+      return await super.calcFee(utxos: utxos, outputs: outputs,
+          network: network, memo: memo, feeRate: feeRate);
+    }
     final txb = BitcoinTransactionBuilder(utxos: utxos,
         outputs: outputs, fee: BigInt.zero, network: network);
     final scanSecret = mwebHd.derive(0x80000000).privKey!;

From fea7e7a09767d2f296a4ec4e09bb0b9f26beba4e Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Fri, 26 Apr 2024 01:07:58 +0100
Subject: [PATCH 19/31] Fix fee estimation for send all

---
 cw_bitcoin/lib/electrum_wallet.dart | 25 +++++++------------------
 cw_bitcoin/lib/litecoin_wallet.dart |  5 +++++
 2 files changed, 12 insertions(+), 18 deletions(-)

diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index 586c73ed8..281255e53 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -245,24 +245,13 @@ abstract class ElectrumWalletBase
       throw BitcoinTransactionNoInputsException();
     }
 
-    int estimatedSize;
-    if (network is BitcoinCashNetwork) {
-      estimatedSize = ForkedTransactionBuilder.estimateTransactionSize(
-        utxos: utxos,
-        outputs: outputs,
-        network: network as BitcoinCashNetwork,
-        memo: memo,
-      );
-    } else {
-      estimatedSize = BitcoinTransactionBuilder.estimateTransactionSize(
-        utxos: utxos,
-        outputs: outputs,
-        network: network,
-        memo: memo,
-      );
-    }
-
-    int fee = feeAmountWithFeeRate(feeRate, 0, 0, size: estimatedSize);
+    int fee = await calcFee(
+      utxos: utxos,
+      outputs: outputs,
+      network: network,
+      memo: memo,
+      feeRate: feeRate,
+    );
 
     if (fee == 0) {
       throw BitcoinTransactionNoFeeException();
diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index c9bee05f1..bd69b9e05 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -298,6 +298,11 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
       return await super.calcFee(utxos: utxos, outputs: outputs,
           network: network, memo: memo, feeRate: feeRate);
     }
+    if (outputs.length == 1 && outputs[0].toOutput.amount == BigInt.zero) {
+      outputs = [BitcoinScriptOutput(
+          script: outputs[0].toOutput.scriptPubKey,
+          value: utxos.sumOfUtxosValue())];
+    }
     final txb = BitcoinTransactionBuilder(utxos: utxos,
         outputs: outputs, fee: BigInt.zero, network: network);
     final scanSecret = mwebHd.derive(0x80000000).privKey!;

From 66d98a282cbbaa4e8aad1596b74bbd0a17795593 Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Fri, 26 Apr 2024 09:16:40 +0100
Subject: [PATCH 20/31] Don't hash mweb addresses

---
 cw_bitcoin/lib/electrum_wallet.dart | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index 281255e53..23a15c280 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -113,11 +113,13 @@ abstract class ElectrumWalletBase
   SyncStatus syncStatus;
 
   List<String> get scriptHashes => walletAddresses.addressesByReceiveType
+      .where((addr) => addressTypeFromStr(addr.address, network) is! MwebAddress)
       .map((addr) => scriptHash(addr.address, network: network))
       .toList();
 
   List<String> get publicScriptHashes => walletAddresses.allAddresses
       .where((addr) => !addr.isHidden)
+      .where((addr) => addressTypeFromStr(addr.address, network) is! MwebAddress)
       .map((addr) => scriptHash(addr.address, network: network))
       .toList();
 
@@ -1205,7 +1207,8 @@ abstract class ElectrumWalletBase
   }
 
   Future<ElectrumBalance> fetchBalances() async {
-    final addresses = walletAddresses.allAddresses.toList();
+    final addresses = walletAddresses.allAddresses.where((address) =>
+        addressTypeFromStr(address.address, network) is! MwebAddress).toList();
     final balanceFutures = <Future<Map<String, dynamic>>>[];
     for (var i = 0; i < addresses.length; i++) {
       final addressRecord = addresses[i];

From 796c95315ff65d8e6251c92d88ea60ce9de80625 Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Fri, 26 Apr 2024 11:19:40 +0100
Subject: [PATCH 21/31] More fee fixes

---
 cw_bitcoin/lib/litecoin_wallet.dart | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)

diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index bd69b9e05..fce466b1e 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -303,8 +303,11 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
           script: outputs[0].toOutput.scriptPubKey,
           value: utxos.sumOfUtxosValue())];
     }
+    final preOutputSum = outputs.fold<BigInt>(BigInt.zero,
+        (acc, output) => acc + output.toOutput.amount);
+    final fee = utxos.sumOfUtxosValue() - preOutputSum;
     final txb = BitcoinTransactionBuilder(utxos: utxos,
-        outputs: outputs, fee: BigInt.zero, network: network);
+        outputs: outputs, fee: fee, network: network);
     final scanSecret = mwebHd.derive(0x80000000).privKey!;
     final spendSecret = mwebHd.derive(0x80000001).privKey!;
     final stub = await CwMweb.stub();
@@ -317,17 +320,16 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
     final tx = BtcTransaction.fromRaw(hex.encode(resp.rawTx));
     final posUtxos = utxos.where((utxo) => tx.inputs.any((input) =>
         input.txId == utxo.utxo.txHash && input.txIndex == utxo.utxo.vout)).toList();
-    final preOutputSum = outputs.fold<int>(0, (acc, output) => acc + output.toOutput.amount.toInt());
     final posOutputSum = tx.outputs.fold<int>(0, (acc, output) => acc + output.amount.toInt());
     final mwebInputSum = utxos.sumOfUtxosValue() - posUtxos.sumOfUtxosValue();
-    final expectedPegin = max(0, preOutputSum - mwebInputSum.toInt());
-    var fee = posOutputSum - expectedPegin;
-    if (expectedPegin > 0) {
-      fee += await super.calcFee(utxos: posUtxos, outputs: tx.outputs.map((output) =>
+    final expectedPegin = max(0, (preOutputSum - mwebInputSum).toInt());
+    var feeIncrease = posOutputSum - expectedPegin;
+    if (expectedPegin > 0 && fee == 0) {
+      feeIncrease += await super.calcFee(utxos: posUtxos, outputs: tx.outputs.map((output) =>
           BitcoinScriptOutput(script: output.scriptPubKey, value: output.amount)).toList(),
           network: network, memo: memo, feeRate: feeRate) + feeRate * 41;
     }
-    return fee;
+    return fee.toInt() + feeIncrease;
   }
 
   @override

From f0157101fbfaee74381c974ba7474385d2f22e00 Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Fri, 26 Apr 2024 12:32:38 +0100
Subject: [PATCH 22/31] Broadcast mweb

---
 cw_bitcoin/lib/litecoin_wallet.dart           | 16 ++++++++----
 .../lib/pending_bitcoin_transaction.dart      | 25 ++++++++++++++++---
 2 files changed, 32 insertions(+), 9 deletions(-)

diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index fce466b1e..a07875c1f 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -8,6 +8,7 @@ import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
 import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
 import 'package:cw_bitcoin/bitcoin_unspent.dart';
 import 'package:cw_bitcoin/electrum_transaction_info.dart';
+import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
 import 'package:cw_core/crypto_currency.dart';
 import 'package:cw_core/pending_transaction.dart';
 import 'package:cw_core/sync_status.dart';
@@ -308,13 +309,11 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
     final fee = utxos.sumOfUtxosValue() - preOutputSum;
     final txb = BitcoinTransactionBuilder(utxos: utxos,
         outputs: outputs, fee: fee, network: network);
-    final scanSecret = mwebHd.derive(0x80000000).privKey!;
-    final spendSecret = mwebHd.derive(0x80000001).privKey!;
     final stub = await CwMweb.stub();
     final resp = await stub.create(CreateRequest(
         rawTx: txb.buildTransaction((a, b, c, d) => '').toBytes(),
-        scanSecret: hex.decode(scanSecret),
-        spendSecret: hex.decode(spendSecret),
+        scanSecret: hex.decode(mwebHd.derive(0x80000000).privKey!),
+        spendSecret: hex.decode(mwebHd.derive(0x80000001).privKey!),
         feeRatePerKb: Int64(feeRate * 1000),
         dryRun: true));
     final tx = BtcTransaction.fromRaw(hex.encode(resp.rawTx));
@@ -334,7 +333,14 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
 
   @override
   Future<PendingTransaction> createTransaction(Object credentials) async {
-    final tx = await super.createTransaction(credentials);
+    final tx = await super.createTransaction(credentials) as PendingBitcoinTransaction;
+    final stub = await CwMweb.stub();
+    final resp = await stub.create(CreateRequest(
+        rawTx: hex.decode(tx.hex),
+        scanSecret: hex.decode(mwebHd.derive(0x80000000).privKey!),
+        spendSecret: hex.decode(mwebHd.derive(0x80000001).privKey!),
+        feeRatePerKb: Int64.parseInt(tx.feeRate) * 1000));
+    tx.hexOverride = hex.encode(resp.rawTx);
     return tx;
   }
 }
diff --git a/cw_bitcoin/lib/pending_bitcoin_transaction.dart b/cw_bitcoin/lib/pending_bitcoin_transaction.dart
index a59b4f429..46f152708 100644
--- a/cw_bitcoin/lib/pending_bitcoin_transaction.dart
+++ b/cw_bitcoin/lib/pending_bitcoin_transaction.dart
@@ -1,11 +1,14 @@
 import 'package:cw_bitcoin/exceptions.dart';
 import 'package:bitcoin_base/bitcoin_base.dart';
+import 'package:blockchain_utils/blockchain_utils.dart';
 import 'package:cw_core/pending_transaction.dart';
 import 'package:cw_bitcoin/electrum.dart';
 import 'package:cw_bitcoin/bitcoin_amount_format.dart';
 import 'package:cw_bitcoin/electrum_transaction_info.dart';
 import 'package:cw_core/transaction_direction.dart';
 import 'package:cw_core/wallet_type.dart';
+import 'package:cw_mweb/cw_mweb.dart';
+import 'package:cw_mweb/mwebd.pb.dart';
 
 class PendingBitcoinTransaction with PendingTransaction {
   PendingBitcoinTransaction(
@@ -31,12 +34,14 @@ class PendingBitcoinTransaction with PendingTransaction {
   final bool hasChange;
   final bool isSendAll;
   final bool hasTaprootInputs;
+  String? idOverride;
+  String? hexOverride;
 
   @override
-  String get id => _tx.txId();
+  String get id => idOverride ?? _tx.txId();
 
   @override
-  String get hex => _tx.serialize();
+  String get hex => hexOverride ?? _tx.serialize();
 
   @override
   String get amountFormatted => bitcoinAmountToString(amount: amount);
@@ -49,8 +54,7 @@ class PendingBitcoinTransaction with PendingTransaction {
 
   final List<void Function(ElectrumTransactionInfo transaction)> _listeners;
 
-  @override
-  Future<void> commit() async {
+  Future<void> _commit() async {
     int? callId;
 
     final result = await electrumClient.broadcastTransaction(
@@ -78,6 +82,19 @@ class PendingBitcoinTransaction with PendingTransaction {
 
       throw BitcoinTransactionCommitFailed();
     }
+  }
+
+  @override
+  Future<void> commit() async {
+    if (network is LitecoinNetwork) try {
+      final stub = await CwMweb.stub();
+      final resp = await stub.broadcast(BroadcastRequest(rawTx: BytesUtils.fromHexString(hex)));
+      idOverride = resp.txid;
+    } catch (e) {
+      throw BitcoinTransactionCommitFailed();
+    } else {
+      await _commit();
+    }
 
     _listeners.forEach((listener) => listener(transactionInfo()));
   }

From 68688ee0d374672ab4dfaadb865e7a6003f08086 Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Fri, 26 Apr 2024 13:03:42 +0100
Subject: [PATCH 23/31] Remove test files

---
 cw_mweb/test/cw_mweb_method_channel_test.dart | 27 -----------------
 cw_mweb/test/cw_mweb_test.dart                | 29 -------------------
 2 files changed, 56 deletions(-)
 delete mode 100644 cw_mweb/test/cw_mweb_method_channel_test.dart
 delete mode 100644 cw_mweb/test/cw_mweb_test.dart

diff --git a/cw_mweb/test/cw_mweb_method_channel_test.dart b/cw_mweb/test/cw_mweb_method_channel_test.dart
deleted file mode 100644
index bb0523758..000000000
--- a/cw_mweb/test/cw_mweb_method_channel_test.dart
+++ /dev/null
@@ -1,27 +0,0 @@
-import 'package:flutter/services.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:cw_mweb/cw_mweb_method_channel.dart';
-
-void main() {
-  TestWidgetsFlutterBinding.ensureInitialized();
-
-  MethodChannelCwMweb platform = MethodChannelCwMweb();
-  const MethodChannel channel = MethodChannel('cw_mweb');
-
-  setUp(() {
-    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
-      channel,
-      (MethodCall methodCall) async {
-        return '42';
-      },
-    );
-  });
-
-  tearDown(() {
-    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null);
-  });
-
-  test('getPlatformVersion', () async {
-    expect(await platform.getPlatformVersion(), '42');
-  });
-}
diff --git a/cw_mweb/test/cw_mweb_test.dart b/cw_mweb/test/cw_mweb_test.dart
deleted file mode 100644
index 3677659a9..000000000
--- a/cw_mweb/test/cw_mweb_test.dart
+++ /dev/null
@@ -1,29 +0,0 @@
-import 'package:flutter_test/flutter_test.dart';
-import 'package:cw_mweb/cw_mweb.dart';
-import 'package:cw_mweb/cw_mweb_platform_interface.dart';
-import 'package:cw_mweb/cw_mweb_method_channel.dart';
-import 'package:plugin_platform_interface/plugin_platform_interface.dart';
-
-class MockCwMwebPlatform
-    with MockPlatformInterfaceMixin
-    implements CwMwebPlatform {
-
-  @override
-  Future<String?> getPlatformVersion() => Future.value('42');
-}
-
-void main() {
-  final CwMwebPlatform initialPlatform = CwMwebPlatform.instance;
-
-  test('$MethodChannelCwMweb is the default instance', () {
-    expect(initialPlatform, isInstanceOf<MethodChannelCwMweb>());
-  });
-
-  test('getPlatformVersion', () async {
-    CwMweb cwMwebPlugin = CwMweb();
-    MockCwMwebPlatform fakePlatform = MockCwMwebPlatform();
-    CwMwebPlatform.instance = fakePlatform;
-
-    expect(await cwMwebPlugin.getPlatformVersion(), '42');
-  });
-}

From 70b07b2d4739c9f35bd6c21d4b1836e5896b2ae4 Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Fri, 26 Apr 2024 13:16:04 +0100
Subject: [PATCH 24/31] One more

---
 .../cakewallet/cw_mweb/CwMwebPluginTest.kt    | 27 -------------------
 1 file changed, 27 deletions(-)
 delete mode 100644 cw_mweb/android/src/test/kotlin/com/cakewallet/cw_mweb/CwMwebPluginTest.kt

diff --git a/cw_mweb/android/src/test/kotlin/com/cakewallet/cw_mweb/CwMwebPluginTest.kt b/cw_mweb/android/src/test/kotlin/com/cakewallet/cw_mweb/CwMwebPluginTest.kt
deleted file mode 100644
index baa81332f..000000000
--- a/cw_mweb/android/src/test/kotlin/com/cakewallet/cw_mweb/CwMwebPluginTest.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.cakewallet.mweb
-
-import io.flutter.plugin.common.MethodCall
-import io.flutter.plugin.common.MethodChannel
-import kotlin.test.Test
-import org.mockito.Mockito
-
-/*
- * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation.
- *
- * Once you have built the plugin's example app, you can run these tests from the command
- * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
- * you can run them directly from IDEs that support JUnit such as Android Studio.
- */
-
-internal class CwMwebPluginTest {
-  @Test
-  fun onMethodCall_getPlatformVersion_returnsExpectedValue() {
-    val plugin = CwMwebPlugin()
-
-    val call = MethodCall("getPlatformVersion", null)
-    val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java)
-    plugin.onMethodCall(call, mockResult)
-
-    Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE)
-  }
-}

From b1f03345502aeb8c9a9a3d4e354514f9d8b6d674 Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Sat, 27 Apr 2024 14:26:43 +0100
Subject: [PATCH 25/31] Confirm sent txns

---
 cw_bitcoin/lib/litecoin_wallet.dart           | 73 +++++++++++++++----
 .../lib/pending_bitcoin_transaction.dart      |  3 +
 2 files changed, 61 insertions(+), 15 deletions(-)

diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index a07875c1f..483d86645 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -1,5 +1,6 @@
 import 'dart:async';
 import 'dart:math';
+import 'package:collection/collection.dart';
 import 'package:convert/convert.dart';
 import 'package:crypto/crypto.dart';
 import 'package:fixnum/fixnum.dart';
@@ -122,6 +123,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
     );
   }
 
+  int mwebUtxosHeight = 0;
+
   @action
   @override
   Future<void> startSync() async {
@@ -142,6 +145,10 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
         } else {
           syncStatus = SyncedSyncStatus();
         }
+        if (resp.mwebUtxosHeight > mwebUtxosHeight) {
+          mwebUtxosHeight = resp.mwebUtxosHeight;
+          await checkMwebUtxosSpent();
+        }
       });
     processMwebUtxos();
   }
@@ -170,14 +177,19 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
         date = DateTime.fromMillisecondsSinceEpoch(utxo.blockTime * 1000);
         confirmations = status.blockHeaderHeight - utxo.height + 1;
       }
-      final tx = ElectrumTransactionInfo(WalletType.litecoin,
-        id: utxo.outputId, height: utxo.height,
-        amount: utxo.value.toInt(), fee: 0,
-        direction: TransactionDirection.incoming,
-        isPending: utxo.height == 0,
-        date: date, confirmations: confirmations,
-        inputAddresses: [],
-        outputAddresses: [utxo.address]);
+      var tx = transactionHistory.transactions.values.firstWhereOrNull(
+          (tx) => tx.outputAddresses?.contains(utxo.outputId) ?? false);
+      if (tx == null)
+        tx = ElectrumTransactionInfo(WalletType.litecoin,
+          id: utxo.outputId, height: utxo.height,
+          amount: utxo.value.toInt(), fee: 0,
+          direction: TransactionDirection.incoming,
+          isPending: utxo.height == 0,
+          date: date, confirmations: confirmations,
+          inputAddresses: [],
+          outputAddresses: [utxo.address]);
+      tx.isPending = utxo.height == 0;
+      tx.confirmations = confirmations;
       if (transactionHistory.transactions[utxo.outputId] == null) {
         final addressRecord = walletAddresses.allAddresses.firstWhere(
             (addressRecord) => addressRecord.address == utxo.address);
@@ -194,11 +206,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
   }
 
   Future<void> checkMwebUtxosSpent() async {
-    final List<String> outputIds = [];
-    mwebUtxos.forEach((outputId, utxo) {
-      if (utxo.height > 0)
-        outputIds.add(outputId);
-    });
+    while ((await Future.wait(transactionHistory.transactions.values
+        .where((tx) => tx.direction == TransactionDirection.outgoing && tx.isPending)
+        .map(checkPendingTransaction))).any((x) => x));
+    final outputIds = mwebUtxos.values
+        .where((utxo) => utxo.height > 0)
+        .map((utxo) => utxo.outputId).toList();
     final stub = await CwMweb.stub();
     final resp = await stub.spent(SpentRequest(outputId: outputIds));
     final spent = resp.outputId;
@@ -212,8 +225,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
     var output = AccumulatorSink<Digest>();
     var input = sha256.startChunkedConversion(output);
     for (final outputId in spent) {
-      input.add(hex.decode(outputId));
-      final utxo = mwebUtxos[outputId]!;
+      final utxo = mwebUtxos[outputId];
+      if (utxo == null) continue;
       final addressRecord = walletAddresses.allAddresses.firstWhere(
           (addressRecord) => addressRecord.address == utxo.address);
       if (!inputAddresses.contains(utxo.address))
@@ -222,7 +235,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
       amount += utxo.value.toInt();
       inputAddresses.add(utxo.address);
       mwebUtxos.remove(outputId);
+      input.add(hex.decode(outputId));
     }
+    if (inputAddresses.isEmpty) return;
     input.close();
     var digest = output.events.single;
     final tx = ElectrumTransactionInfo(WalletType.litecoin,
@@ -238,6 +253,33 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
     await transactionHistory.save();
   }
 
+  Future<bool> checkPendingTransaction(ElectrumTransactionInfo tx) async {
+    if (!tx.isPending) return false;
+    final outputId = <String>[], target = <String>{};
+    final isHash = RegExp(r'^[a-f0-9]{64}$').hasMatch;
+    final spendingOutputIds = tx.inputAddresses?.where(isHash) ?? [];
+    final payingToOutputIds = tx.outputAddresses?.where(isHash) ?? [];
+    outputId.addAll(spendingOutputIds);
+    outputId.addAll(payingToOutputIds);
+    target.addAll(spendingOutputIds);
+    for (final outputId in payingToOutputIds) {
+      final spendingTx = transactionHistory.transactions.values.firstWhereOrNull(
+          (tx) => tx.inputAddresses?.contains(outputId) ?? false);
+      if (spendingTx != null && !spendingTx.isPending)
+        target.add(outputId);
+    }
+    if (outputId.isEmpty) return false;
+    final stub = await CwMweb.stub();
+    final resp = await stub.spent(SpentRequest(outputId: outputId));
+    if (resp.outputId.toSet() != target) return false;
+    final status = await stub.status(StatusRequest());
+    if (!tx.isPending) return false;
+    tx.height = status.mwebUtxosHeight;
+    tx.confirmations = 1;
+    tx.isPending = false;
+    return true;
+  }
+
   @override
   Future<void> updateUnspentCoins() async {
     await super.updateUnspentCoins();
@@ -341,6 +383,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
         spendSecret: hex.decode(mwebHd.derive(0x80000001).privKey!),
         feeRatePerKb: Int64.parseInt(tx.feeRate) * 1000));
     tx.hexOverride = hex.encode(resp.rawTx);
+    tx.outputs = resp.outputId;
     return tx;
   }
 }
diff --git a/cw_bitcoin/lib/pending_bitcoin_transaction.dart b/cw_bitcoin/lib/pending_bitcoin_transaction.dart
index 46f152708..69bdaba70 100644
--- a/cw_bitcoin/lib/pending_bitcoin_transaction.dart
+++ b/cw_bitcoin/lib/pending_bitcoin_transaction.dart
@@ -36,6 +36,7 @@ class PendingBitcoinTransaction with PendingTransaction {
   final bool hasTaprootInputs;
   String? idOverride;
   String? hexOverride;
+  List<String>? outputs;
 
   @override
   String get id => idOverride ?? _tx.txId();
@@ -110,5 +111,7 @@ class PendingBitcoinTransaction with PendingTransaction {
       date: DateTime.now(),
       isPending: true,
       confirmations: 0,
+      inputAddresses: _tx.inputs.map((input) => input.txId).toList(),
+      outputAddresses: outputs,
       fee: fee);
 }

From 2c941efcc319e147daf490c67825e3e0daca62f2 Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Sat, 27 Apr 2024 17:40:47 +0100
Subject: [PATCH 26/31] Couple of fixes

---
 cw_bitcoin/lib/litecoin_wallet.dart | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index 483d86645..ea5176a00 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -188,6 +188,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
           date: date, confirmations: confirmations,
           inputAddresses: [],
           outputAddresses: [utxo.address]);
+      tx.height = utxo.height;
       tx.isPending = utxo.height == 0;
       tx.confirmations = confirmations;
       if (transactionHistory.transactions[utxo.outputId] == null) {
@@ -271,7 +272,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
     if (outputId.isEmpty) return false;
     final stub = await CwMweb.stub();
     final resp = await stub.spent(SpentRequest(outputId: outputId));
-    if (resp.outputId.toSet() != target) return false;
+    if (!setEquals(resp.outputId.toSet(), target)) return false;
     final status = await stub.status(StatusRequest());
     if (!tx.isPending) return false;
     tx.height = status.mwebUtxosHeight;

From 8964774c5188fd72b6271014f6a603eb53664a36 Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Sun, 28 Apr 2024 11:17:28 +0100
Subject: [PATCH 27/31] Resign inputs after mweb create

---
 cw_bitcoin/lib/litecoin_wallet.dart | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index ea5176a00..6444e217d 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -10,6 +10,7 @@ import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
 import 'package:cw_bitcoin/bitcoin_unspent.dart';
 import 'package:cw_bitcoin/electrum_transaction_info.dart';
 import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
+import 'package:cw_bitcoin/utils.dart';
 import 'package:cw_core/crypto_currency.dart';
 import 'package:cw_core/pending_transaction.dart';
 import 'package:cw_core/sync_status.dart';
@@ -278,6 +279,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
     tx.height = status.mwebUtxosHeight;
     tx.confirmations = 1;
     tx.isPending = false;
+    await transactionHistory.save();
     return true;
   }
 
@@ -366,7 +368,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
     final mwebInputSum = utxos.sumOfUtxosValue() - posUtxos.sumOfUtxosValue();
     final expectedPegin = max(0, (preOutputSum - mwebInputSum).toInt());
     var feeIncrease = posOutputSum - expectedPegin;
-    if (expectedPegin > 0 && fee == 0) {
+    if (expectedPegin > 0 && fee == BigInt.zero) {
       feeIncrease += await super.calcFee(utxos: posUtxos, outputs: tx.outputs.map((output) =>
           BitcoinScriptOutput(script: output.scriptPubKey, value: output.amount)).toList(),
           network: network, memo: memo, feeRate: feeRate) + feeRate * 41;
@@ -383,7 +385,18 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
         scanSecret: hex.decode(mwebHd.derive(0x80000000).privKey!),
         spendSecret: hex.decode(mwebHd.derive(0x80000001).privKey!),
         feeRatePerKb: Int64.parseInt(tx.feeRate) * 1000));
-    tx.hexOverride = hex.encode(resp.rawTx);
+    final tx2 = BtcTransaction.fromRaw(hex.encode(resp.rawTx));
+    tx.hexOverride = tx2.copyWith(witnesses: tx2.inputs.asMap().entries.map((e) {
+      final utxo = unspentCoins.firstWhere((utxo) =>
+          utxo.hash == e.value.txId && utxo.vout == e.value.txIndex);
+      final key = generateECPrivate(hd: utxo.bitcoinAddressRecord.isHidden ?
+          walletAddresses.sideHd : walletAddresses.mainHd,
+          index: utxo.bitcoinAddressRecord.index, network: network);
+      final digest = tx2.getTransactionSegwitDigit(txInIndex: e.key,
+          script: key.getPublic().toP2pkhAddress().toScriptPubKey(),
+          amount: BigInt.from(utxo.value));
+      return TxWitnessInput(stack: [key.signInput(digest), key.getPublic().toHex()]);
+    }).toList()).toHex();
     tx.outputs = resp.outputId;
     return tx;
   }

From b39b2bbb996087fb9b7fdb5a6339d18c03fd7c48 Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Sun, 28 Apr 2024 14:12:12 +0100
Subject: [PATCH 28/31] Some more fixes

---
 cw_bitcoin/lib/electrum_wallet.dart             |  1 -
 cw_bitcoin/lib/litecoin_wallet.dart             | 11 ++++++-----
 cw_bitcoin/lib/pending_bitcoin_transaction.dart |  5 +++--
 3 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index 23a15c280..ffb637004 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -792,7 +792,6 @@ abstract class ElectrumWalletBase
           coin.isFrozen = coinInfo.isFrozen;
           coin.isSending = coinInfo.isSending;
           coin.note = coinInfo.note;
-          coin.bitcoinAddressRecord.balance += coinInfo.value;
         } else {
           _addCoinInfo(coin);
         }
diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index 6444e217d..6cabcc34e 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -133,6 +133,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
     final stub = await CwMweb.stub();
     Timer.periodic(
       const Duration(milliseconds: 1500), (timer) async {
+        if (syncStatus is FailedSyncStatus) return;
         final height = await electrumClient.getCurrentBlockChainTip() ?? 0;
         final resp = await stub.status(StatusRequest());
         if (resp.blockHeaderHeight < height) {
@@ -145,10 +146,10 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
           syncStatus = SyncingSyncStatus(1, 0.999);
         } else {
           syncStatus = SyncedSyncStatus();
-        }
-        if (resp.mwebUtxosHeight > mwebUtxosHeight) {
-          mwebUtxosHeight = resp.mwebUtxosHeight;
-          await checkMwebUtxosSpent();
+          if (resp.mwebUtxosHeight > mwebUtxosHeight) {
+            mwebUtxosHeight = resp.mwebUtxosHeight;
+            await checkMwebUtxosSpent();
+          }
         }
       });
     processMwebUtxos();
@@ -192,7 +193,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
       tx.height = utxo.height;
       tx.isPending = utxo.height == 0;
       tx.confirmations = confirmations;
-      if (transactionHistory.transactions[utxo.outputId] == null) {
+      if (transactionHistory.transactions[tx.id] == null) {
         final addressRecord = walletAddresses.allAddresses.firstWhere(
             (addressRecord) => addressRecord.address == utxo.address);
         addressRecord.txCount++;
diff --git a/cw_bitcoin/lib/pending_bitcoin_transaction.dart b/cw_bitcoin/lib/pending_bitcoin_transaction.dart
index 69bdaba70..20cb1ebc6 100644
--- a/cw_bitcoin/lib/pending_bitcoin_transaction.dart
+++ b/cw_bitcoin/lib/pending_bitcoin_transaction.dart
@@ -1,3 +1,4 @@
+import 'package:grpc/grpc.dart';
 import 'package:cw_bitcoin/exceptions.dart';
 import 'package:bitcoin_base/bitcoin_base.dart';
 import 'package:blockchain_utils/blockchain_utils.dart';
@@ -91,8 +92,8 @@ class PendingBitcoinTransaction with PendingTransaction {
       final stub = await CwMweb.stub();
       final resp = await stub.broadcast(BroadcastRequest(rawTx: BytesUtils.fromHexString(hex)));
       idOverride = resp.txid;
-    } catch (e) {
-      throw BitcoinTransactionCommitFailed();
+    } on GrpcError catch (e) {
+      throw BitcoinTransactionCommitFailed(errorMessage: e.message);
     } else {
       await _commit();
     }

From a936a3b6610ca81b025342d290dc26ec95a6f458 Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Sun, 28 Apr 2024 19:46:07 +0100
Subject: [PATCH 29/31] Update balance after sending

---
 cw_bitcoin/lib/litecoin_wallet.dart | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index 6cabcc34e..4c589b134 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -399,6 +399,10 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
       return TxWitnessInput(stack: [key.signInput(digest), key.getPublic().toHex()]);
     }).toList()).toHex();
     tx.outputs = resp.outputId;
-    return tx;
+    return tx..addListener((transaction) async {
+      transaction.inputAddresses?.forEach(mwebUtxos.remove);
+      await updateUnspent();
+      await updateBalance();
+    });
   }
 }

From d1ccf6eb0b26632854fda3583fa215f3bcfdd534 Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Mon, 29 Apr 2024 12:11:59 +0100
Subject: [PATCH 30/31] Correctly update address records

---
 cw_bitcoin/lib/litecoin_wallet.dart | 28 +++++++++++++++++++++++-----
 1 file changed, 23 insertions(+), 5 deletions(-)

diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index 4c589b134..703571bd5 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -188,15 +188,20 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
           direction: TransactionDirection.incoming,
           isPending: utxo.height == 0,
           date: date, confirmations: confirmations,
-          inputAddresses: [],
-          outputAddresses: [utxo.address]);
+          inputAddresses: [], outputAddresses: [utxo.outputId]);
       tx.height = utxo.height;
       tx.isPending = utxo.height == 0;
       tx.confirmations = confirmations;
-      if (transactionHistory.transactions[tx.id] == null) {
+      var isNew = transactionHistory.transactions[tx.id] == null;
+      if (!(tx.outputAddresses?.contains(utxo.address) ?? false)) {
+        tx.outputAddresses?.add(utxo.address);
+        isNew = true;
+      }
+      if (isNew) {
         final addressRecord = walletAddresses.allAddresses.firstWhere(
             (addressRecord) => addressRecord.address == utxo.address);
-        addressRecord.txCount++;
+        if (!(tx.inputAddresses?.contains(utxo.address) ?? false))
+          addressRecord.txCount++;
         addressRecord.balance += utxo.value.toInt();
         addressRecord.setAsUsed();
       }
@@ -400,7 +405,20 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
     }).toList()).toHex();
     tx.outputs = resp.outputId;
     return tx..addListener((transaction) async {
-      transaction.inputAddresses?.forEach(mwebUtxos.remove);
+      final addresses = <String>{};
+      transaction.inputAddresses?.forEach((id) {
+        final utxo = mwebUtxos.remove(id);
+        if (utxo == null) return;
+        final addressRecord = walletAddresses.allAddresses.firstWhere(
+            (addressRecord) => addressRecord.address == utxo.address);
+        if (!addresses.contains(utxo.address)) {
+          addressRecord.txCount++;
+          addresses.add(utxo.address);
+        }
+        addressRecord.balance -= utxo.value.toInt();
+      });
+      transaction.inputAddresses?.addAll(addresses);
+      transactionHistory.addOne(transaction);
       await updateUnspent();
       await updateBalance();
     });

From 7978ad447690b386f2058c10f318f5045b1aa24b Mon Sep 17 00:00:00 2001
From: Hector Chu <hectorchu@gmail.com>
Date: Mon, 29 Apr 2024 13:08:05 +0100
Subject: [PATCH 31/31] Update confs

---
 cw_bitcoin/lib/litecoin_wallet.dart | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index 703571bd5..fba4bbc4a 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -149,6 +149,14 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
           if (resp.mwebUtxosHeight > mwebUtxosHeight) {
             mwebUtxosHeight = resp.mwebUtxosHeight;
             await checkMwebUtxosSpent();
+            for (final transaction in transactionHistory.transactions.values) {
+              if (transaction.isPending) continue;
+              final confirmations = mwebUtxosHeight - transaction.height + 1;
+              if (transaction.confirmations == confirmations) continue;
+              transaction.confirmations = confirmations;
+              transactionHistory.addOne(transaction);
+            }
+            await transactionHistory.save();
           }
         }
       });