This commit is contained in:
Anthony Tritonn 2025-06-19 20:41:42 -07:00 committed by GitHub
commit d4efb61d92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
139 changed files with 4865 additions and 56 deletions

2
.gitignore vendored
View file

@ -3,6 +3,7 @@
*.log
*.pyc
*.swp
*.zip
.DS_Store
.atom/
.buildlog/
@ -140,6 +141,7 @@ lib/tron/tron.dart
lib/wownero/wownero.dart
lib/zano/zano.dart
lib/decred/decred.dart
lib/xelis/xelis.dart
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png

View file

@ -84,6 +84,12 @@ Cake Wallet includes support for several cryptocurrencies, including:
* Automatically generate new addresses
* Specify multiple recipients for batch sending
### Xelis Specific Features
* Store XEL and all native assets/tokens
* Add custom tokens by asset ID
* Specify multiple recipients for batch sending
# Monero.com by Cake Wallet for Android and iOS
## Open Source Monero-Only Wallet

View file

@ -23,6 +23,7 @@ analyzer:
lib/tron/cw_tron.dart,
lib/wownero/cw_wownero.dart,
lib/zano/cw_zano.dart,
lib/xelis/cw_xelis.dart,
]
language:
strict-casts: true

View file

@ -14,4 +14,15 @@
<natures>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
<filteredResources>
<filter>
<id>1744859372158</id>
<name></name>
<type>30</type>
<matcher>
<id>org.eclipse.core.resources.regexFilterMatcher</id>
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>

View file

@ -1,2 +1,13 @@
arguments=
auto.sync=false
build.scans.enabled=false
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
connection.project.dir=
eclipse.preferences.version=1
eclipse.preferences.version=1
gradle.user.home=
java.home=
jvm.arguments=
offline.mode=false
override.workspace.settings=false
show.console.view=false
show.executions.view=false

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View file

@ -0,0 +1,25 @@
-
uri: us-node.xelis.io
useSSL: true
is_default: true
-
uri: pl-node.xelis.io
useSSL: true
-
uri: de-node.xelis.io
useSSL: true
-
uri: fr-node.xelis.io
useSSL: true
-
uri: sg-node.xelis.io
useSSL: true
-
uri: uk-node.xelis.io
useSSL: true
-
uri: ca-node.xelis.io
useSSL: true
-
uri: testnet-node.xelis.io
useSSL: true

View file

