diff --git a/packages/dragon_logs/CHANGELOG.md b/packages/dragon_logs/CHANGELOG.md
index dc7c7e6c..b969474d 100644
--- a/packages/dragon_logs/CHANGELOG.md
+++ b/packages/dragon_logs/CHANGELOG.md
@@ -1,3 +1,14 @@
+## 1.2.0
+
+- **BREAKING**: Add WASM web support with OPFS-only storage
+- **BREAKING**: Remove `file_system_access_api` and `js` dependencies
+- **BREAKING**: Require Dart SDK `>=3.3.0` for extension types support
+- Add `package:web` for modern web APIs compatibility
+- Migrate from `dart:html` and `dart:js` to `dart:js_interop` and `package:web`
+- Add WASM-specific platform detection using `dart.tool.dart2wasm`
+- Implement Origin Private File System (OPFS) using modern JS interop
+- Maintain full API compatibility while supporting both regular web and WASM compilation
+
## 1.1.0
- Bump packages to latest versions.
@@ -24,20 +35,15 @@ Refactor to share more code between web and native platforms (focused mainly on
- Stable release
- Tweak: Localisation initialisation no longer needs to be inialised before logs.
-
## 0.1.1-preview.1
- Memory improvement for log flushing.
- Bug fixes.
-
## 0.1.0-preview.1
- Bug fixes.
-
## 0.0.1-preview.1
- Initial preview version.
-
-
diff --git a/packages/dragon_logs/README.md b/packages/dragon_logs/README.md
index 3de2e124..7d51ee22 100644
--- a/packages/dragon_logs/README.md
+++ b/packages/dragon_logs/README.md
@@ -1,16 +1,4 @@
-# 🚚 Repository Moved
-
-> **⚠️ This repository has been migrated to the Komodo DeFi SDK Flutter monorepo.**
->
-> 📍 **New location:** [packages/dragon_logs_flutter](https://github.com/KomodoPlatform/komodo-defi-sdk-flutter/tree/main/packages/dragon_logs_flutter)
->
-> 🔄 **Active development** continues in the monorepo. Please update your forks, bookmarks, and links.
->
-> 💡 **For issues, PRs, and contributions**, please use the [main monorepo](https://github.com/KomodoPlatform/komodo-defi-sdk-flutter).
-
----
-
-# Dragon Logs (Archived)
+# Dragon Logs
@@ -28,7 +16,7 @@ Dragon Logs aims to simplify the logging and log storage process in your Flutter
- ✅ Cross-platform log storage
- ✅ Cross-platform logs download
-- ⬜ Flutter web wasm support
+- ✅ Flutter web wasm support
- ⬜ Web multi-threading support
- ⬜ Log levels (e.g. debug, info, warning, error)
- ⬜ Performance metrics (in progress)
diff --git a/packages/dragon_logs/example/pubspec.lock b/packages/dragon_logs/example/pubspec.lock
index d81b2a59..886ae595 100644
--- a/packages/dragon_logs/example/pubspec.lock
+++ b/packages/dragon_logs/example/pubspec.lock
@@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: async
- sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
+ sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev"
source: hosted
- version: "2.12.0"
+ version: "2.13.0"
boolean_selector:
dependency: transitive
description:
@@ -63,23 +63,23 @@ packages:
path: ".."
relative: true
source: path
- version: "1.0.4"
+ version: "1.2.0+1"
fake_async:
dependency: transitive
description:
name: fake_async
- sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
+ sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
- version: "1.3.2"
+ version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
- sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
+ sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
url: "https://pub.dev"
source: hosted
- version: "2.1.3"
+ version: "2.1.4"
file:
dependency: transitive
description:
@@ -88,14 +88,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.1"
- file_system_access_api:
- dependency: transitive
- description:
- name: file_system_access_api
- sha256: c961c5020ab4e5f05200dbdd9809c5256c3dc4a1fe5746ca7d8cf8e8cc11c47d
- url: "https://pub.dev"
- source: hosted
- version: "2.0.0"
fixnum:
dependency: transitive
description:
@@ -136,26 +128,18 @@ packages:
dependency: transitive
description:
name: intl
- sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
+ sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
url: "https://pub.dev"
source: hosted
- version: "0.19.0"
- js:
- dependency: transitive
- description:
- name: js
- sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
- url: "https://pub.dev"
- source: hosted
- version: "0.7.2"
+ version: "0.20.2"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
- sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
+ sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
url: "https://pub.dev"
source: hosted
- version: "10.0.8"
+ version: "10.0.9"
leak_tracker_flutter_testing:
dependency: transitive
description:
@@ -232,10 +216,10 @@ packages:
dependency: transitive
description:
name: path_provider_android
- sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2"
+ sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
url: "https://pub.dev"
source: hosted
- version: "2.2.15"
+ version: "2.2.17"
path_provider_foundation:
dependency: transitive
description:
@@ -389,10 +373,10 @@ packages:
dependency: transitive
description:
name: url_launcher_web
- sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9"
+ sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
url: "https://pub.dev"
source: hosted
- version: "2.4.0"
+ version: "2.4.1"
url_launcher_windows:
dependency: transitive
description:
@@ -421,26 +405,26 @@ packages:
dependency: transitive
description:
name: vm_service
- sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
+ sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
url: "https://pub.dev"
source: hosted
- version: "14.3.1"
+ version: "15.0.0"
web:
dependency: transitive
description:
name: web
- sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
+ sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
- version: "1.1.0"
+ version: "1.1.1"
win32:
dependency: transitive
description:
name: win32
- sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e
+ sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03"
url: "https://pub.dev"
source: hosted
- version: "5.10.1"
+ version: "5.14.0"
xdg_directories:
dependency: transitive
description:
@@ -450,5 +434,5 @@ packages:
source: hosted
version: "1.1.0"
sdks:
- dart: ">=3.7.0-0 <4.0.0"
+ dart: ">=3.8.0 <4.0.0"
flutter: ">=3.27.0"
diff --git a/packages/dragon_logs/lib/src/storage/file_log_storage.dart b/packages/dragon_logs/lib/src/storage/file_log_storage.dart
index 55693f20..452dc864 100644
--- a/packages/dragon_logs/lib/src/storage/file_log_storage.dart
+++ b/packages/dragon_logs/lib/src/storage/file_log_storage.dart
@@ -78,8 +78,8 @@ class FileLogStorage
Future deleteOldLogs(int size) async {
while (await getLogFolderSize() > size) {
final files = await getLogFiles();
- final sortedFiles = files.entries.toList()
- ..sort((a, b) => a.key.compareTo(b.key));
+ final sortedFiles =
+ files.entries.toList()..sort((a, b) => a.key.compareTo(b.key));
await sortedFiles.first.value.delete();
}
}
@@ -101,8 +101,8 @@ class FileLogStorage
Stream exportLogsStream() async* {
final files = await getLogFiles();
- final sortedFiles = files.values.toList()
- ..sort((a, b) => a.path.compareTo(b.path));
+ final sortedFiles =
+ files.values.toList()..sort((a, b) => a.path.compareTo(b.path));
for (final file in sortedFiles) {
final stats = file.statSync();
final sizeKb = stats.size / 1024;
@@ -127,9 +127,10 @@ class FileLogStorage
@override
Future deleteExportedFiles() async {
- final archives = _exportFilesDirectory
- .listSync(followLinks: false, recursive: true)
- .whereType();
+ final archives =
+ _exportFilesDirectory
+ .listSync(followLinks: false, recursive: true)
+ .whereType();
final deleteArchivesFutures = archives.map((archive) => archive.delete());
@@ -216,10 +217,9 @@ class FileLogStorage
await raf.close();
// Use share_plus to share the log file
- await Share.shareXFiles(
- [XFile(file.path, mimeType: 'text/plain')],
- text: 'App log file export',
- );
+ await Share.shareXFiles([
+ XFile(file.path, mimeType: 'text/plain'),
+ ], text: 'App log file export');
}
static Future getLogFolderPath() async {
diff --git a/packages/dragon_logs/lib/src/storage/log_storage.dart b/packages/dragon_logs/lib/src/storage/log_storage.dart
index 64552313..8db04163 100644
--- a/packages/dragon_logs/lib/src/storage/log_storage.dart
+++ b/packages/dragon_logs/lib/src/storage/log_storage.dart
@@ -1,5 +1,6 @@
import 'package:dragon_logs/src/storage/platform_instance/log_storage_web_platform.dart'
- if (dart.library.io) 'package:dragon_logs/src/storage/platform_instance/log_storage_native_platform.dart';
+ if (dart.library.io) 'package:dragon_logs/src/storage/platform_instance/log_storage_native_platform.dart'
+ if (dart.tool.dart2wasm) 'package:dragon_logs/src/storage/platform_instance/log_storage_wasm_platform.dart';
abstract class LogStorage {
Future init();
diff --git a/packages/dragon_logs/lib/src/storage/opfs_interop.dart b/packages/dragon_logs/lib/src/storage/opfs_interop.dart
new file mode 100644
index 00000000..78715d52
--- /dev/null
+++ b/packages/dragon_logs/lib/src/storage/opfs_interop.dart
@@ -0,0 +1,74 @@
+import 'dart:js_interop';
+import 'dart:js_interop_unsafe';
+import 'package:web/web.dart';
+
+/// JavaScript async iterator result type
+@JS()
+@anonymous
+extension type JSIteratorResult._(JSObject _) implements JSObject {
+ external bool get done;
+ external JSAny? get value;
+}
+
+/// JavaScript async iterator type
+@JS()
+@anonymous
+extension type JSAsyncIterator._(JSObject _) implements JSObject {
+ external JSPromise next();
+}
+
+/// Extensions for FileSystemDirectoryHandle to provide missing async iterator methods
+/// that are available in the JavaScript File System API but not exposed in Flutter's web package.
+@JS()
+extension FileSystemDirectoryHandleExtension on FileSystemDirectoryHandle {
+ /// Returns an async iterator for the values (handles) in this directory.
+ /// Equivalent to calling `directoryHandle.values()` in JavaScript.
+ external JSAsyncIterator values();
+
+ /// Returns an async iterator for the keys (names) in this directory.
+ /// Equivalent to calling `directoryHandle.keys()` in JavaScript.
+ external JSAsyncIterator keys();
+
+ /// Returns an async iterator for the entries (name-handle pairs) in this directory.
+ /// Equivalent to calling `directoryHandle.entries()` in JavaScript.
+ external JSAsyncIterator entries();
+}
+
+/// Helper extensions to convert JavaScript async iterators to Dart async iterables
+extension JSAsyncIteratorExtension on JSAsyncIterator {
+ /// Converts a JavaScript async iterator to a Dart Stream
+ Stream asStream() async* {
+ while (true) {
+ final result = await next().toDart;
+ if (result.done) break;
+ yield result.value;
+ }
+ }
+}
+
+/// Extension to provide async iteration capabilities for FileSystemDirectoryHandle values
+extension FileSystemDirectoryHandleValuesIterable on FileSystemDirectoryHandle {
+ /// Returns a Stream of FileSystemHandle objects for async iteration over directory contents
+ Stream valuesStream() {
+ return values().asStream().map((jsValue) => jsValue as FileSystemHandle);
+ }
+
+ /// Returns a Stream of file/directory names for async iteration over directory contents
+ Stream keysStream() {
+ return keys().asStream().map((jsValue) => (jsValue as JSString).toDart);
+ }
+
+ /// Returns a Stream of [name, handle] pairs for async iteration over directory contents
+ static const int nameIndex = 0;
+ static const int handleIndex = 1;
+ Stream<(String, FileSystemHandle)> entriesStream() {
+ return entries().asStream().map((jsValue) {
+ // The entries() iterator returns [name, handle] arrays
+ // Use js_interop_unsafe to access array elements by numeric index
+ final jsObject = jsValue as JSObject;
+ final name = jsObject.getProperty(nameIndex.toJS).toDart;
+ final handle = jsObject.getProperty(handleIndex.toJS);
+ return (name, handle);
+ });
+ }
+}
diff --git a/packages/dragon_logs/lib/src/storage/platform_instance/log_storage_wasm_platform.dart b/packages/dragon_logs/lib/src/storage/platform_instance/log_storage_wasm_platform.dart
new file mode 100644
index 00000000..f4af52fa
--- /dev/null
+++ b/packages/dragon_logs/lib/src/storage/platform_instance/log_storage_wasm_platform.dart
@@ -0,0 +1,4 @@
+import 'package:dragon_logs/src/storage/log_storage.dart';
+import 'package:dragon_logs/src/storage/web_log_storage_wasm.dart';
+
+LogStorage getLogStorageInstance() => WebLogStorageWasm();
\ No newline at end of file
diff --git a/packages/dragon_logs/lib/src/storage/platform_instance/log_storage_web_platform.dart b/packages/dragon_logs/lib/src/storage/platform_instance/log_storage_web_platform.dart
index f42f53b6..4a6a79bd 100644
--- a/packages/dragon_logs/lib/src/storage/platform_instance/log_storage_web_platform.dart
+++ b/packages/dragon_logs/lib/src/storage/platform_instance/log_storage_web_platform.dart
@@ -1,4 +1,4 @@
import 'package:dragon_logs/src/storage/log_storage.dart';
-import 'package:dragon_logs/src/storage/web_log_storage.dart';
+import 'package:dragon_logs/src/storage/web_log_storage_wasm.dart';
-LogStorage getLogStorageInstance() => WebLogStorage();
+LogStorage getLogStorageInstance() => WebLogStorageWasm();
diff --git a/packages/dragon_logs/lib/src/storage/web_log_storage.dart b/packages/dragon_logs/lib/src/storage/web_log_storage.dart
deleted file mode 100644
index 7446cc40..00000000
--- a/packages/dragon_logs/lib/src/storage/web_log_storage.dart
+++ /dev/null
@@ -1,249 +0,0 @@
-// TODO: Remove after completing wasm implementation
-// ignore_for_file: deprecated_member_use
-
-import 'dart:async';
-import 'dart:html' as html;
-import 'dart:html';
-import 'dart:typed_data';
-
-import 'package:dragon_logs/src/storage/input_output_mixin.dart';
-import 'package:dragon_logs/src/storage/log_storage.dart';
-import 'package:dragon_logs/src/storage/queue_mixin.dart';
-import 'package:file_system_access_api/file_system_access_api.dart';
-import 'package:intl/intl.dart';
-import 'package:js/js.dart';
-import 'package:js/js_util.dart' as js;
-
-/// Declare navigator like in a Web Worker context.
-@JS()
-external dynamic get navigator;
-
-class WebLogStorage
- with QueueMixin, CommonLogStorageOperations
- implements LogStorage {
- // TODO: Multi-day support
- // final List _logHandles = [];
- FileSystemDirectoryHandle? _logDirectory;
-
- FileSystemFileHandle? _currentLogFile;
- FileSystemWritableFileStream? _currentLogStream;
- String _currentLogFileName = "";
-
- late Timer _flushTimer;
-
- late final StorageManager? storage =
- js.getProperty(navigator, "storage") as StorageManager?;
-
- @override
- Future init() async {
- if (!FileSystemAccess.supported) {
- throw Exception(
- "FileSystemAccess not supported for log storage on this browser",
- );
- }
-
- final now = DateTime.now();
- _currentLogFileName = logFileNameOfDate(now);
-
- FileSystemDirectoryHandle? root = await storage?.getDirectory();
-
- if (root != null) {
- _logDirectory = await root.getDirectoryHandle(
- "dragon_logs",
- create: true,
- );
-
- // await initWriteDate(now);
- } else {
- throw Exception("Could not get root directory");
- }
-
- initQueueFlusher();
- }
-
- @override
- Future writeToTextFile(String logs) async {
- if (_currentLogStream == null) {
- await initWriteDate(DateTime.now());
- }
-
- try {
- await _currentLogStream!.writeAsText('$logs\n');
-
- await closeLogFile();
- await initWriteDate(DateTime.now());
- } catch (e) {
- rethrow;
- }
- }
-
- @override
- // TODO: implement so that we don't have to delete the whole file
- Future deleteOldLogs(int size) async {
- await startFlush();
-
- try {
- while (await getLogFolderSize() > size) {
- final files = await _getLogFiles();
-
- final sortedFiles = files
- .where(
- (handle) => CommonLogStorageOperations.isLogFileNameValid(
- handle.name,
- ),
- )
- .toList()
- ..sort((a, b) {
- final aDate = CommonLogStorageOperations.tryParseLogFileDate(
- a.name,
- );
- final bDate = CommonLogStorageOperations.tryParseLogFileDate(
- b.name,
- );
-
- if (aDate == null || bDate == null) {
- return 0;
- }
-
- return aDate.compareTo(bDate);
- });
- await sortedFiles.first.remove();
- }
- } catch (e) {
- rethrow;
- } finally {
- endFlush();
- }
- }
-
- Future initWriteDate(DateTime date) async {
- await closeLogFile();
-
- _currentLogFileName = logFileNameOfDate(date);
-
- _currentLogFile ??= await _logDirectory?.getFileHandle(
- _currentLogFileName,
- create: true,
- );
-
- final sizeBytes = (await _currentLogFile?.getFile())?.size ?? 0;
-
- _currentLogStream = await _currentLogFile?.createWritable(
- keepExistingData: true,
- );
-
- await _currentLogStream?.seek(sizeBytes);
- }
-
- @override
- Future getLogFolderSize() async {
- final files = await _getLogFiles();
-
- final htmlFileObjects = await Future.wait(
- files.map((e) => e.getFile()),
- );
-
- final int totalSize = htmlFileObjects.fold(
- 0,
- (int? previousValue, File file) => (previousValue ?? 0) + file.size,
- );
-
- return totalSize;
- }
-
- //TODO! Move to web worker for web so we can access sync flush method instead
- // of this workaround
- @override
- Future closeLogFile() async {
- if (_currentLogStream != null) {
- await _currentLogStream!.close();
-
- _currentLogStream = null;
- }
- }
-
- @override
- Stream exportLogsStream() async* {
- for (final file in await _getLogFiles()) {
- String content = await _readFileContent(await file.getFile());
- yield content;
- }
- }
-
- /// Returns a list of OPFS file handles for all log files EXCLUDING any
- /// temporary write file (if it exists) identified by the `.crswap` extension.
- Future> _getLogFiles() async {
- final files = await _logDirectory?.values
- .where((handle) => handle.kind == FileSystemKind.file)
- .cast()
- .where((handle) => !handle.name.endsWith('.crswap'))
- .toList() ??
- [];
-
- print('_getLogFiles: ${files.map((e) => e.name).join(',\n')}');
-
- return files..sort((a, b) => a.name.compareTo(b.name));
- }
-
- Future _readFileContent(html.File file) async {
- final completer = Completer();
- final reader = html.FileReader();
-
- StreamSubscription? loadEndSubscription;
- StreamSubscription? errorSubscription;
-
- loadEndSubscription = reader.onLoadEnd.listen((event) {
- completer.complete(reader.result as String);
- });
-
- errorSubscription = reader.onError.listen((error) {
- completer.completeError("Error reading file: $error");
- });
-
- reader.readAsText(file);
-
- return completer.future.whenComplete(() {
- loadEndSubscription?.cancel();
- errorSubscription?.cancel();
- });
- }
-
- @override
- Future deleteExportedFiles() async {
- // Since it's a web implementation, we just need to ensure necessary permissions.
- // Note: Real-world applications should handle permissions gracefully, prompting users as needed.
- }
-
- @override
- // TODO: Multi-threading support in web worker
- Future exportLogsToDownload() async {
- final bytesStream = exportLogsStream().asyncExpand((event) {
- return Stream.fromIterable(event.codeUnits);
- });
-
- final formatter = DateFormat('yyyyMMdd_HHmmss');
- final filename = 'log_${formatter.format(DateTime.now())}.txt';
-
- List bytes = await bytesStream.toList();
- final blob = html.Blob([Uint8List.fromList(bytes)]);
- final url = html.Url.createObjectUrlFromBlob(blob);
- // ignore: unused_local_variable
- final anchor = html.AnchorElement(href: url)
- ..target = 'blank'
- ..download = filename
- ..click();
- html.Url.revokeObjectUrl(url);
- }
-
- void dispose() async {
- _flushTimer.cancel();
- await closeLogFile(); // Close the log file once during the dispose method
- }
-}
-
-//TODO!
-Future flushInWebWorker() async {
- // final logStorage = WebLogStorage();
- // await logStorage.init();
- // await logStorage.flushLogQueue();
-}
diff --git a/packages/dragon_logs/lib/src/storage/web_log_storage_wasm.dart b/packages/dragon_logs/lib/src/storage/web_log_storage_wasm.dart
new file mode 100644
index 00000000..833ee934
--- /dev/null
+++ b/packages/dragon_logs/lib/src/storage/web_log_storage_wasm.dart
@@ -0,0 +1,232 @@
+import 'dart:async';
+import 'dart:js_interop';
+import 'dart:typed_data';
+
+import 'package:dragon_logs/src/storage/input_output_mixin.dart';
+import 'package:dragon_logs/src/storage/log_storage.dart';
+import 'package:dragon_logs/src/storage/opfs_interop.dart';
+import 'package:dragon_logs/src/storage/queue_mixin.dart';
+import 'package:intl/intl.dart';
+import 'package:web/web.dart';
+
+/// WASM-compatible web log storage implementation using OPFS
+class WebLogStorageWasm
+ with QueueMixin, CommonLogStorageOperations
+ implements LogStorage {
+ FileSystemDirectoryHandle? _logDirectory;
+ FileSystemFileHandle? _currentLogFile;
+ FileSystemWritableFileStream? _currentLogStream;
+ String _currentLogFileName = "";
+
+ @override
+ Future init() async {
+ final now = DateTime.now();
+ _currentLogFileName = logFileNameOfDate(now);
+
+ // Get the OPFS root directory
+ final storageManager = window.navigator.storage;
+ final root = await storageManager.getDirectory().toDart;
+
+ // Create or get the dragon_logs directory
+ _logDirectory =
+ await root
+ .getDirectoryHandle(
+ "dragon_logs",
+ FileSystemGetDirectoryOptions(create: true),
+ )
+ .toDart;
+
+ initQueueFlusher();
+ }
+
+ @override
+ Future writeToTextFile(String logs) async {
+ if (_currentLogStream == null) {
+ await initWriteDate(DateTime.now());
+ }
+
+ try {
+ await _currentLogStream!.write('$logs\n'.toJS).toDart;
+ await closeLogFile();
+ await initWriteDate(DateTime.now());
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ Future initWriteDate(DateTime date) async {
+ await closeLogFile();
+
+ _currentLogFileName = logFileNameOfDate(date);
+
+ _currentLogFile =
+ await _logDirectory!
+ .getFileHandle(
+ _currentLogFileName,
+ FileSystemGetFileOptions(create: true),
+ )
+ .toDart;
+
+ final file = await _currentLogFile!.getFile().toDart;
+ final sizeBytes = file.size.toInt();
+
+ _currentLogStream =
+ await _currentLogFile!
+ .createWritable(
+ FileSystemCreateWritableOptions(keepExistingData: true),
+ )
+ .toDart;
+
+ await _currentLogStream!.seek(sizeBytes).toDart;
+ }
+
+ @override
+ Future deleteOldLogs(int size) async {
+ await startFlush();
+
+ try {
+ while (await getLogFolderSize() > size) {
+ final files = await _getLogFiles();
+
+ final sortedFiles =
+ files
+ .where(
+ (handle) => CommonLogStorageOperations.isLogFileNameValid(
+ handle.name,
+ ),
+ )
+ .toList()
+ ..sort((a, b) {
+ final aDate = CommonLogStorageOperations.tryParseLogFileDate(
+ a.name,
+ );
+ final bDate = CommonLogStorageOperations.tryParseLogFileDate(
+ b.name,
+ );
+
+ if (aDate == null || bDate == null) {
+ return 0;
+ }
+
+ return aDate.compareTo(bDate);
+ });
+
+ if (sortedFiles.isEmpty) {
+ break;
+ }
+
+ await _logDirectory!
+ .removeEntry(
+ sortedFiles.first.name,
+ FileSystemRemoveOptions(recursive: false),
+ )
+ .toDart;
+ }
+ } catch (e) {
+ rethrow;
+ } finally {
+ endFlush();
+ }
+ }
+
+ @override
+ Future getLogFolderSize() async {
+ final files = await _getLogFiles();
+
+ int totalSize = 0;
+ for (final handle in files) {
+ final file = await handle.getFile().toDart;
+ totalSize += file.size.toInt();
+ }
+
+ return totalSize;
+ }
+
+ @override
+ Future closeLogFile() async {
+ if (_currentLogStream != null) {
+ await _currentLogStream!.close().toDart;
+ _currentLogStream = null;
+ }
+ }
+
+ @override
+ Stream exportLogsStream() async* {
+ final files = await _getLogFiles();
+
+ for (final fileHandle in files) {
+ final file = await fileHandle.getFile().toDart;
+ final content = await _readFileContent(file);
+ yield content;
+ }
+ }
+
+ /// Returns a list of OPFS file handles for all log files EXCLUDING any
+ /// temporary write file (if it exists) identified by the `.crswap` extension.
+ Future> _getLogFiles() async {
+ final files = [];
+
+ // Use the async iterator provided by FileSystemDirectoryHandle.values()
+ // via our custom interop extension
+ await for (final handle in _logDirectory!.valuesStream()) {
+ if (handle.kind == 'file' && !handle.name.endsWith('.crswap')) {
+ files.add(handle as FileSystemFileHandle);
+ }
+ }
+
+ files.sort((a, b) => a.name.compareTo(b.name));
+ return files;
+ }
+
+ Future _readFileContent(File file) async {
+ final completer = Completer();
+ final reader = FileReader();
+
+ reader.onLoadEnd.listen((event) {
+ final result = reader.result;
+ if (result != null) {
+ completer.complete(result.toString());
+ } else {
+ completer.complete('');
+ }
+ });
+
+ reader.readAsText(file);
+ return completer.future;
+ }
+
+ @override
+ Future deleteExportedFiles() async {
+ // Since it's a web implementation, we just need to ensure necessary permissions.
+ // Note: Real-world applications should handle permissions gracefully, prompting users as needed.
+ }
+
+ @override
+ Future exportLogsToDownload() async {
+ final bytesStream = exportLogsStream().asyncExpand((event) {
+ return Stream.fromIterable(event.codeUnits);
+ });
+
+ final formatter = DateFormat('yyyyMMdd_HHmmss');
+ final filename = 'log_${formatter.format(DateTime.now())}.txt';
+
+ final bytes = await bytesStream.toList();
+ final blob = Blob([Uint8List.fromList(bytes).toJS].toJS);
+ final url = URL.createObjectURL(blob);
+
+ final anchor =
+ HTMLAnchorElement()
+ ..href = url
+ ..download = filename
+ ..style.display = 'none';
+
+ document.body!.appendChild(anchor);
+ anchor.click();
+ document.body!.removeChild(anchor);
+ URL.revokeObjectURL(url);
+ }
+
+ void dispose() async {
+ await closeLogFile();
+ }
+}
diff --git a/packages/dragon_logs/pubspec.yaml b/packages/dragon_logs/pubspec.yaml
index 00ee726d..1a32c712 100644
--- a/packages/dragon_logs/pubspec.yaml
+++ b/packages/dragon_logs/pubspec.yaml
@@ -1,12 +1,12 @@
name: dragon_logs
description: An efficient cross-platform Flutter log storage framework with minimal dependencies.
-version: 1.1.0
+version: 1.2.0+1
-repository: https://github.com/KomodoPlatform/dragon_logs_flutter
+repository: https://github.com/KomodoPlatform/komodo-defi-sdk-flutter/tree/main/packages/dragon_logs
homepage: https://komodoplatform.com
environment:
- sdk: ">=3.0.0 <4.0.0"
+ sdk: ^3.7.0
dev_dependencies:
lints: ^5.1.1
@@ -33,13 +33,11 @@ dependencies:
# Last approved via KW PR #1106
share_plus: ^10.1.4
- file_system_access_api: ^2.0.0
-
# ====== Flutter.dev/Dart.dev approved ======
# Secure review for Flutter.dev/Dart.dev packages not strictly required since
# they are Google/Dart products, but still recommended.
- intl: ">=0.19.0 <0.21.0" # The latest `stable` Flutter version is pinned to 0.19
+ intl: ^0.20.2
path_provider: ^2.1.5
path: ^1.8.3
- js: ^0.7.2
+ web: ^1.1.0
diff --git a/packages/komodo_coin_updates/lib/src/data/coin_config_provider.dart b/packages/komodo_coin_updates/lib/src/data/coin_config_provider.dart
index de6c7851..791952e2 100644
--- a/packages/komodo_coin_updates/lib/src/data/coin_config_provider.dart
+++ b/packages/komodo_coin_updates/lib/src/data/coin_config_provider.dart
@@ -16,11 +16,18 @@ class CoinConfigProvider {
'https://api.github.com/repos/KomodoPlatform/coins',
this.coinsPath = 'coins',
this.coinsConfigPath = 'utils/coins_config_unfiltered.json',
+ this.githubToken,
});
- factory CoinConfigProvider.fromConfig(RuntimeUpdateConfig config) {
+ factory CoinConfigProvider.fromConfig(
+ RuntimeUpdateConfig config, {
+ String? githubToken,
+ }) {
// TODO(Francois): derive all the values from the config
- return CoinConfigProvider(branch: config.coinsRepoBranch);
+ return CoinConfigProvider(
+ branch: config.coinsRepoBranch,
+ githubToken: githubToken,
+ );
}
final String branch;
@@ -28,6 +35,7 @@ class CoinConfigProvider {
final String coinsGithubApiUrl;
final String coinsPath;
final String coinsConfigPath;
+ final String? githubToken;
/// Fetches the coins from the repository.
/// [commit] is the commit hash to fetch the coins from.
@@ -81,8 +89,30 @@ class CoinConfigProvider {
final client = http.Client();
final url = Uri.parse('$coinsGithubApiUrl/branches/$branch');
final header = {'Accept': 'application/vnd.github+json'};
+
+ // Add authentication header if token is available
+ if (githubToken != null) {
+ header['Authorization'] = 'Bearer $githubToken';
+ print('CoinConfigProvider: Using authentication for GitHub API request');
+ } else {
+ print(
+ 'CoinConfigProvider: No GitHub token available - making unauthenticated request',
+ );
+ }
+
final response = await client.get(url, headers: header);
+ if (response.statusCode != 200) {
+ print(
+ 'CoinConfigProvider: GitHub API request failed: ${response.statusCode} ${response.reasonPhrase}',
+ );
+ print('CoinConfigProvider: Response body: ${response.body}');
+ throw Exception(
+ 'Failed to retrieve latest commit hash: $branch'
+ '[${response.statusCode}]: ${response.reasonPhrase}',
+ );
+ }
+
final json = jsonDecode(response.body) as Map;
final commit = json['commit'] as Map;
final latestCommitHash = commit['sha'] as String;
diff --git a/packages/komodo_coin_updates/lib/src/data/coin_config_repository.dart b/packages/komodo_coin_updates/lib/src/data/coin_config_repository.dart
index ab776e44..e4fd54ca 100644
--- a/packages/komodo_coin_updates/lib/src/data/coin_config_repository.dart
+++ b/packages/komodo_coin_updates/lib/src/data/coin_config_repository.dart
@@ -21,12 +21,17 @@ class CoinConfigRepository implements CoinConfigStorage {
/// Creates a coin config storage provider with default databases.
/// The default databases are HiveLazyBoxProvider.
/// The default databases are named 'coins' and 'coins_settings'.
- CoinConfigRepository.withDefaults(RuntimeUpdateConfig config)
- : coinConfigProvider = CoinConfigProvider.fromConfig(config),
- coinsDatabase = HiveLazyBoxProvider(name: 'coins'),
- coinSettingsDatabase = HiveBoxProvider(
- name: 'coins_settings',
- );
+ CoinConfigRepository.withDefaults(
+ RuntimeUpdateConfig config, {
+ String? githubToken,
+ }) : coinConfigProvider = CoinConfigProvider.fromConfig(
+ config,
+ githubToken: githubToken,
+ ),
+ coinsDatabase = HiveLazyBoxProvider(name: 'coins'),
+ coinSettingsDatabase = HiveBoxProvider(
+ name: 'coins_settings',
+ );
/// The provider that fetches the coins and coin configs.
final CoinConfigProvider coinConfigProvider;
diff --git a/packages/komodo_coin_updates/pubspec.yaml b/packages/komodo_coin_updates/pubspec.yaml
index f5a837f7..838db99a 100644
--- a/packages/komodo_coin_updates/pubspec.yaml
+++ b/packages/komodo_coin_updates/pubspec.yaml
@@ -5,13 +5,13 @@ publish_to: none # publishable packages can't have git dependencies
environment:
sdk: ^3.7.0
- flutter: ">=3.29.0 <3.30.0"
+ flutter: ">=3.29.0 <3.36.0"
# Add regular dependencies here.
dependencies:
equatable: ^2.0.7
flutter_bloc: ^9.1.1
- hive: 2.2.3
+ hive: 2.2.3
hive_flutter: 1.1.0
http: ^1.4.0
komodo_defi_types:
diff --git a/packages/komodo_coins/lib/src/komodo_coins_base.dart b/packages/komodo_coins/lib/src/komodo_coins_base.dart
index af44046a..af5bd011 100644
--- a/packages/komodo_coins/lib/src/komodo_coins_base.dart
+++ b/packages/komodo_coins/lib/src/komodo_coins_base.dart
@@ -94,6 +94,12 @@ class KomodoCoins {
assets[assetId] = asset;
// }
}
+ }
+ // Log exceptions related to missing config fields
+ on MissingProtocolFieldException catch (e) {
+ debugPrint(
+ 'Skipping asset ${entry.key} due to missing protocol field: $e',
+ );
} catch (e) {
debugPrint(
'Error parsing asset ${entry.key}: $e , '
diff --git a/packages/komodo_defi_framework/app_build/build_config.json b/packages/komodo_defi_framework/app_build/build_config.json
index 6bf6b554..e171eeab 100644
--- a/packages/komodo_defi_framework/app_build/build_config.json
+++ b/packages/komodo_defi_framework/app_build/build_config.json
@@ -1,7 +1,7 @@
{
"api": {
- "api_commit_hash": "a9fbf6096257b802e0aac4d936dcc5d2f0c28461",
- "branch": "main",
+ "api_commit_hash": "6172ba8d1df0541dd319d4193cad1cb26df50eee",
+ "branch": "dev",
"fetch_at_build_enabled": true,
"concurrent_downloads_enabled": true,
"source_urls": [
@@ -12,49 +12,49 @@
"web": {
"matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-wasm|mm2_[a-f0-9]{7,40}-wasm|mm2-[a-f0-9]{7,40}-wasm)\\.zip$",
"valid_zip_sha256_checksums": [
- "d4476637d566a87974ed54bd708e83f9d05a45a7dfae7ade57c0d00b0f0df631"
+ "abde4d74279850004445df3f1a6ecd095dbd5b12f4821c4bb2a14c1aa94ab770"
],
"path": "web/kdf/bin"
},
"ios": {
"matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-ios-aarch64|mm2_[a-f0-9]{7,40}-ios-aarch64|mm2-[a-f0-9]{7,40}-ios-aarch64-CI)\\.zip$",
"valid_zip_sha256_checksums": [
- "efd8e8e738541a4838a2b044edc60030db9a4ba14392e30fb1a152472d4f4313"
+ "8661a477437563e8978f47baf1486f9b6ed900e3f0fb030dedee60843ecfc882"
],
"path": "ios"
},
"macos": {
"matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-mac-arm64|mm2-[a-f0-9]{7,40}-Darwin-Release)\\.zip$",
"valid_zip_sha256_checksums": [
- "bc411c8d95dbe565b0e56871babaea7412ccbd1ad7f525f3cf56a384a4a77ee7"
+ "142782fd8689c3106614f73065c6848192873f83f853eb357156f5d7c053fc92"
],
"path": "macos/bin"
},
"windows": {
"matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-win-x86-64|mm2_[a-f0-9]{7,40}-win-x86-64|mm2-[a-f0-9]{7,40}-Win64)\\.zip$",
"valid_zip_sha256_checksums": [
- "d9849d01962b4e05899cde7ec17f6b9e8ba9411f484369724c7a73a4b6a3fb80"
+ "8697178c85cd047a7f0a9331fd8f91dc256a6732dc311ad81b1fe7eba55baab9"
],
"path": "windows/bin"
},
"android-armv7": {
"matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-android-armv7|mm2_[a-f0-9]{7,40}-android-armv7|mm2-[a-f0-9]{7,40}-android-armv7-CI)\\.zip$",
"valid_zip_sha256_checksums": [
- "10ae609f3c7e4ed47e5a1134dd74da84375f9a1c6538c985afb1c148d58f8756"
+ "b3226bced064770a09eb556d411538d5d41e4c4513deb3feb05b5b3c04896c27"
],
"path": "android/app/src/main/cpp/libs/armeabi-v7a"
},
"android-aarch64": {
"matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-android-aarch64|mm2_[a-f0-9]{7,40}-android-aarch64|mm2-[a-f0-9]{7,40}-android-aarch64-CI)\\.zip$",
"valid_zip_sha256_checksums": [
- "ab4b5311e0d1b6b2f57ed1783d5b7a51c4b7558cbf0bad593d9235f6a32db906"
+ "e4111bbce3fa991430d632f8c0bf5d3ff55e505c34bde87f454db474f27eaa29"
],
"path": "android/app/src/main/cpp/libs/arm64-v8a"
},
"linux": {
"matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-linux-x86-64|mm2_[a-f0-9]{7,40}-linux-x86-64|mm2-[a-f0-9]{7,40}-Linux-Release)\\.zip$",
"valid_zip_sha256_checksums": [
- "913a165e434ed9696c0e8c9a1875bfd6448e291f85d2e7e8dae78618ef3534e3"
+ "cfe775cd2b4215bb2e5cb3516adb040ec8df64f8de9dc50e154f0ee863051e72"
],
"path": "linux/bin"
}
@@ -63,7 +63,7 @@
"coins": {
"fetch_at_build_enabled": true,
"update_commit_on_build": true,
- "bundled_coins_repo_commit": "1fe493212b34937d82c668e6118a1356d7eb2f06",
+ "bundled_coins_repo_commit": "322575ff3230d91e739be33861062173e1925cd3",
"coins_repo_api_url": "https://api.github.com/repos/KomodoPlatform/coins",
"coins_repo_content_url": "https://komodoplatform.github.io/coins",
"coins_repo_branch": "master",
diff --git a/packages/komodo_defi_framework/lib/src/operations/kdf_operations_wasm.dart b/packages/komodo_defi_framework/lib/src/operations/kdf_operations_wasm.dart
index 2e3d9b18..78dd7a6f 100644
--- a/packages/komodo_defi_framework/lib/src/operations/kdf_operations_wasm.dart
+++ b/packages/komodo_defi_framework/lib/src/operations/kdf_operations_wasm.dart
@@ -135,10 +135,11 @@ class KdfOperationsWasm implements IKdfOperations {
// Check for code property
if (jsObj.hasProperty('code'.toJS).toDart) {
- final code = jsObj.getProperty('code'.toJS);
- // Print all properties of the JSObject
- if (isInstance(code, 'JSNumber')) {
- final errorCode = (code! as js_interop.JSNumber).toDartInt;
+ final code = jsObj.getProperty('code'.toJS);
+ // Check if the property is a JSNumber
+ if (code != null &&
+ isInstance(code, 'JSNumber')) {
+ final errorCode = code.toDartInt;
_log(
'KdfOperationsWasm: Resolved as JSObject->JSNumber error code: $errorCode',
);
@@ -147,9 +148,10 @@ class KdfOperationsWasm implements IKdfOperations {
}
// Try toNumber method
- final asNumber = jsObj.callMethod('toNumber'.toJS);
+ final asNumber =
+ jsObj.callMethod('toNumber'.toJS);
if (asNumber?.isDefinedAndNotNull ?? false) {
- final errorCode = (asNumber! as js_interop.JSNumber).toDartInt;
+ final errorCode = asNumber!.toDartInt;
_log(
'KdfOperationsWasm: Resolved as JSNumber error code: $errorCode',
);
@@ -188,8 +190,10 @@ class KdfOperationsWasm implements IKdfOperations {
@override
Future kdfMainStatus() async {
await _ensureLoaded();
- final status = _kdfModule!.callMethod('mm2_main_status'.toJS);
- return MainStatus.fromDefaultInt(status! as int);
+ final status = _kdfModule!
+ .callMethod('mm2_main_status'.toJS)
+ ?.toDartInt;
+ return MainStatus.fromDefaultInt(status!);
}
@override
@@ -431,9 +435,11 @@ class KdfOperationsWasm implements IKdfOperations {
'init_wasm',
'__wbg_init',
],
- value: (key) =>
- 'Has property: ${_kdfModule!.has(key as String)} with type: '
- '${_kdfModule!.getProperty(key.toJS).runtimeType}',
+ value: (key) {
+ final jsKey = (key as String).toJS;
+ return 'Has property: ${_kdfModule!.hasProperty(jsKey).toDart} with type: '
+ '${_kdfModule!.getProperty(jsKey).runtimeType}';
+ },
);
_log('KDF Has properties: $debugProperties');
diff --git a/packages/komodo_defi_sdk/example/lib/main.dart b/packages/komodo_defi_sdk/example/lib/main.dart
index 78ccd65a..bf3e53f0 100644
--- a/packages/komodo_defi_sdk/example/lib/main.dart
+++ b/packages/komodo_defi_sdk/example/lib/main.dart
@@ -1,6 +1,7 @@
// lib/main.dart
import 'dart:async';
+import 'package:dragon_logs/dragon_logs.dart' as dragon;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:kdf_sdk_example/blocs/auth/auth_bloc.dart';
@@ -17,6 +18,7 @@ final GlobalKey _navigatorKey = GlobalKey();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
+ await dragon.DragonLogs.init();
// Create instance manager
final instanceManager = KdfInstanceManager();
@@ -24,9 +26,11 @@ void main() async {
// Create default SDK instance with config
final defaultSdk = KomodoDefiSdk(config: _config);
await defaultSdk.initialize();
+ dragon.log('Default SDK instance initialized');
// Register default instance
await instanceManager.registerInstance('Local Instance', _config, defaultSdk);
+ dragon.log('Registered default instance');
runApp(
MultiRepositoryProvider(
@@ -115,6 +119,7 @@ class _KomodoAppState extends State {
// Load known users
await _fetchKnownUsers(instance);
+ dragon.log('Initialized instance ${instance.name}');
}
void _updateInstanceUser(String instanceName, KdfUser? user) {
@@ -125,6 +130,15 @@ class _KomodoAppState extends State {
? 'Current wallet: ${user.walletId.name}'
: 'Not signed in';
});
+ dragon.DragonLogs.setSessionMetadata({
+ 'instance': instanceName,
+ if (user != null) 'user': user.walletId.compoundId,
+ });
+ dragon.log(
+ user != null
+ ? 'User ${user.walletId.compoundId} authenticated in $instanceName'
+ : 'User signed out of $instanceName',
+ );
}
Future _fetchKnownUsers(KdfInstanceState instance) async {
@@ -135,7 +149,7 @@ class _KomodoAppState extends State {
state.knownUsers = users;
setState(() {});
} catch (e, s) {
- print('Error fetching known users: $e');
+ dragon.log('Error fetching known users: $e', 'ERROR');
debugPrintStack(stackTrace: s);
}
}
@@ -182,6 +196,16 @@ class _KomodoAppState extends State {
: Colors.red,
child: const Icon(Icons.cloud),
),
+ IconButton(
+ icon: const Icon(Icons.download),
+ tooltip: 'Export Logs',
+ onPressed: () async {
+ await dragon.DragonLogs.exportLogsToDownload();
+ _scaffoldKey.currentState?.showSnackBar(
+ const SnackBar(content: Text('Logs exported')),
+ );
+ },
+ ),
const SizedBox(width: 16),
],
],
diff --git a/packages/komodo_defi_sdk/example/linux/flutter/generated_plugin_registrant.cc b/packages/komodo_defi_sdk/example/linux/flutter/generated_plugin_registrant.cc
index d0e7f797..38dd0bc6 100644
--- a/packages/komodo_defi_sdk/example/linux/flutter/generated_plugin_registrant.cc
+++ b/packages/komodo_defi_sdk/example/linux/flutter/generated_plugin_registrant.cc
@@ -7,9 +7,13 @@
#include "generated_plugin_registrant.h"
#include
+#include
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
+ g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
+ fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
+ url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
}
diff --git a/packages/komodo_defi_sdk/example/linux/flutter/generated_plugins.cmake b/packages/komodo_defi_sdk/example/linux/flutter/generated_plugins.cmake
index a9f2fe5a..a1cc4f39 100644
--- a/packages/komodo_defi_sdk/example/linux/flutter/generated_plugins.cmake
+++ b/packages/komodo_defi_sdk/example/linux/flutter/generated_plugins.cmake
@@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_linux
+ url_launcher_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
diff --git a/packages/komodo_defi_sdk/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/komodo_defi_sdk/example/macos/Flutter/GeneratedPluginRegistrant.swift
index 46b0a8b7..b83e6002 100644
--- a/packages/komodo_defi_sdk/example/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/packages/komodo_defi_sdk/example/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -9,6 +9,7 @@ import flutter_secure_storage_darwin
import local_auth_darwin
import mobile_scanner
import path_provider_foundation
+import share_plus
import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
@@ -16,5 +17,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FLALocalAuthPlugin.register(with: registry.registrar(forPlugin: "FLALocalAuthPlugin"))
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
+ SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
}
diff --git a/packages/komodo_defi_sdk/example/pubspec.lock b/packages/komodo_defi_sdk/example/pubspec.lock
index 20c959f6..1360f7b0 100644
--- a/packages/komodo_defi_sdk/example/pubspec.lock
+++ b/packages/komodo_defi_sdk/example/pubspec.lock
@@ -57,6 +57,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.19.1"
+ cross_file:
+ dependency: transitive
+ description:
+ name: cross_file
+ sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.3.4+2"
crypto:
dependency: transitive
description:
@@ -81,6 +89,13 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.2.1"
+ dragon_logs:
+ dependency: "direct main"
+ description:
+ path: "../../dragon_logs"
+ relative: true
+ source: path
+ version: "1.2.0+1"
equatable:
dependency: "direct main"
description:
@@ -147,6 +162,11 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.0.0"
+ flutter_localizations:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
@@ -315,56 +335,56 @@ packages:
path: "../../komodo_coins"
relative: true
source: path
- version: "0.2.0+0"
+ version: "0.3.0+0"
komodo_defi_framework:
dependency: "direct overridden"
description:
path: "../../komodo_defi_framework"
relative: true
source: path
- version: "0.2.0"
+ version: "0.3.0+0"
komodo_defi_local_auth:
dependency: "direct overridden"
description:
path: "../../komodo_defi_local_auth"
relative: true
source: path
- version: "0.2.0+0"
+ version: "0.3.0+0"
komodo_defi_rpc_methods:
dependency: "direct overridden"
description:
path: "../../komodo_defi_rpc_methods"
relative: true
source: path
- version: "0.2.0+0"
+ version: "0.3.0+0"
komodo_defi_sdk:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
- version: "0.2.0+0"
+ version: "0.3.0+0"
komodo_defi_types:
dependency: "direct main"
description:
path: "../../komodo_defi_types"
relative: true
source: path
- version: "0.2.0+0"
+ version: "0.3.0+0"
komodo_ui:
dependency: "direct main"
description:
path: "../../komodo_ui"
relative: true
source: path
- version: "0.2.0+0"
+ version: "0.3.0+0"
komodo_wallet_build_transformer:
dependency: "direct overridden"
description:
path: "../../komodo_wallet_build_transformer"
relative: true
source: path
- version: "0.2.0+0"
+ version: "0.3.0+0"
leak_tracker:
dependency: transitive
description:
@@ -469,6 +489,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.16.0"
+ mime:
+ dependency: transitive
+ description:
+ name: mime
+ sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.0"
mobile_scanner:
dependency: transitive
description:
@@ -589,6 +617,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.3"
+ share_plus:
+ dependency: transitive
+ description:
+ name: share_plus
+ sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da
+ url: "https://pub.dev"
+ source: hosted
+ version: "10.1.4"
+ share_plus_platform_interface:
+ dependency: transitive
+ description:
+ name: share_plus_platform_interface
+ sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.0.2"
shared_preferences:
dependency: transitive
description:
@@ -722,6 +766,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
+ url_launcher_linux:
+ dependency: transitive
+ description:
+ name: url_launcher_linux
+ sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.2.1"
+ url_launcher_platform_interface:
+ dependency: transitive
+ description:
+ name: url_launcher_platform_interface
+ sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.2"
+ url_launcher_web:
+ dependency: transitive
+ description:
+ name: url_launcher_web
+ sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.1"
+ url_launcher_windows:
+ dependency: transitive
+ description:
+ name: url_launcher_windows
+ sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.4"
uuid:
dependency: transitive
description:
diff --git a/packages/komodo_defi_sdk/example/pubspec.yaml b/packages/komodo_defi_sdk/example/pubspec.yaml
index 77b2871a..9ae11b5a 100644
--- a/packages/komodo_defi_sdk/example/pubspec.yaml
+++ b/packages/komodo_defi_sdk/example/pubspec.yaml
@@ -14,6 +14,8 @@ dependencies:
flutter_bloc: ^9.1.1
flutter_secure_storage: ^10.0.0-beta.4
+ dragon_logs:
+ path: ../../dragon_logs
komodo_defi_sdk:
path: ../
diff --git a/packages/komodo_defi_sdk/example/pubspec_overrides.yaml b/packages/komodo_defi_sdk/example/pubspec_overrides.yaml
index b5025daf..996f2849 100644
--- a/packages/komodo_defi_sdk/example/pubspec_overrides.yaml
+++ b/packages/komodo_defi_sdk/example/pubspec_overrides.yaml
@@ -1,5 +1,7 @@
-# melos_managed_dependency_overrides: komodo_cex_market_data,komodo_coin_updates,komodo_coins,komodo_defi_framework,komodo_defi_local_auth,komodo_defi_rpc_methods,komodo_defi_sdk,komodo_defi_types,komodo_ui,komodo_wallet_build_transformer
+# melos_managed_dependency_overrides: dragon_logs,komodo_cex_market_data,komodo_coin_updates,komodo_coins,komodo_defi_framework,komodo_defi_local_auth,komodo_defi_rpc_methods,komodo_defi_sdk,komodo_defi_types,komodo_ui,komodo_wallet_build_transformer
dependency_overrides:
+ dragon_logs:
+ path: ../../dragon_logs
komodo_cex_market_data:
path: ../../komodo_cex_market_data
komodo_coin_updates:
diff --git a/packages/komodo_defi_sdk/example/windows/flutter/generated_plugin_registrant.cc b/packages/komodo_defi_sdk/example/windows/flutter/generated_plugin_registrant.cc
index 011734da..f4b698cb 100644
--- a/packages/komodo_defi_sdk/example/windows/flutter/generated_plugin_registrant.cc
+++ b/packages/komodo_defi_sdk/example/windows/flutter/generated_plugin_registrant.cc
@@ -8,10 +8,16 @@
#include
#include
+#include
+#include
void RegisterPlugins(flutter::PluginRegistry* registry) {
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
LocalAuthPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("LocalAuthPlugin"));
+ SharePlusWindowsPluginCApiRegisterWithRegistrar(
+ registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
+ UrlLauncherWindowsRegisterWithRegistrar(
+ registry->GetRegistrarForPlugin("UrlLauncherWindows"));
}
diff --git a/packages/komodo_defi_sdk/example/windows/flutter/generated_plugins.cmake b/packages/komodo_defi_sdk/example/windows/flutter/generated_plugins.cmake
index aa117f18..7b3a5a56 100644
--- a/packages/komodo_defi_sdk/example/windows/flutter/generated_plugins.cmake
+++ b/packages/komodo_defi_sdk/example/windows/flutter/generated_plugins.cmake
@@ -5,6 +5,8 @@
list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_windows
local_auth_windows
+ share_plus
+ url_launcher_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
diff --git a/packages/komodo_defi_sdk/lib/src/market_data/market_data_manager.dart b/packages/komodo_defi_sdk/lib/src/market_data/market_data_manager.dart
index 2bb6fdb6..9c43d5bf 100644
--- a/packages/komodo_defi_sdk/lib/src/market_data/market_data_manager.dart
+++ b/packages/komodo_defi_sdk/lib/src/market_data/market_data_manager.dart
@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:collection';
+import 'dart:developer';
import 'package:decimal/decimal.dart';
import 'package:komodo_cex_market_data/komodo_cex_market_data.dart';
@@ -127,6 +128,73 @@ class CexMarketDataManager implements MarketDataManager {
return assetId.symbol.configSymbol;
}
+ /// Determines if the request can be handled by Komodo price repository
+ /// NOTE: currently only supports USDT and USD fiat currencies
+ /// and does not support specific price dates (always uses current price)
+ bool _canUseKomodoRepository({
+ DateTime? priceDate,
+ String fiatCurrency = 'usdt',
+ }) {
+ return priceDate == null &&
+ (fiatCurrency.toLowerCase() == 'usdt' ||
+ fiatCurrency.toLowerCase() == 'usd');
+ }
+
+ /// Attempts to get price from Komodo repository
+ Future _tryKomodoPrice(String symbol) async {
+ try {
+ final komodoPrices = await _komodoPriceRepository.getKomodoPrices();
+ final priceData = komodoPrices[symbol];
+
+ if (priceData != null) {
+ return Decimal.parse(priceData.price.toString());
+ }
+ } catch (e) {
+ log(
+ 'Failed to get price from Komodo repository for symbol: $symbol',
+ error: e,
+ );
+ // Ignore errors and fall back
+ }
+ return null;
+ }
+
+ /// Gets price with automatic fallback logic
+ Future _getPriceWithFallback(
+ AssetId assetId, {
+ DateTime? priceDate,
+ String fiatCurrency = 'usdt',
+ }) async {
+ final symbol = _getTradingSymbol(assetId);
+
+ // Try Komodo repository first if applicable
+ if (_canUseKomodoRepository(
+ priceDate: priceDate,
+ fiatCurrency: fiatCurrency,
+ )) {
+ final komodoPrice = await _tryKomodoPrice(symbol);
+ if (komodoPrice != null) {
+ return komodoPrice;
+ }
+ }
+
+ // Fallback to CEX repository
+ try {
+ final priceDouble = await _priceRepository.getCoinFiatPrice(
+ symbol,
+ priceDate: priceDate,
+ fiatCoinId: fiatCurrency,
+ );
+ return Decimal.parse(priceDouble.toString());
+ } catch (e) {
+ log(
+ 'Failed to get price from Cex Repository for symbol $symbol',
+ error: e,
+ );
+ return null;
+ }
+ }
+
@override
Decimal? priceIfKnown(
AssetId assetId, {
@@ -143,6 +211,7 @@ class CexMarketDataManager implements MarketDataManager {
fiatCurrency: fiatCurrency,
);
+ // Check cache first
return _priceCache[cacheKey];
}
@@ -169,23 +238,20 @@ class CexMarketDataManager implements MarketDataManager {
return cachedPrice;
}
- try {
- final priceDouble = await _priceRepository.getCoinFiatPrice(
- _getTradingSymbol(assetId),
- priceDate: priceDate,
- fiatCoinId: fiatCurrency,
- );
+ final price = await _getPriceWithFallback(
+ assetId,
+ priceDate: priceDate,
+ fiatCurrency: fiatCurrency,
+ );
- // Convert double to Decimal via string
- final price = Decimal.parse(priceDouble.toString());
+ if (price == null) {
+ throw StateError('Failed to get price for ${assetId.name}');
+ }
- // Cache the result
- _priceCache[cacheKey] = price;
+ // Cache the result
+ _priceCache[cacheKey] = price;
- return price;
- } catch (e) {
- throw StateError('Failed to get price for ${assetId.name}: $e');
- }
+ return price;
}
@override
@@ -208,23 +274,31 @@ class CexMarketDataManager implements MarketDataManager {
return cachedPrice;
}
+ // Check if ticker is known in CEX repository for fallback scenarios
final tradingSymbol = _getTradingSymbol(assetId);
final isKnownTicker = _knownTickers?.contains(tradingSymbol) ?? false;
- if (!isKnownTicker) {
+ // If not using Komodo repository and ticker is not known in CEX, return null
+ if (!_canUseKomodoRepository(
+ priceDate: priceDate,
+ fiatCurrency: fiatCurrency,
+ ) &&
+ !isKnownTicker) {
return null;
}
- try {
- final price = await fiatPrice(
- assetId,
- priceDate: priceDate,
- fiatCurrency: fiatCurrency,
- );
- return price;
- } catch (_) {
- return null;
+ final price = await _getPriceWithFallback(
+ assetId,
+ priceDate: priceDate,
+ fiatCurrency: fiatCurrency,
+ );
+
+ if (price != null) {
+ // Cache the result
+ _priceCache[cacheKey] = price;
}
+
+ return price;
}
@override
diff --git a/packages/komodo_defi_sdk/pubspec.yaml b/packages/komodo_defi_sdk/pubspec.yaml
index 3e09ae61..0313a03d 100644
--- a/packages/komodo_defi_sdk/pubspec.yaml
+++ b/packages/komodo_defi_sdk/pubspec.yaml
@@ -13,7 +13,7 @@ publish_to: "none"
environment:
sdk: ^3.7.0
- flutter: ">=3.29.0 <3.30.0"
+ flutter: ">=3.29.0 <3.36.0"
dependencies:
collection: ^1.18.0
diff --git a/packages/komodo_defi_types/analysis_options.yaml b/packages/komodo_defi_types/analysis_options.yaml
index a60f3c10..a36ca9e8 100644
--- a/packages/komodo_defi_types/analysis_options.yaml
+++ b/packages/komodo_defi_types/analysis_options.yaml
@@ -3,6 +3,7 @@ analyzer:
public_member_api_docs: ignore
invalid_annotation_target: ignore
use_if_null_to_convert_nulls_to_bools: ignore
+ omit_local_variable_types: ignore
include: package:very_good_analysis/analysis_options.6.0.0.yaml
\ No newline at end of file
diff --git a/packages/komodo_defi_types/lib/src/utils/mnemonic_validator.dart b/packages/komodo_defi_types/lib/src/utils/mnemonic_validator.dart
index 9d2cb0ce..db4e8084 100644
--- a/packages/komodo_defi_types/lib/src/utils/mnemonic_validator.dart
+++ b/packages/komodo_defi_types/lib/src/utils/mnemonic_validator.dart
@@ -1,8 +1,12 @@
// TODO: This may be better suited to be moved to the UI package.
+import 'dart:typed_data';
+
+import 'package:crypto/crypto.dart';
import 'package:flutter/services.dart' show rootBundle;
final Set _validMnemonicWords = {};
+final Map _wordToIndex = {};
const _validLengths = [12, 15, 18, 21, 24];
@@ -11,6 +15,8 @@ enum MnemonicFailedReason {
customNotSupportedForHd,
customNotAllowed,
invalidLength,
+ invalidWord,
+ invalidChecksum,
}
class MnemonicValidator {
@@ -19,7 +25,13 @@ class MnemonicValidator {
final wordlist = await rootBundle.loadString(
'packages/komodo_defi_types/assets/bip-0039/english-wordlist.txt',
);
- _validMnemonicWords.addAll(wordlist.split('\n').map((w) => w.trim()));
+ final words = wordlist.split('\n').map((w) => w.trim()).toList();
+ _validMnemonicWords.addAll(words);
+
+ // Build word-to-index mapping for BIP39 validation
+ for (int i = 0; i < words.length; i++) {
+ _wordToIndex[words[i]] = i;
+ }
}
}
@@ -50,19 +62,31 @@ class MnemonicValidator {
return MnemonicFailedReason.invalidLength;
}
- final isValidBip39 = validateBip39(input);
+ // Get detailed validation error if any
+ final detailedError = _getDetailedValidationError(input);
- if (isValidBip39) {
+ // If no error, it's a valid BIP39 mnemonic
+ if (detailedError == null) {
return null;
}
+ // For specific errors, return them directly
+ if (detailedError == MnemonicFailedReason.empty ||
+ detailedError == MnemonicFailedReason.invalidLength) {
+ return detailedError;
+ }
+
+ // For HD wallets, any BIP39 error means it's not supported
if (isHd) {
return MnemonicFailedReason.customNotSupportedForHd;
}
+ // For non-HD wallets, check if custom seeds are allowed
if (!allowCustomSeed) {
return MnemonicFailedReason.customNotAllowed;
}
+
+ // Custom seed is allowed, so return null (valid)
return null;
}
@@ -73,7 +97,7 @@ class MnemonicValidator {
'Call MnemonicValidator.init() first.',
);
- final inputWordsList = input.split(' ');
+ final inputWordsList = input.trim().split(' ');
if (!_validLengths.contains(inputWordsList.length)) {
return false;
@@ -84,6 +108,94 @@ class MnemonicValidator {
)) {
return false;
}
- return true;
+
+ // Validate checksum
+ return _validateChecksum(inputWordsList);
+ }
+
+ /// Validates the BIP39 checksum for a given mnemonic
+ bool _validateChecksum(List words) {
+ try {
+ // Convert words to indices
+ final indices = [];
+ for (final word in words) {
+ final index = _wordToIndex[word];
+ if (index == null) return false;
+ indices.add(index);
+ }
+
+ // Convert indices to binary string (11 bits per word)
+ final binaryString =
+ indices.map((i) => i.toRadixString(2).padLeft(11, '0')).join();
+
+ // Calculate entropy and checksum lengths
+ final totalBits = binaryString.length;
+ final checksumBits = totalBits ~/ 33; // Checksum is 1 bit per 3 words
+ final entropyBits = totalBits - checksumBits;
+
+ // Extract entropy and checksum
+ final entropyBinary = binaryString.substring(0, entropyBits);
+ final checksumBinary = binaryString.substring(entropyBits);
+
+ // Convert entropy to bytes
+ final entropyBytes = _binaryToBytes(entropyBinary);
+
+ // Calculate SHA256 hash of entropy
+ final hash = sha256.convert(entropyBytes);
+ final hashBits = _bytesToBinary(hash.bytes);
+
+ // Extract first checksumBits from hash
+ final calculatedChecksum = hashBits.substring(0, checksumBits);
+
+ // Compare checksums
+ return checksumBinary == calculatedChecksum;
+ } catch (e) {
+ return false;
+ }
+ }
+
+ /// Converts a binary string to bytes
+ Uint8List _binaryToBytes(String binary) {
+ final bytes = [];
+ for (int i = 0; i < binary.length; i += 8) {
+ final byte = binary.substring(i, i + 8);
+ bytes.add(int.parse(byte, radix: 2));
+ }
+ return Uint8List.fromList(bytes);
+ }
+
+ /// Converts bytes to binary string
+ String _bytesToBinary(List bytes) {
+ return bytes.map((b) => b.toRadixString(2).padLeft(8, '0')).join();
}
+
+ /// Gets detailed validation error for a mnemonic
+ MnemonicFailedReason? _getDetailedValidationError(String input) {
+ final words = input.trim().split(' ');
+
+ if (words.isEmpty || words.every((w) => w.isEmpty)) {
+ return MnemonicFailedReason.empty;
+ }
+
+ if (!_validLengths.contains(words.length)) {
+ return MnemonicFailedReason.invalidLength;
+ }
+
+ // Check for invalid words
+ for (final word in words) {
+ if (!_validMnemonicWords.contains(word)) {
+ return MnemonicFailedReason.invalidWord;
+ }
+ }
+
+ // Check checksum
+ if (!_validateChecksum(words)) {
+ return MnemonicFailedReason.invalidChecksum;
+ }
+
+ return null;
+ }
+
+ /// Checks if the wordlist has been initialized
+ bool get isInitialized => _validMnemonicWords.isNotEmpty;
}
diff --git a/packages/komodo_defi_types/pubspec.yaml b/packages/komodo_defi_types/pubspec.yaml
index 4aa6b12f..e29c9051 100644
--- a/packages/komodo_defi_types/pubspec.yaml
+++ b/packages/komodo_defi_types/pubspec.yaml
@@ -7,9 +7,10 @@ publish_to: none
environment:
sdk: ">=3.7.0 <4.0.0"
# TODO: Refactor to pure Dart package
- flutter: ">=3.29.0 <3.30.0"
+ flutter: ">=3.29.0 <3.36.0"
dependencies:
+ crypto: ^3.0.6
decimal: ^3.2.1
equatable: ^2.0.7
flutter:
diff --git a/packages/komodo_ui/pubspec.yaml b/packages/komodo_ui/pubspec.yaml
index 2216893b..6584b47d 100644
--- a/packages/komodo_ui/pubspec.yaml
+++ b/packages/komodo_ui/pubspec.yaml
@@ -6,7 +6,7 @@ publish_to: none
environment:
sdk: ^3.7.0
- flutter: ">=3.29.0 <3.30.0"
+ flutter: ">=3.29.0 <3.36.0"
dependencies:
decimal: ^3.2.1
diff --git a/packages/komodo_wallet_build_transformer/lib/src/steps/github/github_api_provider.dart b/packages/komodo_wallet_build_transformer/lib/src/steps/github/github_api_provider.dart
index 2c6c16db..d0240fe0 100644
--- a/packages/komodo_wallet_build_transformer/lib/src/steps/github/github_api_provider.dart
+++ b/packages/komodo_wallet_build_transformer/lib/src/steps/github/github_api_provider.dart
@@ -17,8 +17,8 @@ class GithubApiProvider {
required String repo,
required String branch,
String? token,
- }) : _branch = branch,
- _baseUrl = 'https://api.github.com/repos/$owner/$repo' {
+ }) : _branch = branch,
+ _baseUrl = 'https://api.github.com/repos/$owner/$repo' {
if (token != null) {
_log.finer('Using authentication token for GitHub API requests.');
_headers['Authorization'] = 'Bearer $token';
@@ -32,10 +32,11 @@ class GithubApiProvider {
required String baseUrl,
required String branch,
String? token,
- }) : _branch = branch,
- _baseUrl = baseUrl {
- final repoMatch = RegExp(r'^https://api\.github\.com/repos/([^/]+)/([^/]+)')
- .firstMatch(baseUrl);
+ }) : _branch = branch,
+ _baseUrl = baseUrl {
+ final repoMatch = RegExp(
+ r'^https://api\.github\.com/repos/([^/]+)/([^/]+)',
+ ).firstMatch(baseUrl);
assert(repoMatch != null, 'Invalid GitHub repository URL: $baseUrl');
if (token != null) {
@@ -59,8 +60,10 @@ class GithubApiProvider {
final fileMetadataUrl = '$_baseUrl/contents/$filePath?ref=$_branch';
_log.finest('Fetching file metadata from $fileMetadataUrl');
- final fileContentResponse =
- await http.get(Uri.parse(fileMetadataUrl), headers: _headers);
+ final fileContentResponse = await http.get(
+ Uri.parse(fileMetadataUrl),
+ headers: _headers,
+ );
if (fileContentResponse.statusCode != 200) {
throw Exception(
'Failed to fetch remote file metadata at $fileMetadataUrl: '
@@ -84,14 +87,21 @@ class GithubApiProvider {
///
/// Returns a [Future] that completes with a [String] representing the latest
/// commit hash.
- Future getLatestCommitHash({
- String branch = 'master',
- }) async {
+ Future getLatestCommitHash({String branch = 'master'}) async {
final apiUrl = '$_baseUrl/commits/$branch';
- _log.finest('Fetching latest commit hash from $apiUrl');
+ _log
+ ..finest('Fetching latest commit hash from $apiUrl')
+ ..finest('Using authentication: ${hasToken ? 'yes' : 'no'}');
final response = await http.get(Uri.parse(apiUrl), headers: _headers);
if (response.statusCode != 200) {
+ _log
+ ..severe(
+ 'GitHub API request failed: '
+ '${response.statusCode} ${response.reasonPhrase}',
+ )
+ ..severe('Response body: ${response.body}')
+ ..severe('Request headers: $_headers');
throw Exception(
'Failed to retrieve latest commit hash: $branch'
'[${response.statusCode}]: ${response.reasonPhrase}',
@@ -126,14 +136,17 @@ class GithubApiProvider {
final respString = response.body;
final data = jsonDecode(respString) as List;
- final files = data
- .where(
- (dynamic item) => (item as Map)['type'] == 'file',
- )
- .map(
- (dynamic file) => GitHubFile.fromJson(file as Map),
- )
- .toList();
+ final files =
+ data
+ .where(
+ (dynamic item) =>
+ (item as Map)['type'] == 'file',
+ )
+ .map(
+ (dynamic file) =>
+ GitHubFile.fromJson(file as Map),
+ )
+ .toList();
_log
..fine('Directory $repoPath contains ${data.length} items')
diff --git a/packages/komodo_wallet_cli/bin/update_api_config.dart b/packages/komodo_wallet_cli/bin/update_api_config.dart
index d9519d42..b4f643b3 100644
--- a/packages/komodo_wallet_cli/bin/update_api_config.dart
+++ b/packages/komodo_wallet_cli/bin/update_api_config.dart
@@ -110,7 +110,9 @@ void main(List arguments) async {
final repo = args['repo'] as String;
final configPath = args['config'] as String;
final outputDir = args['output-dir'] as String;
- final token = args['token'] as String?;
+ final token =
+ args['token'] as String? ??
+ Platform.environment['GITHUB_API_PUBLIC_READONLY_TOKEN'];
final platform = args['platform'] as String;
final source = args['source'] as String;
final mirrorUrl = args['mirror-url'] as String;
diff --git a/packages/komodo_wallet_cli/pubspec.lock b/packages/komodo_wallet_cli/pubspec.lock
index 7f826679..01ec342e 100644
--- a/packages/komodo_wallet_cli/pubspec.lock
+++ b/packages/komodo_wallet_cli/pubspec.lock
@@ -191,7 +191,7 @@ packages:
path: "../komodo_wallet_build_transformer"
relative: true
source: path
- version: "0.2.0+0"
+ version: "0.3.0+0"
lints:
dependency: transitive
description:
diff --git a/playground/pubspec.lock b/playground/pubspec.lock
index 05aeabd6..fe1e8ba0 100644
--- a/playground/pubspec.lock
+++ b/playground/pubspec.lock
@@ -381,35 +381,35 @@ packages:
path: "../packages/komodo_coins"
relative: true
source: path
- version: "0.2.0+0"
+ version: "0.3.0+0"
komodo_defi_framework:
dependency: "direct main"
description:
path: "../packages/komodo_defi_framework"
relative: true
source: path
- version: "0.2.0"
+ version: "0.3.0+0"
komodo_defi_rpc_methods:
dependency: "direct overridden"
description:
path: "../packages/komodo_defi_rpc_methods"
relative: true
source: path
- version: "0.2.0+0"
+ version: "0.3.0+0"
komodo_defi_types:
dependency: "direct main"
description:
path: "../packages/komodo_defi_types"
relative: true
source: path
- version: "0.2.0+0"
+ version: "0.3.0+0"
komodo_wallet_build_transformer:
dependency: "direct overridden"
description:
path: "../packages/komodo_wallet_build_transformer"
relative: true
source: path
- version: "0.2.0+0"
+ version: "0.3.0+0"
leak_tracker:
dependency: transitive
description:
diff --git a/playground/pubspec.yaml b/playground/pubspec.yaml
index 7c25bd5f..c2bb0fd9 100644
--- a/playground/pubspec.yaml
+++ b/playground/pubspec.yaml
@@ -21,7 +21,7 @@ version: 1.0.0+1
environment:
sdk: ^3.7.0
- flutter: ">=3.29.0 <3.30.0"
+ flutter: ">=3.29.0 <3.36.0"
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
diff --git a/products/dex_dungeon/pubspec.lock b/products/dex_dungeon/pubspec.lock
index 33569640..1ba798c3 100644
--- a/products/dex_dungeon/pubspec.lock
+++ b/products/dex_dungeon/pubspec.lock
@@ -497,56 +497,56 @@ packages:
path: "../../packages/komodo_coins"
relative: true
source: path
- version: "0.2.0+0"
+ version: "0.3.0+0"
komodo_defi_framework:
dependency: transitive
description:
path: "../../packages/komodo_defi_framework"
relative: true
source: path
- version: "0.2.0"
+ version: "0.3.0+0"
komodo_defi_local_auth:
dependency: transitive
description:
path: "../../packages/komodo_defi_local_auth"
relative: true
source: path
- version: "0.2.0+0"
+ version: "0.3.0+0"
komodo_defi_rpc_methods:
dependency: transitive
description:
path: "../../packages/komodo_defi_rpc_methods"
relative: true
source: path
- version: "0.2.0+0"
+ version: "0.3.0+0"
komodo_defi_sdk:
dependency: "direct main"
description:
path: "../../packages/komodo_defi_sdk"
relative: true
source: path
- version: "0.2.0+0"
+ version: "0.3.0+0"
komodo_defi_types:
dependency: "direct main"
description:
path: "../../packages/komodo_defi_types"
relative: true
source: path
- version: "0.2.0+0"
+ version: "0.3.0+0"
komodo_ui:
dependency: transitive
description:
path: "../../packages/komodo_ui"
relative: true
source: path
- version: "0.2.0+0"
+ version: "0.3.0+0"
komodo_wallet_build_transformer:
dependency: transitive
description:
path: "../../packages/komodo_wallet_build_transformer"
relative: true
source: path
- version: "0.2.0+0"
+ version: "0.3.0+0"
leak_tracker:
dependency: transitive
description: