mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-03-30 11:09:00 +00:00
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into CW-519-tor
This commit is contained in:
commit
8644ba2069
66 changed files with 465 additions and 569 deletions
.github/ISSUE_TEMPLATE
android/app
assets/images
configure_cake_wallet.shcw_bitcoin/lib
cw_core/lib
cw_nano
lib
banano_balance.dartnano_balance.dartnano_client.dartnano_transaction_info.dartnano_util.dartnano_wallet.dartnano_wallet_service.dartpending_nano_transaction.dart
pubspec.yamllib
buy
entities
exchange/provider
nano
src/screens
buy
dashboard/pages
exchange_trade
send
support_chat/widgets
view_model
macos/Flutter
pubspec_base.yamlres/values
strings_ar.arbstrings_bg.arbstrings_cs.arbstrings_de.arbstrings_en.arbstrings_es.arbstrings_fr.arbstrings_ha.arbstrings_hi.arbstrings_hr.arbstrings_id.arbstrings_it.arbstrings_ja.arbstrings_ko.arbstrings_my.arbstrings_nl.arbstrings_pl.arbstrings_pt.arbstrings_ru.arbstrings_th.arbstrings_tl.arbstrings_tr.arbstrings_uk.arbstrings_ur.arbstrings_yo.arbstrings_zh.arb
tool
33
.github/ISSUE_TEMPLATE/bug-report-🪲-.md
vendored
Normal file
33
.github/ISSUE_TEMPLATE/bug-report-🪲-.md
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
name: "Bug Report \U0001FAB2 "
|
||||
about: 'Report a bug '
|
||||
title: ''
|
||||
labels: Bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Platform:**
|
||||
- OS: [e.g. iOS 15.1, Android 14]
|
||||
- Device: [e.g. iPhone 14, Galaxy S21]
|
||||
- Cake Wallet Version: [e.g. 4.12.1]
|
||||
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Not sure where to start?
|
||||
url: https://guides.cakewallet.com
|
||||
about: Start by reading checking out the guides!
|
||||
- name: Need help?
|
||||
url: https://cakewallet.com/#contact
|
||||
about: Use our live chat or send a support email!
|
20
.github/ISSUE_TEMPLATE/feature-or-enhancement-request-✨.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature-or-enhancement-request-✨.md
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
name: Feature or Enhancement Request ✨
|
||||
about: Suggest an idea for Cake Wallet
|
||||
title: ''
|
||||
labels: Enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
|
@ -37,7 +37,7 @@ if (appPropertiesFile.exists()) {
|
|||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
compileSdkVersion 34
|
||||
|
||||
lintOptions {
|
||||
disable 'InvalidPackage'
|
||||
|
|
|
@ -71,8 +71,8 @@
|
|||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
<provider
|
||||
android:name="com.pichillilorenzo.flutter_inappwebview.InAppWebViewFileProvider"
|
||||
android:authorities="${applicationId}.flutter_inappwebview.fileprovider"
|
||||
android:name="com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFileProvider"
|
||||
android:authorities="${applicationId}.flutter_inappwebview_android.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
|
|
BIN
assets/images/kaspa_icon.png
Normal file
BIN
assets/images/kaspa_icon.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 6.8 KiB |
|
@ -1,4 +1,24 @@
|
|||
cd scripts/android
|
||||
IOS="ios"
|
||||
ANDROID="android"
|
||||
|
||||
PLATFORMS=($IOS $ANDROID)
|
||||
PLATFORM=$1
|
||||
|
||||
if ! [[ " ${PLATFORMS[*]} " =~ " ${PLATFORM} " ]]; then
|
||||
echo "specify platform: ./configure_cake_wallet.sh ios|android"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$PLATFORM" == "$IOS" ]; then
|
||||
echo "Configuring for iOS"
|
||||
cd scripts/ios
|
||||
fi
|
||||
|
||||
if [ "$PLATFORM" == "$ANDROID" ]; then
|
||||
echo "Configuring for Android"
|
||||
cd scripts/android
|
||||
fi
|
||||
|
||||
source ./app_env.sh cakewallet
|
||||
./app_config.sh
|
||||
cd ../.. && flutter pub get
|
|
@ -1,13 +1,9 @@
|
|||
import 'dart:convert';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
||||
import 'package:cw_core/balance.dart';
|
||||
|
||||
class ElectrumBalance extends Balance {
|
||||
const ElectrumBalance(
|
||||
{required this.confirmed,
|
||||
required this.unconfirmed,
|
||||
required this.frozen})
|
||||
const ElectrumBalance({required this.confirmed, required this.unconfirmed, required this.frozen})
|
||||
: super(confirmed, unconfirmed);
|
||||
|
||||
static ElectrumBalance? fromJSON(String? jsonSource) {
|
||||
|
@ -28,12 +24,10 @@ class ElectrumBalance extends Balance {
|
|||
final int frozen;
|
||||
|
||||
@override
|
||||
String get formattedAvailableBalance =>
|
||||
bitcoinAmountToString(amount: confirmed - unconfirmed.abs() - frozen);
|
||||
String get formattedAvailableBalance => bitcoinAmountToString(amount: confirmed - frozen);
|
||||
|
||||
@override
|
||||
String get formattedAdditionalBalance =>
|
||||
bitcoinAmountToString(amount: unconfirmed);
|
||||
String get formattedAdditionalBalance => bitcoinAmountToString(amount: unconfirmed);
|
||||
|
||||
@override
|
||||
String get formattedUnAvailableBalance {
|
||||
|
@ -41,6 +35,6 @@ class ElectrumBalance extends Balance {
|
|||
return frozenFormatted == '0.0' ? '' : frozenFormatted;
|
||||
}
|
||||
|
||||
String toJSON() => json.encode(
|
||||
{'confirmed': confirmed, 'unconfirmed': unconfirmed, 'frozen': frozen});
|
||||
String toJSON() =>
|
||||
json.encode({'confirmed': confirmed, 'unconfirmed': unconfirmed, 'frozen': frozen});
|
||||
}
|
||||
|
|
|
@ -62,6 +62,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(receiveAddress) : receiveAddress;
|
||||
}
|
||||
|
||||
@override
|
||||
String get primaryAddress => getAddress(index: 0, hd: mainHd);
|
||||
|
||||
@override
|
||||
set address(String addr) => null;
|
||||
|
||||
|
|
|
@ -95,6 +95,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
|||
CryptoCurrency.banano,
|
||||
CryptoCurrency.usdtPoly,
|
||||
CryptoCurrency.usdcEPoly,
|
||||
CryptoCurrency.kaspa,
|
||||
];
|
||||
|
||||
static const havenCurrencies = [
|
||||
|
@ -206,6 +207,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
|||
static const banano = CryptoCurrency(title: 'BAN', fullName: 'Banano', raw: 86, name: 'banano', iconPath: 'assets/images/nano_icon.png', decimals: 29);
|
||||
static const usdtPoly = CryptoCurrency(title: 'USDT', tag: 'POLY', fullName: 'Tether USD (PoS)', raw: 87, name: 'usdtpoly', iconPath: 'assets/images/usdt_icon.png', decimals: 6);
|
||||
static const usdcEPoly = CryptoCurrency(title: 'USDC.E', tag: 'POLY', fullName: 'USD Coin (PoS)', raw: 88, name: 'usdcepoly', iconPath: 'assets/images/usdc_icon.png', decimals: 6);
|
||||
static const kaspa = CryptoCurrency(title: 'KAS', fullName: 'Kaspa', raw: 89, name: 'kaspa', iconPath: 'assets/images/kaspa_icon.png', decimals: 8);
|
||||
|
||||
|
||||
static final Map<int, CryptoCurrency> _rawCurrencyMap =
|
||||
|
|
|
@ -15,6 +15,7 @@ abstract class TransactionInfo extends Object with Keyable {
|
|||
String? feeFormatted();
|
||||
void changeFiatAmount(String amount);
|
||||
String? to;
|
||||
String? from;
|
||||
|
||||
@override
|
||||
dynamic get keyIndex => id;
|
||||
|
|
|
@ -10,6 +10,8 @@ abstract class WalletAddresses {
|
|||
|
||||
String get address;
|
||||
|
||||
String? get primaryAddress => null;
|
||||
|
||||
set address(String address);
|
||||
|
||||
Map<String, String> addressesMap;
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
import 'package:cw_core/balance.dart';
|
||||
import 'package:cw_nano/nano_util.dart';
|
||||
import 'package:nanoutil/nanoutil.dart';
|
||||
|
||||
class BananoBalance extends Balance {
|
||||
final BigInt currentBalance;
|
||||
final BigInt receivableBalance;
|
||||
|
||||
BananoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0) {
|
||||
}
|
||||
BananoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0);
|
||||
|
||||
@override
|
||||
String get formattedAvailableBalance {
|
||||
return NanoUtil.getRawAsUsableString(currentBalance.toString(), NanoUtil.rawPerBanano);
|
||||
return NanoAmounts.getRawAsUsableString(currentBalance.toString(), NanoAmounts.rawPerBanano);
|
||||
}
|
||||
|
||||
@override
|
||||
String get formattedAdditionalBalance {
|
||||
return NanoUtil.getRawAsUsableString(receivableBalance.toString(), NanoUtil.rawPerBanano);
|
||||
return NanoAmounts.getRawAsUsableString(receivableBalance.toString(), NanoAmounts.rawPerBanano);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,34 +1,35 @@
|
|||
import 'package:cw_core/balance.dart';
|
||||
import 'package:cw_nano/nano_util.dart';
|
||||
import 'package:nanoutil/nanoutil.dart';
|
||||
|
||||
BigInt stringAmountToBigInt(String amount) {
|
||||
return BigInt.parse(NanoUtil.getAmountAsRaw(amount, NanoUtil.rawPerNano));
|
||||
return BigInt.parse(NanoAmounts.getAmountAsRaw(amount, NanoAmounts.rawPerNano));
|
||||
}
|
||||
|
||||
class NanoBalance extends Balance {
|
||||
final BigInt currentBalance;
|
||||
final BigInt receivableBalance;
|
||||
late String formattedCurrentBalance;
|
||||
late String formattedReceivableBalance;
|
||||
|
||||
NanoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0) {
|
||||
this.formattedCurrentBalance = "";
|
||||
this.formattedReceivableBalance = "";
|
||||
}
|
||||
NanoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0);
|
||||
|
||||
NanoBalance.fromString(
|
||||
{required this.formattedCurrentBalance, required this.formattedReceivableBalance})
|
||||
NanoBalance.fromFormattedString(
|
||||
{required String formattedCurrentBalance, required String formattedReceivableBalance})
|
||||
: currentBalance = stringAmountToBigInt(formattedCurrentBalance),
|
||||
receivableBalance = stringAmountToBigInt(formattedReceivableBalance),
|
||||
super(0, 0);
|
||||
|
||||
NanoBalance.fromRawString(
|
||||
{required String currentBalance, required String receivableBalance})
|
||||
: currentBalance = BigInt.parse(currentBalance),
|
||||
receivableBalance = BigInt.parse(receivableBalance),
|
||||
super(0, 0);
|
||||
|
||||
@override
|
||||
String get formattedAvailableBalance {
|
||||
return NanoUtil.getRawAsUsableString(currentBalance.toString(), NanoUtil.rawPerNano);
|
||||
return NanoAmounts.getRawAsUsableString(currentBalance.toString(), NanoAmounts.rawPerNano);
|
||||
}
|
||||
|
||||
@override
|
||||
String get formattedAdditionalBalance {
|
||||
return NanoUtil.getRawAsUsableString(receivableBalance.toString(), NanoUtil.rawPerNano);
|
||||
return NanoAmounts.getRawAsUsableString(receivableBalance.toString(), NanoAmounts.rawPerNano);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,10 @@ import 'dart:convert';
|
|||
import 'package:cw_core/nano_account_info_response.dart';
|
||||
import 'package:cw_nano/nano_balance.dart';
|
||||
import 'package:cw_nano/nano_transaction_model.dart';
|
||||
import 'package:cw_nano/nano_util.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:nanodart/nanodart.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:nanoutil/nanoutil.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class NanoClient {
|
||||
|
@ -61,6 +61,13 @@ class NanoClient {
|
|||
),
|
||||
);
|
||||
final data = await jsonDecode(response.body);
|
||||
if (response.statusCode != 200 ||
|
||||
data["error"] != null ||
|
||||
data["balance"] == null ||
|
||||
data["receivable"] == null) {
|
||||
throw Exception(
|
||||
"Error while trying to get balance! ${data["error"] != null ? data["error"] : ""}");
|
||||
}
|
||||
final String currentBalance = data["balance"] as String;
|
||||
final String receivableBalance = data["receivable"] as String;
|
||||
final BigInt cur = BigInt.parse(currentBalance);
|
||||
|
@ -203,7 +210,7 @@ class NanoClient {
|
|||
String? previousHash,
|
||||
}) async {
|
||||
// our address:
|
||||
final String publicAddress = NanoUtil.privateKeyToAddress(privateKey);
|
||||
final String publicAddress = NanoDerivations.privateKeyToAddress(privateKey);
|
||||
|
||||
// first get the current account balance:
|
||||
if (balanceAfterTx == null) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:cw_core/format_amount.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_nano/nano_util.dart';
|
||||
import 'package:nanoutil/nanoutil.dart';
|
||||
|
||||
class NanoTransactionInfo extends TransactionInfo {
|
||||
NanoTransactionInfo({
|
||||
|
@ -13,6 +13,8 @@ class NanoTransactionInfo extends TransactionInfo {
|
|||
required this.confirmed,
|
||||
required this.date,
|
||||
required this.confirmations,
|
||||
required this.to,
|
||||
required this.from,
|
||||
}) : this.amount = amountRaw.toInt();
|
||||
|
||||
final String id;
|
||||
|
@ -24,14 +26,17 @@ class NanoTransactionInfo extends TransactionInfo {
|
|||
final bool confirmed;
|
||||
final int confirmations;
|
||||
final String tokenSymbol;
|
||||
final String? to;
|
||||
final String? from;
|
||||
String? _fiatAmount;
|
||||
|
||||
bool get isPending => !this.confirmed;
|
||||
|
||||
@override
|
||||
String amountFormatted() {
|
||||
final String amt = NanoUtil.getRawAsUsableString(amountRaw.toString(), NanoUtil.rawPerNano);
|
||||
final String acc = NanoUtil.getRawAccuracy(amountRaw.toString(), NanoUtil.rawPerNano);
|
||||
final String amt =
|
||||
NanoAmounts.getRawAsUsableString(amountRaw.toString(), NanoAmounts.rawPerNano);
|
||||
final String acc = NanoAmounts.getRawAccuracy(amountRaw.toString(), NanoAmounts.rawPerNano);
|
||||
return "$acc$amt $tokenSymbol";
|
||||
}
|
||||
|
||||
|
@ -54,6 +59,8 @@ class NanoTransactionInfo extends TransactionInfo {
|
|||
confirmed: data['confirmed'] as bool,
|
||||
confirmations: data['confirmations'] as int,
|
||||
tokenSymbol: data['tokenSymbol'] as String,
|
||||
to: data['to'] as String,
|
||||
from: data['from'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -66,5 +73,7 @@ class NanoTransactionInfo extends TransactionInfo {
|
|||
'confirmed': confirmed,
|
||||
'confirmations': confirmations,
|
||||
'tokenSymbol': tokenSymbol,
|
||||
'to': to,
|
||||
'from': from,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,193 +0,0 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:convert/convert.dart';
|
||||
import "package:ed25519_hd_key/ed25519_hd_key.dart";
|
||||
import 'package:libcrypto/libcrypto.dart';
|
||||
import 'package:nanodart/nanodart.dart';
|
||||
import 'package:decimal/decimal.dart';
|
||||
|
||||
class NanoUtil {
|
||||
// standard:
|
||||
static String seedToPrivate(String seed, int index) {
|
||||
return NanoKeys.seedToPrivate(seed, index);
|
||||
}
|
||||
|
||||
static String seedToAddress(String seed, int index) {
|
||||
return NanoAccounts.createAccount(
|
||||
NanoAccountType.NANO, privateKeyToPublic(seedToPrivate(seed, index)));
|
||||
}
|
||||
|
||||
static String seedToMnemonic(String seed) {
|
||||
return NanoMnemomics.seedToMnemonic(seed).join(" ");
|
||||
}
|
||||
|
||||
static Future<String> mnemonicToSeed(String mnemonic) async {
|
||||
return NanoMnemomics.mnemonicListToSeed(mnemonic.split(' '));
|
||||
}
|
||||
|
||||
static String privateKeyToPublic(String privateKey) {
|
||||
// return NanoHelpers.byteToHex(Ed25519Blake2b.getPubkey(NanoHelpers.hexToBytes(privateKey))!);
|
||||
return NanoKeys.createPublicKey(privateKey);
|
||||
}
|
||||
|
||||
static String addressToPublicKey(String publicAddress) {
|
||||
return NanoAccounts.extractPublicKey(publicAddress);
|
||||
}
|
||||
|
||||
// universal:
|
||||
static String privateKeyToAddress(String privateKey) {
|
||||
return NanoAccounts.createAccount(NanoAccountType.NANO, privateKeyToPublic(privateKey));
|
||||
}
|
||||
|
||||
static String publicKeyToAddress(String publicKey) {
|
||||
return NanoAccounts.createAccount(NanoAccountType.NANO, publicKey);
|
||||
}
|
||||
|
||||
// standard + hd:
|
||||
static bool isValidSeed(String seed) {
|
||||
// Ensure seed is 64 or 128 characters long
|
||||
if (seed == null || (seed.length != 64 && seed.length != 128)) {
|
||||
return false;
|
||||
}
|
||||
// Ensure seed only contains hex characters, 0-9;A-F
|
||||
return NanoHelpers.isHexString(seed);
|
||||
}
|
||||
|
||||
// // hd:
|
||||
static Future<String> hdMnemonicListToSeed(List<String> words) async {
|
||||
// if (words.length != 24) {
|
||||
// throw Exception('Expected a 24-word list, got a ${words.length} list');
|
||||
// }
|
||||
final Uint8List salt = Uint8List.fromList(utf8.encode('mnemonic'));
|
||||
final Pbkdf2 hasher = Pbkdf2(iterations: 2048);
|
||||
final String seed = await hasher.sha512(words.join(' '), salt);
|
||||
return seed;
|
||||
}
|
||||
|
||||
static Future<String> hdSeedToPrivate(String seed, int index) async {
|
||||
List<int> seedBytes = hex.decode(seed);
|
||||
KeyData data = await ED25519_HD_KEY.derivePath("m/44'/165'/$index'", seedBytes);
|
||||
return hex.encode(data.key);
|
||||
}
|
||||
|
||||
static Future<String> hdSeedToAddress(String seed, int index) async {
|
||||
return NanoAccounts.createAccount(
|
||||
NanoAccountType.NANO, privateKeyToPublic(await hdSeedToPrivate(seed, index)));
|
||||
}
|
||||
|
||||
static Future<String> uniSeedToAddress(String seed, int index, String type) {
|
||||
if (type == "standard") {
|
||||
return Future<String>.value(seedToAddress(seed, index));
|
||||
} else if (type == "hd") {
|
||||
return hdSeedToAddress(seed, index);
|
||||
} else {
|
||||
throw Exception('Unknown seed type');
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String> uniSeedToPrivate(String seed, int index, String type) {
|
||||
if (type == "standard") {
|
||||
return Future<String>.value(seedToPrivate(seed, index));
|
||||
} else if (type == "hd") {
|
||||
return hdSeedToPrivate(seed, index);
|
||||
} else {
|
||||
throw Exception('Unknown seed type');
|
||||
}
|
||||
}
|
||||
|
||||
static bool isValidBip39Seed(String seed) {
|
||||
// Ensure seed is 128 characters long
|
||||
if (seed.length != 128) {
|
||||
return false;
|
||||
}
|
||||
// Ensure seed only contains hex characters, 0-9;A-F
|
||||
return NanoHelpers.isHexString(seed);
|
||||
}
|
||||
|
||||
// number util:
|
||||
|
||||
static const int maxDecimalDigits = 6; // Max digits after decimal
|
||||
static BigInt rawPerNano = BigInt.parse("1000000000000000000000000000000");
|
||||
static BigInt rawPerNyano = BigInt.parse("1000000000000000000000000");
|
||||
static BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000");
|
||||
static BigInt rawPerXMR = BigInt.parse("1000000000000");
|
||||
static BigInt convertXMRtoNano = BigInt.parse("1000000000000000000");
|
||||
// static BigInt convertXMRtoNano = BigInt.parse("1000000000000000000000000000");
|
||||
|
||||
/// Convert raw to ban and return as BigDecimal
|
||||
///
|
||||
/// @param raw 100000000000000000000000000000
|
||||
/// @return Decimal value 1.000000000000000000000000000000
|
||||
///
|
||||
static Decimal getRawAsDecimal(String? raw, BigInt? rawPerCur) {
|
||||
rawPerCur ??= rawPerNano;
|
||||
final Decimal amount = Decimal.parse(raw.toString());
|
||||
final Decimal result = (amount / Decimal.parse(rawPerCur.toString())).toDecimal();
|
||||
return result;
|
||||
}
|
||||
|
||||
static String truncateDecimal(Decimal input, {int digits = maxDecimalDigits}) {
|
||||
Decimal bigger = input.shift(digits);
|
||||
bigger = bigger.floor(); // chop off the decimal: 1.059 -> 1.05
|
||||
bigger = bigger.shift(-digits);
|
||||
return bigger.toString();
|
||||
}
|
||||
|
||||
/// Return raw as a NANO amount.
|
||||
///
|
||||
/// @param raw 100000000000000000000000000000
|
||||
/// @returns 1
|
||||
///
|
||||
static String getRawAsUsableString(String? raw, BigInt rawPerCur) {
|
||||
final String res =
|
||||
truncateDecimal(getRawAsDecimal(raw, rawPerCur), digits: maxDecimalDigits + 9);
|
||||
|
||||
if (raw == null || raw == "0" || raw == "00000000000000000000000000000000") {
|
||||
return "0";
|
||||
}
|
||||
|
||||
if (!res.contains(".")) {
|
||||
return res;
|
||||
}
|
||||
|
||||
final String numAmount = res.split(".")[0];
|
||||
String decAmount = res.split(".")[1];
|
||||
|
||||
// truncate:
|
||||
if (decAmount.length > maxDecimalDigits) {
|
||||
decAmount = decAmount.substring(0, maxDecimalDigits);
|
||||
// remove trailing zeros:
|
||||
decAmount = decAmount.replaceAllMapped(RegExp(r'0+$'), (Match match) => '');
|
||||
if (decAmount.isEmpty) {
|
||||
return numAmount;
|
||||
}
|
||||
}
|
||||
|
||||
return "$numAmount.$decAmount";
|
||||
}
|
||||
|
||||
static String getRawAccuracy(String? raw, BigInt rawPerCur) {
|
||||
final String rawString = getRawAsUsableString(raw, rawPerCur);
|
||||
final String rawDecimalString = getRawAsDecimal(raw, rawPerCur).toString();
|
||||
|
||||
if (raw == null || raw.isEmpty || raw == "0") {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (rawString != rawDecimalString) {
|
||||
return "~";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/// Return readable string amount as raw string
|
||||
/// @param amount 1.01
|
||||
/// @returns 101000000000000000000000000000
|
||||
///
|
||||
static String getAmountAsRaw(String amount, BigInt rawPerCur) {
|
||||
final Decimal asDecimal = Decimal.parse(amount);
|
||||
final Decimal rawDecimal = Decimal.parse(rawPerCur.toString());
|
||||
return (asDecimal * rawDecimal).toString();
|
||||
}
|
||||
}
|
|
@ -18,7 +18,6 @@ import 'package:cw_nano/nano_client.dart';
|
|||
import 'package:cw_nano/nano_transaction_credentials.dart';
|
||||
import 'package:cw_nano/nano_transaction_history.dart';
|
||||
import 'package:cw_nano/nano_transaction_info.dart';
|
||||
import 'package:cw_nano/nano_util.dart';
|
||||
import 'package:cw_nano/nano_wallet_keys.dart';
|
||||
import 'package:cw_nano/pending_nano_transaction.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
@ -27,6 +26,7 @@ import 'package:cw_nano/nano_wallet_addresses.dart';
|
|||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:nanodart/nanodart.dart';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:nanoutil/nanoutil.dart';
|
||||
|
||||
part 'nano_wallet.g.dart';
|
||||
|
||||
|
@ -83,6 +83,8 @@ abstract class NanoWalletBase
|
|||
@observable
|
||||
late ObservableMap<CryptoCurrency, NanoBalance> balance;
|
||||
|
||||
static const int POLL_INTERVAL_SECONDS = 10;
|
||||
|
||||
// initialize the different forms of private / public key we'll need:
|
||||
Future<void> init() async {
|
||||
if (_derivationType == DerivationType.unknown) {
|
||||
|
@ -100,11 +102,21 @@ abstract class NanoWalletBase
|
|||
if (_derivationType == DerivationType.nano) {
|
||||
_hexSeed = bip39.mnemonicToEntropy(_mnemonic).toUpperCase();
|
||||
} else {
|
||||
_hexSeed = await NanoUtil.hdMnemonicListToSeed(_mnemonic.split(' '));
|
||||
_hexSeed = await NanoDerivations.hdMnemonicListToSeed(_mnemonic.split(' '));
|
||||
}
|
||||
}
|
||||
_privateKey = await NanoUtil.uniSeedToPrivate(_hexSeed!, 0, type);
|
||||
_publicAddress = await NanoUtil.uniSeedToAddress(_hexSeed!, 0, type);
|
||||
NanoDerivationType derivationType =
|
||||
type == "standard" ? NanoDerivationType.STANDARD : NanoDerivationType.HD;
|
||||
_privateKey = await NanoDerivations.universalSeedToPrivate(
|
||||
_hexSeed!,
|
||||
index: 0,
|
||||
type: derivationType,
|
||||
);
|
||||
_publicAddress = await NanoDerivations.universalSeedToAddress(
|
||||
_hexSeed!,
|
||||
index: 0,
|
||||
type: derivationType,
|
||||
);
|
||||
this.walletInfo.address = _publicAddress!;
|
||||
|
||||
await walletAddresses.init();
|
||||
|
@ -125,6 +137,7 @@ abstract class NanoWalletBase
|
|||
@override
|
||||
void close() {
|
||||
_client.stop();
|
||||
_receiveTimer?.cancel();
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -139,6 +152,7 @@ abstract class NanoWalletBase
|
|||
|
||||
try {
|
||||
await _updateBalance();
|
||||
await updateTransactions();
|
||||
await _updateRep();
|
||||
await _receiveAll();
|
||||
} catch (e) {
|
||||
|
@ -173,8 +187,8 @@ abstract class NanoWalletBase
|
|||
if (txOut.sendAll) {
|
||||
amt = balance[currency]?.currentBalance ?? BigInt.zero;
|
||||
} else {
|
||||
amt = BigInt.tryParse(NanoUtil.getAmountAsRaw(
|
||||
txOut.cryptoAmount?.replaceAll(',', '.') ?? "0", NanoUtil.rawPerNano)) ??
|
||||
amt = BigInt.tryParse(NanoAmounts.getAmountAsRaw(
|
||||
txOut.cryptoAmount?.replaceAll(',', '.') ?? "0", NanoAmounts.rawPerNano)) ??
|
||||
BigInt.zero;
|
||||
}
|
||||
|
||||
|
@ -186,9 +200,7 @@ abstract class NanoWalletBase
|
|||
|
||||
final block = await _client.constructSendBlock(
|
||||
amountRaw: amt.toString(),
|
||||
destinationAddress: txOut.isParsedAddress
|
||||
? txOut.extractedAddress!
|
||||
: txOut.address,
|
||||
destinationAddress: txOut.isParsedAddress ? txOut.extractedAddress! : txOut.address,
|
||||
privateKey: _privateKey!,
|
||||
balanceAfterTx: runningBalance,
|
||||
previousHash: previousHash,
|
||||
|
@ -236,10 +248,10 @@ abstract class NanoWalletBase
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> updateTransactions() async {
|
||||
Future<bool> updateTransactions() async {
|
||||
try {
|
||||
if (_isTransactionUpdating) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
_isTransactionUpdating = true;
|
||||
|
@ -247,8 +259,10 @@ abstract class NanoWalletBase
|
|||
transactionHistory.addMany(transactions);
|
||||
await transactionHistory.save();
|
||||
_isTransactionUpdating = false;
|
||||
return true;
|
||||
} catch (_) {
|
||||
_isTransactionUpdating = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -261,16 +275,17 @@ abstract class NanoWalletBase
|
|||
final Map<String, NanoTransactionInfo> result = {};
|
||||
|
||||
for (var transactionModel in transactions) {
|
||||
final bool isSend = transactionModel.type == "send";
|
||||
result[transactionModel.hash] = NanoTransactionInfo(
|
||||
id: transactionModel.hash,
|
||||
amountRaw: transactionModel.amount,
|
||||
height: transactionModel.height,
|
||||
direction: transactionModel.type == "send"
|
||||
? TransactionDirection.outgoing
|
||||
: TransactionDirection.incoming,
|
||||
direction: isSend ? TransactionDirection.outgoing : TransactionDirection.incoming,
|
||||
confirmed: transactionModel.confirmed,
|
||||
date: transactionModel.date ?? DateTime.now(),
|
||||
confirmations: transactionModel.confirmed ? 1 : 0,
|
||||
to: isSend ? transactionModel.account : address,
|
||||
from: isSend ? address : transactionModel.account,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -312,11 +327,10 @@ abstract class NanoWalletBase
|
|||
Future<void> startSync() async {
|
||||
try {
|
||||
syncStatus = AttemptingSyncStatus();
|
||||
await _updateBalance();
|
||||
await updateTransactions();
|
||||
|
||||
// setup a timer to receive transactions periodically:
|
||||
_receiveTimer?.cancel();
|
||||
_receiveTimer = Timer.periodic(const Duration(seconds: 15), (timer) async {
|
||||
_receiveTimer = Timer.periodic(const Duration(seconds: POLL_INTERVAL_SECONDS), (timer) async {
|
||||
// get our balance:
|
||||
await _updateBalance();
|
||||
// if we have anything to receive, process it:
|
||||
|
@ -325,6 +339,14 @@ abstract class NanoWalletBase
|
|||
}
|
||||
});
|
||||
|
||||
// also run once, immediately:
|
||||
await _updateBalance();
|
||||
bool updateSuccess = await updateTransactions();
|
||||
if (!updateSuccess) {
|
||||
syncStatus = FailedSyncStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
syncStatus = SyncedSyncStatus();
|
||||
} catch (e) {
|
||||
print(e);
|
||||
|
@ -353,9 +375,11 @@ abstract class NanoWalletBase
|
|||
|
||||
final data = json.decode(jsonSource) as Map;
|
||||
final mnemonic = data['mnemonic'] as String;
|
||||
final balance = NanoBalance.fromString(
|
||||
formattedCurrentBalance: data['currentBalance'] as String? ?? "0",
|
||||
formattedReceivableBalance: data['receivableBalance'] as String? ?? "0");
|
||||
|
||||
final balance = NanoBalance.fromRawString(
|
||||
currentBalance: data['currentBalance'] as String? ?? "0",
|
||||
receivableBalance: data['receivableBalance'] as String? ?? "0",
|
||||
);
|
||||
|
||||
DerivationType derivationType = DerivationType.nano;
|
||||
if (data['derivationType'] == "DerivationType.bip39") {
|
||||
|
@ -374,12 +398,26 @@ abstract class NanoWalletBase
|
|||
}
|
||||
|
||||
Future<void> _updateBalance() async {
|
||||
var oldBalance = balance[currency];
|
||||
try {
|
||||
balance[currency] = await _client.getBalance(_publicAddress!);
|
||||
} catch (e) {
|
||||
print("Failed to get balance $e");
|
||||
// if we don't have a balance, we should at least create one, since it's a late binding
|
||||
// otherwise, it's better to just leave it as whatever it was before:
|
||||
if (balance[currency] == null) {
|
||||
balance[currency] =
|
||||
NanoBalance(currentBalance: BigInt.zero, receivableBalance: BigInt.zero);
|
||||
}
|
||||
}
|
||||
// don't save unnecessarily:
|
||||
// trying to save too frequently can cause problems with the file system
|
||||
// since nano is updated frequently this can be a problem, so we only save if there is a change:
|
||||
if (oldBalance == null ||
|
||||
balance[currency]!.currentBalance != oldBalance.currentBalance ||
|
||||
balance[currency]!.receivableBalance != oldBalance.receivableBalance) {
|
||||
await save();
|
||||
}
|
||||
await save();
|
||||
}
|
||||
|
||||
Future<void> _updateRep() async {
|
||||
|
@ -394,11 +432,19 @@ abstract class NanoWalletBase
|
|||
}
|
||||
|
||||
Future<void> regenerateAddress() async {
|
||||
final String type = (_derivationType == DerivationType.nano) ? "standard" : "hd";
|
||||
_privateKey =
|
||||
await NanoUtil.uniSeedToPrivate(_hexSeed!, this.walletAddresses.account!.id, type);
|
||||
_publicAddress =
|
||||
await NanoUtil.uniSeedToAddress(_hexSeed!, this.walletAddresses.account!.id, type);
|
||||
final NanoDerivationType type = (_derivationType == DerivationType.nano)
|
||||
? NanoDerivationType.STANDARD
|
||||
: NanoDerivationType.HD;
|
||||
_privateKey = await NanoDerivations.universalSeedToPrivate(
|
||||
_hexSeed!,
|
||||
index: this.walletAddresses.account!.id,
|
||||
type: type,
|
||||
);
|
||||
_publicAddress = await NanoDerivations.universalSeedToAddress(
|
||||
_hexSeed!,
|
||||
index: this.walletAddresses.account!.id,
|
||||
type: type,
|
||||
);
|
||||
|
||||
this.walletInfo.address = _publicAddress!;
|
||||
this.walletAddresses.address = _publicAddress!;
|
||||
|
|
|
@ -6,12 +6,12 @@ import 'package:cw_core/wallet_info.dart';
|
|||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_nano/nano_mnemonic.dart' as nm;
|
||||
import 'package:cw_nano/nano_util.dart';
|
||||
import 'package:cw_nano/nano_wallet.dart';
|
||||
import 'package:cw_nano/nano_wallet_creation_credentials.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:nanodart/nanodart.dart';
|
||||
import 'package:nanoutil/nanoutil.dart';
|
||||
|
||||
class NanoWalletService extends WalletService<NanoNewWalletCredentials,
|
||||
NanoRestoreWalletFromSeedCredentials, NanoRestoreWalletFromKeysCredentials> {
|
||||
|
@ -30,7 +30,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
|
|||
// nano standard:
|
||||
DerivationType derivationType = DerivationType.nano;
|
||||
String seedKey = NanoSeeds.generateSeed();
|
||||
String mnemonic = NanoUtil.seedToMnemonic(seedKey);
|
||||
String mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey);
|
||||
|
||||
credentials.walletInfo!.derivationType = derivationType;
|
||||
|
||||
|
@ -95,7 +95,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
|
|||
// we can't derive the mnemonic from the key in all cases, only if it's a "nano" seed
|
||||
if (credentials.seedKey.length == 64) {
|
||||
try {
|
||||
mnemonic = NanoUtil.seedToMnemonic(credentials.seedKey);
|
||||
mnemonic = NanoDerivations.standardSeedToMnemonic(credentials.seedKey);
|
||||
} catch (e) {
|
||||
throw Exception("Wasn't a valid nano style seed!");
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_nano/nano_client.dart';
|
||||
import 'package:cw_nano/nano_util.dart';
|
||||
import 'package:nanoutil/nanoutil.dart';
|
||||
|
||||
class PendingNanoTransaction with PendingTransaction {
|
||||
PendingNanoTransaction({
|
||||
|
@ -18,13 +18,13 @@ class PendingNanoTransaction with PendingTransaction {
|
|||
|
||||
@override
|
||||
String get amountFormatted {
|
||||
final String amt = NanoUtil.getRawAsUsableString(amount.toString(), NanoUtil.rawPerNano);
|
||||
final String amt = NanoAmounts.getRawAsUsableString(amount.toString(), NanoAmounts.rawPerNano);
|
||||
return amt;
|
||||
}
|
||||
|
||||
String get accurateAmountFormatted {
|
||||
final String amt = NanoUtil.getRawAsUsableString(amount.toString(), NanoUtil.rawPerNano);
|
||||
final String acc = NanoUtil.getRawAccuracy(amount.toString(), NanoUtil.rawPerNano);
|
||||
final String amt = NanoAmounts.getRawAsUsableString(amount.toString(), NanoAmounts.rawPerNano);
|
||||
final String acc = NanoAmounts.getRawAccuracy(amount.toString(), NanoAmounts.rawPerNano);
|
||||
return "$acc$amt";
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,10 @@ dependencies:
|
|||
hex: ^0.2.0
|
||||
http: ^1.1.0
|
||||
shared_preferences: ^2.0.15
|
||||
nanoutil:
|
||||
git:
|
||||
url: https://github.com/perishllc/nanoutil.git
|
||||
ref: c37e72817cf0a28162f43124f79661d6c8e0098f
|
||||
cw_core:
|
||||
path: ../cw_core
|
||||
|
||||
|
|
|
@ -66,8 +66,10 @@ class DFXBuyProvider extends BuyProvider {
|
|||
}
|
||||
}
|
||||
|
||||
String get walletAddress =>
|
||||
wallet.walletAddresses.primaryAddress ?? wallet.walletAddresses.address;
|
||||
|
||||
Future<String> getSignMessage() async {
|
||||
final walletAddress = wallet.walletAddresses.address;
|
||||
final uri = Uri.https(_baseUrl, _authPath, {'address': walletAddress});
|
||||
|
||||
var response = await http.get(uri, headers: {'accept': 'application/json'});
|
||||
|
@ -83,7 +85,6 @@ class DFXBuyProvider extends BuyProvider {
|
|||
|
||||
Future<String> signUp() async {
|
||||
final signMessage = getSignature(await getSignMessage());
|
||||
final walletAddress = wallet.walletAddresses.address;
|
||||
|
||||
final requestBody = jsonEncode({
|
||||
'wallet': walletName,
|
||||
|
@ -92,8 +93,11 @@ class DFXBuyProvider extends BuyProvider {
|
|||
});
|
||||
|
||||
final uri = Uri.https(_baseUrl, _signUpPath);
|
||||
var response = await http.post(uri,
|
||||
headers: {'Content-Type': 'application/json'}, body: requestBody);
|
||||
var response = await http.post(
|
||||
uri,
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: requestBody,
|
||||
);
|
||||
|
||||
if (response.statusCode == 201) {
|
||||
final responseBody = jsonDecode(response.body);
|
||||
|
@ -103,14 +107,12 @@ class DFXBuyProvider extends BuyProvider {
|
|||
final message = responseBody['message'] ?? 'Service unavailable in your country';
|
||||
throw Exception(message);
|
||||
} else {
|
||||
throw Exception(
|
||||
'Failed to sign up. Status: ${response.statusCode} ${response.body}');
|
||||
throw Exception('Failed to sign up. Status: ${response.statusCode} ${response.body}');
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> signIn() async {
|
||||
final signMessage = getSignature(await getSignMessage());
|
||||
final walletAddress = wallet.walletAddresses.address;
|
||||
|
||||
final requestBody = jsonEncode({
|
||||
'address': walletAddress,
|
||||
|
@ -118,8 +120,11 @@ class DFXBuyProvider extends BuyProvider {
|
|||
});
|
||||
|
||||
final uri = Uri.https(_baseUrl, _signInPath);
|
||||
var response = await http.post(uri,
|
||||
headers: {'Content-Type': 'application/json'}, body: requestBody);
|
||||
var response = await http.post(
|
||||
uri,
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: requestBody,
|
||||
);
|
||||
|
||||
if (response.statusCode == 201) {
|
||||
final responseBody = jsonDecode(response.body);
|
||||
|
@ -129,8 +134,7 @@ class DFXBuyProvider extends BuyProvider {
|
|||
final message = responseBody['message'] ?? 'Service unavailable in your country';
|
||||
throw Exception(message);
|
||||
} else {
|
||||
throw Exception(
|
||||
'Failed to sign in. Status: ${response.statusCode} ${response.body}');
|
||||
throw Exception('Failed to sign in. Status: ${response.statusCode} ${response.body}');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -142,8 +146,7 @@ class DFXBuyProvider extends BuyProvider {
|
|||
case WalletType.litecoin:
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.bitcoinCash:
|
||||
return wallet.signMessage(message,
|
||||
address: wallet.walletAddresses.address);
|
||||
return wallet.signMessage(message, address: walletAddress);
|
||||
default:
|
||||
throw Exception("WalletType is not available for DFX ${wallet.type}");
|
||||
}
|
||||
|
@ -178,8 +181,7 @@ class DFXBuyProvider extends BuyProvider {
|
|||
|
||||
if (await canLaunchUrl(uri)) {
|
||||
if (DeviceInfo.instance.isMobile) {
|
||||
Navigator.of(context)
|
||||
.pushNamed(Routes.webViewPage, arguments: ["DFX Connect", uri]);
|
||||
Navigator.of(context).pushNamed(Routes.webViewPage, arguments: [title, uri]);
|
||||
} else {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ class OnRamperBuyProvider extends BuyProvider {
|
|||
return color.value.toRadixString(16).replaceAll(RegExp(r'^ff'), "");
|
||||
}
|
||||
|
||||
Uri requestOnramperUrl(BuildContext context) {
|
||||
Uri requestOnramperUrl(BuildContext context, bool? isBuyAction) {
|
||||
String primaryColor,
|
||||
secondaryColor,
|
||||
primaryTextColor,
|
||||
|
@ -79,23 +79,24 @@ class OnRamperBuyProvider extends BuyProvider {
|
|||
return Uri.https(_baseUrl, '', <String, dynamic>{
|
||||
'apiKey': _apiKey,
|
||||
'defaultCrypto': _normalizeCryptoCurrency,
|
||||
'sell_defaultCrypto': _normalizeCryptoCurrency,
|
||||
'networkWallets': '${networkName}:${wallet.walletAddresses.address}',
|
||||
'supportSell': "false",
|
||||
'supportSwap': "false",
|
||||
'primaryColor': primaryColor,
|
||||
'secondaryColor': secondaryColor,
|
||||
'primaryTextColor': primaryTextColor,
|
||||
'secondaryTextColor': secondaryTextColor,
|
||||
'containerColor': containerColor,
|
||||
'cardColor': cardColor
|
||||
'cardColor': cardColor,
|
||||
'mode': isBuyAction == true ? 'buy' : 'sell',
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> launchProvider(BuildContext context, bool? isBuyAction) async {
|
||||
final uri = requestOnramperUrl(context);
|
||||
final uri = requestOnramperUrl(context, isBuyAction);
|
||||
if (DeviceInfo.instance.isMobile) {
|
||||
Navigator.of(context)
|
||||
.pushNamed(Routes.webViewPage, arguments: [S.of(context).buy, uri]);
|
||||
.pushNamed(Routes.webViewPage, arguments: [title, uri]);
|
||||
} else {
|
||||
await launchUrl(uri);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> impl
|
|||
static List<FiatCurrency> get all => _all.values.toList();
|
||||
|
||||
static List<FiatCurrency> get currenciesAvailableToBuyWith =>
|
||||
[aud, bgn, brl, cad, chf, clp, cop, czk, dkk, egp, eur, gbp, gtq, hkd, hrk, huf, idr, ils, inr, isk, jpy, krw, mad, mxn, myr, ngn, nok, nzd, php, pkr, pln, ron, sek, sgd, thb, twd, usd, vnd, zar];
|
||||
[aud, bgn, brl, cad, chf, clp, cop, czk, dkk, egp, eur, gbp, gtq, hkd, hrk, huf, idr, ils, inr, isk, jpy, krw, mad, mxn, myr, ngn, nok, nzd, php, pkr, pln, ron, sek, sgd, thb, twd, usd, vnd, zar, tur];
|
||||
|
||||
static const ars = FiatCurrency(symbol: 'ARS', countryCode: "arg", fullName: "Argentine Peso");
|
||||
static const aud = FiatCurrency(symbol: 'AUD', countryCode: "aus", fullName: "Australian Dollar");
|
||||
|
@ -60,6 +60,7 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> impl
|
|||
static const vef = FiatCurrency(symbol: 'VEF', countryCode: "ven", fullName: "Venezuelan Bolivar Bolívar");
|
||||
static const vnd = FiatCurrency(symbol: 'VND', countryCode: "vnm", fullName: "Vietnamese Dong đồng");
|
||||
static const zar = FiatCurrency(symbol: 'ZAR', countryCode: "saf", fullName: "South African Rand");
|
||||
static const tur = FiatCurrency(symbol: 'TRY', countryCode: "tur", fullName: "Turkish Lira");
|
||||
|
||||
static final _all = {
|
||||
FiatCurrency.ars.raw: FiatCurrency.ars,
|
||||
|
@ -109,7 +110,8 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> impl
|
|||
FiatCurrency.usd.raw: FiatCurrency.usd,
|
||||
FiatCurrency.vef.raw: FiatCurrency.vef,
|
||||
FiatCurrency.vnd.raw: FiatCurrency.vnd,
|
||||
FiatCurrency.zar.raw: FiatCurrency.zar
|
||||
FiatCurrency.zar.raw: FiatCurrency.zar,
|
||||
FiatCurrency.tur.raw: FiatCurrency.tur,
|
||||
};
|
||||
|
||||
static FiatCurrency deserialize({required String raw}) => _all[raw]!;
|
||||
|
|
|
@ -78,7 +78,8 @@ class ProvidersHelper {
|
|||
return [ProviderType.askEachTime, ProviderType.dfx];
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.ethereum:
|
||||
return [ProviderType.askEachTime, ProviderType.moonpaySell, ProviderType.dfx];
|
||||
return [ProviderType.askEachTime, ProviderType.onramper,
|
||||
ProviderType.moonpaySell, ProviderType.dfx];
|
||||
case WalletType.litecoin:
|
||||
case WalletType.bitcoinCash:
|
||||
return [ProviderType.askEachTime, ProviderType.moonpaySell];
|
||||
|
|
|
@ -265,6 +265,9 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
|
|||
}
|
||||
|
||||
String _normalizeCurrency(CryptoCurrency currency) {
|
||||
if (currency.title == "USDC" && currency.tag == "POLY") {
|
||||
throw "Only Bridged USDC (USDC.e) is allowed in ChangeNow";
|
||||
}
|
||||
switch (currency) {
|
||||
case CryptoCurrency.zec:
|
||||
return 'zec';
|
||||
|
|
|
@ -173,7 +173,7 @@ class CWNano extends Nano {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> updateTransactions(Object wallet) async {
|
||||
Future<bool> updateTransactions(Object wallet) async {
|
||||
return (wallet as NanoWallet).updateTransactions();
|
||||
}
|
||||
|
||||
|
@ -189,116 +189,10 @@ class CWNano extends Nano {
|
|||
}
|
||||
|
||||
class CWNanoUtil extends NanoUtil {
|
||||
// standard:
|
||||
@override
|
||||
String seedToPrivate(String seed, int index) {
|
||||
return ND.NanoKeys.seedToPrivate(seed, index);
|
||||
}
|
||||
|
||||
@override
|
||||
String seedToAddress(String seed, int index) {
|
||||
return ND.NanoAccounts.createAccount(
|
||||
ND.NanoAccountType.NANO, privateKeyToPublic(seedToPrivate(seed, index)));
|
||||
}
|
||||
|
||||
@override
|
||||
String seedToMnemonic(String seed) {
|
||||
return NanoMnemomics.seedToMnemonic(seed).join(" ");
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> mnemonicToSeed(String mnemonic) async {
|
||||
return NanoMnemomics.mnemonicListToSeed(mnemonic.split(' '));
|
||||
}
|
||||
|
||||
@override
|
||||
String privateKeyToPublic(String privateKey) {
|
||||
// return NanoHelpers.byteToHex(Ed25519Blake2b.getPubkey(NanoHelpers.hexToBytes(privateKey))!);
|
||||
return ND.NanoKeys.createPublicKey(privateKey);
|
||||
}
|
||||
|
||||
@override
|
||||
String addressToPublicKey(String publicAddress) {
|
||||
return ND.NanoAccounts.extractPublicKey(publicAddress);
|
||||
}
|
||||
|
||||
// universal:
|
||||
@override
|
||||
String privateKeyToAddress(String privateKey) {
|
||||
return ND.NanoAccounts.createAccount(ND.NanoAccountType.NANO, privateKeyToPublic(privateKey));
|
||||
}
|
||||
|
||||
@override
|
||||
String publicKeyToAddress(String publicKey) {
|
||||
return ND.NanoAccounts.createAccount(ND.NanoAccountType.NANO, publicKey);
|
||||
}
|
||||
|
||||
// standard + hd:
|
||||
@override
|
||||
bool isValidSeed(String seed) {
|
||||
// Ensure seed is 64 or 128 characters long
|
||||
if (seed.length != 64 && seed.length != 128) {
|
||||
return false;
|
||||
}
|
||||
// Ensure seed only contains hex characters, 0-9;A-F
|
||||
return ND.NanoHelpers.isHexString(seed);
|
||||
}
|
||||
|
||||
// hd:
|
||||
@override
|
||||
Future<String> hdMnemonicListToSeed(List<String> words) async {
|
||||
// if (words.length != 24) {
|
||||
// throw Exception('Expected a 24-word list, got a ${words.length} list');
|
||||
// }
|
||||
final Uint8List salt = Uint8List.fromList(utf8.encode('mnemonic'));
|
||||
final Pbkdf2 hasher = Pbkdf2(iterations: 2048);
|
||||
final String seed = await hasher.sha512(words.join(' '), salt);
|
||||
return seed;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> hdSeedToPrivate(String seed, int index) async {
|
||||
List<int> seedBytes = hex.decode(seed);
|
||||
KeyData data = await ED25519_HD_KEY.derivePath("m/44'/165'/$index'", seedBytes);
|
||||
return hex.encode(data.key);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> hdSeedToAddress(String seed, int index) async {
|
||||
return ND.NanoAccounts.createAccount(
|
||||
ND.NanoAccountType.NANO, privateKeyToPublic(await hdSeedToPrivate(seed, index)));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> uniSeedToAddress(String seed, int index, String type) {
|
||||
if (type == "standard") {
|
||||
return Future<String>.value(seedToAddress(seed, index));
|
||||
} else if (type == "hd") {
|
||||
return hdSeedToAddress(seed, index);
|
||||
} else {
|
||||
throw Exception('Unknown seed type');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> uniSeedToPrivate(String seed, int index, String type) {
|
||||
if (type == "standard") {
|
||||
return Future<String>.value(seedToPrivate(seed, index));
|
||||
} else if (type == "hd") {
|
||||
return hdSeedToPrivate(seed, index);
|
||||
} else {
|
||||
throw Exception('Unknown seed type');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool isValidBip39Seed(String seed) {
|
||||
// Ensure seed is 128 characters long
|
||||
if (seed.length != 128) {
|
||||
return false;
|
||||
}
|
||||
// Ensure seed only contains hex characters, 0-9;A-F
|
||||
return ND.NanoHelpers.isHexString(seed);
|
||||
return NanoDerivations.isValidBip39Seed(seed);
|
||||
}
|
||||
|
||||
// number util:
|
||||
|
@ -309,92 +203,20 @@ class CWNanoUtil extends NanoUtil {
|
|||
BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000");
|
||||
BigInt rawPerXMR = BigInt.parse("1000000000000");
|
||||
BigInt convertXMRtoNano = BigInt.parse("1000000000000000000");
|
||||
// static BigInt convertXMRtoNano = BigInt.parse("1000000000000000000000000000");
|
||||
|
||||
/// Convert raw to ban and return as BigDecimal
|
||||
///
|
||||
/// @param raw 100000000000000000000000000000
|
||||
/// @return Decimal value 1.000000000000000000000000000000
|
||||
///
|
||||
Decimal _getRawAsDecimal(String? raw, BigInt? rawPerCur) {
|
||||
rawPerCur ??= rawPerNano;
|
||||
final Decimal amount = Decimal.parse(raw.toString());
|
||||
final Decimal result = (amount / Decimal.parse(rawPerCur.toString())).toDecimal();
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
String getRawAsDecimalString(String? raw, BigInt? rawPerCur) {
|
||||
final Decimal result = _getRawAsDecimal(raw, rawPerCur);
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
String truncateDecimal(Decimal input, {int digits = maxDecimalDigits}) {
|
||||
Decimal bigger = input.shift(digits);
|
||||
bigger = bigger.floor(); // chop off the decimal: 1.059 -> 1.05
|
||||
bigger = bigger.shift(-digits);
|
||||
return bigger.toString();
|
||||
}
|
||||
|
||||
/// Return raw as a NANO amount.
|
||||
///
|
||||
/// @param raw 100000000000000000000000000000
|
||||
/// @returns 1
|
||||
///
|
||||
@override
|
||||
String getRawAsUsableString(String? raw, BigInt rawPerCur) {
|
||||
final String res =
|
||||
truncateDecimal(_getRawAsDecimal(raw, rawPerCur), digits: maxDecimalDigits + 9);
|
||||
|
||||
if (raw == null || raw == "0" || raw == "00000000000000000000000000000000") {
|
||||
return "0";
|
||||
}
|
||||
|
||||
if (!res.contains(".")) {
|
||||
return res;
|
||||
}
|
||||
|
||||
final String numAmount = res.split(".")[0];
|
||||
String decAmount = res.split(".")[1];
|
||||
|
||||
// truncate:
|
||||
if (decAmount.length > maxDecimalDigits) {
|
||||
decAmount = decAmount.substring(0, maxDecimalDigits);
|
||||
// remove trailing zeros:
|
||||
decAmount = decAmount.replaceAllMapped(RegExp(r'0+$'), (Match match) => '');
|
||||
if (decAmount.isEmpty) {
|
||||
return numAmount;
|
||||
}
|
||||
}
|
||||
|
||||
return "$numAmount.$decAmount";
|
||||
return NanoAmounts.getRawAsUsableString(raw, rawPerCur);
|
||||
}
|
||||
|
||||
@override
|
||||
String getRawAccuracy(String? raw, BigInt rawPerCur) {
|
||||
final String rawString = getRawAsUsableString(raw, rawPerCur);
|
||||
final String rawDecimalString = _getRawAsDecimal(raw, rawPerCur).toString();
|
||||
|
||||
if (raw == null || raw.isEmpty || raw == "0") {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (rawString != rawDecimalString) {
|
||||
return "~";
|
||||
}
|
||||
return "";
|
||||
return NanoAmounts.getRawAccuracy(raw, rawPerCur);
|
||||
}
|
||||
|
||||
/// Return readable string amount as raw string
|
||||
/// @param amount 1.01
|
||||
/// @returns 101000000000000000000000000000
|
||||
///
|
||||
@override
|
||||
String getAmountAsRaw(String amount, BigInt rawPerCur) {
|
||||
final Decimal asDecimal = Decimal.parse(amount);
|
||||
final Decimal rawDecimal = Decimal.parse(rawPerCur.toString());
|
||||
return (asDecimal * rawDecimal).toString();
|
||||
return NanoAmounts.getAmountAsRaw(amount, rawPerCur);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -411,29 +233,29 @@ class CWNanoUtil extends NanoUtil {
|
|||
if (seedKey != null) {
|
||||
if (seedKey.length == 64) {
|
||||
try {
|
||||
mnemonic = nanoUtil!.seedToMnemonic(seedKey);
|
||||
mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey);
|
||||
} catch (e) {
|
||||
print("not a valid 'nano' seed key");
|
||||
}
|
||||
}
|
||||
if (derivationType == DerivationType.bip39) {
|
||||
publicAddress = await hdSeedToAddress(seedKey, 0);
|
||||
publicAddress = await NanoDerivations.hdSeedToAddress(seedKey, index: 0);
|
||||
} else if (derivationType == DerivationType.nano) {
|
||||
publicAddress = await seedToAddress(seedKey, 0);
|
||||
publicAddress = await NanoDerivations.standardSeedToAddress(seedKey, index: 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (derivationType == DerivationType.bip39) {
|
||||
if (mnemonic != null) {
|
||||
seedKey = await hdMnemonicListToSeed(mnemonic.split(' '));
|
||||
publicAddress = await hdSeedToAddress(seedKey, 0);
|
||||
seedKey = await NanoDerivations.hdMnemonicListToSeed(mnemonic.split(' '));
|
||||
publicAddress = await NanoDerivations.hdSeedToAddress(seedKey, index: 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (derivationType == DerivationType.nano) {
|
||||
if (mnemonic != null) {
|
||||
seedKey = await mnemonicToSeed(mnemonic);
|
||||
publicAddress = await seedToAddress(seedKey, 0);
|
||||
seedKey = await NanoDerivations.standardMnemonicToSeed(mnemonic);
|
||||
publicAddress = await NanoDerivations.standardSeedToAddress(seedKey, index: 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -461,7 +283,7 @@ class CWNanoUtil extends NanoUtil {
|
|||
return [DerivationType.bip39];
|
||||
} else if (seedKey?.length == 64) {
|
||||
try {
|
||||
mnemonic = nanoUtil!.seedToMnemonic(seedKey!);
|
||||
mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey!);
|
||||
} catch (e) {
|
||||
print("not a valid 'nano' seed key");
|
||||
}
|
||||
|
@ -475,19 +297,19 @@ class CWNanoUtil extends NanoUtil {
|
|||
nanoClient.connect(node);
|
||||
|
||||
if (mnemonic != null) {
|
||||
seedKey = await hdMnemonicListToSeed(mnemonic.split(' '));
|
||||
publicAddressBip39 = await hdSeedToAddress(seedKey, 0);
|
||||
seedKey = await NanoDerivations.hdMnemonicListToSeed(mnemonic.split(' '));
|
||||
publicAddressBip39 = await NanoDerivations.hdSeedToAddress(seedKey, index: 0);
|
||||
|
||||
seedKey = await mnemonicToSeed(mnemonic);
|
||||
publicAddressStandard = await seedToAddress(seedKey, 0);
|
||||
seedKey = await NanoDerivations.standardMnemonicToSeed(mnemonic);
|
||||
publicAddressStandard = await NanoDerivations.standardSeedToAddress(seedKey, index: 0);
|
||||
} else if (seedKey != null) {
|
||||
try {
|
||||
publicAddressBip39 = await hdSeedToAddress(seedKey, 0);
|
||||
publicAddressBip39 = await NanoDerivations.hdSeedToAddress(seedKey, index: 0);
|
||||
} catch (e) {
|
||||
return [DerivationType.nano];
|
||||
}
|
||||
try {
|
||||
publicAddressStandard = await seedToAddress(seedKey, 0);
|
||||
publicAddressStandard = await NanoDerivations.standardSeedToAddress(seedKey, index: 0);
|
||||
} catch (e) {
|
||||
return [DerivationType.bip39];
|
||||
}
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:cake_wallet/buy/buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart';
|
||||
import 'package:cake_wallet/buy/wyre/wyre_buy_provider.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/store/dashboard/orders_store.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_view_model.dart';
|
||||
|
@ -72,10 +69,10 @@ class BuyWebViewPageBodyState extends State<BuyWebViewPageBody> {
|
|||
Widget build(BuildContext context) {
|
||||
return InAppWebView(
|
||||
key: _webViewkey,
|
||||
initialOptions: InAppWebViewGroupOptions(
|
||||
crossPlatform: InAppWebViewOptions(transparentBackground: true),
|
||||
initialSettings: InAppWebViewSettings(
|
||||
transparentBackground: true,
|
||||
),
|
||||
initialUrlRequest: URLRequest(url: Uri.tryParse(widget.url ?? '')),
|
||||
initialUrlRequest: URLRequest(url: WebUri(widget.url ?? '')),
|
||||
onWebViewCreated: (InAppWebViewController controller) =>
|
||||
setState(() => _webViewController = controller));
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
|
@ -35,21 +33,21 @@ class WebViewPageBodyState extends State<WebViewPageBody> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InAppWebView(
|
||||
initialOptions: InAppWebViewGroupOptions(
|
||||
crossPlatform: InAppWebViewOptions(transparentBackground: true),
|
||||
initialSettings: InAppWebViewSettings(
|
||||
transparentBackground: true,
|
||||
),
|
||||
initialUrlRequest: URLRequest(url: widget.uri),
|
||||
androidOnPermissionRequest: (_, __, resources) async {
|
||||
initialUrlRequest: URLRequest(url: WebUri.uri(widget.uri)),
|
||||
onPermissionRequest: (controller, request) async {
|
||||
bool permissionGranted = await Permission.camera.status == PermissionStatus.granted;
|
||||
if (!permissionGranted) {
|
||||
permissionGranted = await Permission.camera.request().isGranted;
|
||||
}
|
||||
|
||||
return PermissionRequestResponse(
|
||||
resources: resources,
|
||||
return PermissionResponse(
|
||||
resources: request.resources,
|
||||
action: permissionGranted
|
||||
? PermissionRequestResponseAction.GRANT
|
||||
: PermissionRequestResponseAction.DENY,
|
||||
? PermissionResponseAction.GRANT
|
||||
: PermissionResponseAction.DENY,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
|
@ -14,6 +16,7 @@ import 'package:cake_wallet/utils/feature_flag.dart';
|
|||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
|
@ -200,7 +203,7 @@ class CryptoBalanceWidget extends StatelessWidget {
|
|||
additionalFiatBalance: balance.fiatAdditionalBalance,
|
||||
frozenBalance: balance.frozenBalance,
|
||||
frozenFiatBalance: balance.fiatFrozenBalance,
|
||||
currency: balance.formattedAssetTitle,
|
||||
currency: balance.asset,
|
||||
hasAdditionalBalance:
|
||||
dashboardViewModel.balanceViewModel.hasAdditionalBalance,
|
||||
);
|
||||
|
@ -216,7 +219,7 @@ class CryptoBalanceWidget extends StatelessWidget {
|
|||
}
|
||||
|
||||
class BalanceRowWidget extends StatelessWidget {
|
||||
const BalanceRowWidget({
|
||||
BalanceRowWidget({
|
||||
required this.availableBalanceLabel,
|
||||
required this.availableBalance,
|
||||
required this.availableFiatBalance,
|
||||
|
@ -238,7 +241,7 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
final String additionalFiatBalance;
|
||||
final String frozenBalance;
|
||||
final String frozenFiatBalance;
|
||||
final String currency;
|
||||
final CryptoCurrency currency;
|
||||
final bool hasAdditionalBalance;
|
||||
|
||||
// void _showBalanceDescription(BuildContext context) {
|
||||
|
@ -268,7 +271,7 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
|
@ -325,13 +328,48 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
Text(currency,
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.assetTitleColor,
|
||||
height: 1)),
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.18,
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
currency.iconPath != null
|
||||
? Container(
|
||||
child: Image.asset(
|
||||
currency.iconPath!,
|
||||
height: 30.0,
|
||||
width: 30.0,
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
height: 30.0,
|
||||
width: 30.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
currency.title.substring(0, min(currency.title.length, 2)),
|
||||
style: TextStyle(fontSize: 11),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
currency.title,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.assetTitleColor,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (frozenBalance.isNotEmpty)
|
||||
|
@ -374,7 +412,9 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
fontSize: 20,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
color: Theme.of(context)
|
||||
.extension<BalancePageTheme>()!
|
||||
.balanceAmountColor,
|
||||
height: 1,
|
||||
),
|
||||
maxLines: 1,
|
||||
|
@ -388,7 +428,7 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -49,7 +49,7 @@ class TransactionsPage extends StatelessWidget {
|
|||
onTap: () => Navigator.of(context).pushNamed(Routes.webViewPage, arguments: [
|
||||
'',
|
||||
Uri.parse(
|
||||
'https://guides.cakewallet.com/docs/bugs-service-status/why_are_my_funds_not_appearing/')
|
||||
'https://guides.cakewallet.com/docs/FAQ/why_are_my_funds_not_appearing/')
|
||||
]),
|
||||
title: S.of(context).syncing_wallet_alert_title,
|
||||
subTitle: S.of(context).syncing_wallet_alert_content,
|
||||
|
|
|
@ -261,7 +261,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
|
|||
fee: S.of(popupContext).send_fee,
|
||||
feeValue: widget.exchangeTradeViewModel.sendViewModel
|
||||
.pendingTransaction!.feeFormatted,
|
||||
rightButtonText: S.of(popupContext).ok,
|
||||
rightButtonText: S.of(popupContext).send,
|
||||
leftButtonText: S.of(popupContext).cancel,
|
||||
actionRightButton: () async {
|
||||
Navigator.of(popupContext).pop();
|
||||
|
|
|
@ -423,7 +423,7 @@ class SendPage extends BasePage {
|
|||
feeValue: sendViewModel.pendingTransaction!.feeFormatted,
|
||||
feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||
outputs: sendViewModel.outputs,
|
||||
rightButtonText: S.of(_dialogContext).ok,
|
||||
rightButtonText: S.of(_dialogContext).send,
|
||||
leftButtonText: S.of(_dialogContext).cancel,
|
||||
actionRightButton: () {
|
||||
Navigator.of(_dialogContext).pop();
|
||||
|
|
|
@ -22,17 +22,17 @@ class ChatwootWidgetState extends State<ChatwootWidget> {
|
|||
@override
|
||||
Widget build(BuildContext context) => InAppWebView(
|
||||
key: _webViewkey,
|
||||
initialOptions: InAppWebViewGroupOptions(
|
||||
crossPlatform: InAppWebViewOptions(transparentBackground: true),
|
||||
),
|
||||
initialUrlRequest: URLRequest(url: Uri.tryParse(widget.supportUrl)),
|
||||
initialSettings: InAppWebViewSettings(
|
||||
transparentBackground: true,
|
||||
),
|
||||
initialUrlRequest: URLRequest(url: WebUri(widget.supportUrl)),
|
||||
onWebViewCreated: (InAppWebViewController controller) {
|
||||
controller.addWebMessageListener(
|
||||
WebMessageListener(
|
||||
jsObjectName: 'ReactNativeWebView',
|
||||
onPostMessage: (String? message, Uri? sourceOrigin, bool isMainFrame,
|
||||
JavaScriptReplyProxy replyProxy) {
|
||||
final shortenedMessage = message?.substring(16);
|
||||
onPostMessage: (WebMessage? message, WebUri? sourceOrigin, bool isMainFrame,
|
||||
PlatformJavaScriptReplyProxy replyProxy) {
|
||||
final shortenedMessage = message?.data.toString().substring(16);
|
||||
if (shortenedMessage != null && isJsonString(shortenedMessage)) {
|
||||
final parsedMessage = jsonDecode(shortenedMessage);
|
||||
final eventType = parsedMessage["event"];
|
||||
|
|
|
@ -125,6 +125,8 @@ abstract class BalanceViewModelBase with Store {
|
|||
case WalletType.haven:
|
||||
case WalletType.ethereum:
|
||||
case WalletType.polygon:
|
||||
case WalletType.nano:
|
||||
case WalletType.banano:
|
||||
return S.current.xmr_available_balance;
|
||||
default:
|
||||
return S.current.confirmed;
|
||||
|
@ -139,6 +141,9 @@ abstract class BalanceViewModelBase with Store {
|
|||
case WalletType.ethereum:
|
||||
case WalletType.polygon:
|
||||
return S.current.xmr_full_balance;
|
||||
case WalletType.nano:
|
||||
case WalletType.banano:
|
||||
return S.current.receivable_balance;
|
||||
default:
|
||||
return S.current.unconfirmed;
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ class TransactionListItem extends ActionListItem with Keyable {
|
|||
break;
|
||||
case WalletType.nano:
|
||||
amount = calculateFiatAmountRaw(
|
||||
cryptoAmount: double.parse(nanoUtil!.getRawAsDecimalString(
|
||||
cryptoAmount: double.parse(nanoUtil!.getRawAsUsableString(
|
||||
nano!.getTransactionAmountRaw(transaction).toString(), nanoUtil!.rawPerNano)),
|
||||
price: price);
|
||||
break;
|
||||
|
|
|
@ -247,11 +247,15 @@ abstract class TransactionDetailsViewModelBase with Store {
|
|||
void _addNanoListItems(TransactionInfo tx, DateFormat dateFormat) {
|
||||
final _items = [
|
||||
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||
if (showRecipientAddress && tx.to != null)
|
||||
StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!),
|
||||
if (showRecipientAddress && tx.from != null)
|
||||
StandartListItem(title: S.current.transaction_details_source_address, value: tx.from!),
|
||||
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
|
||||
StandartListItem(title: S.current.confirmations, value: (tx.confirmations > 0).toString()),
|
||||
StandartListItem(title: S.current.confirmed_tx, value: (tx.confirmations > 0).toString()),
|
||||
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
|
||||
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
|
||||
];
|
||||
|
||||
items.addAll(_items);
|
||||
|
|
|
@ -9,6 +9,7 @@ import connectivity_plus_macos
|
|||
import cw_monero
|
||||
import device_info_plus
|
||||
import devicelocale
|
||||
import flutter_inappwebview_macos
|
||||
import flutter_secure_storage_macos
|
||||
import in_app_review
|
||||
import package_info
|
||||
|
@ -24,6 +25,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||
CwMoneroPlugin.register(with: registry.registrar(forPlugin: "CwMoneroPlugin"))
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin"))
|
||||
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
|
||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||
InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin"))
|
||||
FLTPackageInfoPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlugin"))
|
||||
|
|
|
@ -43,7 +43,7 @@ dependencies:
|
|||
auto_size_text: ^3.0.0
|
||||
dotted_border: ^2.0.0+2
|
||||
smooth_page_indicator: ^1.0.0+2
|
||||
flutter_inappwebview: ^5.7.2+3
|
||||
flutter_inappwebview: ^6.0.0
|
||||
flutter_spinkit: ^5.1.0
|
||||
uni_links: ^0.5.1
|
||||
lottie: ^1.3.0
|
||||
|
|
|
@ -765,5 +765,8 @@
|
|||
"connected": "متصل",
|
||||
"disconnected": "انقطع الاتصال",
|
||||
"onion_only": "البصل فقط",
|
||||
"connecting": "توصيل"
|
||||
"connecting": "توصيل",
|
||||
"receivable_balance": "التوازن القادم",
|
||||
"confirmed_tx": "مؤكد",
|
||||
"transaction_details_source_address": "عنوان المصدر"
|
||||
}
|
|
@ -761,5 +761,8 @@
|
|||
"connected": "Свързани",
|
||||
"disconnected": "Изключен",
|
||||
"onion_only": "Само лук",
|
||||
"connecting": "Свързване"
|
||||
"connecting": "Свързване",
|
||||
"receivable_balance": "Баланс за вземания",
|
||||
"confirmed_tx": "Потвърдено",
|
||||
"transaction_details_source_address": "Адрес на източника"
|
||||
}
|
|
@ -761,5 +761,8 @@
|
|||
"connected": "Připojeno",
|
||||
"disconnected": "Odpojené",
|
||||
"onion_only": "Pouze cibule",
|
||||
"connecting": "Spojovací"
|
||||
"connecting": "Spojovací",
|
||||
"receivable_balance": "Zůstatek pohledávek",
|
||||
"confirmed_tx": "Potvrzeno",
|
||||
"transaction_details_source_address": "Zdrojová adresa"
|
||||
}
|
|
@ -769,5 +769,8 @@
|
|||
"connected": "In Verbindung gebracht",
|
||||
"disconnected": "Getrennt",
|
||||
"onion_only": "Nur Zwiebel",
|
||||
"connecting": "Verbinden"
|
||||
"connecting": "Verbinden",
|
||||
"receivable_balance": "Forderungsbilanz",
|
||||
"confirmed_tx": "Bestätigt",
|
||||
"transaction_details_source_address": "Quelladresse"
|
||||
}
|
|
@ -770,5 +770,8 @@
|
|||
"connected": "Connected",
|
||||
"disconnected": "Disconnected",
|
||||
"onion_only": "Onion only",
|
||||
"connecting": "Connecting"
|
||||
"connecting": "Connecting",
|
||||
"receivable_balance": "Receivable Balance",
|
||||
"confirmed_tx": "Confirmed",
|
||||
"transaction_details_source_address": "Source address"
|
||||
}
|
|
@ -769,5 +769,8 @@
|
|||
"connected": "Conectado",
|
||||
"disconnected": "Desconectado",
|
||||
"onion_only": "Solo cebolla",
|
||||
"connecting": "Conexión"
|
||||
"connecting": "Conexión",
|
||||
"receivable_balance": "Saldo de cuentas por cobrar",
|
||||
"confirmed_tx": "Confirmado",
|
||||
"transaction_details_source_address": "Dirección de la fuente"
|
||||
}
|
|
@ -769,5 +769,8 @@
|
|||
"connected": "Connecté",
|
||||
"disconnected": "Débranché",
|
||||
"onion_only": "Oignon seulement",
|
||||
"connecting": "De liaison"
|
||||
"connecting": "De liaison",
|
||||
"receivable_balance": "Solde de créances",
|
||||
"confirmed_tx": "Confirmé",
|
||||
"transaction_details_source_address": "Adresse source"
|
||||
}
|
|
@ -751,5 +751,8 @@
|
|||
"connected": "Wanda aka haɗa",
|
||||
"disconnected": "Katse",
|
||||
"onion_only": "Albasa kawai",
|
||||
"connecting": "Haɗa"
|
||||
"connecting": "Haɗa",
|
||||
"receivable_balance": "Daidaituwa da daidaituwa",
|
||||
"confirmed_tx": "Tabbatar",
|
||||
"transaction_details_source_address": "Adireshin Incord"
|
||||
}
|
|
@ -769,5 +769,8 @@
|
|||
"connected": "जुड़े हुए",
|
||||
"disconnected": "डिस्कनेक्ट किया गया",
|
||||
"onion_only": "केवल प्याज",
|
||||
"connecting": "कनेक्ट"
|
||||
"connecting": "कनेक्ट",
|
||||
"receivable_balance": "प्राप्य शेष",
|
||||
"confirmed_tx": "की पुष्टि",
|
||||
"transaction_details_source_address": "स्रोत पता"
|
||||
}
|
|
@ -767,5 +767,8 @@
|
|||
"connected": "Povezan",
|
||||
"disconnected": "Isključen",
|
||||
"onion_only": "Samo luk",
|
||||
"connecting": "Spoj"
|
||||
"connecting": "Spoj",
|
||||
"receivable_balance": "Stanje potraživanja",
|
||||
"confirmed_tx": "Potvrđen",
|
||||
"transaction_details_source_address": "Adresa izvora"
|
||||
}
|
|
@ -757,5 +757,8 @@
|
|||
"connected": "Terhubung",
|
||||
"disconnected": "Terputus",
|
||||
"onion_only": "Bawang saja",
|
||||
"connecting": "Menghubungkan"
|
||||
"connecting": "Menghubungkan",
|
||||
"receivable_balance": "Saldo piutang",
|
||||
"confirmed_tx": "Dikonfirmasi",
|
||||
"transaction_details_source_address": "Alamat sumber"
|
||||
}
|
|
@ -769,5 +769,8 @@
|
|||
"connected": "Collegato",
|
||||
"disconnected": "Disconnesso",
|
||||
"onion_only": "Solo cipolla",
|
||||
"connecting": "Connessione"
|
||||
"connecting": "Connessione",
|
||||
"receivable_balance": "Bilanciamento creditizio",
|
||||
"confirmed_tx": "Confermato",
|
||||
"transaction_details_source_address": "Indirizzo di partenza"
|
||||
}
|
|
@ -769,5 +769,8 @@
|
|||
"connected": "接続",
|
||||
"disconnected": "切断された",
|
||||
"onion_only": "オニオンのみ",
|
||||
"connecting": "接続"
|
||||
"connecting": "接続",
|
||||
"receivable_balance": "売掛金残高",
|
||||
"confirmed_tx": "確認済み",
|
||||
"transaction_details_source_address": "ソースアドレス"
|
||||
}
|
|
@ -767,5 +767,8 @@
|
|||
"connected": "연결",
|
||||
"disconnected": "연결이 끊어졌습니다",
|
||||
"onion_only": "양파 만",
|
||||
"connecting": "연결"
|
||||
"connecting": "연결",
|
||||
"receivable_balance": "채권 잔액",
|
||||
"confirmed_tx": "확인",
|
||||
"transaction_details_source_address": "소스 주소"
|
||||
}
|
|
@ -767,5 +767,8 @@
|
|||
"connected": "ချိတ်ဆက်ထားသော",
|
||||
"disconnected": "ချို့ယွင်းချက်",
|
||||
"onion_only": "သာကြက်သွန်",
|
||||
"connecting": "ချိတ်ဆက်"
|
||||
"connecting": "ချိတ်ဆက်",
|
||||
"receivable_balance": "လက်ကျန်ငွေ",
|
||||
"confirmed_tx": "အတည်ပြုသည်",
|
||||
"transaction_details_source_address": "အရင်းအမြစ်လိပ်စာ"
|
||||
}
|
|
@ -769,5 +769,8 @@
|
|||
"connected": "Verbonden",
|
||||
"disconnected": "Losgekoppeld",
|
||||
"onion_only": "Alleen ui",
|
||||
"connecting": "Verbinden"
|
||||
"connecting": "Verbinden",
|
||||
"receivable_balance": "Het saldo",
|
||||
"confirmed_tx": "Bevestigd",
|
||||
"transaction_details_source_address": "Bron adres"
|
||||
}
|
|
@ -769,5 +769,8 @@
|
|||
"connected": "Połączony",
|
||||
"disconnected": "Bezładny",
|
||||
"onion_only": "Tylko cebula",
|
||||
"connecting": "Złączony"
|
||||
"connecting": "Złączony",
|
||||
"receivable_balance": "Saldo należności",
|
||||
"confirmed_tx": "Potwierdzony",
|
||||
"transaction_details_source_address": "Adres źródłowy"
|
||||
}
|
|
@ -768,5 +768,8 @@
|
|||
"connected": "Conectado",
|
||||
"disconnected": "Desconectado",
|
||||
"onion_only": "Apenas cebola",
|
||||
"connecting": "Conectando"
|
||||
"connecting": "Conectando",
|
||||
"receivable_balance": "Saldo a receber",
|
||||
"confirmed_tx": "Confirmado",
|
||||
"transaction_details_source_address": "Endereço de Origem"
|
||||
}
|
|
@ -769,5 +769,8 @@
|
|||
"connected": "Связанный",
|
||||
"disconnected": "Отключен",
|
||||
"onion_only": "Только лук",
|
||||
"connecting": "Соединение"
|
||||
"connecting": "Соединение",
|
||||
"receivable_balance": "Баланс дебиторской задолженности",
|
||||
"confirmed_tx": "Подтвержденный",
|
||||
"transaction_details_source_address": "Адрес источника"
|
||||
}
|
|
@ -767,5 +767,8 @@
|
|||
"connected": "ซึ่งเชื่อมต่อกัน",
|
||||
"disconnected": "ตัดการเชื่อมต่อ",
|
||||
"onion_only": "หัวหอมเท่านั้น",
|
||||
"connecting": "การเชื่อมต่อ"
|
||||
"connecting": "การเชื่อมต่อ",
|
||||
"receivable_balance": "ยอดลูกหนี้",
|
||||
"confirmed_tx": "ซึ่งยืนยันแล้ว",
|
||||
"transaction_details_source_address": "ที่อยู่แหล่งกำเนิด"
|
||||
}
|
|
@ -763,5 +763,8 @@
|
|||
"connected": "Konektado",
|
||||
"disconnected": "Naka -disconnect",
|
||||
"onion_only": "Onion lamang",
|
||||
"connecting": "Pagkonekta"
|
||||
"connecting": "Pagkonekta",
|
||||
"receivable_balance": "Natatanggap na balanse",
|
||||
"confirmed_tx": "Nakumpirma",
|
||||
"transaction_details_source_address": "SOURCE ADDRESS"
|
||||
}
|
|
@ -767,5 +767,8 @@
|
|||
"connected": "Bağlı",
|
||||
"disconnected": "Bağlantı kesildi",
|
||||
"onion_only": "Sadece soğan",
|
||||
"connecting": "Bağlanıyor"
|
||||
"connecting": "Bağlanıyor",
|
||||
"receivable_balance": "Alacak bakiyesi",
|
||||
"confirmed_tx": "Onaylanmış",
|
||||
"transaction_details_source_address": "Kaynak adresi"
|
||||
}
|
|
@ -769,5 +769,8 @@
|
|||
"connected": "З'єднаний",
|
||||
"disconnected": "Роз'єднаний",
|
||||
"onion_only": "Тільки цибуля",
|
||||
"connecting": "З'єднання"
|
||||
"connecting": "З'єднання",
|
||||
"receivable_balance": "Баланс дебіторської заборгованості",
|
||||
"confirmed_tx": "Підтверджений",
|
||||
"transaction_details_source_address": "Адреса джерела"
|
||||
}
|
|
@ -761,5 +761,8 @@
|
|||
"connected": "منسلک",
|
||||
"disconnected": "منقطع",
|
||||
"onion_only": "صرف پیاز",
|
||||
"connecting": "رابطہ قائم کرنا"
|
||||
"connecting": "رابطہ قائم کرنا",
|
||||
"receivable_balance": "قابل وصول توازن",
|
||||
"confirmed_tx": "تصدیق",
|
||||
"transaction_details_source_address": "ماخذ ایڈریس"
|
||||
}
|
|
@ -763,5 +763,8 @@
|
|||
"connected": "Sopọ",
|
||||
"disconnected": "Ge asopọ",
|
||||
"onion_only": "Alubosa nikan",
|
||||
"connecting": "Asopọ"
|
||||
"connecting": "Asopọ",
|
||||
"receivable_balance": "Iwontunws.funfun ti o gba",
|
||||
"confirmed_tx": "Jẹrisi",
|
||||
"transaction_details_source_address": "Adirẹsi orisun"
|
||||
}
|
|
@ -768,5 +768,8 @@
|
|||
"connected": "连接的",
|
||||
"disconnected": "断开连接",
|
||||
"onion_only": "仅洋葱",
|
||||
"connecting": "连接"
|
||||
"connecting": "连接",
|
||||
"receivable_balance": "应收余额",
|
||||
"confirmed_tx": "确认的",
|
||||
"transaction_details_source_address": "源地址"
|
||||
}
|
|
@ -759,7 +759,7 @@ import 'package:convert/convert.dart';
|
|||
import "package:ed25519_hd_key/ed25519_hd_key.dart";
|
||||
import 'package:libcrypto/libcrypto.dart';
|
||||
import 'package:nanodart/nanodart.dart' as ND;
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:nanoutil/nanoutil.dart';
|
||||
""";
|
||||
const nanoCwPart = "part 'cw_nano.dart';";
|
||||
const nanoContent = """
|
||||
|
@ -795,7 +795,7 @@ abstract class Nano {
|
|||
Map<String, String> getKeys(Object wallet);
|
||||
Object createNanoTransactionCredentials(List<Output> outputs);
|
||||
Future<void> changeRep(Object wallet, String address);
|
||||
Future<void> updateTransactions(Object wallet);
|
||||
Future<bool> updateTransactions(Object wallet);
|
||||
BigInt getTransactionAmountRaw(TransactionInfo transactionInfo);
|
||||
String getRepresentative(Object wallet);
|
||||
}
|
||||
|
@ -810,20 +810,6 @@ abstract class NanoAccountList {
|
|||
}
|
||||
|
||||
abstract class NanoUtil {
|
||||
String seedToPrivate(String seed, int index);
|
||||
String seedToAddress(String seed, int index);
|
||||
String seedToMnemonic(String seed);
|
||||
Future<String> mnemonicToSeed(String mnemonic);
|
||||
String privateKeyToPublic(String privateKey);
|
||||
String addressToPublicKey(String publicAddress);
|
||||
String privateKeyToAddress(String privateKey);
|
||||
String publicKeyToAddress(String publicKey);
|
||||
bool isValidSeed(String seed);
|
||||
Future<String> hdMnemonicListToSeed(List<String> words);
|
||||
Future<String> hdSeedToPrivate(String seed, int index);
|
||||
Future<String> hdSeedToAddress(String seed, int index);
|
||||
Future<String> uniSeedToAddress(String seed, int index, String type);
|
||||
Future<String> uniSeedToPrivate(String seed, int index, String type);
|
||||
bool isValidBip39Seed(String seed);
|
||||
static const int maxDecimalDigits = 6; // Max digits after decimal
|
||||
BigInt rawPerNano = BigInt.parse("1000000000000000000000000000000");
|
||||
|
@ -831,7 +817,6 @@ abstract class NanoUtil {
|
|||
BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000");
|
||||
BigInt rawPerXMR = BigInt.parse("1000000000000");
|
||||
BigInt convertXMRtoNano = BigInt.parse("1000000000000000000");
|
||||
String getRawAsDecimalString(String? raw, BigInt? rawPerCur);
|
||||
String getRawAsUsableString(String? raw, BigInt rawPerCur);
|
||||
String getRawAccuracy(String? raw, BigInt rawPerCur);
|
||||
String getAmountAsRaw(String amount, BigInt rawPerCur);
|
||||
|
|
Loading…
Reference in a new issue