@ -1,5 +1,5 @@
@echo off
set cw_win_app_config=--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron
set cw_win_app_config=--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --xelis
set cw_root=%cd%
set cw_archive_name=Cake Wallet.zip
set cw_archive_path=%cw_root%\%cw_archive_name%
@ -24,7 +24,7 @@ IF NOT EXIST "%secrets_file_path%" (
) ELSE (echo === Using previously/already generated secrets file: %secrets_file_path% ===)
echo === Generating mobx models ===
for /d %%i in (cw_core cw_monero cw_bitcoin cw_ethereum cw_evm cw_polygon cw_nano cw_bitcoin_cash cw_solana cw_tron .) do (
for /d %%i in (cw_core cw_monero cw_bitcoin cw_ethereum cw_evm cw_polygon cw_nano cw_bitcoin_cash cw_solana cw_tron cw_xelis.) do (
cd %%i
call flutter pub get > nul
call dart run build_runner build --delete-conflicting-outputs > nul

View file

@ -111,7 +111,9 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
CryptoCurrency.zano,
CryptoCurrency.ton,
CryptoCurrency.flip,
CryptoCurrency.deuro
CryptoCurrency.deuro,
CryptoCurrency.xel,
CryptoCurrency.xet
];
static const havenCurrencies = [
@ -234,6 +236,9 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
static const flip = CryptoCurrency(title: 'FLIP', tag: 'ETH', fullName: 'Chainflip', raw: 97, name: 'flip', iconPath: 'assets/images/flip_icon.png', decimals: 18);
static const deuro = CryptoCurrency(title: 'DEURO', tag: 'ETH', fullName: 'Decentralized Euro', raw: 98, name: 'deuro', iconPath: 'assets/images/deuro_icon.png', decimals: 18);
static const xel = CryptoCurrency(title: 'XEL', fullName: 'Xelis', raw: 99, name: 'xel', iconPath: 'assets/images/xelis_icon.png', decimals: 8);
static const xet = CryptoCurrency(title: 'XET', fullName: 'Testnet Xelis', raw: 100, name: 'xet', iconPath: 'assets/images/xelis_testnet_icon.png', decimals: 8);
static final Map<int, CryptoCurrency> _rawCurrencyMap =
[...all, ...havenCurrencies].fold<Map<int, CryptoCurrency>>(<int, CryptoCurrency>{}, (acc, item) {
acc.addAll({item.raw: item});

View file

@ -34,6 +34,11 @@ CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) {
return CryptoCurrency.zano;
case WalletType.decred:
return CryptoCurrency.dcr;
case WalletType.xelis:
if (isTestnet == true) {
return CryptoCurrency.xet;
}
return CryptoCurrency.xel;
case WalletType.none:
throw Exception(
'Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
@ -70,6 +75,8 @@ WalletType? walletTypeForCurrency(CryptoCurrency currency) {
return WalletType.zano;
case CryptoCurrency.dcr:
return WalletType.decred;
case CryptoCurrency.xel:
return WalletType.xelis;
default:
return null;
}

View file

@ -22,3 +22,4 @@ const MWEB_UTXO_TYPE_ID = 20;
const HAVEN_SEED_STORE_TYPE_ID = 21;
const ZANO_ASSET_TYPE_ID = 22;
const PAYJOIN_SESSION_TYPE_ID = 23;
const XELIS_ASSET_TYPE_ID = 24;

View file

@ -104,6 +104,7 @@ class Node extends HiveObject with Keyable {
case WalletType.tron:
case WalletType.zano:
case WalletType.decred:
case WalletType.xelis:
return Uri.parse(
"http${isSSL ? "s" : ""}://$uriRaw${path!.startsWith("/") || path!.isEmpty ? path : "/$path"}");
case WalletType.none:
@ -165,6 +166,7 @@ class Node extends HiveObject with Keyable {
case WalletType.polygon:
case WalletType.solana:
case WalletType.tron:
case WalletType.xelis:
return requestElectrumServer();
case WalletType.zano:
return requestZanoNode();
@ -360,13 +362,13 @@ class Node extends HiveObject with Keyable {
}
Future<bool> requestDecredNode() async {
if (uri.host == "default-spv-nodes") {
// Just show default port as ok. The wallet will connect to a list of known
// nodes automatically.
return true;
}
try {
final socket = await Socket.connect(uri.host, uri.port, timeout: Duration(seconds: 5));
if (uri.host == "default-spv-nodes") {
// Just show default port as ok. The wallet will connect to a list of known
// nodes automatically.
return true;
}
try {
final socket = await Socket.connect(uri.host, uri.port, timeout: Duration(seconds: 5));
socket.destroy();
return true;
} catch (_) {

View file

@ -18,6 +18,7 @@ const walletTypes = [
WalletType.tron,
WalletType.zano,
WalletType.decred,
WalletType.xelis,
];
@HiveType(typeId: WALLET_TYPE_TYPE_ID)
@ -65,7 +66,10 @@ enum WalletType {
zano,
@HiveField(14)
decred
decred,
@HiveField(15)
xelis
}
int serializeToInt(WalletType type) {
@ -98,6 +102,8 @@ int serializeToInt(WalletType type) {
return 12;
case WalletType.decred:
return 13;
case WalletType.xelis:
return 14;
case WalletType.none:
return -1;
}
@ -133,6 +139,8 @@ WalletType deserializeFromInt(int raw) {
return WalletType.zano;
case 13:
return WalletType.decred;
case 14:
return WalletType.xelis;
default:
throw Exception(
'Unexpected token: $raw for WalletType deserializeFromInt');
@ -169,6 +177,8 @@ String walletTypeToString(WalletType type) {
return 'Zano';
case WalletType.decred:
return 'Decred';
case WalletType.xelis:
return 'Xelis';
case WalletType.none:
return '';
}
@ -204,6 +214,8 @@ String walletTypeToDisplayName(WalletType type) {
return 'Zano (ZANO)';
case WalletType.decred:
return 'Decred (DCR)';
case WalletType.xelis:
return 'Xelis (XEL)';
case WalletType.none:
return '';
}
@ -242,6 +254,11 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal
return CryptoCurrency.zano;
case WalletType.decred:
return CryptoCurrency.dcr;
case WalletType.xelis:
if (isTestnet) {
return CryptoCurrency.xet;
}
return CryptoCurrency.xel;
case WalletType.none:
throw Exception(
'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');

38
cw_xelis/.gitignore vendored Normal file
View file

@ -0,0 +1,38 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
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/
.flutter-plugins
.flutter-plugins-dependencies
flutter_rust_bridge_local.yaml
build/
rust_temp/
rust_src/
lib/src/**
xelis-flutter-ffi/

27
cw_xelis/.metadata Normal file
View file

@ -0,0 +1,27 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "c23637390482d4cf9598c3ce3f2be31aa7332daf"
channel: "stable"
project_type: plugin
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf
# 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'

3
cw_xelis/CHANGELOG.md Normal file
View file

@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.

1
cw_xelis/LICENSE Normal file
View file

@ -0,0 +1 @@
TODO: Add your license here.

18
cw_xelis/README.md Normal file
View file

@ -0,0 +1,18 @@
# cw_xelis
A new Flutter plugin project.
## Getting Started
This project is a starting point for a Flutter
[plug-in package](https://flutter.dev/to/develop-plugins),
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://docs.flutter.dev), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
The plugin project was generated without specifying the `--platforms` flag, no platforms are currently supported.
To add platforms, run `flutter create -t plugin --platforms <platforms> .` in this directory.
You can also find a detailed instruction on how to add platforms in the `pubspec.yaml` at https://flutter.dev/to/pubspec-plugin-platforms.

View file

@ -0,0 +1,13 @@
include: package:flutter_lints/flutter.yaml
analyzer:
exclude:
- "lib/src/api/generated/**"
- "**/*.g.dart"
- "**/*frb_generated*.dart"
errors:
# Ignore specific errors that might come from generated code
invalid_annotation_target: ignore
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

45
cw_xelis/example/.gitignore vendored Normal file
View file

@ -0,0 +1,45 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
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
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

View file

@ -0,0 +1,16 @@
# cw_xelis_example
Demonstrates how to use the cw_xelis plugin.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View file

@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View file

@ -0,0 +1,25 @@
// This is a basic Flutter integration test.
//
// Since integration tests run in a full Flutter application, they can interact
// with the host side of a plugin implementation, unlike Dart unit tests.
//
// For more information about Flutter integration tests, please see
// https://flutter.dev/to/integration-testing
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:cw_xelis/cw_xelis.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('getPlatformVersion test', (WidgetTester tester) async {
final CwXelis plugin = CwXelis();
final String? version = await plugin.getPlatformVersion();
// The version string depends on the host platform running the test, so
// just assert that some non-empty string is returned.
expect(version?.isNotEmpty, true);
});
}

View file

@ -0,0 +1,63 @@
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:cw_xelis/cw_xelis.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
final _cwXelisPlugin = CwXelis();
@override
void initState() {
super.initState();
initPlatformState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
String platformVersion;
// Platform messages may fail, so we use a try/catch PlatformException.
// We also handle the message potentially returning null.
try {
platformVersion =
await _cwXelisPlugin.getPlatformVersion() ?? 'Unknown platform version';
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_platformVersion = platformVersion;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Text('Running on: $_platformVersion\n'),
),
),
);
}
}

View file

@ -0,0 +1,755 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
asn1lib:
dependency: transitive
description:
name: asn1lib
sha256: "1c296cd268f486cabcc3930e9b93a8133169305f18d722916e675959a88f6d2c"
url: "https://pub.dev"
source: hosted
version: "1.5.9"
async:
dependency: transitive
description:
name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted
version: "2.11.0"
blockchain_utils:
dependency: transitive
description:
path: "."
ref: cake-update-v2
resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57"
url: "https://github.com/cake-tech/blockchain_utils"
source: git
version: "3.3.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
build_cli_annotations:
dependency: transitive
description:
name: build_cli_annotations
sha256: b59d2769769efd6c9ff6d4c4cede0be115a566afc591705c2040b707534b1172
url: "https://pub.dev"
source: hosted
version: "2.1.0"
cake_backup:
dependency: transitive
description:
path: "."
ref: main
resolved-ref: "3aba867dcab6737f6707782f5db15d71f303db38"
url: "https://github.com/cake-tech/cake_backup.git"
source: git
version: "1.0.0+1"
characters:
dependency: transitive
description:
name: characters
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
clock:
dependency: transitive
description:
name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted
version: "1.1.1"
collection:
dependency: transitive
description:
name: collection
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
url: "https://pub.dev"
source: hosted
version: "1.19.0"
convert:
dependency: transitive
description:
name: convert
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
url: "https://pub.dev"
source: hosted
version: "3.1.2"
crypto:
dependency: transitive
description:
name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
url: "https://pub.dev"
source: hosted
version: "3.0.6"
cryptography:
dependency: transitive
description:
name: cryptography
sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05
url: "https://pub.dev"
source: hosted
version: "2.7.0"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.dev"
source: hosted
version: "1.0.8"
cw_core:
dependency: transitive
description:
path: "../../cw_core"
relative: true
source: path
version: "0.0.1"
cw_xelis:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "0.0.1"
decimal:
dependency: transitive
description:
name: decimal
sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
encrypt:
dependency: transitive
description:
name: encrypt
sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2"
url: "https://pub.dev"
source: hosted
version: "5.0.3"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
file:
dependency: transitive
description:
name: file
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_driver:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
url: "https://pub.dev"
source: hosted
version: "5.0.0"
flutter_mobx:
dependency: transitive
description:
name: flutter_mobx
sha256: ba5e93467866a2991259dc51cffd41ef45f695c667c2b8e7b087bf24118b50fe
url: "https://pub.dev"
source: hosted
version: "2.3.0"
flutter_rust_bridge:
dependency: transitive
description:
name: flutter_rust_bridge
sha256: "5a5c7a5deeef2cc2ffe6076a33b0429f4a20ceac22a397297aed2b1eb067e611"
url: "https://pub.dev"
source: hosted
version: "2.9.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
freezed_annotation:
dependency: transitive
description:
name: freezed_annotation
sha256: c87ff004c8aa6af2d531668b46a4ea379f7191dc6dfa066acd53d506da6e044b
url: "https://pub.dev"
source: hosted
version: "3.0.0"
fuchsia_remote_debug_protocol:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
globbing:
dependency: transitive
description:
name: globbing
sha256: "4f89cfaf6fa74c9c1740a96259da06bd45411ede56744e28017cc534a12b6e2d"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
hive:
dependency: transitive
description:
name: hive
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
url: "https://pub.dev"
source: hosted
version: "2.2.3"
http:
dependency: transitive
description:
name: http
sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
integration_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
intl:
dependency: transitive
description:
name: intl
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
url: "https://pub.dev"
source: hosted
version: "0.19.0"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.6.7"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.9.0"
json_rpc_2:
dependency: transitive
description:
name: json_rpc_2
sha256: "3c46c2633aec07810c3d6a2eb08d575b5b4072980db08f1344e66aeb53d6e4a7"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
jsontool:
dependency: transitive
description:
name: jsontool
sha256: e49bf419e82d90f009426cd7fdec8d54ba8382975b3454ed16a3af3ee1d1b697
url: "https://pub.dev"
source: hosted
version: "2.1.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
url: "https://pub.dev"
source: hosted
version: "10.0.7"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
url: "https://pub.dev"
source: hosted
version: "3.0.8"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
lints:
dependency: transitive
description:
name: lints
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
url: "https://pub.dev"
source: hosted
version: "5.1.1"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted
version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
version: "1.15.0"
mobx:
dependency: transitive
description:
name: mobx
sha256: bf1a90e5bcfd2851fc6984e20eef69557c65d9e4d0a88f5be4cf72c9819ce6b0
url: "https://pub.dev"
source: hosted
version: "2.5.0"
mutex:
dependency: transitive
description:
name: mutex
sha256: "8827da25de792088eb33e572115a5eb0d61d61a3c01acbc8bcbe76ed78f1a1f2"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
on_chain:
dependency: transitive
description:
path: "."
ref: cake-update-v2
resolved-ref: "096865a8c6b89c260beadfec04f7e184c40a3273"
url: "https://github.com/cake-tech/on_chain.git"
source: git
version: "3.7.0"
path:
dependency: transitive
description:
name: path
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted
version: "1.9.0"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
url: "https://pub.dev"
source: hosted
version: "2.1.5"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
url: "https://pub.dev"
source: hosted
version: "2.2.17"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
platform:
dependency: transitive
description:
name: platform
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
url: "https://pub.dev"
source: hosted
version: "3.1.5"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
url: "https://pub.dev"
source: hosted
version: "3.9.1"
process:
dependency: transitive
description:
name: process
sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32"
url: "https://pub.dev"
source: hosted
version: "5.0.2"
provider:
dependency: transitive
description:
name: provider
sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84"
url: "https://pub.dev"
source: hosted
version: "6.1.5"
rational:
dependency: transitive
description:
name: rational
sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336
url: "https://pub.dev"
source: hosted
version: "2.2.3"
shared_preferences:
dependency: transitive
description:
name: shared_preferences
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
url: "https://pub.dev"
source: hosted
version: "2.5.3"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
url: "https://pub.dev"
source: hosted
version: "2.4.10"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
socks5_proxy:
dependency: transitive
description:
name: socks5_proxy
sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053"
url: "https://pub.dev"
source: hosted
version: "1.0.6"
source_span:
dependency: transitive
description:
name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.10.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
url: "https://pub.dev"
source: hosted
version: "1.12.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.2"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
sync_http:
dependency: transitive
description:
name: sync_http
sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
system_info2:
dependency: transitive
description:
name: system_info2
sha256: "65206bbef475217008b5827374767550a5420ce70a04d2d7e94d1d2253f3efc9"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
url: "https://pub.dev"
source: hosted
version: "0.7.3"
tuple:
dependency: transitive
description:
name: tuple
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
url: "https://pub.dev"
source: hosted
version: "2.0.2"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
unorm_dart:
dependency: transitive
description:
name: unorm_dart
sha256: "8e3870a1caa60bde8352f9597dd3535d8068613269444f8e35ea8925ec84c1f5"
url: "https://pub.dev"
source: hosted
version: "0.3.1+1"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
very_good_analysis:
dependency: transitive
description:
name: very_good_analysis
sha256: "62d2b86d183fb81b2edc22913d9f155d26eb5cf3855173adb1f59fac85035c63"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
url: "https://pub.dev"
source: hosted
version: "14.3.0"
web:
dependency: transitive
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
web_socket:
dependency: transitive
description:
name: web_socket
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
url: "https://pub.dev"
source: hosted
version: "3.0.3"
web_socket_client:
dependency: transitive
description:
name: web_socket_client
sha256: "394789177aa3bc1b7b071622a1dbf52a4631d7ce23c555c39bb2523e92316b07"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
webdriver:
dependency: transitive
description:
name: webdriver
sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8"
url: "https://pub.dev"
source: hosted
version: "3.0.4"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
xelis_dart_sdk:
dependency: transitive
description:
name: xelis_dart_sdk
sha256: "990c6be6be5f0410764fd4034f940d2870bc70974feb3ed465123102a3bd9dfd"
url: "https://pub.dev"
source: hosted
version: "0.28.0"
xelis_flutter:
dependency: transitive
description:
path: "."
ref: "83bda92f1b833fe5d8584aa429d5143a3698b33f"
resolved-ref: "83bda92f1b833fe5d8584aa429d5143a3698b33f"
url: "https://github.com/xelis-project/xelis-flutter-ffi.git"
source: git
version: "0.2.0"
sdks:
dart: ">=3.6.0 <4.0.0"
flutter: ">=3.27.0"

View file

@ -0,0 +1,85 @@
name: cw_xelis_example
description: "Demonstrates how to use the cw_xelis plugin."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
environment:
sdk: '>=3.0.6 <4.0.0'
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
cw_xelis:
# When depending on this package from a real application you should use:
# cw_xelis: ^x.y.z
# See https://dart.dev/tools/pub/dependencies#version-constraints
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
dev_dependencies:
integration_test:
sdk: flutter
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^5.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:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/to/asset-from-package
# To add custom fonts to your application, 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 from package dependencies,
# see https://flutter.dev/to/font-from-package

View file

@ -0,0 +1,27 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:cw_xelis_example/main.dart';
void main() {
testWidgets('Verify Platform version', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that platform version is retrieved.
expect(
find.byWidgetPredicate(
(Widget widget) => widget is Text &&
widget.data!.startsWith('Running on:'),
),
findsOneWidget,
);
});
}

View file

@ -0,0 +1,14 @@
// You have generated a new plugin project without specifying the `--platforms`
// flag. A plugin project with no platform support was generated. To add a
// platform, run `flutter create -t plugin --platforms <platforms> .` under the
// same directory. You can also find a detailed instruction on how to add
// platforms in the `pubspec.yaml` at
// https://flutter.dev/to/pubspec-plugin-platforms.
import 'cw_xelis_platform_interface.dart';
class CwXelis {
Future<String?> getPlatformVersion() {
return CwXelisPlatform.instance.getPlatformVersion();
}
}

View file

@ -0,0 +1,17 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'cw_xelis_platform_interface.dart';
/// An implementation of [CwXelisPlatform] that uses method channels.
class MethodChannelCwXelis extends CwXelisPlatform {
/// The method channel used to interact with the native platform.
@visibleForTesting
final methodChannel = const MethodChannel('cw_xelis');
@override
Future<String?> getPlatformVersion() async {
final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
return version;
}
}

View file

@ -0,0 +1,29 @@
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'cw_xelis_method_channel.dart';
abstract class CwXelisPlatform extends PlatformInterface {
/// Constructs a CwXelisPlatform.
CwXelisPlatform() : super(token: _token);
static final Object _token = Object();
static CwXelisPlatform _instance = MethodChannelCwXelis();
/// The default instance of [CwXelisPlatform] to use.
///
/// Defaults to [MethodChannelCwXelis].
static CwXelisPlatform get instance => _instance;
/// Platform-specific implementations should set this with their own
/// platform-specific class that extends [CwXelisPlatform] when
/// they register themselves.
static set instance(CwXelisPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
Future<String?> getPlatformVersion() {
throw UnimplementedError('platformVersion() has not been implemented.');
}
}

View file

@ -0,0 +1 @@
export 'package:xelis_flutter/src/api/api.dart';

View file

@ -0,0 +1 @@
export 'package:xelis_flutter/src/api/logger.dart';

View file

@ -0,0 +1,24 @@
export 'package:xelis_flutter/src/api/network.dart';
import 'package:xelis_flutter/src/api/network.dart';
extension NetworkName on Network {
String get name {
switch (this) {
case Network.mainnet:
return 'mainnet';
case Network.testnet:
return 'testnet';
}
}
static Network fromName(String name) {
switch (name) {
case 'mainnet':
return Network.mainnet;
case 'testnet':
return Network.testnet;
default:
throw ArgumentError('Unknown network name: $name');
}
}
}

View file

@ -0,0 +1 @@
export 'package:xelis_flutter/src/api/progress_report.dart';

View file

@ -0,0 +1 @@
export 'package:xelis_flutter/src/api/seed_search_engine.dart';

View file

@ -0,0 +1 @@
export 'package:xelis_flutter/src/api/table_generation.dart';

View file

@ -0,0 +1 @@
export 'package:xelis_flutter/src/api/utils.dart';

View file

@ -0,0 +1 @@
export 'package:xelis_flutter/src/api/wallet.dart';

View file

@ -0,0 +1 @@
export 'package:xelis_flutter/src/frb_generated.dart';

View file

@ -0,0 +1,115 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/hive_type_ids.dart';
import 'package:hive/hive.dart';
part 'xelis_asset.g.dart';
@HiveType(typeId: XelisAsset.typeId)
class XelisAsset extends CryptoCurrency with HiveObjectMixin {
@override
@HiveField(0)
final String name;
@HiveField(1)
final String symbol;
@HiveField(2)
final String id;
@HiveField(3)
final int decimals;
@HiveField(4, defaultValue: true)
bool _enabled;
@override
@HiveField(5)
final String? iconPath;
@override
@HiveField(6)
final String? tag;
@override
@HiveField(7, defaultValue: false)
final bool isPotentialScam;
XelisAsset({
required this.name,
required this.symbol,
required this.id,
required this.decimals,
this.iconPath,
this.tag = 'XEL',
bool enabled = true,
this.isPotentialScam = false,
}) : _enabled = enabled,
super(
name: id.toLowerCase(),
title: symbol.toUpperCase(),
fullName: name,
tag: tag,
iconPath: iconPath,
decimals: decimals,
isPotentialScam: isPotentialScam,
);
factory XelisAsset.fromMetadata({
required String name,
required String id,
required String symbol,
required int decimals,
String? iconPath,
bool isPotentialScam = false,
}) {
return XelisAsset(
name: name,
symbol: symbol,
decimals: decimals,
id: id,
iconPath: iconPath,
isPotentialScam: isPotentialScam,
);
}
@override
bool get enabled => _enabled;
@override
set enabled(bool value) => _enabled = value;
XelisAsset.copyWith(XelisAsset other, String? icon, String? tag)
: name = other.name,
symbol = other.symbol,
decimals = other.decimals,
_enabled = other.enabled,
id = other.id,
tag = other.tag,
iconPath = icon,
isPotentialScam = other.isPotentialScam,
super(
title: other.symbol.toUpperCase(),
name: other.symbol.toLowerCase(),
decimals: other.decimals,
fullName: other.name,
tag: other.tag,
iconPath: icon,
isPotentialScam: other.isPotentialScam,
);
static const typeId = XELIS_ASSET_TYPE_ID;
static const boxName = 'XelisAssets';
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other is XelisAsset) {
return other.id == id;
}
return false;
}
@override
int get hashCode => id.hashCode;
}

View file

@ -0,0 +1,56 @@
import 'dart:convert';
import 'package:intl/intl.dart';
import 'package:cw_core/balance.dart';
import 'package:cw_xelis/xelis_formatting.dart';
import 'package:xelis_dart_sdk/xelis_dart_sdk.dart' as xelis_sdk;
class XelisAssetBalance extends Balance {
XelisAssetBalance({
required this.balance,
required this.decimals,
this.asset = xelis_sdk.xelisAsset,
this.symbol = "XEL"
}): super(balance, 0);
final int balance;
final int decimals;
final String asset;
final String symbol;
String get formatted {
final formatter = NumberFormat('0.00##########', 'en_US');
final value = (BigInt.from(balance) / BigInt.from(10).pow(decimals)).toDouble();
return formatter.format(value);
}
String toJSON() => json.encode({
'balance': balance.toString(),
'decimals': decimals,
'asset': asset,
'symbol': symbol
});
static XelisAssetBalance fromJSON(String jsonSource) {
final decoded = json.decode(jsonSource) as Map;
return XelisAssetBalance(
balance: decoded['balance'],
decimals: decoded['decimals'],
asset: decoded['asset'],
symbol: decoded['symbol'],
);
}
static XelisAssetBalance zero({int? decimals, String? asset, String? symbol}) {
return XelisAssetBalance(
balance: 0,
decimals: decimals ?? 8,
);
}
@override
String get formattedAvailableBalance => XelisFormatter.formatAmount(balance, decimals: decimals);
@override
String get formattedAdditionalBalance => '0';
}

View file

@ -0,0 +1,64 @@
import 'package:xelis_dart_sdk/xelis_dart_sdk.dart' as xelis_sdk;
abstract class Event {
const Event();
}
class NewTopoheight extends Event {
final int topoheight;
const NewTopoheight(this.topoheight);
}
class NewTransaction extends Event {
final xelis_sdk.TransactionEntry tx;
const NewTransaction(this.tx);
}
class BalanceChanged extends Event {
final String asset;
final int balance;
const BalanceChanged(this.asset, this.balance);
}
class Online extends Event {
const Online();
}
class Offline extends Event {
const Offline();
}
class Rescan extends Event {
final int startTopoheight;
const Rescan(this.startTopoheight);
}
class HistorySynced extends Event {
final int topoheight;
const HistorySynced(this.topoheight);
}
class NewAsset extends Event {
final String asset;
final int decimals;
final int? max_supply;
final String name;
final String? owner;
final String ticker;
final int topoheight;
const NewAsset(
this.asset,
this.decimals,
this.max_supply,
this.name,
this.owner,
this.ticker,
this.topoheight
);
}
class SyncError extends Event {
final String message;
const SyncError(this.message);
}

View file

@ -0,0 +1,25 @@
import 'package:cw_core/crypto_currency.dart';
class XelisMnemonicIsIncorrectException implements Exception {
@override
String toString() =>
'Xelis mnemonic has incorrect format. Mnemonic should contain 25 words separated by space.';
}
class XelisTransactionCreationException implements Exception {
final String exceptionMessage;
XelisTransactionCreationException(CryptoCurrency currency)
: exceptionMessage = 'Wrong balance. Not enough ${currency.title} on your balance.';
@override
String toString() => exceptionMessage;
}
class XelisTooManyOutputsException implements Exception {
final int count;
XelisTooManyOutputsException(this.count);
@override
String toString() =>
'Cannot include more than 255 transfers in a single TX. Attempted to use $count.';
}

View file

@ -0,0 +1,54 @@
import 'dart:math';
class XelisFormatter {
static int parseXelisAmount(String amount) {
try {
return (double.parse(amount) * pow(10, 8)).round();
} catch (_) {
return 0;
}
}
static double parseXelisAmountToDouble(int amount) {
try {
return amount / pow(10, 8);
} catch (_) {
return 0;
}
}
static int parseAmount(String amount, int decimals) {
try {
return (double.parse(amount) * pow(10, decimals)).round();
} catch (_) {
return 0;
}
}
static double parseAmountToDouble(int amount, int decimals) {
try {
return amount / pow(10, decimals);
} catch (_) {
return 0;
}
}
static String formatAmountWithSymbol(
int rawAmount, {
required int decimals,
String? symbol,
}) {
final formatted = rawAmount / pow(10, decimals);
// final symbol = assetId == null || assetId == xelisAsset ? 'XEL' : assetId;
final sym = symbol ?? 'XEL';
return '$formatted $sym';
}
static String formatAmount(
int rawAmount, {
required int decimals,
}) {
final formatted = rawAmount / pow(10, decimals);
return '$formatted';
}
}

View file

@ -0,0 +1,41 @@
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_xelis/xelis_formatting.dart';
class XelisPendingTransaction with PendingTransaction {
XelisPendingTransaction(
{
required this.txid,
required this.amount,
required this.fee,
required this.decimals,
required this.send,
});
final String amount;
final int fee;
final String txid;
final int decimals;
final Future<void> Function() send;
@override
String get id => txid;
@override
String get amountFormatted => amount.toString();
@override
String get feeFormatted => XelisFormatter.formatAmount(fee, decimals: 8);
@override
String get hex => "";
@override
Future<void> commit() async {
return send();
}
@override
Future<String?> commitUR() {
throw UnimplementedError();
}
}

View file

@ -0,0 +1,27 @@
import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:cw_xelis/src/api/network.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_type.dart';
Future<String> pathForXelisNetworkFile(String name) async {
final walletDir = await pathForWalletDir(name: name, type: WalletType.xelis);
return p.join(walletDir, 'network.txt');
}
Future<void> saveXelisNetwork(String name, Network network) async {
final path = await pathForXelisNetworkFile(name);
await File(path).writeAsString(network.name);
}
Future<Network> loadXelisNetwork(String name) async {
final path = await pathForXelisNetworkFile(name);
final file = File(path);
if (!await file.exists()) {
throw FileSystemException('Missing Xelis network file', path);
}
final contents = await file.readAsString();
return NetworkName.fromName(contents.trim());
}

View file

@ -0,0 +1,12 @@
import 'package:cw_core/output_info.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_xelis/xelis_transaction_priority.dart';
class XelisTransactionCredentials {
XelisTransactionCredentials(this.outputs, {required this.priority, required this.currency});
final List<OutputInfo> outputs;
final XelisTransactionPriority? priority;
final CryptoCurrency currency;
}

View file

@ -0,0 +1,95 @@
import 'dart:convert';
import 'dart:core';
import 'package:mobx/mobx.dart';
import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_xelis/xelis_transaction_info.dart';
part 'xelis_transaction_history.g.dart';
const transactionsHistoryFileName = 'xelis_transactions.json';
class XelisTransactionHistory = XelisTransactionHistoryBase with _$XelisTransactionHistory;
abstract class XelisTransactionHistoryBase
extends TransactionHistoryBase<XelisTransactionInfo> with Store {
XelisTransactionHistoryBase(
{required this.walletInfo, required String password, required this.encryptionFileUtils})
: _password = password {
transactions = ObservableMap<String, XelisTransactionInfo>();
}
final WalletInfo walletInfo;
final EncryptionFileUtils encryptionFileUtils;
String _password;
Future<void> init() async {
clear();
await _load();
}
@override
Future<void> save() async {
try {
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
final path = '$dirPath/$transactionsHistoryFileName';
final transactionMaps = transactions.map((key, value) => MapEntry(key, value.toJson()));
final data = json.encode({'transactions': transactionMaps});
await encryptionFileUtils.write(path: path, password: _password, data: data);
} catch (e, s) {
printV('Error while saving xelis transaction history: ${e.toString()}');
printV(s);
}
}
@override
void addOne(XelisTransactionInfo transaction) =>
transactions[transaction.id] = transaction;
@override
void addMany(Map<String, XelisTransactionInfo> transactions) =>
this.transactions.addAll(transactions);
Future<Map<String, dynamic>> _read() async {
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
final path = '$dirPath/$transactionsHistoryFileName';
final content = await encryptionFileUtils.read(path: path, password: _password);
if (content.isEmpty) {
return {};
}
return json.decode(content) as Map<String, dynamic>;
}
Future<void> _load() async {
try {
final content = await _read();
final txs = content['transactions'] as Map<String, dynamic>? ?? {};
txs.entries.forEach((entry) {
final val = entry.value;
if (val is Map<String, dynamic>) {
final tx = XelisTransactionInfo.fromJson(val, isAssetEnabled: (id) => true); // asset filtering needs to happen elsewhere before serializing
addOne(tx);
}
});
} catch (e) {
printV(e);
}
}
bool update(Map<String, XelisTransactionInfo> txs) {
var foundOldTx = false;
txs.forEach((_, tx) {
if (!transactions.containsKey(tx.id)) {
transactions[tx.id] = tx;
} else {
foundOldTx = true;
}
});
return foundOldTx;
}
}

View file

@ -0,0 +1,319 @@
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_xelis/xelis_formatting.dart';
import 'package:xelis_dart_sdk/xelis_dart_sdk.dart' as xelis_sdk;
import 'package:cw_xelis/src/api/wallet.dart' as x_wallet;
import 'package:cw_core/format_amount.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'dart:math';
class XelisTxRecipient {
final String address;
final String amount;
final bool isChange;
const XelisTxRecipient({
required this.address,
required this.amount,
required this.isChange,
});
}
class XelisTransfer {
final x_wallet.XelisAssetMetadata meta;
final int amount;
XelisTransfer({
required this.meta,
required this.amount
});
String format() {
final amountDouble = (BigInt.from(amount) / BigInt.from(10).pow(meta.decimals)).toString();
return '${formatAmount(amountDouble)} ${meta.ticker}';
}
}
class XelisTransactionInfo extends TransactionInfo {
XelisTransactionInfo({
required this.id,
required this.height,
required this.direction,
required this.date,
required this.xelAmount,
required this.xelFee,
required this.decimals,
required this.assetSymbols,
required this.assetIds,
required this.assetAmounts,
required this.to,
required this.from,
this.isTestnet = false,
}) :
amount = xelAmount.toInt(),
fee = xelFee.toInt()
;
final String id;
final int amount;
final int fee;
final int height;
final BigInt xelAmount;
final BigInt xelFee;
final DateTime date;
final TransactionDirection direction;
final List<BigInt> assetAmounts;
final List<int> decimals;
final List<String> assetSymbols;
final List<String> assetIds;
final String? to;
final String? from;
final bool isTestnet;
final int confirmations = 3; // static/unused atm, purely for compatibility
String? _fiatAmount;
@override
String amountFormatted() {
final List<String> formattedAssets = [];
if (formattedAssets.length > 1) return ":MULTI:" + multiFormatted();
final amount = (assetAmounts[0] / BigInt.from(10).pow(decimals[0])).toString();
return '$amount ${assetSymbols[0]}';
}
String multiFormatted() {
final List<String> formattedAssets = [];
for (int i = 0; i < assetSymbols.length; i++) {
final amount = (assetAmounts[i] / BigInt.from(10).pow(decimals[i])).toString();
formattedAssets.add('$amount ${assetSymbols[i]}');
}
return formattedAssets.join('\n\n');
}
@override
String feeFormatted() =>
XelisFormatter.formatAmountWithSymbol(fee, decimals: 8, symbol: isTestnet ? 'XET' : 'XEL');
@override
String fiatAmount() => _fiatAmount ?? '';
@override
void changeFiatAmount(String amount) {
_fiatAmount = formatAmount(amount);
}
static Future<XelisTransactionInfo> fromTransactionEntry(
xelis_sdk.TransactionEntry entry,
{required x_wallet.XelisWallet wallet, required bool Function(String assetId) isAssetEnabled, bool isTestnet = false}
) async {
final txType = entry.txEntryType;
late TransactionDirection direction;
BigInt amount = BigInt.zero;
BigInt fee = BigInt.zero;
String? to;
String? from;
String asset = xelis_sdk.xelisAsset;
final Map<String, BigInt> assetAmounts = {};
final Map<String, int> assetDecimals = {};
final Map<String, String> assetSymbolsMap = {};
switch (txType) {
case xelis_sdk.IncomingEntry():
direction = TransactionDirection.incoming;
for (final transfer in txType.transfers) {
final asset = transfer.asset;
if (!isAssetEnabled(asset)) continue;
assetAmounts[asset] = (assetAmounts[asset] ?? BigInt.zero) + BigInt.from(transfer.amount);
final meta = await wallet.getAssetMetadata(asset: asset);
assetDecimals[asset] = meta.decimals;
assetSymbolsMap[asset] = meta.ticker;
}
from = txType.from;
break;
case xelis_sdk.OutgoingEntry():
direction = TransactionDirection.outgoing;
final formattedTransfers = <String>[];
for (final transfer in txType.transfers) {
final asset = transfer.asset;
if (!isAssetEnabled(asset)) continue;
final meta = await wallet.getAssetMetadata(asset: asset);
final formatted = XelisTransfer(meta: meta, amount: transfer.amount).format();
assetDecimals[asset] = meta.decimals;
assetSymbolsMap[asset] = meta.ticker;
assetAmounts[asset] = (assetAmounts[asset] ?? BigInt.zero) + BigInt.from(transfer.amount);
if (txType.transfers.length > 1) {
formattedTransfers.add("${transfer.destination} [ $formatted ]");
} else {
formattedTransfers.add("${transfer.destination}");
}
}
to = formattedTransfers.join('\n\n');
fee = BigInt.from(txType.fee);
break;
case xelis_sdk.BurnEntry():
direction = TransactionDirection.outgoing;
final asset = txType.asset;
final meta = await wallet.getAssetMetadata(asset: asset);
if (!isAssetEnabled(asset)) {
break;
}
assetAmounts[asset] = BigInt.from(txType.amount);
assetDecimals[asset] = meta.decimals;
assetSymbolsMap[asset] = meta.ticker;
to = "Burned";
fee = BigInt.from(txType.fee);
break;
case xelis_sdk.CoinbaseEntry():
direction = TransactionDirection.incoming;
final meta = await wallet.getAssetMetadata(asset: xelis_sdk.xelisAsset);
assetAmounts[xelis_sdk.xelisAsset] = BigInt.from(txType.reward);
assetDecimals[xelis_sdk.xelisAsset] = meta.decimals;
assetSymbolsMap[xelis_sdk.xelisAsset] = meta.ticker;
break;
case xelis_sdk.InvokeContractEntry():
direction = TransactionDirection.outgoing;
for (final entry in txType.deposits.entries) {
final asset = entry.key;
final amount = entry.value;
if (!isAssetEnabled(asset)) {
continue;
}
assetAmounts[asset] = (assetAmounts[asset] ?? BigInt.zero) + BigInt.from(amount);
final meta = await wallet.getAssetMetadata(asset: asset);
assetDecimals[asset] = meta.decimals;
assetSymbolsMap[asset] = meta.ticker;
}
fee = BigInt.from(txType.fee);
to = "SCID:\n${txType.contract}\n\nChunk ID:\n${txType.chunkId}";
break;
case xelis_sdk.DeployContractEntry():
direction = TransactionDirection.outgoing;
final meta = await wallet.getAssetMetadata(asset: xelis_sdk.xelisAsset);
assetAmounts[xelis_sdk.xelisAsset] = BigInt.zero;
assetDecimals[xelis_sdk.xelisAsset] = meta.decimals;
assetSymbolsMap[xelis_sdk.xelisAsset] = meta.ticker;
fee = BigInt.from(txType.fee);
default:
direction = TransactionDirection.outgoing;
break;
}
final filteredAssetIds = assetAmounts.keys.where(isAssetEnabled).toList();
final assetIds = filteredAssetIds;
final assetSymbols = assetIds.map((id) => assetSymbolsMap[id] ?? '???').toList();
final decimals = assetIds.map((id) => assetDecimals[id] ?? 8).toList();
final amounts = assetIds.map((id) => assetAmounts[id]!).toList();
final xelAmount = assetAmounts[xelis_sdk.xelisAsset] ?? BigInt.zero;
return XelisTransactionInfo(
id: entry.hash,
height: entry.topoheight,
direction: direction,
date: entry.timestamp ?? DateTime.now(),
xelAmount: xelAmount,
xelFee: fee,
to: to,
from: from,
decimals: decimals,
assetSymbols: assetSymbols,
assetIds: assetIds,
assetAmounts: amounts,
isTestnet: isTestnet
);
}
factory XelisTransactionInfo.fromJson(
Map<String, dynamic> data, {
required bool Function(String assetId) isAssetEnabled,
}) {
final allAssetIds = List<String>.from(data['assetIds']);
final allAssetSymbols = List<String>.from(data['assetSymbols']);
final allAssetAmounts = (data['assetAmounts'] as List)
.map<BigInt>((val) => BigInt.parse(val.toString()))
.toList();
final allDecimals = List<int>.from(data['decimals']);
final filteredIndices = <int>[];
for (int i = 0; i < allAssetIds.length; i++) {
if (isAssetEnabled(allAssetIds[i])) {
filteredIndices.add(i);
}
}
final assetIds = filteredIndices.map((i) => allAssetIds[i]).toList();
final assetSymbols = filteredIndices.map((i) => allAssetSymbols[i]).toList();
final assetAmounts = filteredIndices.map((i) => allAssetAmounts[i]).toList();
final decimals = filteredIndices.map((i) => allDecimals[i]).toList();
final xelAmount = assetAmounts.isNotEmpty ? assetAmounts[0] : BigInt.zero;
return XelisTransactionInfo(
id: data['id'] as String,
height: data['height'] as int,
decimals: decimals,
assetAmounts: assetAmounts,
xelAmount: xelAmount,
xelFee: BigInt.parse(data['xelFee']),
direction: parseTransactionDirectionFromInt(data['direction'] as int),
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
assetSymbols: assetSymbols,
assetIds: assetIds,
to: data['to'],
from: data['from'],
isTestnet: data['isTestnet'],
);
}
Map<String, dynamic> toJson() => {
'id': id,
'height': height,
'decimals': decimals,
'assetSymbols': assetSymbols,
'assetIds': assetIds,
'assetAmounts': assetAmounts.map((e) => e.toString()).toList(),
'xelAmount': xelAmount.toString(),
'xelFee': xelFee.toString(),
'direction': direction.index,
'date': date.millisecondsSinceEpoch,
'to': to,
'from': from,
'isTestnet': isTestnet,
};
}

View file

@ -0,0 +1,48 @@
import 'package:cw_core/transaction_priority.dart';
class XelisTransactionPriority extends TransactionPriority {
const XelisTransactionPriority({required String title, required int raw})
: super(title: title, raw: raw);
static const List<XelisTransactionPriority> all = [medium];
static const XelisTransactionPriority slow = XelisTransactionPriority(title: 'Slow', raw: 1);
static const XelisTransactionPriority medium =
XelisTransactionPriority(title: 'Medium', raw: 2);
static const XelisTransactionPriority fast = XelisTransactionPriority(title: 'Fast', raw: 4);
static XelisTransactionPriority deserialize({required int raw}) {
switch (raw) {
case 1:
return slow;
case 2:
return medium;
case 4:
return fast;
default:
throw Exception('Unexpected token: $raw for XelisTransactionPriority deserialize');
}
}
String get units => 'atom';
@override
String toString() {
var label = '';
switch (this) {
case XelisTransactionPriority.slow:
label = 'Slow';
break;
case XelisTransactionPriority.medium:
label = 'Standard';
break;
case XelisTransactionPriority.fast:
label = 'Fast';
break;
default:
break;
}
return label;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,61 @@
import 'package:mobx/mobx.dart';
import 'package:cw_core/wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/address_info.dart';
import 'package:cw_xelis/src/api/wallet.dart' as x_wallet;
part 'xelis_wallet_addresses.g.dart';
class XelisWalletAddresses = XelisWalletAddressesBase with _$XelisWalletAddresses;
abstract class XelisWalletAddressesBase extends WalletAddresses with Store {
XelisWalletAddressesBase(this.walletInfo, this.wallet)
: super(walletInfo);
final WalletInfo walletInfo;
final x_wallet.XelisWallet wallet;
@observable
String selectedAddr = '';
@override
@computed
String get address {
return selectedAddr;
}
@override
set address(String addr) {
selectedAddr = addr;
}
@override
Future<void> init() async {
address = wallet.getAddressStr();
addressesMap[address] = '';
addressInfos[0] = [
AddressInfo(
address: address,
label: '',
accountIndex: 0,
)
];
usedAddresses.add(address);
await saveAddressesInBox();
}
@override
Future<void> updateAddressesInBox() async {}
List<AddressInfo> getAddressInfos() => addressInfos[0] ?? [];
Future<void> updateAddress(String addr, String label) async {
final infos = addressInfos[0];
if (infos == null) return;
for (var info in infos) {
if (info.address == addr) {
info.label = label;
}
}
await saveAddressesInBox();
}
}

View file

@ -0,0 +1,15 @@
import 'package:cw_core/wallet_credentials.dart';
class XelisNewWalletCredentials extends WalletCredentials {
XelisNewWalletCredentials(
{required String name, String? password})
: super(name: name, password: password);
}
class XelisRestoreWalletFromSeedCredentials extends WalletCredentials {
XelisRestoreWalletFromSeedCredentials(
{required String name, required this.mnemonic, int height = 0, String? password})
: super(name: name, password: password, height: height);
final String mnemonic;
}

View file

@ -0,0 +1,497 @@
import 'dart:io';
import 'dart:async';
import 'package:system_info2/system_info2.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/root_dir.dart';
import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_xelis/xelis_wallet.dart';
import 'package:cw_xelis/src/api/network.dart';
import 'package:cw_xelis/src/api/wallet.dart' as x_wallet;
import 'package:cw_xelis/xelis_wallet_creation_credentials.dart';
import 'package:cw_xelis/xelis_store_utils.dart';
import 'package:cw_xelis/src/api/logger.dart' as x_logger;
import 'package:cw_xelis/src/api/api.dart' as x_api;
import 'package:collection/collection.dart';
import 'package:hive/hive.dart';
import 'package:flutter/foundation.dart';
import 'package:mutex/mutex.dart';
class MemoryTierCalculator {
Future<int> getDeviceRAMInGB() async {
if (kIsWeb) return 2; // Default for web
try {
final totalRAM = SysInfo.getTotalPhysicalMemory();
// Convert bytes to GB (1 GB = 1024³ bytes)
final ramGB = totalRAM / (1024 * 1024 * 1024);
return ramGB.round();
} catch (e) {
print('Error getting RAM info: $e');
return 4; // Default fallback
}
}
}
class XelisLoggerFactory {
static bool _isSetup = false;
static const LOG_LEVEL = 3;
/*
Log level for FFI Rust outputs in xelis_flutter
0: None
1: Error
2: Warn
3: Info
4: Debug
5: Trace
*/
static Future<void> setupIfNeeded() async {
if (_isSetup) return;
await x_api.setUpRustLogger();
_setupLogStream();
_isSetup = true;
}
static void _setupLogStream() {
x_api.createLogStream().listen((entry) {
final logLine = 'XELIS LOG | [${entry.level.name}] ${entry.tag}: ${entry.msg}';
switch (entry.level) {
case x_logger.Level.error:
if (LOG_LEVEL > 0) {
printV('$logLine');
}
break;
case x_logger.Level.warn:
if (LOG_LEVEL > 1) {
printV('⚠️ $logLine');
}
break;
case x_logger.Level.info:
if (LOG_LEVEL > 2) {
printV(' $logLine');
}
break;
case x_logger.Level.debug:
if (LOG_LEVEL > 3) {
printV('🐛 $logLine');
}
break;
case x_logger.Level.trace:
if (LOG_LEVEL > 4) {
printV('🔍 $logLine');
}
break;
}
},
onError: (dynamic e) {
printV("Error receiving Xelis Rust logs: $e");
});
}
}
enum XelisTableSize {
initial,
web,
low,
medium,
high;
BigInt get l1Size {
switch (this) {
case XelisTableSize.initial:
case XelisTableSize.web:
return BigInt.from(23);
case XelisTableSize.low:
return BigInt.from(24);
case XelisTableSize.medium:
return BigInt.from(25);
case XelisTableSize.high:
return BigInt.from(26);
}
}
static Future<XelisTableSize> getPlatformDefault() async {
if (kIsWeb) {
return XelisTableSize.web;
}
final calculator = MemoryTierCalculator();
final ramInGB = await calculator.getDeviceRAMInGB();
if (ramInGB <= 2) {
return XelisTableSize.web;
} else if (ramInGB <= 4) {
return XelisTableSize.low;
} else if (ramInGB <= 8) {
return XelisTableSize.medium;
} else {
return XelisTableSize.high;
}
}
}
bool get kIsMobile {
if (kIsWeb) return false;
return Platform.isAndroid || Platform.isIOS;
}
Future<BigInt> getTableSize() async {
final tableSize = await XelisTableSize.getPlatformDefault();
return tableSize.l1Size;
}
class XelisTableState {
final XelisTableSize currentSize;
final XelisTableSize _desiredSize;
XelisTableSize get desiredSize {
if (kIsWeb) {
return XelisTableSize.low;
}
return _desiredSize;
}
const XelisTableState({
this.currentSize = XelisTableSize.low,
XelisTableSize desiredSize = XelisTableSize.high,
}) : _desiredSize = desiredSize;
XelisTableState copyWith({
XelisTableSize? currentSize,
XelisTableSize? desiredSize,
}) {
return XelisTableState(
currentSize: currentSize ?? this.currentSize,
desiredSize: kIsWeb ? XelisTableSize.low : (desiredSize ?? this._desiredSize),
);
}
factory XelisTableState.fromJson(Map<String, dynamic> json) {
return XelisTableState(
currentSize: XelisTableSize.values[json['currentSize'] as int],
desiredSize: XelisTableSize.values[json['desiredSize'] as int],
);
}
Map<String, dynamic> toJson() => {
'currentSize': currentSize.index,
'desiredSize': _desiredSize.index,
};
}
class XelisWalletService extends WalletService<
XelisNewWalletCredentials,
XelisRestoreWalletFromSeedCredentials, // TODO: add view key credentials when supported by Xelis
XelisNewWalletCredentials,
XelisNewWalletCredentials
> {
XelisWalletService(this.walletInfoSource, {required this.isDirect}) {
XelisLoggerFactory.setupIfNeeded();
}
final Box<WalletInfo> walletInfoSource;
final bool isDirect;
static bool isGenerating = false;
static final _tableUpgradeMutex = Mutex();
static Completer<void>? _tableUpgradeCompleter;
static XelisWallet? _activeWallet;
@override
WalletType getType() => WalletType.xelis;
@override
Future<bool> isWalletExit(String name) async =>
await File(await pathForWalletDir(name: name, type: getType())).exists();
Future<void> _closeActiveWalletIfNeeded() async {
if (_activeWallet != null) {
try {
await _activeWallet!.close();
} catch (e) {
printV("Error closing active Xelis wallet: $e");
}
_activeWallet = null;
}
}
Future<XelisTableState> _getTableState() async {
final tablesPath = await _getTablePath();
final tablesDir = Directory(tablesPath);
final desiredSize = await XelisTableSize.getPlatformDefault();
final files = await tablesDir.list().toList();
// Check for the device-appropriate full table
final expectedFullTableName = 'tables_${desiredSize.l1Size}.bin';
final hasFullTables = files.any((file) =>
file is File && file.path.contains(expectedFullTableName)
);
final hasLowTables = files.isNotEmpty;
final currentSize = hasFullTables
? desiredSize
: hasLowTables
? XelisTableSize.initial
: XelisTableSize.initial;
return XelisTableState(
currentSize: currentSize,
desiredSize: desiredSize,
);
}
Future<String> _getTablePath() async {
final root = await getAppDir();
final prefix = walletTypeToString(getType()).toLowerCase();
final tablesDir = Directory('${root.path}/wallets/$prefix/tables/');
if (!await tablesDir.exists()) {
await tablesDir.create(recursive: true);
}
return tablesDir.path;
}
@override
Future<XelisWallet> create(XelisNewWalletCredentials credentials, {bool? isTestnet}) async {
final fullPath = await pathForWalletDir(name: credentials.name, type: getType());
final tableState = await _getTableState();
final tablesRoot = await _getTablePath();
final network = isTestnet == true ? Network.testnet : Network.mainnet;
await _closeActiveWalletIfNeeded();
final frbWallet = await x_wallet.createXelisWallet(
name: fullPath,
directory: "",
password: credentials.password ?? "x",
network: network,
precomputedTablesPath: tablesRoot,
l1Size: (await _getTableState()).currentSize.l1Size,
);
credentials.walletInfo!.address = frbWallet.getAddressStr();
credentials.walletInfo!.network = network.name;
final wallet = XelisWallet(
walletInfo:credentials.walletInfo!,
libWallet: frbWallet,
password: credentials.password ?? "x",
network: network,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
);
await wallet.init();
await wallet.save();
unawaited(_upgradeTablesIfNeeded());
_activeWallet = wallet;
return wallet;
}
@override
Future<XelisWallet> openWallet(String name, String password) async {
final walletInfo = walletInfoSource.values
.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
final fullPath = await pathForWalletDir(name: name, type: getType());
final tableState = await _getTableState();
final tablesRoot = await _getTablePath();
late final Network network;
if (walletInfo?.network != null) {
network = NetworkName.fromName(walletInfo!.network!);
} else {
network = await loadXelisNetwork(name);
}
await _closeActiveWalletIfNeeded();
late final x_wallet.XelisWallet frbWallet;
try {
frbWallet = await x_wallet.openXelisWallet(
name: fullPath,
directory: "",
password: password,
network: network,
precomputedTablesPath: tablesRoot,
l1Size: (await _getTableState()).currentSize.l1Size,
);
} catch (_) {
try {
await restoreWalletFilesFromBackup(name);
frbWallet = await x_wallet.openXelisWallet(
name: fullPath,
directory: "",
password: password,
network: network,
precomputedTablesPath: tablesRoot,
l1Size: (await _getTableState()).currentSize.l1Size,
);
} catch(_) {
rethrow;
}
}
final wallet = XelisWallet(
walletInfo: walletInfo,
libWallet: frbWallet,
password: password,
network: network,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
);
saveBackup(name);
await wallet.init();
await wallet.save();
unawaited(_upgradeTablesIfNeeded());
_activeWallet = wallet;
return wallet;
}
@override
Future<void> rename(String currentName, String password, String newName) async {
final currentWalletInfo = walletInfoSource.values
.firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
final fullPath = await pathForWalletDir(name: currentName, type: getType());
final newPath = await pathForWalletDir(name: newName, type: getType());
final newDir = Directory(newPath);
final exists = await newDir.exists();
if (exists) {
throw 'A wallet with this name already exists.';
}
await Directory(fullPath).rename(newPath);
await saveBackup(newName);
final newWalletInfo = currentWalletInfo;
newWalletInfo.id = WalletBase.idFor(newName, getType());
newWalletInfo.name = newName;
newWalletInfo.dirPath = await pathForWalletDir(name: newName, type: getType());
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
}
@override
Future<void> remove(String wallet) async {
File(await pathForWalletDir(name: wallet, type: getType())).deleteSync(recursive: true);
final walletInfo = walletInfoSource.values
.firstWhere((info) => info.id == WalletBase.idFor(wallet, getType()));
await walletInfoSource.delete(walletInfo.key);
}
@override
Future<XelisWallet> restoreFromSeed(XelisRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
final fullPath = await pathForWalletDir(name: credentials.walletInfo!.name, type: getType());
final tableState = await _getTableState();
final tablesRoot = await _getTablePath();
final network = isTestnet == true ? Network.testnet : Network.mainnet;
await _closeActiveWalletIfNeeded();
final frbWallet = await x_wallet.createXelisWallet(
name: fullPath,
directory: "",
password: credentials.password ?? "x",
seed: credentials.mnemonic,
network: network,
precomputedTablesPath: tablesRoot,
l1Size: (await _getTableState()).currentSize.l1Size,
);
credentials.walletInfo!.address = frbWallet.getAddressStr();
credentials.walletInfo!.network = network.name;
final wallet = XelisWallet(
walletInfo: credentials.walletInfo!,
libWallet: frbWallet,
password: credentials.password ?? "x",
network: network,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
);
await wallet.init();
await wallet.save();
unawaited(_upgradeTablesIfNeeded());
_activeWallet = wallet;
return wallet;
}
Future<void> _upgradeTablesIfNeeded() async {
if (isGenerating || kIsWeb) return;
if (_tableUpgradeCompleter != null) {
try {
await _tableUpgradeCompleter!.future;
return;
} catch (_) {
// Previous upgrade failed, try again
}
}
await _tableUpgradeMutex.protect(() async {
if (_tableUpgradeCompleter != null) {
try {
await _tableUpgradeCompleter!.future;
return;
} catch (_) {}
}
final state = await _getTableState();
if (state.currentSize == state.desiredSize) return;
_tableUpgradeCompleter = Completer<void>();
isGenerating = true;
try {
printV("Xelis: Starting background table generation...");
final tablesPath = await _getTablePath();
await x_wallet.updateTables(
precomputedTablesPath: tablesPath,
l1Size: await getTableSize(),
);
printV("Xelis: Table upgrade to ${state.desiredSize.name} complete");
_tableUpgradeCompleter?.complete();
} catch (e, s) {
printV("Xelis: Failed to generate tables, $e, $s");
_tableUpgradeCompleter?.completeError(e);
} finally {
isGenerating = false;
_tableUpgradeCompleter = null;
}
});
}
@override
Future<XelisWallet> restoreFromHardwareWallet(
XelisNewWalletCredentials credentials) async =>
throw UnimplementedError();
@override
Future<XelisWallet> restoreFromKeys(
XelisNewWalletCredentials credentials, {bool? isTestnet}) async =>
throw UnimplementedError();
}

91
cw_xelis/pubspec.yaml Normal file
View file

@ -0,0 +1,91 @@
name: cw_xelis
description: "A new Flutter plugin project."
version: 0.0.1
homepage:
publish_to: none
environment:
sdk: '>=3.6.0 <4.0.0'
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
http: ^1.1.0
path_provider: ^2.0.11
mobx: ^2.0.7+4
shared_preferences: ^2.0.15
flutter_mobx: ^2.0.6+1
intl: ^0.19.0
cw_core:
path: ../cw_core
xelis_flutter:
git:
url: https://github.com/xelis-project/xelis-flutter-ffi.git
ref: 83bda92f1b833fe5d8584aa429d5143a3698b33f
# path: ./xelis-flutter-ffi
xelis_dart_sdk: ^0.28.0
mutex: ^3.1.0
hive: ^2.2.3
system_info2: ^4.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
build_runner: ^2.4.7
mobx_codegen: ^2.3.0
hive_generator: ^2.0.1
build: ^2.4.0
path: ^1.8.3
analyzer: ^6.2.0
dependency_overrides:
watcher: ^1.1.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.
# 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/to/asset-from-package
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# 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/to/font-from-package

View file

@ -121,7 +121,7 @@ abstract class ZanoWalletBase
}
@override
int calculateEstimatedFee(TransactionPriority priority, [int? amount = null]) =>
int (TransactionPriority priority, [int? amount = null]) =>
getCurrentTxFee(priority);
@override

View file

@ -100,6 +100,15 @@ class WalletKeysAndSeedPageRobot {
commonTestCases.hasText(appStore.wallet!.privateKey!);
tester.printToConsole('$walletName wallet has private key properly displayed');
}
if (walletType == WalletType.xelis) {
if (hasSeed) {
commonTestCases.hasText(appStore.wallet!.seed);
tester.printToConsole('$walletName wallet has seeds properly displayed');
commonTestCases.hasText(appStore.wallet!.langSeed(lang.nameEnglish));
tester.printToConsole('$walletName wallet has language seed variant properly displayed');
}
}
}
await commonTestCases.defaultSleepTime(seconds: 5);

BIN
key.jks Normal file

Binary file not shown.

View file

@ -5,6 +5,7 @@ class InitialExecutionState extends ExecutionState {}
class LoadingTemplateExecutingState extends ExecutionState {}
class IsExecutingState extends ExecutionState {}
class IsLoadingState extends ExecutionState {}
class ExecutedSuccessfullyState extends ExecutionState {
ExecutedSuccessfullyState({this.payload});

View file

@ -10,6 +10,7 @@ import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/xelis/xelis.dart';
import 'package:cake_wallet/utils/language_list.dart';
import 'package:cw_core/wallet_type.dart';
@ -50,6 +51,8 @@ class SeedValidator extends Validator<MnemonicItem> {
return zano!.getWordList(language);
case WalletType.decred:
return decred!.getDecredWordList();
case WalletType.xelis:
return xelis!.getXelisWordList(language);
case WalletType.none:
case WalletType.haven:
return [];

View file

@ -91,6 +91,7 @@ class WalletCreationService {
case WalletType.banano:
case WalletType.zano:
case WalletType.decred:
case WalletType.xelis:
return false;
}
}

View file

@ -64,6 +64,7 @@ class WalletLoadingService {
}
final walletService = walletServiceFactory.call(type);
final walletPassword = password ?? (await keyService.getWalletPassword(walletName: name));
final wallet = await walletService.openWallet(name, walletPassword);
if (type == WalletType.monero) {
@ -93,6 +94,7 @@ class WalletLoadingService {
try {
final walletService = walletServiceFactory.call(walletInfo.type);
final walletPassword = await keyService.getWalletPassword(walletName: walletInfo.name);
wallet = await walletService.openWallet(walletInfo.name, walletPassword);
if (walletInfo.type == WalletType.monero) {

View file

@ -80,6 +80,7 @@ import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/xelis/xelis.dart';
import 'package:cake_wallet/reactions/on_authentication_state_change.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/solana/solana.dart';
@ -1153,6 +1154,8 @@ Future<void> setup({
return zano!.createZanoWalletService(_walletInfoSource);
case WalletType.decred:
return decred!.createDecredWalletService(_walletInfoSource, _unspentCoinsInfoSource);
case WalletType.xelis:
return xelis!.createXelisWalletService(_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput);
case WalletType.haven:
return HavenWalletService(_walletInfoSource);
case WalletType.none:

View file

@ -46,6 +46,8 @@ const wowneroDefaultNodeUri = 'node3.monerodevs.org:34568';
const zanoDefaultNodeUri = 'zano.cakewallet.com:11211';
const moneroWorldNodeUri = '.moneroworld.com';
const decredDefaultUri = "default-spv-nodes";
const xelisDefaultUri = "us-node.xelis.io";
const xelisTestnetUri = "testnet-node.xelis.io";
Future<void> defaultSettingsMigration(
{required int version,
@ -511,6 +513,17 @@ Future<void> defaultSettingsMigration(
enabled: true,
);
break;
case 50:
await _fixNodesUseSSLFlag(nodes);
await addWalletNodeList(nodes: nodes, type: WalletType.xelis);
await _changeDefaultNode(
nodes: nodes,
sharedPreferences: sharedPreferences,
type: WalletType.xelis,
currentNodePreferenceKey: PreferencesKey.currentXelisNodeIdKey,
useSSL: true,
);
break;
default:
break;
}
@ -617,6 +630,8 @@ String _getDefaultNodeUri(WalletType type) {
return zanoDefaultNodeUri;
case WalletType.decred:
return decredDefaultUri;
case WalletType.xelis:
return xelisDefaultUri;
case WalletType.banano:
case WalletType.none:
return '';
@ -807,6 +822,12 @@ Node? getBitcoinTestnetDefaultElectrumServer({required Box<Node> nodes}) {
nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoin);
}
Node? getXelisTestnetDefault({required Box<Node> nodes}) {
return nodes.values
.firstWhereOrNull((Node node) => node.uriRaw == xelisTestnetUri) ??
nodes.values.firstWhereOrNull((node) => node.type == WalletType.xelis);
}
Node? getDefaultNode({required Box<Node> nodes, required WalletType type}) {
final defaultUri = _getDefaultNodeUri(type);
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == defaultUri) ??
@ -1045,6 +1066,7 @@ Future<void> checkCurrentNodes(
final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
final currentDecredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey);
final currentXelisNodeId = sharedPreferences.getInt(PreferencesKey.currentXelisNodeIdKey);
final currentBitcoinCashNodeId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
final currentSolanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey);
@ -1079,6 +1101,8 @@ Future<void> checkCurrentNodes(
nodeSource.values.firstWhereOrNull((node) => node.key == currentWowneroNodeId);
final currentZanoNode =
nodeSource.values.firstWhereOrNull((node) => node.key == currentZanoNodeId);
final currentXelisNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentXelisNodeId);
if (currentMoneroNode == null) {
final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
@ -1175,6 +1199,15 @@ Future<void> checkCurrentNodes(
await nodeSource.add(node);
await sharedPreferences.setInt(PreferencesKey.currentDecredNodeIdKey, node.key as int);
}
if (currentXelisNodeServer == null) {
final node = Node(uri: xelisDefaultUri, type: WalletType.xelis);
await nodeSource.add(node);
final xelisTestnet =
Node(uri: xelisTestnetUri, type: WalletType.xelis, useSSL: true);
await nodeSource.add(xelisTestnet);
await sharedPreferences.setInt(PreferencesKey.currentXelisNodeIdKey, node.key as int);
}
}
Future<void> resetBitcoinElectrumServer(

View file

@ -46,6 +46,9 @@ Future<List<Node>> loadDefaultNodes(WalletType type) async {
case WalletType.decred:
path = 'assets/decred_node_list.yml';
break;
case WalletType.xelis:
path = 'assets/xelis_node_list.yml';
break;
case WalletType.banano:
case WalletType.none:
path = '';
@ -96,6 +99,7 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
final tronNodes = await loadDefaultNodes(WalletType.tron);
final decredNodes = await loadDefaultNodes(WalletType.decred);
final zanoNodes = await loadDefaultNodes(WalletType.zano);
final xelisNodes = await loadDefaultNodes(WalletType.xelis);
final nodes = moneroNodes +
bitcoinElectrumServerList +
@ -108,7 +112,8 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
solanaNodes +
tronNodes +
zanoNodes +
decredNodes;
decredNodes +
xelisNodes;
await nodeSource.clear();
await nodeSource.addAll(nodes);

View file

@ -19,6 +19,7 @@ class PreferencesKey {
static const currentSolanaNodeIdKey = 'current_node_id_sol';
static const currentTronNodeIdKey = 'current_node_id_trx';
static const currentWowneroNodeIdKey = 'current_node_id_wow';
static const currentXelisNodeIdKey = 'current_node_id_xelis';
static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
static const shouldSaveRecipientAddressKey = 'save_recipient_address';
@ -52,6 +53,7 @@ class PreferencesKey {
static const zanoTransactionPriority = 'current_fee_priority_zano';
static const wowneroTransactionPriority = 'current_fee_priority_wownero';
static const decredTransactionPriority = 'current_fee_priority_decred';
static const xelisTransactionPriority = 'current_fee_priority_xelis';
static const customBitcoinFeeRate = 'custom_electrum_fee_rate';
static const silentPaymentsCardDisplay = 'silentPaymentsCardDisplay';
static const silentPaymentsAlwaysScan = 'silentPaymentsAlwaysScan';

View file

@ -6,6 +6,7 @@ import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/xelis/xelis.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_type.dart';
@ -35,6 +36,8 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
return zano!.getTransactionPriorities();
case WalletType.decred:
return decred!.getTransactionPriorities();
case WalletType.xelis:
return xelis!.getTransactionPriorities();
case WalletType.none:
case WalletType.haven:
return [];

View file

@ -52,6 +52,8 @@ import 'package:cw_core/window_size.dart';
import 'package:logging/logging.dart';
import 'package:cake_wallet/core/trade_monitor.dart';
import 'frb_init.g.dart';
final navigatorKey = GlobalKey<NavigatorState>();
final rootKey = GlobalKey<RootState>();
final RouteObserver<PageRoute<dynamic>> routeObserver = RouteObserver<PageRoute<dynamic>>();
@ -63,6 +65,7 @@ Future<void> main({Key? topLevelKey}) async {
Future<void> runAppWithZone({Key? topLevelKey}) async {
bool isAppRunning = false;
await frb_init();
await runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized();
FlutterError.onError = ExceptionHandler.onError;
@ -228,7 +231,7 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
payjoinSessionSource: payjoinSessionSource,
anonpayInvoiceInfo: anonpayInvoiceInfo,
havenSeedStore: havenSeedStore,
initialMigrationVersion: 49,
initialMigrationVersion: 50,
);
}

View file

@ -17,6 +17,7 @@ bool isBIP39Wallet(WalletType walletType) {
case WalletType.haven:
case WalletType.zano:
case WalletType.decred:
case WalletType.xelis:
case WalletType.none:
return false;
}

View file

@ -8,6 +8,7 @@ import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/xelis/xelis.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
@ -54,6 +55,11 @@ Future<void> startFiatRateUpdate(
tron!.getTronTokenCurrencies(appStore.wallet!).where((element) => element.enabled);
}
if (appStore.wallet!.type == WalletType.xelis) {
currencies =
xelis!.getXelisAssets(appStore.wallet!).where((element) => element.enabled);
}
if (currencies != null) {
for (final currency in currencies) {
// skip potential scams:

View file

@ -7,6 +7,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/xelis/xelis.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/balance.dart';
@ -132,6 +133,10 @@ void startCurrentWalletChangeReaction(
currencies =
tron!.getTronTokenCurrencies(appStore.wallet!).where((element) => element.enabled);
}
if (wallet.type == WalletType.xelis) {
currencies =
xelis!.getXelisAssets(appStore.wallet!).where((element) => element.enabled);
}
if (currencies != null) {
for (final currency in currencies) {

View file

@ -45,6 +45,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
final wowneroIcon = Image.asset('assets/images/wownero_icon.png', height: 24, width: 24);
final zanoIcon = Image.asset('assets/images/zano_icon.png', height: 24, width: 24);
final decredIcon = Image.asset('assets/images/decred_icon.png', height: 24, width: 24);
final xelisIcon = Image.asset('assets/images/xelis_icon.png', height: 24, width: 24);
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
Image _newWalletImage(BuildContext context) => Image.asset(
@ -181,6 +182,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
return zanoIcon;
case WalletType.decred:
return decredIcon;
case WalletType.xelis:
return xelisIcon;
default:
return nonWalletTypeIcon;
}

View file

@ -37,7 +37,8 @@ class MenuWidgetState extends State<MenuWidget> {
this.tronIcon = Image.asset('assets/images/trx_icon.png'),
this.wowneroIcon = Image.asset('assets/images/wownero_icon.png'),
this.zanoIcon = Image.asset('assets/images/zano_icon.png'),
this.decredIcon = Image.asset('assets/images/decred_menu.png');
this.decredIcon = Image.asset('assets/images/decred_menu.png'),
this.xelisIcon = Image.asset('assets/images/xelis_icon.png');
final largeScreen = 731;
@ -64,6 +65,7 @@ class MenuWidgetState extends State<MenuWidget> {
Image wowneroIcon;
Image zanoIcon;
Image decredIcon;
Image xelisIcon;
@override
void initState() {
@ -255,6 +257,8 @@ class MenuWidgetState extends State<MenuWidget> {
return zanoIcon;
case WalletType.decred:
return decredIcon;
case WalletType.xelis:
return xelisIcon;
default:
throw Exception('No icon for ${type.toString()}');
}

View file

@ -275,7 +275,8 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
);
}),
if (widget.privacySettingsViewModel.type == WalletType.bitcoin ||
widget.privacySettingsViewModel.type == WalletType.decred)
widget.privacySettingsViewModel.type == WalletType.decred ||
widget.privacySettingsViewModel.type == WalletType.xelis)
Builder(builder: (_) {
final val = testnetValue ?? false;
return SettingsSwitcherCell(

View file

@ -24,7 +24,7 @@ class RescanPage extends BasePage {
@override
Widget body(BuildContext context) {
Widget child;
if (_rescanViewModel.wallet.type != WalletType.decred) {
if (_rescanViewModel.wallet.type != WalletType.decred && _rescanViewModel.wallet.type != WalletType.xelis) {
child = Padding(
padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
child: Column(

View file

@ -584,6 +584,7 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody>
if (seedWords.length == 14 && walletRestoreViewModel.type == WalletType.wownero) return true;
if (seedWords.length == 26 && walletRestoreViewModel.type == WalletType.zano) return true;
if (seedWords.length == 25 && walletRestoreViewModel.type == WalletType.xelis) return true;
if (seedWords.length == 12 && walletRestoreViewModel.type == WalletType.monero) {
return walletRestoreFromSeedFormKey.currentState?.blockchainHeightKey.currentState

View file

@ -129,6 +129,7 @@ class WalletListBodyState extends State<WalletListBody> {
final tronIcon = Image.asset('assets/images/trx_icon.png', height: 24, width: 24);
final wowneroIcon = Image.asset('assets/images/wownero_icon.png', height: 24, width: 24);
final zanoIcon = Image.asset('assets/images/zano_icon.png', height: 24, width: 24);
final xelisIcon = Image.asset('assets/images/xelis_icon.png', height: 24, width: 24);
final scrollController = ScrollController();
final double tileHeight = 60;
Flushbar<void>? _progressBar;

View file

@ -56,6 +56,13 @@ class WalletUnlockPageState extends AuthPageState<WalletUnlockPage> {
});
}
if (state is IsLoadingState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
// null duration to make it indefinite until its disposed
_authBar = createBar<void>(S.of(context).loading_wallet, context, duration: null)..show(context);
});
}
if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
dismissFlushBar(_authBar);

View file

@ -5,6 +5,7 @@ import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/utilities.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/xelis/xelis.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:cake_wallet/di.dart';
@ -137,6 +138,7 @@ abstract class SettingsStoreBase with Store {
TransactionPriority? initialBitcoinCashTransactionPriority,
TransactionPriority? initialZanoTransactionPriority,
TransactionPriority? initialDecredTransactionPriority,
TransactionPriority? initialXelisTransactionPriority,
Country? initialCakePayCountry})
: nodes = ObservableMap<WalletType, Node>.of(nodes),
powNodes = ObservableMap<WalletType, Node>.of(powNodes),
@ -225,6 +227,9 @@ abstract class SettingsStoreBase with Store {
if (initialDecredTransactionPriority != null) {
priority[WalletType.decred] = initialDecredTransactionPriority;
}
if (initialXelisTransactionPriority != null) {
priority[WalletType.xelis] = initialXelisTransactionPriority;
}
if (initialCakePayCountry != null) {
selectedCakePayCountry = initialCakePayCountry;
@ -283,6 +288,9 @@ abstract class SettingsStoreBase with Store {
case WalletType.decred:
key = PreferencesKey.decredTransactionPriority;
break;
case WalletType.xelis:
key = PreferencesKey.xelisTransactionPriority;
break;
default:
key = null;
}
@ -912,6 +920,7 @@ abstract class SettingsStoreBase with Store {
TransactionPriority? wowneroTransactionPriority;
TransactionPriority? zanoTransactionPriority;
TransactionPriority? decredTransactionPriority;
TransactionPriority? xelisTransactionPriority;
if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) {
havenTransactionPriority = monero?.deserializeMoneroTransactionPriority(
@ -945,6 +954,10 @@ abstract class SettingsStoreBase with Store {
decredTransactionPriority = decred?.deserializeDecredTransactionPriority(
sharedPreferences.getInt(PreferencesKey.decredTransactionPriority)!);
}
if (sharedPreferences.getInt(PreferencesKey.xelisTransactionPriority) != null) {
xelisTransactionPriority = xelis?.deserializeXelisTransactionPriority(
sharedPreferences.getInt(PreferencesKey.xelisTransactionPriority)!);
}
moneroTransactionPriority ??= monero?.getDefaultTransactionPriority();
bitcoinTransactionPriority ??= bitcoin?.getMediumTransactionPriority();
@ -954,6 +967,7 @@ abstract class SettingsStoreBase with Store {
bitcoinCashTransactionPriority ??= bitcoinCash?.getDefaultTransactionPriority();
wowneroTransactionPriority ??= wownero?.getDefaultTransactionPriority();
decredTransactionPriority ??= decred?.getDecredTransactionPriorityMedium();
xelisTransactionPriority ??= xelis?.getXelisTransactionPriorityMedium();
polygonTransactionPriority ??= polygon?.getDefaultTransactionPriority();
zanoTransactionPriority ??= zano?.getDefaultTransactionPriority();
@ -1050,6 +1064,7 @@ abstract class SettingsStoreBase with Store {
final wowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey);
final zanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey);
final decredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey);
final xelisNodeId = sharedPreferences.getInt(PreferencesKey.currentXelisNodeIdKey);
/// get the selected node, if null, then use the default
final moneroNode = nodeSource.get(nodeId) ??
@ -1068,6 +1083,8 @@ abstract class SettingsStoreBase with Store {
nodeSource.values.firstWhereOrNull((e) => e.uriRaw == nanoDefaultNodeUri);
final decredNode = nodeSource.get(decredNodeId) ??
nodeSource.values.firstWhereOrNull((e) => e.uriRaw == decredDefaultUri);
final xelisNode = nodeSource.get(xelisNodeId) ??
nodeSource.values.firstWhereOrNull((e) => e.uriRaw == xelisDefaultUri);
final nanoPowNode = powNodeSource.get(nanoPowNodeId) ??
nodeSource.values.firstWhereOrNull((e) => e.uriRaw == nanoDefaultPowNodeUri);
final solanaNode = nodeSource.get(solanaNodeId) ??
@ -1164,6 +1181,10 @@ abstract class SettingsStoreBase with Store {
nodes[WalletType.decred] = decredNode;
}
if (xelisNode != null) {
nodes[WalletType.xelis] = xelisNode;
}
final savedSyncMode = SyncMode.all.firstWhere((element) {
return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 2); // default to 2 - daily sync
});
@ -1334,6 +1355,7 @@ abstract class SettingsStoreBase with Store {
initialLitecoinTransactionPriority: litecoinTransactionPriority,
initialBitcoinCashTransactionPriority: bitcoinCashTransactionPriority,
initialDecredTransactionPriority: decredTransactionPriority,
initialXelisTransactionPriority: xelisTransactionPriority,
initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet,
initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact,
initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact,
@ -1412,6 +1434,11 @@ abstract class SettingsStoreBase with Store {
priority[WalletType.decred] = decred!.deserializeDecredTransactionPriority(
sharedPreferences.getInt(PreferencesKey.decredTransactionPriority)!);
}
if (xelis != null &&
sharedPreferences.getInt(PreferencesKey.xelisTransactionPriority) != null) {
priority[WalletType.xelis] = xelis!.deserializeXelisTransactionPriority(
sharedPreferences.getInt(PreferencesKey.xelisTransactionPriority)!);
}
final generateSubaddresses =
sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey);
@ -1522,6 +1549,7 @@ abstract class SettingsStoreBase with Store {
final wowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey);
final zanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey);
final decredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey);
final xelisNodeId = sharedPreferences.getInt(PreferencesKey.currentXelisNodeIdKey);
final moneroNode = nodeSource.get(nodeId);
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
@ -1535,6 +1563,7 @@ abstract class SettingsStoreBase with Store {
final wowneroNode = nodeSource.get(wowneroNodeId);
final zanoNode = nodeSource.get(zanoNodeId);
final decredNode = nodeSource.get(decredNodeId);
final xelisNode = nodeSource.get(xelisNodeId);
if (moneroNode != null) {
nodes[WalletType.monero] = moneroNode;
@ -1589,6 +1618,9 @@ abstract class SettingsStoreBase with Store {
nodes[WalletType.decred] = decredNode;
}
if (xelisNode != null) {
nodes[WalletType.xelis] = xelisNode;
}
// MIGRATED:
useTOTP2FA = await SecureKey.getBool(
@ -1728,6 +1760,9 @@ abstract class SettingsStoreBase with Store {
case WalletType.decred:
await _sharedPreferences.setInt(PreferencesKey.currentDecredNodeIdKey, node.key as int);
break;
case WalletType.xelis:
await _sharedPreferences.setInt(PreferencesKey.currentXelisNodeIdKey, node.key as int);
break;
case WalletType.zano:
await _sharedPreferences.setInt(PreferencesKey.currentZanoNodeIdKey, node.key as int);
default:

View file

@ -11,7 +11,103 @@ class AddressFormatter {
TextAlign? textAlign,
bool shouldTruncate = false,
}) {
// Check for parentheses in the address
final bracketIndex = address.indexOf('[');
if (bracketIndex != -1) {
// Split address and amount parts
final addressPart = address.substring(0, bracketIndex).trim();
final amountPart = address.substring(bracketIndex);
// For truncated addresses, handle differently
if (shouldTruncate) {
final addressWidget = _buildAddressWidget(
address: addressPart,
walletType: walletType,
evenTextStyle: evenTextStyle,
oddTextStyle: oddTextStyle,
textAlign: textAlign,
shouldTruncate: shouldTruncate,
);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
addressWidget,
Text(amountPart, style: evenTextStyle),
],
);
}
// For full addresses, integrate amount with last line
final cleanAddress = addressPart.replaceAll('bitcoincash:', '');
final isMWEB = addressPart.startsWith('ltcmweb');
final chunkSize = walletType != null ? _getChunkSize(walletType) : 4;
// Build chunks
final chunks = <String>[];
if (isMWEB) {
const mwebDisplayPrefix = 'ltcmweb';
chunks.add(mwebDisplayPrefix);
final startIndex = mwebDisplayPrefix.length;
for (int i = startIndex; i < cleanAddress.length; i += chunkSize) {
final chunk = cleanAddress.substring(
i,
math.min(i + chunkSize, cleanAddress.length),
);
chunks.add(chunk);
}
} else {
for (int i = 0; i < cleanAddress.length; i += chunkSize) {
final chunk = cleanAddress.substring(
i,
math.min(i + chunkSize, cleanAddress.length),
);
chunks.add(chunk);
}
}
// Build text spans with amount appended to last chunk
final spans = <TextSpan>[];
for (int i = 0; i < chunks.length; i++) {
final style = (i % 2 == 0) ? evenTextStyle : oddTextStyle ?? evenTextStyle.copyWith(color: evenTextStyle.color!.withAlpha(128));
if (i == chunks.length - 1) {
// Last chunk - append amount
spans.add(TextSpan(text: '${chunks[i]} ', style: style));
spans.add(TextSpan(text: amountPart, style: evenTextStyle));
} else {
spans.add(TextSpan(text: '${chunks[i]} ', style: style));
}
}
return RichText(
text: TextSpan(children: spans),
textAlign: textAlign ?? TextAlign.start,
overflow: TextOverflow.visible,
);
}
// No parentheses - use original logic
return _buildAddressWidget(
address: address,
walletType: walletType,
evenTextStyle: evenTextStyle,
oddTextStyle: oddTextStyle,
textAlign: textAlign,
shouldTruncate: shouldTruncate,
);
}
static Widget _buildAddressWidget({
required String address,
WalletType? walletType,
required TextStyle evenTextStyle,
TextStyle? oddTextStyle,
TextAlign? textAlign,
bool shouldTruncate = false,
}) {
final cleanAddress = address.replaceAll('bitcoincash:', '');
final isMWEB = address.startsWith('ltcmweb');
final chunkSize = walletType != null ? _getChunkSize(walletType) : 4;

View file

@ -56,6 +56,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
case WalletType.haven:
case WalletType.zano:
case WalletType.decred:
case WalletType.xelis:
return false;
}
}

View file

@ -112,7 +112,8 @@ abstract class BalanceViewModelBase with Store {
isEVMCompatibleChain(wallet.type) ||
wallet.type == WalletType.solana ||
wallet.type == WalletType.tron ||
wallet.type == WalletType.zano;
wallet.type == WalletType.zano ||
wallet.type == WalletType.xelis;
@computed
bool get hasAccounts => wallet.type == WalletType.monero || wallet.type == WalletType.wownero;

View file

@ -14,6 +14,7 @@ import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/xelis/xelis.dart';
import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/dashboard/orders_store.dart';
@ -468,7 +469,8 @@ abstract class DashboardViewModelBase with Store {
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
@computed
bool get isTestnet => wallet.type == WalletType.bitcoin && bitcoin!.isTestnet(wallet);
bool get isTestnet => (wallet.type == WalletType.bitcoin && bitcoin!.isTestnet(wallet)) ||
(wallet.type == WalletType.xelis && xelis!.isTestnet(wallet));
@computed
bool get hasRescan => wallet.hasRescan;
@ -859,6 +861,7 @@ abstract class DashboardViewModelBase with Store {
return true;
case WalletType.zano:
case WalletType.haven:
case WalletType.xelis: // TODO: finalize whether to change this
case WalletType.none:
return false;
}

View file

@ -14,6 +14,7 @@ import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cake_wallet/xelis/xelis.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/utils/print_verbose.dart';
@ -109,6 +110,14 @@ abstract class HomeSettingsViewModelBase with Store {
);
}
if (_balanceViewModel.wallet.type == WalletType.xelis) {
await xelis!.updateAssetState(
_balanceViewModel.wallet,
token,
contractAddress,
);
}
if (_balanceViewModel.wallet.type == WalletType.tron) {
await tron!.addTronToken(_balanceViewModel.wallet, token, contractAddress);
}
@ -148,6 +157,9 @@ abstract class HomeSettingsViewModelBase with Store {
if (_balanceViewModel.wallet.type == WalletType.zano) {
await zano!.deleteZanoAsset(_balanceViewModel.wallet, token);
}
if (_balanceViewModel.wallet.type == WalletType.xelis) {
await xelis!.deleteAsset(_balanceViewModel.wallet, token);
}
_updateTokensList();
} finally {
isDeletingToken = false;
@ -206,6 +218,11 @@ abstract class HomeSettingsViewModelBase with Store {
case WalletType.tron:
defaultTokenAddresses = tron!.getDefaultTokenContractAddresses();
break;
case WalletType.xelis:
// TODO
// defaultTokenAddresses = xelis!.getDefaultAssetIDs();
// break;
return false;
case WalletType.zano:
case WalletType.banano:
case WalletType.monero:
@ -388,6 +405,10 @@ abstract class HomeSettingsViewModelBase with Store {
return await solana!.getSPLToken(_balanceViewModel.wallet, contractAddress);
}
if (_balanceViewModel.wallet.type == WalletType.xelis) {
return await xelis!.getAsset(_balanceViewModel.wallet, contractAddress);
}
if (_balanceViewModel.wallet.type == WalletType.tron) {
return await tron!.getTronToken(_balanceViewModel.wallet, contractAddress);
}
@ -430,6 +451,12 @@ abstract class HomeSettingsViewModelBase with Store {
solana!.addSPLToken(_balanceViewModel.wallet, token, address);
}
if (_balanceViewModel.wallet.type == WalletType.xelis) {
final id = xelis!.getAssetId(token);
xelis!.updateAssetState(_balanceViewModel.wallet, token, id);
if (!value) await xelis!.removeAssetTransactionsInHistory(_balanceViewModel.wallet, token);
}
if (_balanceViewModel.wallet.type == WalletType.tron) {
final address = tron!.getTokenAddress(token);
tron!.addTronToken(_balanceViewModel.wallet, token, address);
@ -501,6 +528,15 @@ abstract class HomeSettingsViewModelBase with Store {
.toList()
..sort(_sortFunc));
}
if (_balanceViewModel.wallet.type == WalletType.xelis) {
tokens.addAll(xelis!
.getXelisAssets(_balanceViewModel.wallet)
.where((element) => _matchesSearchText(element))
.toList()
..sort(_sortFunc));
}
}
@action
@ -549,7 +585,12 @@ abstract class HomeSettingsViewModelBase with Store {
return zano!.getZanoAssetAddress(asset);
}
// We return null if it's neither Tron, Polygon, Ethereum or Solana wallet (which is actually impossible because we only display home settings for either of these three wallets).
if (_balanceViewModel.wallet.type == WalletType.xelis) {
return xelis!.getAssetId(asset);
}
// We return null if it's neither Tron, Polygon, Ethereum, Xelis or Solana wallet (which is actually impossible because we only display home settings for either of these five wallets).
return null;
}
}

View file

@ -36,6 +36,7 @@ abstract class ReceiveOptionViewModelBase with Store {
_options = [ReceivePageOption.mainnet];
break;
case WalletType.decred:
case WalletType.xelis:
if (_wallet.isTestnet) {
_options = [
ReceivePageOption.testnet,

View file

@ -10,6 +10,7 @@ import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cake_wallet/xelis/xelis.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
@ -46,10 +47,17 @@ class TransactionListItem extends ActionListItem with Keyable {
bool get hasTokens =>
isEVMCompatibleChain(balanceViewModel.wallet.type) ||
balanceViewModel.wallet.type == WalletType.solana ||
balanceViewModel.wallet.type == WalletType.tron;
balanceViewModel.wallet.type == WalletType.tron ||
balanceViewModel.wallet.type == WalletType.xelis;
String get formattedCryptoAmount {
return displayMode == BalanceDisplayMode.hiddenBalance ? '---' : transaction.amountFormatted();
late final String amtText;
if (transaction.amountFormatted() == "MULTI") {
amtText = S.current.multi_transfer;
} else {
amtText = transaction.amountFormatted();
}
return displayMode == BalanceDisplayMode.hiddenBalance ? '---' : amtText;
}
String get formattedTitle {
@ -108,6 +116,7 @@ class TransactionListItem extends ActionListItem with Keyable {
WalletType.wownero,
WalletType.litecoin,
WalletType.zano,
WalletType.xelis
].contains(balanceViewModel.wallet.type)) {
return formattedPendingStatus;
}
@ -143,6 +152,11 @@ class TransactionListItem extends ActionListItem with Keyable {
final asset = tron!.assetOfTransaction(balanceViewModel.wallet, transaction);
return asset;
}
if (balanceViewModel.wallet.type == WalletType.xelis) {
final asset = xelis!.assetOfTransaction(balanceViewModel.wallet, transaction);
return asset;
}
} catch (e) {
return null;
}
@ -224,6 +238,13 @@ class TransactionListItem extends ActionListItem with Keyable {
cryptoAmount: decred!.formatterDecredAmountToDouble(amount: transaction.amount),
price: price);
break;
case WalletType.xelis:
final asset = xelis!.assetOfTransaction(balanceViewModel.wallet, transaction);
final price = balanceViewModel.fiatConvertationStore.prices[asset];
amount = calculateFiatAmountRaw(
cryptoAmount: xelis!.formatterXelisAmountToDouble(amount: xelis!.getTransactionAmountRaw(transaction)),
price: price);
break;
case WalletType.none:
case WalletType.banano:
case WalletType.haven:

View file

@ -253,11 +253,16 @@ abstract class ExchangeTradeViewModelBase with Store {
wallet.currency == CryptoCurrency.sol &&
tradesStore.trade!.from.tag == CryptoCurrency.sol.title;
bool _isXelisAsset() =>
wallet.currency == CryptoCurrency.xel &&
tradesStore.trade!.from.tag == CryptoCurrency.xel.title;
return tradesStore.trade!.from == wallet.currency ||
tradesStore.trade!.provider == ExchangeProviderDescription.xmrto ||
_isEthToken() ||
_isPolygonToken() ||
_isSplToken() ||
_isTronToken();
_isTronToken() ||
_isXelisAsset();
}
}

View file

@ -779,6 +779,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
depositCurrency = CryptoCurrency.dcr;
receiveCurrency = CryptoCurrency.xmr;
break;
case WalletType.xelis:
depositCurrency = CryptoCurrency.xel;
receiveCurrency = CryptoCurrency.btc;
break;
case WalletType.none:
break;
}

View file

@ -87,6 +87,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
case WalletType.bitcoin:
case WalletType.zano:
case WalletType.decred:
case WalletType.xelis:
return false;
}
}

View file

@ -51,6 +51,8 @@ abstract class NodeListViewModelBase with Store {
Node node;
if (_appStore.wallet!.type == WalletType.bitcoin && _appStore.wallet!.isTestnet) {
node = getBitcoinTestnetDefaultElectrumServer(nodes: _nodeSource)!;
} else if (_appStore.wallet!.type == WalletType.xelis && _appStore.wallet!.isTestnet) {
node = getXelisTestnetDefault(nodes: _nodeSource)!;
} else {
node = getDefaultNode(nodes: _nodeSource, type: _appStore.wallet!.type)!;
}

View file

@ -47,6 +47,9 @@ class WalletRestoreFromQRCode {
'decred': WalletType.decred,
'decred-wallet': WalletType.decred,
'decred_wallet': WalletType.decred,
'xelis': WalletType.xelis,
'xelis-wallet': WalletType.xelis,
'xelis_wallet': WalletType.xelis,
};
static WalletType? _extractWalletType(String code) {

View file

@ -1,5 +1,6 @@
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/xelis/xelis.dart';
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
@ -91,6 +92,7 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
return transactionPriority == polygon!.getPolygonTransactionPrioritySlow();
case WalletType.decred:
return transactionPriority == decred!.getDecredTransactionPrioritySlow();
case WalletType.xelis:
case WalletType.none:
case WalletType.nano:
case WalletType.banano:

View file

@ -11,6 +11,7 @@ import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cake_wallet/xelis/xelis.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:flutter/material.dart';
@ -116,6 +117,9 @@ abstract class OutputBase with Store {
case WalletType.zano:
_amount = zano!.formatterParseAmount(amount: _cryptoAmount, currency: cryptoCurrencyHandler());
break;
case WalletType.xelis:
_amount = xelis!.formatterStringDoubleToAmount(_cryptoAmount, currency: cryptoCurrencyHandler());
break;
case WalletType.none:
case WalletType.haven:
case WalletType.nano:
@ -153,6 +157,10 @@ abstract class OutputBase with Store {
return solana!.getEstimateFees(_wallet) ?? 0.0;
}
if (_wallet.type == WalletType.xelis) {
return xelis!.getEstimateFees(_wallet) ?? 0.0;
}
int? fee = _wallet.calculateEstimatedFee(
_settingsStore.priority[_wallet.type]!, formattedCryptoAmount);
@ -193,6 +201,10 @@ abstract class OutputBase with Store {
if (_wallet.type == WalletType.decred) {
return decred!.formatterDecredAmountToDouble(amount: fee);
}
if (_wallet.type == WalletType.xelis) {
return xelis!.formatterXelisAmountToDouble(amount: BigInt.from(fee));
}
} catch (e) {
printV(e.toString());
}
@ -205,7 +217,8 @@ abstract class OutputBase with Store {
try {
final currency = (isEVMCompatibleChain(_wallet.type) ||
_wallet.type == WalletType.solana ||
_wallet.type == WalletType.tron)
_wallet.type == WalletType.tron ||
_wallet.type == WalletType.xelis)
? _wallet.currency
: cryptoCurrencyHandler();
final fiat = calculateFiatAmountRaw(
@ -302,6 +315,7 @@ abstract class OutputBase with Store {
case WalletType.zano:
case WalletType.nano:
case WalletType.decred:
case WalletType.xelis:
maximumFractionDigits = 12;
break;
case WalletType.bitcoin:

View file

@ -101,5 +101,6 @@ abstract class SendTemplateViewModelBase with Store {
bool get hasMultipleTokens => isEVMCompatibleChain(_wallet.type) ||
_wallet.type == WalletType.solana ||
_wallet.type == WalletType.tron;
_wallet.type == WalletType.tron ||
_wallet.type == WalletType.xelis;
}

View file

@ -25,6 +25,11 @@ import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/xelis/xelis.dart';
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/wallet_contact.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/routes.dart';
@ -69,7 +74,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
hasMultipleTokens = isEVMCompatibleChain(wallet.type) ||
wallet.type == WalletType.solana ||
wallet.type == WalletType.tron ||
wallet.type == WalletType.zano;
wallet.type == WalletType.zano ||
wallet.type == WalletType.xelis;
}
UnspentCoinsListViewModel unspentCoinsListViewModel;
@ -91,7 +97,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
hasMultipleTokens = isEVMCompatibleChain(appStore.wallet!.type) ||
appStore.wallet!.type == WalletType.solana ||
appStore.wallet!.type == WalletType.tron ||
appStore.wallet!.type == WalletType.zano,
appStore.wallet!.type == WalletType.zano ||
appStore.wallet!.type == WalletType.xelis,
outputs = ObservableList<Output>(),
_settingsStore = appStore.settingsStore,
fiatFromSettings = appStore.settingsStore.fiatCurrency,
@ -199,6 +206,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.polygon:
case WalletType.tron:
case WalletType.solana:
case WalletType.xelis:
return wallet.currency;
default:
return selectedCryptoCurrency;
@ -482,6 +490,13 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
}
}
if (wallet.type == WalletType.xelis) {
final outputCount = pendingTransaction?.outputCount ?? 0;
if (outputCount > 255) {
throw Exception("Xelis does not support more than 255 outputs");
}
}
state = ExecutedSuccessfullyState();
return pendingTransaction;
} catch (e) {
@ -534,6 +549,11 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
}
}
String normalizeAmount(String amount) {
if (amount.contains('.')) return amount;
return '$amount.0';
}
@action
Future<void> commitTransaction(BuildContext context) async {
if (pendingTransaction == null) {
@ -558,11 +578,25 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
return;
}
String address = outputs.fold('', (acc, value) {
return value.isParsedAddress
? '$acc${value.address}\n${value.extractedAddress}\n\n'
: '$acc${value.address}\n\n';
});
late String address;
if (walletType == WalletType.xelis) {
address = outputs.fold('', (acc, value) {
final nameLine = value.isParsedAddress ? '${value.address}\n' : '';
final realAddress = value.isParsedAddress ? value.extractedAddress : value.address;
final amount = normalizeAmount(value.cryptoAmount ?? '0.0');
final symbol = value.cryptoCurrencyHandler().title ?? '';
final amountPart = outputs.length > 1 ? ' ($amount $symbol)' : '';
return '$acc$nameLine$realAddress$amountPart\n\n';
});
} else {
address = outputs.fold('', (acc, value) {
return value.isParsedAddress
? '$acc${value.address}\n${value.extractedAddress}\n\n'
: '$acc${value.address}\n\n';
});
}
address = address.trim();
@ -677,6 +711,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.decred:
this.coinTypeToSpendFrom = UnspentCoinType.any;
return decred!.createDecredTransactionCredentials(outputs, priority!);
case WalletType.xelis:
return xelis!.createXelisTransactionCredentials(outputs, priority: priority!, currency: selectedCryptoCurrency);
default:
throw Exception('Unexpected wallet type: ${wallet.type}');
}

View file

@ -48,6 +48,11 @@ abstract class TransactionDetailsViewModelBase with Store {
final dateFormat = DateFormatter.withCurrentLocal();
final tx = transactionInfo;
final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}';
final description = transactionDescriptionBox.values.firstWhere(
(val) => val.id == descriptionKey || val.id == transactionInfo.txHash,
orElse: () => TransactionDescription(id: descriptionKey));
// TODO: can be cleaned further
switch (wallet.type) {
case WalletType.monero:
@ -88,16 +93,14 @@ abstract class TransactionDetailsViewModelBase with Store {
case WalletType.decred:
_addDecredListItems(tx, dateFormat);
break;
case WalletType.xelis:
_addXelisListItems(tx, dateFormat, description.recipientAddress?.isEmpty ?? true);
break;
case WalletType.none:
case WalletType.banano:
break;
}
final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}';
final description = transactionDescriptionBox.values.firstWhere(
(val) => val.id == descriptionKey || val.id == transactionInfo.txHash,
orElse: () => TransactionDescription(id: descriptionKey));
if (showRecipientAddress && !isRecipientAddressShown) {
final recipientAddress = description.recipientAddress;
@ -193,6 +196,8 @@ abstract class TransactionDetailsViewModelBase with Store {
return 'https://explorer.zano.org/transaction/${txId}';
case WalletType.decred:
return 'https://${wallet.isTestnet ? "testnet" : "dcrdata"}.decred.org/tx/${txId.split(':')[0]}';
case WalletType.xelis:
return 'https://${wallet.isTestnet ? "testnet-" : ""}explorer.xelis.io/txs/${txId}';
case WalletType.none:
return '';
}
@ -227,6 +232,8 @@ abstract class TransactionDetailsViewModelBase with Store {
return S.current.view_transaction_on + 'explorer.zano.org';
case WalletType.decred:
return S.current.view_transaction_on + 'dcrdata.decred.org';
case WalletType.xelis:
return S.current.view_transaction_on + 'explorer.xelis.io';
case WalletType.none:
return '';
}
@ -562,6 +569,58 @@ abstract class TransactionDetailsViewModelBase with Store {
items.addAll(_items);
}
void _addXelisListItems(TransactionInfo tx, DateFormat dateFormat, bool showTo) {
final _items = [
StandartListItem(
title: S.current.transaction_details_transaction_id,
value: tx.id,
key: ValueKey('standard_list_item_transaction_details_id_key'),
),
StandartListItem(
title: S.current.transaction_details_date,
value: dateFormat.format(tx.date),
key: ValueKey('standard_list_item_transaction_details_date_key'),
),
StandartListItem(
title: S.current.transaction_details_height,
value: '${tx.height}',
key: ValueKey('standard_list_item_transaction_details_height_key'),
),
if (!tx.amountFormatted().startsWith(":MULTI:"))
StandartListItem(
title: S.current.transaction_details_amount,
value: tx.amountFormatted(),
key: ValueKey('standard_list_item_transaction_details_amount_key'),
),
if (tx.amountFormatted().startsWith(":MULTI:"))
StandartListItem(
title: S.current.transaction_details_multi_breakdown,
value: tx.amountFormatted().split(":MULTI:")[1]!,
key: ValueKey('standard_list_item_transaction_details_multi_breakdown_key'),
),
if (tx.feeFormatted()?.isNotEmpty ?? false && tx.direction == TransactionDirection.outgoing)
StandartListItem(
title: S.current.transaction_details_fee,
value: tx.feeFormatted()!,
key: ValueKey('standard_list_item_transaction_details_fee_key'),
),
if (showRecipientAddress && tx.to != null && showTo)
StandartListItem(
title: S.current.transaction_details_recipient_address,
value: tx.to!,
key: ValueKey('standard_list_item_transaction_details_recipient_address_key'),
),
if (tx.from != null)
StandartListItem(
title: S.current.transaction_details_source_address,
value: tx.from!,
key: ValueKey('standard_list_item_transaction_details_source_address_key'),
),
];
items.addAll(_items);
}
void addBumpFeesListItems(TransactionInfo tx, String rawTransaction) {
transactionPriority = bitcoin!.getBitcoinTransactionPriorityMedium();
final inputsCount = (transactionInfo.inputAddresses?.isEmpty ?? true)
@ -629,7 +688,7 @@ abstract class TransactionDetailsViewModelBase with Store {
);
}
if (transactionInfo.outputAddresses != null && transactionInfo.outputAddresses!.isNotEmpty) {
if (transactionInfo.outputAddresses != null && transactionInfo.outputAddresses!.isNotEmpty && wallet.type != WalletType.xelis) {
final outputAddresses = transactionInfo.outputAddresses!.map((element) {
if (element.contains('OP_RETURN:') && element.length > 40) {
return element.substring(0, 40) + '...';
@ -670,7 +729,7 @@ abstract class TransactionDetailsViewModelBase with Store {
value: tx.feeFormatted()!,
key: ValueKey('standard_list_item_transaction_details_fee_key'),
),
if (showRecipientAddress && tx.to != null)
if (showRecipientAddress && tx.to != null && tx.direction == TransactionDirection.outgoing)
StandartListItem(
title: S.current.transaction_details_recipient_address,
value: tron!.getTronBase58Address(tx.to!, wallet),

View file

@ -242,6 +242,22 @@ class DecredURI extends PaymentURI {
}
}
class XelisURI extends PaymentURI {
XelisURI({required String amount, required String address})
: super(amount: amount, address: address);
@override
String toString() {
var base = 'xelis:' + address;
if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}';
}
return base;
}
}
abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store {
WalletAddressListViewModelBase({
required AppStore appStore,
@ -350,6 +366,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
return ZanoURI(amount: amount, address: address.address);
case WalletType.decred:
return DecredURI(amount: amount, address: address.address);
case WalletType.xelis:
return XelisURI(amount: amount, address: address.address);
case WalletType.none:
throw Exception('Unexpected type: ${type.toString()}');
}

View file

@ -164,6 +164,7 @@ abstract class WalletKeysViewModelBase with Store {
case WalletType.bitcoinCash:
case WalletType.none:
case WalletType.haven:
case WalletType.xelis:
// final keys = bitcoin!.getWalletKeys(_appStore.wallet!);
//
// items.addAll([
@ -252,6 +253,8 @@ abstract class WalletKeysViewModelBase with Store {
return 'zano-wallet';
case WalletType.decred:
return 'decred-wallet';
case WalletType.xelis:
return 'xelis-wallet';
default:
throw Exception('Unexpected wallet type: ${_wallet.type.toString()}');
}

View file

@ -16,6 +16,7 @@ import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/xelis/xelis.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
@ -47,7 +48,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
String selectedMnemonicLanguage;
bool get hasLanguageSelector =>
[WalletType.monero, WalletType.haven, WalletType.wownero].contains(type);
[WalletType.monero, WalletType.haven, WalletType.wownero, WalletType.xelis].contains(type);
bool get showLanguageSelector =>
newWalletArguments?.mnemonic == null && hasLanguageSelector;
@ -141,6 +142,11 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
);
case WalletType.decred:
return decred!.createDecredNewWalletCredentials(name: name);
case WalletType.xelis:
return xelis!.createXelisNewWalletCredentials(
name: name,
password: walletPassword,
);
case WalletType.none:
case WalletType.haven:
throw Exception('Unexpected type: ${type.toString()}');

Some files were not shown because too many files have changed in this diff Show more