mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 19:49:22 +00:00
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into bitcoin-derivations
This commit is contained in:
commit
e9cd76f534
123 changed files with 3653 additions and 766 deletions
34
.github/workflows/pr_test_build.yml
vendored
34
.github/workflows/pr_test_build.yml
vendored
|
@ -2,11 +2,10 @@ name: PR Test Build
|
|||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
PR_test_build:
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
env:
|
||||
STORE_PASS: test@cake_wallet
|
||||
|
@ -23,12 +22,12 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: '8.x'
|
||||
java-version: "8.x"
|
||||
|
||||
- name: Flutter action
|
||||
uses: subosito/flutter-action@v1
|
||||
with:
|
||||
flutter-version: '3.10.x'
|
||||
flutter-version: "3.10.x"
|
||||
channel: stable
|
||||
|
||||
- name: Install package dependencies
|
||||
|
@ -132,6 +131,7 @@ jobs:
|
|||
echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart
|
||||
echo "const robinhoodCIdApiSecret = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart
|
||||
echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart
|
||||
|
||||
- name: Rename app
|
||||
run: echo -e "id=com.cakewallet.test\nname=$GITHUB_HEAD_REF" > /opt/android/cake_wallet/android/app.properties
|
||||
|
@ -141,18 +141,18 @@ jobs:
|
|||
cd /opt/android/cake_wallet
|
||||
flutter build apk --release
|
||||
|
||||
# - name: Push to App Center
|
||||
# run: |
|
||||
# echo 'Installing App Center CLI tools'
|
||||
# npm install -g appcenter-cli
|
||||
# echo "Publishing test to App Center"
|
||||
# appcenter distribute release \
|
||||
# --group "Testers" \
|
||||
# --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \
|
||||
# --release-notes ${GITHUB_HEAD_REF} \
|
||||
# --app Cake-Labs/Cake-Wallet \
|
||||
# --token ${{ secrets.APP_CENTER_TOKEN }} \
|
||||
# --quiet
|
||||
# - name: Push to App Center
|
||||
# run: |
|
||||
# echo 'Installing App Center CLI tools'
|
||||
# npm install -g appcenter-cli
|
||||
# echo "Publishing test to App Center"
|
||||
# appcenter distribute release \
|
||||
# --group "Testers" \
|
||||
# --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \
|
||||
# --release-notes ${GITHUB_HEAD_REF} \
|
||||
# --app Cake-Labs/Cake-Wallet \
|
||||
# --token ${{ secrets.APP_CENTER_TOKEN }} \
|
||||
# --quiet
|
||||
|
||||
- name: Rename apk file
|
||||
run: |
|
||||
|
@ -172,6 +172,6 @@ jobs:
|
|||
token: ${{ secrets.SLACK_APP_TOKEN }}
|
||||
path: /opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk
|
||||
channel: ${{ secrets.SLACK_APK_CHANNEL }}
|
||||
title: '${{github.head_ref}}.apk'
|
||||
title: "${{github.head_ref}}.apk"
|
||||
filename: ${{github.head_ref}}.apk
|
||||
initial_comment: ${{ github.event.head_commit.message }}
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -124,6 +124,7 @@ lib/bitcoin/bitcoin.dart
|
|||
lib/monero/monero.dart
|
||||
lib/haven/haven.dart
|
||||
lib/ethereum/ethereum.dart
|
||||
lib/nano/nano.dart
|
||||
|
||||
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
|
||||
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png
|
||||
|
|
|
@ -25,10 +25,6 @@
|
|||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="true">
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.SplashScreenDrawable"
|
||||
android:resource="@drawable/launch_background"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<color android:color="#000000"/> <!-- Dark background color -->
|
||||
</item>
|
||||
</layer-list>
|
|
@ -1,12 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
<item>
|
||||
<color android:color="#FFFFFF"/> <!-- Light background color -->
|
||||
</item>
|
||||
</layer-list>
|
||||
|
|
BIN
assets/images/walletconnect_logo.png
Normal file
BIN
assets/images/walletconnect_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 73 KiB |
|
@ -5,4 +5,5 @@
|
|||
-
|
||||
uri: workers.perish.co
|
||||
-
|
||||
uri: worker.nanoriver.cc:443
|
||||
uri: worker.nanoriver.cc
|
||||
useSSL: true
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
Enhance Monero coin control
|
||||
Add Filipino localization
|
||||
Bug Fixes
|
||||
Fix 2FA code issue
|
||||
Bug fixes
|
||||
Minor enhancements
|
|
@ -1,5 +1,4 @@
|
|||
New Buy Provider Robinhood
|
||||
Fix sending Ethereum issue
|
||||
Enhance Monero coin control
|
||||
Add Filipino localization
|
||||
Bug Fixes
|
||||
Ethereum enhancements and bug fixes
|
||||
Fix 2FA code issue
|
||||
Bug fixes
|
||||
Minor enhancements
|
|
@ -7,4 +7,5 @@ cd cw_monero && flutter pub get && flutter packages pub run build_runner build -
|
|||
cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
|
|
|
@ -2304,4 +2304,4 @@ final englishWordlist = <String>[
|
|||
'zero',
|
||||
'zone',
|
||||
'zoo'
|
||||
];
|
||||
];
|
|
@ -128,4 +128,4 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
initialRegularAddressIndex: snp.regularAddressIndex,
|
||||
initialChangeAddressIndex: snp.changeAddressIndex);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,4 +31,4 @@ class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials {
|
|||
: super(name: name, password: password, walletInfo: walletInfo);
|
||||
|
||||
final String wif;
|
||||
}
|
||||
}
|
|
@ -67,4 +67,4 @@ class ElectrumWallletSnapshot {
|
|||
derivationType: derivationType,
|
||||
derivationPath: derivationPath);
|
||||
}
|
||||
}
|
||||
}
|
23
cw_core/lib/nano_account_info_response.dart
Normal file
23
cw_core/lib/nano_account_info_response.dart
Normal file
|
@ -0,0 +1,23 @@
|
|||
class AccountInfoResponse {
|
||||
String frontier;
|
||||
int confirmationHeight;
|
||||
String balance;
|
||||
String representative;
|
||||
String? address;
|
||||
|
||||
AccountInfoResponse({
|
||||
required this.frontier,
|
||||
required this.balance,
|
||||
required this.representative,
|
||||
required this.confirmationHeight,
|
||||
});
|
||||
|
||||
factory AccountInfoResponse.fromJson(Map<String, dynamic> json) {
|
||||
return AccountInfoResponse(
|
||||
frontier: json['frontier'] as String,
|
||||
representative: json['representative'] as String,
|
||||
balance: json['balance'] as String,
|
||||
confirmationHeight: int.parse(json['confirmation_height'] as String),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -13,9 +13,7 @@ import 'package:cw_core/sync_status.dart';
|
|||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
abstract class WalletBase<
|
||||
BalanceType extends Balance,
|
||||
HistoryType extends TransactionHistoryBase,
|
||||
abstract class WalletBase<BalanceType extends Balance, HistoryType extends TransactionHistoryBase,
|
||||
TransactionType extends TransactionInfo> {
|
||||
WalletBase(this.walletInfo);
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@ enum DerivationType {
|
|||
@HiveField(3)
|
||||
bip39,
|
||||
@HiveField(4)
|
||||
electrum1,
|
||||
@HiveField(5)
|
||||
electrum2,
|
||||
}
|
||||
|
||||
|
@ -34,8 +36,8 @@ class DerivationInfo {
|
|||
String balance;
|
||||
String address;
|
||||
int height;
|
||||
DerivationType derivationType;
|
||||
String? derivationPath;
|
||||
final DerivationType derivationType;
|
||||
final String? derivationPath;
|
||||
final String? script_type;
|
||||
final String? description;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_credentials.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
abstract class WalletService<N extends WalletCredentials,
|
||||
RFS extends WalletCredentials, RFK extends WalletCredentials> {
|
||||
abstract class WalletService<N extends WalletCredentials, RFS extends WalletCredentials,
|
||||
RFK extends WalletCredentials> {
|
||||
WalletType getType();
|
||||
|
||||
Future<WalletBase> create(N credentials);
|
||||
|
|
|
@ -208,6 +208,10 @@ I/flutter ( 4474): Gas Used: 53000
|
|||
}
|
||||
}
|
||||
|
||||
Web3Client? getWeb3Client() {
|
||||
return _client;
|
||||
}
|
||||
|
||||
// Future<int> _getDecimalPlacesForContract(DeployedContract contract) async {
|
||||
// final String abi = await rootBundle.loadString("assets/abi_json/erc20_abi.json");
|
||||
// final contractAbi = ContractAbi.fromJson(abi, "ERC20");
|
||||
|
|
|
@ -77,6 +77,8 @@ abstract class EthereumWalletBase
|
|||
|
||||
late final EthPrivateKey _ethPrivateKey;
|
||||
|
||||
EthPrivateKey get ethPrivateKey => _ethPrivateKey;
|
||||
|
||||
late EthereumClient _client;
|
||||
|
||||
int? _gasPrice;
|
||||
|
@ -508,4 +510,6 @@ abstract class EthereumWalletBase
|
|||
@override
|
||||
String signMessage(String message, {String? address = null}) =>
|
||||
bytesToHex(_ethPrivateKey.signPersonalMessageToUint8List(ascii.encode(message)));
|
||||
|
||||
Web3Client? getWeb3Client() => _client.getWeb3Client();
|
||||
}
|
||||
|
|
|
@ -1,32 +1,13 @@
|
|||
import 'package:cw_core/balance.dart';
|
||||
import 'package:cw_core/currency.dart';
|
||||
import 'package:cw_nano/nano_util.dart';
|
||||
|
||||
String rawToFormattedAmount(BigInt amount, Currency currency) {
|
||||
return "";
|
||||
}
|
||||
|
||||
BigInt stringAmountToBigInt(String amount) {
|
||||
return BigInt.zero;
|
||||
}
|
||||
|
||||
class BananoBalance extends Balance {
|
||||
final BigInt currentBalance;
|
||||
final BigInt receivableBalance;
|
||||
late String formattedCurrentBalance;
|
||||
late String formattedReceivableBalance;
|
||||
|
||||
BananoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0) {
|
||||
this.formattedCurrentBalance = "";
|
||||
this.formattedReceivableBalance = "";
|
||||
}
|
||||
|
||||
BananoBalance.fromString(
|
||||
{required this.formattedCurrentBalance, required this.formattedReceivableBalance})
|
||||
: currentBalance = stringAmountToBigInt(formattedCurrentBalance),
|
||||
receivableBalance = stringAmountToBigInt(formattedReceivableBalance),
|
||||
super(0, 0);
|
||||
|
||||
@override
|
||||
String get formattedAvailableBalance {
|
||||
return NanoUtil.getRawAsUsableString(currentBalance.toString(), NanoUtil.rawPerBanano);
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import 'package:cw_core/balance.dart';
|
||||
import 'package:cw_core/currency.dart';
|
||||
import 'package:cw_core/monero_amount_format.dart';
|
||||
import 'package:cw_nano/nano_util.dart';
|
||||
|
||||
BigInt stringAmountToBigInt(String amount) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:async';
|
||||
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';
|
||||
|
@ -52,7 +53,7 @@ class NanoClient {
|
|||
return NanoBalance(currentBalance: cur, receivableBalance: rec);
|
||||
}
|
||||
|
||||
Future<dynamic> getAccountInfo(String address) async {
|
||||
Future<AccountInfoResponse?> getAccountInfo(String address) async {
|
||||
try {
|
||||
final response = await http.post(
|
||||
_node!.uri,
|
||||
|
@ -66,10 +67,10 @@ class NanoClient {
|
|||
),
|
||||
);
|
||||
final data = await jsonDecode(response.body);
|
||||
return data;
|
||||
return AccountInfoResponse.fromJson(data as Map<String, dynamic>);
|
||||
} catch (e) {
|
||||
print("error while getting account info");
|
||||
rethrow;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,15 +80,19 @@ class NanoClient {
|
|||
required String ourAddress,
|
||||
}) async {
|
||||
try {
|
||||
final accountInfo = await getAccountInfo(ourAddress);
|
||||
AccountInfoResponse? accountInfo = await getAccountInfo(ourAddress);
|
||||
|
||||
if (accountInfo == null) {
|
||||
throw Exception("error while getting account info");
|
||||
}
|
||||
|
||||
// construct the change block:
|
||||
Map<String, String> changeBlock = {
|
||||
"type": "state",
|
||||
"account": ourAddress,
|
||||
"previous": accountInfo["frontier"] as String,
|
||||
"previous": accountInfo.frontier,
|
||||
"representative": repAddress,
|
||||
"balance": accountInfo["balance"] as String,
|
||||
"balance": accountInfo.balance,
|
||||
"link": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"link_as_account": "nano_1111111111111111111111111111111111111111111111111111hifc8npp",
|
||||
};
|
||||
|
@ -104,7 +109,7 @@ class NanoClient {
|
|||
final String signature = NanoSignatures.signBlock(hash, privateKey);
|
||||
|
||||
// get PoW for the send block:
|
||||
final String work = await requestWork(accountInfo["frontier"] as String);
|
||||
final String work = await requestWork(accountInfo.frontier);
|
||||
|
||||
changeBlock["signature"] = signature;
|
||||
changeBlock["work"] = work;
|
||||
|
@ -116,8 +121,7 @@ class NanoClient {
|
|||
}
|
||||
|
||||
Future<String> requestWork(String hash) async {
|
||||
return http
|
||||
.post(
|
||||
final response = await http.post(
|
||||
_powNode!.uri,
|
||||
headers: {'Content-type': 'application/json'},
|
||||
body: json.encode(
|
||||
|
@ -126,18 +130,16 @@ class NanoClient {
|
|||
"hash": hash,
|
||||
},
|
||||
),
|
||||
)
|
||||
.then((http.Response response) {
|
||||
if (response.statusCode == 200) {
|
||||
final Map<String, dynamic> decoded = json.decode(response.body) as Map<String, dynamic>;
|
||||
if (decoded.containsKey("error")) {
|
||||
throw Exception("Received error ${decoded["error"]}");
|
||||
}
|
||||
return decoded["work"] as String;
|
||||
} else {
|
||||
throw Exception("Received error ${response.statusCode}");
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
final Map<String, dynamic> decoded = json.decode(response.body) as Map<String, dynamic>;
|
||||
if (decoded.containsKey("error")) {
|
||||
throw Exception("Received error ${decoded["error"]}");
|
||||
}
|
||||
});
|
||||
return decoded["work"] as String;
|
||||
} else {
|
||||
throw Exception("Received work error ${response.body}");
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> send({
|
||||
|
@ -197,24 +199,18 @@ class NanoClient {
|
|||
}
|
||||
|
||||
// get the account info (we need the frontier and representative):
|
||||
final headers = {"Content-Type": "application/json"};
|
||||
final infoBody = jsonEncode({
|
||||
"action": "account_info",
|
||||
"representative": "true",
|
||||
"account": publicAddress,
|
||||
});
|
||||
final infoResponse = await http.post(
|
||||
_node!.uri,
|
||||
headers: headers,
|
||||
body: infoBody,
|
||||
);
|
||||
AccountInfoResponse? infoResponse = await getAccountInfo(publicAddress);
|
||||
if (infoResponse == null) {
|
||||
throw Exception(
|
||||
"error while getting account info! (we probably don't have an open account yet)");
|
||||
}
|
||||
|
||||
String frontier = jsonDecode(infoResponse.body)["frontier"].toString();
|
||||
String frontier = infoResponse.frontier;
|
||||
// override if provided:
|
||||
if (previousHash != null) {
|
||||
frontier = previousHash;
|
||||
}
|
||||
final String representative = jsonDecode(infoResponse.body)["representative"].toString();
|
||||
final String representative = infoResponse.representative;
|
||||
// link = destination address:
|
||||
final String link = NanoAccounts.extractPublicKey(destinationAddress);
|
||||
final String linkAsAccount = destinationAddress;
|
||||
|
@ -270,48 +266,27 @@ class NanoClient {
|
|||
|
||||
// first check if the account is open:
|
||||
// get the account info (we need the frontier and representative):
|
||||
final infoBody = jsonEncode({
|
||||
"action": "account_info",
|
||||
"representative": "true",
|
||||
"account": destinationAddress,
|
||||
});
|
||||
final infoResponse = await http.post(
|
||||
_node!.uri,
|
||||
headers: headers,
|
||||
body: infoBody,
|
||||
);
|
||||
final infoData = jsonDecode(infoResponse.body);
|
||||
AccountInfoResponse? infoData = await getAccountInfo(destinationAddress);
|
||||
String? frontier;
|
||||
String? representative;
|
||||
|
||||
if (infoData["error"] != null) {
|
||||
if (infoData == null) {
|
||||
// account is not open yet, we need to create an open block:
|
||||
openBlock = true;
|
||||
// we don't have a representative set yet:
|
||||
representative = DEFAULT_REPRESENTATIVE;
|
||||
// we don't have a frontier yet:
|
||||
frontier = "0000000000000000000000000000000000000000000000000000000000000000";
|
||||
} else {
|
||||
frontier = infoData.frontier;
|
||||
representative = infoData.representative;
|
||||
}
|
||||
|
||||
// first get the account balance:
|
||||
final balanceBody = jsonEncode({
|
||||
"action": "account_balance",
|
||||
"account": destinationAddress,
|
||||
});
|
||||
|
||||
final balanceResponse = await http.post(
|
||||
_node!.uri,
|
||||
headers: headers,
|
||||
body: balanceBody,
|
||||
);
|
||||
|
||||
final balanceData = jsonDecode(balanceResponse.body);
|
||||
final BigInt currentBalance = BigInt.parse(balanceData["balance"].toString());
|
||||
final BigInt currentBalance = (await getBalance(destinationAddress)).currentBalance;
|
||||
final BigInt txAmount = BigInt.parse(amountRaw);
|
||||
final BigInt balanceAfterTx = currentBalance + txAmount;
|
||||
|
||||
String frontier = infoData["frontier"].toString();
|
||||
String representative = infoData["representative"].toString();
|
||||
|
||||
if (openBlock) {
|
||||
// we don't have a representative set yet:
|
||||
representative = DEFAULT_REPRESENTATIVE;
|
||||
}
|
||||
|
||||
// link = send block hash:
|
||||
final String link = blockHash;
|
||||
// this "linkAsAccount" is meaningless:
|
||||
|
@ -321,8 +296,7 @@ class NanoClient {
|
|||
Map<String, String> receiveBlock = {
|
||||
"type": "state",
|
||||
"account": destinationAddress,
|
||||
"previous":
|
||||
openBlock ? "0000000000000000000000000000000000000000000000000000000000000000" : frontier,
|
||||
"previous": frontier,
|
||||
"representative": representative,
|
||||
"balance": balanceAfterTx.toString(),
|
||||
"link": link,
|
||||
|
@ -422,10 +396,6 @@ class NanoClient {
|
|||
return blocks.keys.length;
|
||||
}
|
||||
|
||||
Future<dynamic> getTransactionDetails(String transactionHash) async {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
void stop() {}
|
||||
|
||||
Future<List<NanoTransactionModel>> fetchTransactions(String address) async {
|
||||
|
|
|
@ -3,7 +3,6 @@ import 'dart:core';
|
|||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_nano/file.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
import 'package:cw_nano/nano_transaction_info.dart';
|
||||
|
|
|
@ -3,18 +3,13 @@ import 'dart:typed_data';
|
|||
|
||||
import 'package:convert/convert.dart';
|
||||
import "package:ed25519_hd_key/ed25519_hd_key.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:libcrypto/libcrypto.dart';
|
||||
import 'package:nanodart/nanodart.dart';
|
||||
import 'package:ed25519_hd_key/ed25519_hd_key.dart';
|
||||
import 'package:nanodart/nanodart.dart';
|
||||
import 'package:decimal/decimal.dart';
|
||||
|
||||
class NanoUtil {
|
||||
// standard:
|
||||
static String seedToPrivate(String seed, int index) {
|
||||
// return NanoHelpers.byteToHex(Ed25519Blake2b.derivePrivkey(NanoHelpers.hexToBytes(seed), index)!)
|
||||
// .toUpperCase();
|
||||
return NanoKeys.seedToPrivate(seed, index);
|
||||
}
|
||||
|
||||
|
@ -31,10 +26,6 @@ class NanoUtil {
|
|||
return NanoMnemomics.mnemonicListToSeed(mnemonic.split(' '));
|
||||
}
|
||||
|
||||
// static String createPublicKey(String privateKey) {
|
||||
// return NanoHelpers.byteToHex(Ed25519Blake2b.getPubkey(NanoHelpers.hexToBytes(privateKey))!);
|
||||
// }
|
||||
|
||||
static String privateKeyToPublic(String privateKey) {
|
||||
// return NanoHelpers.byteToHex(Ed25519Blake2b.getPubkey(NanoHelpers.hexToBytes(privateKey))!);
|
||||
return NanoKeys.createPublicKey(privateKey);
|
||||
|
@ -105,26 +96,9 @@ class NanoUtil {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// static String hdSeedToPrivate(String seed, int index) {
|
||||
// // List<int> seedBytes = hex.decode(seed);
|
||||
// // KeyData data = await ED25519_HD_KEY.derivePath("m/44'/165'/$index'", seedBytes);
|
||||
// // return hex.encode(data.key);
|
||||
// Chain chain = Chain.seed(hex.encode(utf8.encode(seed)));
|
||||
// ExtendedKey key = chain.forPath("m/44'/165'/$index'");
|
||||
// print(key.privateKeyHex());
|
||||
// return "";
|
||||
// }
|
||||
|
||||
// static String hdSeedToAddress(String seed, int index) {
|
||||
// // return NanoAccounts.createAccount(NanoAccountType.NANO, NanoKeys.createPublicKey(seedToPrivate(seed, index)));
|
||||
// return "";
|
||||
// }
|
||||
|
||||
static bool isValidBip39Seed(String seed) {
|
||||
// Ensure seed is 128 characters long
|
||||
if (seed == null || seed.length != 128) {
|
||||
if (seed.length != 128) {
|
||||
return false;
|
||||
}
|
||||
// Ensure seed only contains hex characters, 0-9;A-F
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:io';
|
|||
|
||||
import 'package:cw_core/cake_hive.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/nano_account_info_response.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
|
@ -68,7 +69,7 @@ abstract class NanoWalletBase
|
|||
String? _representativeAddress;
|
||||
Timer? _receiveTimer;
|
||||
|
||||
late NanoClient _client;
|
||||
late final NanoClient _client;
|
||||
bool _isTransactionUpdating;
|
||||
|
||||
@override
|
||||
|
@ -237,7 +238,6 @@ abstract class NanoWalletBase
|
|||
|
||||
_isTransactionUpdating = true;
|
||||
final transactions = await fetchTransactions();
|
||||
transactionHistory.clear();
|
||||
transactionHistory.addMany(transactions);
|
||||
await transactionHistory.save();
|
||||
_isTransactionUpdating = false;
|
||||
|
@ -281,7 +281,7 @@ abstract class NanoWalletBase
|
|||
|
||||
@override
|
||||
Future<void> rescan({required int height}) async {
|
||||
fetchTransactions();
|
||||
updateTransactions();
|
||||
_updateBalance();
|
||||
return;
|
||||
}
|
||||
|
@ -376,14 +376,11 @@ abstract class NanoWalletBase
|
|||
|
||||
Future<void> _updateRep() async {
|
||||
try {
|
||||
final accountInfo = await _client.getAccountInfo(_publicAddress!);
|
||||
if (accountInfo["error"] != null) {
|
||||
// account not found:
|
||||
_representativeAddress = NanoClient.DEFAULT_REPRESENTATIVE;
|
||||
} else {
|
||||
_representativeAddress = accountInfo["representative"] as String;
|
||||
}
|
||||
AccountInfoResponse accountInfo = (await _client.getAccountInfo(_publicAddress!))!;
|
||||
_representativeAddress = accountInfo.representative;
|
||||
} catch (e) {
|
||||
// account not found:
|
||||
_representativeAddress = NanoClient.DEFAULT_REPRESENTATIVE;
|
||||
throw Exception("Failed to get representative address $e");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,7 @@ import 'package:cw_core/wallet_addresses.dart';
|
|||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/nano_account.dart';
|
||||
import 'package:cw_nano/nano_account_list.dart';
|
||||
// import 'package:cw_core/account.dart';
|
||||
// import 'package:cw_core/subaddress.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'nano_wallet_addresses.g.dart';
|
||||
|
||||
|
@ -41,5 +38,13 @@ abstract class NanoWalletAddressesBase extends WalletAddresses with Store {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> updateAddressesInBox() async {}
|
||||
Future<void> updateAddressesInBox() async {
|
||||
try {
|
||||
addressesMap.clear();
|
||||
addressesMap[address] = '';
|
||||
await saveAddressesInBox();
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
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_client.dart';
|
||||
import 'package:cw_nano/nano_mnemonic.dart' as nm;
|
||||
import 'package:cw_nano/nano_util.dart';
|
||||
import 'package:cw_nano/nano_wallet.dart';
|
||||
|
@ -34,10 +32,6 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
|
|||
String seedKey = NanoSeeds.generateSeed();
|
||||
String mnemonic = NanoUtil.seedToMnemonic(seedKey);
|
||||
|
||||
// bip39:
|
||||
// derivationType derivationType = DerivationType.bip39;
|
||||
// String mnemonic = bip39.generateMnemonic();
|
||||
|
||||
credentials.walletInfo!.derivationType = derivationType;
|
||||
|
||||
final wallet = NanoWallet(
|
||||
|
@ -69,8 +63,6 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
|
|||
final currentWalletInfo = walletInfoSource.values
|
||||
.firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
|
||||
|
||||
currentWalletInfo.derivationType = DerivationType.nano; // doesn't matter for the rename action
|
||||
|
||||
String randomWords =
|
||||
(List<String>.from(nm.NanoMnemomics.WORDLIST)..shuffle()).take(24).join(' ');
|
||||
final currentWallet =
|
||||
|
@ -85,111 +77,6 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
|
|||
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
|
||||
}
|
||||
|
||||
static Future<dynamic> getInfoFromSeedOrMnemonic(
|
||||
DerivationType derivationType, {
|
||||
String? seedKey,
|
||||
String? mnemonic,
|
||||
required Node node,
|
||||
}) async {
|
||||
NanoClient nanoClient = NanoClient();
|
||||
nanoClient.connect(node);
|
||||
late String publicAddress;
|
||||
|
||||
if (seedKey != null) {
|
||||
if (derivationType == DerivationType.bip39) {
|
||||
publicAddress = await NanoUtil.hdSeedToAddress(seedKey, 0);
|
||||
} else if (derivationType == DerivationType.nano) {
|
||||
publicAddress = await NanoUtil.seedToAddress(seedKey, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (derivationType == DerivationType.bip39) {
|
||||
if (mnemonic != null) {
|
||||
seedKey = await NanoUtil.hdMnemonicListToSeed(mnemonic.split(' '));
|
||||
publicAddress = await NanoUtil.hdSeedToAddress(seedKey, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (derivationType == DerivationType.nano) {
|
||||
if (mnemonic != null) {
|
||||
seedKey = await NanoUtil.mnemonicToSeed(mnemonic);
|
||||
publicAddress = await NanoUtil.seedToAddress(seedKey, 0);
|
||||
}
|
||||
}
|
||||
|
||||
var accountInfo = await nanoClient.getAccountInfo(publicAddress);
|
||||
accountInfo["address"] = publicAddress;
|
||||
return accountInfo;
|
||||
}
|
||||
|
||||
static Future<List<DerivationType>> compareDerivationMethods(
|
||||
{String? mnemonic, String? seedKey, required Node node}) async {
|
||||
if (mnemonic?.split(' ').length == 12) {
|
||||
return [DerivationType.bip39];
|
||||
}
|
||||
if (seedKey?.length == 128) {
|
||||
return [DerivationType.bip39];
|
||||
} else if (seedKey?.length == 64) {
|
||||
return [DerivationType.nano];
|
||||
}
|
||||
|
||||
late String publicAddressStandard;
|
||||
late String publicAddressBip39;
|
||||
|
||||
try {
|
||||
NanoClient nanoClient = NanoClient();
|
||||
nanoClient.connect(node);
|
||||
|
||||
if (mnemonic != null) {
|
||||
seedKey = await NanoUtil.hdMnemonicListToSeed(mnemonic.split(' '));
|
||||
publicAddressBip39 = await NanoUtil.hdSeedToAddress(seedKey, 0);
|
||||
|
||||
seedKey = await NanoUtil.mnemonicToSeed(mnemonic);
|
||||
publicAddressStandard = await NanoUtil.seedToAddress(seedKey, 0);
|
||||
} else if (seedKey != null) {
|
||||
try {
|
||||
publicAddressBip39 = await NanoUtil.hdSeedToAddress(seedKey, 0);
|
||||
} catch (e) {
|
||||
return [DerivationType.nano];
|
||||
}
|
||||
try {
|
||||
publicAddressStandard = await NanoUtil.seedToAddress(seedKey, 0);
|
||||
} catch (e) {
|
||||
return [DerivationType.bip39];
|
||||
}
|
||||
}
|
||||
|
||||
// check if account has a history:
|
||||
var bip39Info;
|
||||
var standardInfo;
|
||||
|
||||
try {
|
||||
bip39Info = await nanoClient.getAccountInfo(publicAddressBip39);
|
||||
} catch (e) {
|
||||
bip39Info = null;
|
||||
}
|
||||
try {
|
||||
standardInfo = await nanoClient.getAccountInfo(publicAddressStandard);
|
||||
} catch (e) {
|
||||
standardInfo = null;
|
||||
}
|
||||
|
||||
// one of these is *probably* null:
|
||||
if ((bip39Info == null || bip39Info["error"] != null) &&
|
||||
(standardInfo != null && standardInfo["error"] == null)) {
|
||||
return [DerivationType.nano];
|
||||
} else if ((standardInfo == null || standardInfo["error"] != null) &&
|
||||
(bip39Info != null && bip39Info["error"] == null)) {
|
||||
return [DerivationType.bip39];
|
||||
}
|
||||
|
||||
// we don't know for sure:
|
||||
return [DerivationType.nano, DerivationType.bip39];
|
||||
} catch (e) {
|
||||
return [DerivationType.unknown];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<NanoWallet> restoreFromKeys(NanoRestoreWalletFromKeysCredentials credentials) async {
|
||||
if (credentials.seedKey.contains(' ')) {
|
||||
|
@ -203,9 +90,20 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
|
|||
DerivationType derivationType = credentials.derivationType ?? DerivationType.nano;
|
||||
credentials.walletInfo!.derivationType = derivationType;
|
||||
|
||||
String? mnemonic;
|
||||
|
||||
// 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);
|
||||
} catch (e) {
|
||||
throw Exception("Wasn't a valid nano style seed!");
|
||||
}
|
||||
}
|
||||
|
||||
final wallet = await NanoWallet(
|
||||
password: credentials.password!,
|
||||
mnemonic: credentials.seedKey, // we can't derive the mnemonic from the key in all cases
|
||||
mnemonic: mnemonic ?? credentials.seedKey,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
);
|
||||
await wallet.init();
|
||||
|
|
|
@ -37,10 +37,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0
|
||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.10.0"
|
||||
version: "2.11.0"
|
||||
bip32:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -141,10 +141,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c
|
||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
version: "1.3.0"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -173,10 +173,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
|
||||
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
version: "1.17.1"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -342,10 +342,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
|
||||
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.6"
|
||||
version: "1.1.0"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -366,10 +366,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91"
|
||||
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.17.0"
|
||||
version: "0.18.1"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -382,10 +382,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7"
|
||||
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.5"
|
||||
version: "0.6.7"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -394,14 +394,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.8.1"
|
||||
json_rpc_2:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_rpc_2
|
||||
sha256: "5e469bffa23899edacb7b22787780068d650b106a21c76db3c49218ab7ca447e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
libcrypto:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -422,10 +414,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72"
|
||||
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.13"
|
||||
version: "0.12.15"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -438,10 +430,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42"
|
||||
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.8.0"
|
||||
version: "1.9.1"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -486,10 +478,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b
|
||||
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.8.2"
|
||||
version: "1.8.3"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -691,10 +683,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
|
||||
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.16"
|
||||
version: "0.5.1"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -711,14 +703,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -735,14 +719,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
web3dart:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: web3dart
|
||||
sha256: "48b89a5fac0029770a18d1a8bd05ce8431722bacf76184e4301dae05781565e5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.5"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -776,5 +752,5 @@ packages:
|
|||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=2.19.0 <3.0.0"
|
||||
dart: ">=3.0.0 <4.0.0"
|
||||
flutter: ">=3.3.0"
|
||||
|
|
|
@ -271,4 +271,4 @@ class AddressValidator extends TextValidator {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
5
lib/core/wallet_connect/chain_service.dart
Normal file
5
lib/core/wallet_connect/chain_service.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
abstract class ChainService {
|
||||
String getNamespace();
|
||||
String getChainId();
|
||||
List<String> getEvents();
|
||||
}
|
60
lib/core/wallet_connect/eth_transaction_model.dart
Normal file
60
lib/core/wallet_connect/eth_transaction_model.dart
Normal file
|
@ -0,0 +1,60 @@
|
|||
class WCEthereumTransactionModel {
|
||||
final String from;
|
||||
final String to;
|
||||
final String value;
|
||||
final String? nonce;
|
||||
final String? gasPrice;
|
||||
final String? maxFeePerGas;
|
||||
final String? maxPriorityFeePerGas;
|
||||
final String? gas;
|
||||
final String? gasLimit;
|
||||
final String? data;
|
||||
|
||||
WCEthereumTransactionModel({
|
||||
required this.from,
|
||||
required this.to,
|
||||
required this.value,
|
||||
this.nonce,
|
||||
this.gasPrice,
|
||||
this.maxFeePerGas,
|
||||
this.maxPriorityFeePerGas,
|
||||
this.gas,
|
||||
this.gasLimit,
|
||||
this.data,
|
||||
});
|
||||
|
||||
factory WCEthereumTransactionModel.fromJson(Map<String, dynamic> json) {
|
||||
return WCEthereumTransactionModel(
|
||||
from: json['from'] as String,
|
||||
to: json['to'] as String,
|
||||
value: json['value'] as String,
|
||||
nonce: json['nonce'] as String?,
|
||||
gasPrice: json['gasPrice'] as String?,
|
||||
maxFeePerGas: json['maxFeePerGas'] as String?,
|
||||
maxPriorityFeePerGas: json['maxPriorityFeePerGas'] as String?,
|
||||
gas: json['gas'] as String?,
|
||||
gasLimit: json['gasLimit'] as String?,
|
||||
data: json['data'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'from': from,
|
||||
'to': to,
|
||||
'value': value,
|
||||
'nonce': nonce,
|
||||
'gasPrice': gasPrice,
|
||||
'maxFeePerGas': maxFeePerGas,
|
||||
'maxPriorityFeePerGas': maxPriorityFeePerGas,
|
||||
'gas': gas,
|
||||
'gasLimit': gasLimit,
|
||||
'data': data,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'EthereumTransactionModel(from: $from, to: $to, nonce: $nonce, gasPrice: $gasPrice, maxFeePerGas: $maxFeePerGas, maxPriorityFeePerGas: $maxPriorityFeePerGas, gas: $gas, gasLimit: $gasLimit, value: $value, data: $data)';
|
||||
}
|
||||
}
|
35
lib/core/wallet_connect/evm_chain_id.dart
Normal file
35
lib/core/wallet_connect/evm_chain_id.dart
Normal file
|
@ -0,0 +1,35 @@
|
|||
import 'package:cake_wallet/core/wallet_connect/evm_chain_service.dart';
|
||||
|
||||
enum EVMChainId {
|
||||
ethereum,
|
||||
polygon,
|
||||
goerli,
|
||||
mumbai,
|
||||
arbitrum,
|
||||
}
|
||||
|
||||
extension EVMChainIdX on EVMChainId {
|
||||
String chain() {
|
||||
String name = '';
|
||||
|
||||
switch (this) {
|
||||
case EVMChainId.ethereum:
|
||||
name = '1';
|
||||
break;
|
||||
case EVMChainId.polygon:
|
||||
name = '137';
|
||||
break;
|
||||
case EVMChainId.goerli:
|
||||
name = '5';
|
||||
break;
|
||||
case EVMChainId.arbitrum:
|
||||
name = '42161';
|
||||
break;
|
||||
case EVMChainId.mumbai:
|
||||
name = '80001';
|
||||
break;
|
||||
}
|
||||
|
||||
return '${EvmChainServiceImpl.namespace}:$name';
|
||||
}
|
||||
}
|
294
lib/core/wallet_connect/evm_chain_service.dart
Normal file
294
lib/core/wallet_connect/evm_chain_service.dart
Normal file
|
@ -0,0 +1,294 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:cake_wallet/core/wallet_connect/eth_transaction_model.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/evm_chain_id.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/error_display_widget.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/models/connection_model.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/utils/string_parsing.dart';
|
||||
import 'package:convert/convert.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:eth_sig_util/eth_sig_util.dart';
|
||||
import 'package:eth_sig_util/util/utils.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
||||
import 'package:web3dart/web3dart.dart';
|
||||
import 'chain_service.dart';
|
||||
import 'wallet_connect_key_service.dart';
|
||||
|
||||
class EvmChainServiceImpl implements ChainService {
|
||||
final AppStore appStore;
|
||||
final BottomSheetService bottomSheetService;
|
||||
final Web3Wallet wallet;
|
||||
final WalletConnectKeyService wcKeyService;
|
||||
|
||||
static const namespace = 'eip155';
|
||||
static const pSign = 'personal_sign';
|
||||
static const eSign = 'eth_sign';
|
||||
static const eSignTransaction = 'eth_signTransaction';
|
||||
static const eSignTypedData = 'eth_signTypedData_v4';
|
||||
static const eSendTransaction = 'eth_sendTransaction';
|
||||
|
||||
final EVMChainId reference;
|
||||
|
||||
final Web3Client ethClient;
|
||||
|
||||
EvmChainServiceImpl({
|
||||
required this.reference,
|
||||
required this.appStore,
|
||||
required this.wcKeyService,
|
||||
required this.bottomSheetService,
|
||||
required this.wallet,
|
||||
Web3Client? ethClient,
|
||||
}) : ethClient = ethClient ??
|
||||
Web3Client(
|
||||
appStore.settingsStore.getCurrentNode(WalletType.ethereum).uri.toString(),
|
||||
http.Client(),
|
||||
) {
|
||||
|
||||
for (final String event in getEvents()) {
|
||||
wallet.registerEventEmitter(chainId: getChainId(), event: event);
|
||||
}
|
||||
wallet.registerRequestHandler(
|
||||
chainId: getChainId(),
|
||||
method: pSign,
|
||||
handler: personalSign,
|
||||
);
|
||||
wallet.registerRequestHandler(
|
||||
chainId: getChainId(),
|
||||
method: eSign,
|
||||
handler: ethSign,
|
||||
);
|
||||
wallet.registerRequestHandler(
|
||||
chainId: getChainId(),
|
||||
method: eSignTransaction,
|
||||
handler: ethSignTransaction,
|
||||
);
|
||||
wallet.registerRequestHandler(
|
||||
chainId: getChainId(),
|
||||
method: eSendTransaction,
|
||||
handler: ethSignTransaction,
|
||||
);
|
||||
wallet.registerRequestHandler(
|
||||
chainId: getChainId(),
|
||||
method: eSignTypedData,
|
||||
handler: ethSignTypedData,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
@override
|
||||
String getChainId() {
|
||||
return reference.chain();
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> getEvents() {
|
||||
return ['chainChanged', 'accountsChanged'];
|
||||
}
|
||||
|
||||
Future<String?> requestAuthorization(String? text) async {
|
||||
// Show the bottom sheet
|
||||
final bool? isApproved = await bottomSheetService.queueBottomSheet(
|
||||
widget: Web3RequestModal(
|
||||
child: ConnectionWidget(
|
||||
title: S.current.signTransaction,
|
||||
info: [
|
||||
ConnectionModel(
|
||||
text: text,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
) as bool?;
|
||||
|
||||
if (isApproved != null && isApproved == false) {
|
||||
return 'User rejected signature';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<String> personalSign(String topic, dynamic parameters) async {
|
||||
log('received personal sign request: $parameters');
|
||||
|
||||
final String message;
|
||||
if (parameters[0] == null) {
|
||||
message = '';
|
||||
} else {
|
||||
message = parameters[0].toString().utf8Message;
|
||||
}
|
||||
|
||||
final String? authError = await requestAuthorization(message);
|
||||
|
||||
if (authError != null) {
|
||||
return authError;
|
||||
}
|
||||
|
||||
try {
|
||||
// Load the private key
|
||||
final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
|
||||
|
||||
final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey);
|
||||
|
||||
final String signature = hex.encode(
|
||||
credentials.signPersonalMessageToUint8List(Uint8List.fromList(utf8.encode(message))),
|
||||
);
|
||||
|
||||
return '0x$signature';
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
bottomSheetService.queueBottomSheet(
|
||||
isModalDismissible: true,
|
||||
widget: BottomSheetMessageDisplayWidget(
|
||||
message: '${S.current.errorGettingCredentials} ${e.toString()}',
|
||||
),
|
||||
);
|
||||
return 'Failed: Error while getting credentials';
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> ethSign(String topic, dynamic parameters) async {
|
||||
log('received eth sign request: $parameters');
|
||||
|
||||
final String message;
|
||||
if (parameters[1] == null) {
|
||||
message = '';
|
||||
} else {
|
||||
message = parameters[1].toString().utf8Message;
|
||||
}
|
||||
|
||||
final String? authError = await requestAuthorization(message);
|
||||
if (authError != null) {
|
||||
return authError;
|
||||
}
|
||||
|
||||
try {
|
||||
// Load the private key
|
||||
final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
|
||||
|
||||
final EthPrivateKey credentials = EthPrivateKey.fromHex(keys[0].privateKey);
|
||||
|
||||
final String signature = hex.encode(
|
||||
credentials.signPersonalMessageToUint8List(
|
||||
Uint8List.fromList(utf8.encode(message)),
|
||||
),
|
||||
);
|
||||
log(signature);
|
||||
|
||||
return '0x$signature';
|
||||
} catch (e) {
|
||||
log('error: ${e.toString()}');
|
||||
bottomSheetService.queueBottomSheet(
|
||||
isModalDismissible: true,
|
||||
widget: BottomSheetMessageDisplayWidget(message: '${S.current.error}: ${e.toString()}'),
|
||||
);
|
||||
return 'Failed';
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> ethSignTransaction(String topic, dynamic parameters) async {
|
||||
log('received eth sign transaction request: $parameters');
|
||||
|
||||
final paramsData = parameters[0] as Map<String, dynamic>;
|
||||
|
||||
final message = _convertToReadable(paramsData);
|
||||
|
||||
final String? authError = await requestAuthorization(message);
|
||||
|
||||
if (authError != null) {
|
||||
return authError;
|
||||
}
|
||||
|
||||
// Load the private key
|
||||
final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
|
||||
|
||||
final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey);
|
||||
|
||||
WCEthereumTransactionModel ethTransaction =
|
||||
WCEthereumTransactionModel.fromJson(parameters[0] as Map<String, dynamic>);
|
||||
|
||||
final transaction = Transaction(
|
||||
from: EthereumAddress.fromHex(ethTransaction.from),
|
||||
to: EthereumAddress.fromHex(ethTransaction.to),
|
||||
maxGas: ethTransaction.gasLimit != null ? int.tryParse(ethTransaction.gasLimit ?? "") : null,
|
||||
gasPrice: ethTransaction.gasPrice != null
|
||||
? EtherAmount.inWei(BigInt.parse(ethTransaction.gasPrice ?? ""))
|
||||
: null,
|
||||
value: EtherAmount.inWei(BigInt.parse(ethTransaction.value)),
|
||||
data: hexToBytes(ethTransaction.data ?? ""),
|
||||
nonce: ethTransaction.nonce != null ? int.tryParse(ethTransaction.nonce ?? "") : null,
|
||||
);
|
||||
|
||||
try {
|
||||
final result = await ethClient.sendTransaction(credentials, transaction);
|
||||
|
||||
log('Result: $result');
|
||||
|
||||
bottomSheetService.queueBottomSheet(
|
||||
isModalDismissible: true,
|
||||
widget: BottomSheetMessageDisplayWidget(
|
||||
message: S.current.awaitDAppProcessing,
|
||||
isError: false,
|
||||
),
|
||||
);
|
||||
|
||||
return result;
|
||||
} catch (e) {
|
||||
log('An error has occured while signing transaction: ${e.toString()}');
|
||||
bottomSheetService.queueBottomSheet(
|
||||
isModalDismissible: true,
|
||||
widget: BottomSheetMessageDisplayWidget(
|
||||
message: '${S.current.errorSigningTransaction}: ${e.toString()}',
|
||||
),
|
||||
);
|
||||
return 'Failed';
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> ethSignTypedData(String topic, dynamic parameters) async {
|
||||
log('received eth sign typed data request: $parameters');
|
||||
final String? data = parameters[1] as String?;
|
||||
|
||||
final String? authError = await requestAuthorization(data);
|
||||
|
||||
if (authError != null) {
|
||||
return authError;
|
||||
}
|
||||
|
||||
final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
|
||||
|
||||
return EthSigUtil.signTypedData(
|
||||
privateKey: keys[0].privateKey,
|
||||
jsonData: data ?? '',
|
||||
version: TypedDataVersion.V4,
|
||||
);
|
||||
}
|
||||
|
||||
String _convertToReadable(Map<String, dynamic> data) {
|
||||
String gas = int.parse((data['gas'] as String).substring(2), radix: 16).toString();
|
||||
String value = data['value'] != null
|
||||
? (int.parse((data['value'] as String).substring(2), radix: 16) / 1e18).toString() + ' ETH'
|
||||
: '0 ETH';
|
||||
String from = data['from'] as String;
|
||||
String to = data['to'] as String;
|
||||
|
||||
return '''
|
||||
Gas: $gas\n
|
||||
Value: $value\n
|
||||
From: $from\n
|
||||
To: $to
|
||||
''';
|
||||
}
|
||||
}
|
16
lib/core/wallet_connect/models/auth_request_model.dart
Normal file
16
lib/core/wallet_connect/models/auth_request_model.dart
Normal file
|
@ -0,0 +1,16 @@
|
|||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
||||
|
||||
class AuthRequestModel {
|
||||
final String iss;
|
||||
final AuthRequest request;
|
||||
|
||||
AuthRequestModel({
|
||||
required this.iss,
|
||||
required this.request,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AuthRequestModel(iss: $iss, request: $request)';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class BottomSheetQueueItemModel {
|
||||
final Widget widget;
|
||||
final bool isModalDismissible;
|
||||
final Completer<dynamic> completer;
|
||||
|
||||
BottomSheetQueueItemModel({
|
||||
required this.widget,
|
||||
required this.completer,
|
||||
this.isModalDismissible = false,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BottomSheetQueueItemModel(widget: $widget, completer: $completer)';
|
||||
}
|
||||
}
|
16
lib/core/wallet_connect/models/chain_key_model.dart
Normal file
16
lib/core/wallet_connect/models/chain_key_model.dart
Normal file
|
@ -0,0 +1,16 @@
|
|||
class ChainKeyModel {
|
||||
final List<String> chains;
|
||||
final String privateKey;
|
||||
final String publicKey;
|
||||
|
||||
ChainKeyModel({
|
||||
required this.chains,
|
||||
required this.privateKey,
|
||||
required this.publicKey,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ChainKeyModel(chains: $chains, privateKey: $privateKey, publicKey: $publicKey)';
|
||||
}
|
||||
}
|
18
lib/core/wallet_connect/models/connection_model.dart
Normal file
18
lib/core/wallet_connect/models/connection_model.dart
Normal file
|
@ -0,0 +1,18 @@
|
|||
class ConnectionModel {
|
||||
final String? title;
|
||||
final String? text;
|
||||
final List<String>? elements;
|
||||
final Map<String, void Function()>? elementActions;
|
||||
|
||||
ConnectionModel({
|
||||
this.title,
|
||||
this.text,
|
||||
this.elements,
|
||||
this.elementActions,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'WalletConnectRequestModel(title: $title, text: $text, elements: $elements, elementActions: $elementActions)';
|
||||
}
|
||||
}
|
14
lib/core/wallet_connect/models/session_request_model.dart
Normal file
14
lib/core/wallet_connect/models/session_request_model.dart
Normal file
|
@ -0,0 +1,14 @@
|
|||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
||||
|
||||
class SessionRequestModel {
|
||||
final ProposalData request;
|
||||
|
||||
SessionRequestModel({
|
||||
required this.request,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SessionRequestModel(request: $request)';
|
||||
}
|
||||
}
|
72
lib/core/wallet_connect/wallet_connect_key_service.dart
Normal file
72
lib/core/wallet_connect/wallet_connect_key_service.dart
Normal file
|
@ -0,0 +1,72 @@
|
|||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
|
||||
import 'package:cw_core/balance.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
|
||||
abstract class WalletConnectKeyService {
|
||||
/// Returns a list of all the keys.
|
||||
List<ChainKeyModel> getKeys();
|
||||
|
||||
/// Returns a list of all the chain ids.
|
||||
List<String> getChains();
|
||||
|
||||
/// Returns a list of all the keys for a given chain id.
|
||||
/// If the chain is not found, returns an empty list.
|
||||
/// - [chain]: The chain to get the keys for.
|
||||
List<ChainKeyModel> getKeysForChain(String chain);
|
||||
|
||||
/// Returns a list of all the accounts in namespace:chainId:address format.
|
||||
List<String> getAllAccounts();
|
||||
}
|
||||
|
||||
class KeyServiceImpl implements WalletConnectKeyService {
|
||||
KeyServiceImpl(this.wallet)
|
||||
: _keys = [
|
||||
ChainKeyModel(
|
||||
chains: [
|
||||
'eip155:1',
|
||||
'eip155:5',
|
||||
'eip155:137',
|
||||
'eip155:42161',
|
||||
'eip155:80001',
|
||||
],
|
||||
privateKey: ethereum!.getPrivateKey(wallet),
|
||||
publicKey: ethereum!.getPublicKey(wallet),
|
||||
),
|
||||
|
||||
];
|
||||
|
||||
late final WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
|
||||
|
||||
late final List<ChainKeyModel> _keys;
|
||||
|
||||
@override
|
||||
List<String> getChains() {
|
||||
final List<String> chainIds = [];
|
||||
for (final ChainKeyModel key in _keys) {
|
||||
chainIds.addAll(key.chains);
|
||||
}
|
||||
return chainIds;
|
||||
}
|
||||
|
||||
@override
|
||||
List<ChainKeyModel> getKeys() => _keys;
|
||||
|
||||
@override
|
||||
List<ChainKeyModel> getKeysForChain(String chain) {
|
||||
return _keys.where((e) => e.chains.contains(chain)).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> getAllAccounts() {
|
||||
final List<String> accounts = [];
|
||||
for (final ChainKeyModel key in _keys) {
|
||||
for (final String chain in key.chains) {
|
||||
accounts.add('$chain:${key.publicKey}');
|
||||
}
|
||||
}
|
||||
return accounts;
|
||||
}
|
||||
}
|
43
lib/core/wallet_connect/wc_bottom_sheet_service.dart
Normal file
43
lib/core/wallet_connect/wc_bottom_sheet_service.dart
Normal file
|
@ -0,0 +1,43 @@
|
|||
import 'dart:async';
|
||||
import 'package:cake_wallet/core/wallet_connect/models/bottom_sheet_queue_item_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class BottomSheetService {
|
||||
abstract final ValueNotifier<BottomSheetQueueItemModel?> currentSheet;
|
||||
|
||||
Future<dynamic> queueBottomSheet({
|
||||
required Widget widget,
|
||||
bool isModalDismissible = false,
|
||||
});
|
||||
|
||||
void resetCurrentSheet();
|
||||
}
|
||||
|
||||
class BottomSheetServiceImpl implements BottomSheetService {
|
||||
|
||||
@override
|
||||
final ValueNotifier<BottomSheetQueueItemModel?> currentSheet = ValueNotifier(null);
|
||||
|
||||
@override
|
||||
Future<dynamic> queueBottomSheet({
|
||||
required Widget widget,
|
||||
bool isModalDismissible = false,
|
||||
}) async {
|
||||
// Create the bottom sheet queue item
|
||||
final completer = Completer<dynamic>();
|
||||
final queueItem = BottomSheetQueueItemModel(
|
||||
widget: widget,
|
||||
completer: completer,
|
||||
isModalDismissible: isModalDismissible,
|
||||
);
|
||||
|
||||
currentSheet.value = queueItem;
|
||||
|
||||
return await completer.future;
|
||||
}
|
||||
|
||||
@override
|
||||
void resetCurrentSheet() {
|
||||
currentSheet.value = null;
|
||||
}
|
||||
}
|
277
lib/core/wallet_connect/web3wallet_service.dart
Normal file
277
lib/core/wallet_connect/web3wallet_service.dart
Normal file
|
@ -0,0 +1,277 @@
|
|||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:cake_wallet/core/wallet_connect/evm_chain_id.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/evm_chain_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/models/auth_request_model.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/models/session_request_model.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_request_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/error_display_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:eth_sig_util/eth_sig_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
||||
|
||||
import 'wc_bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||
|
||||
part 'web3wallet_service.g.dart';
|
||||
|
||||
class Web3WalletService = Web3WalletServiceBase with _$Web3WalletService;
|
||||
|
||||
abstract class Web3WalletServiceBase with Store {
|
||||
final AppStore appStore;
|
||||
final BottomSheetService _bottomSheetHandler;
|
||||
final WalletConnectKeyService walletKeyService;
|
||||
|
||||
late Web3Wallet _web3Wallet;
|
||||
|
||||
@observable
|
||||
bool isInitialized;
|
||||
|
||||
/// The list of requests from the dapp
|
||||
/// Potential types include, but aren't limited to:
|
||||
/// [SessionProposalEvent], [AuthRequest]
|
||||
@observable
|
||||
ObservableList<PairingInfo> pairings;
|
||||
|
||||
@observable
|
||||
ObservableList<SessionData> sessions;
|
||||
|
||||
@observable
|
||||
ObservableList<StoredCacao> auth;
|
||||
|
||||
Web3WalletServiceBase(this._bottomSheetHandler, this.walletKeyService, this.appStore)
|
||||
: pairings = ObservableList<PairingInfo>(),
|
||||
sessions = ObservableList<SessionData>(),
|
||||
auth = ObservableList<StoredCacao>(),
|
||||
isInitialized = false;
|
||||
|
||||
@action
|
||||
void create() {
|
||||
// Create the web3wallet client
|
||||
_web3Wallet = Web3Wallet(
|
||||
core: Core(projectId: secrets.walletConnectProjectId),
|
||||
metadata: const PairingMetadata(
|
||||
name: 'Cake Wallet',
|
||||
description: 'Cake Wallet',
|
||||
url: 'https://cakewallet.com',
|
||||
icons: ['https://cakewallet.com/assets/image/cake_logo.png'],
|
||||
),
|
||||
);
|
||||
|
||||
// Setup our accounts
|
||||
List<ChainKeyModel> chainKeys = walletKeyService.getKeys();
|
||||
for (final chainKey in chainKeys) {
|
||||
for (final chainId in chainKey.chains) {
|
||||
_web3Wallet.registerAccount(
|
||||
chainId: chainId,
|
||||
accountAddress: chainKey.publicKey,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Setup our listeners
|
||||
log('Created instance of web3wallet');
|
||||
_web3Wallet.core.pairing.onPairingInvalid.subscribe(_onPairingInvalid);
|
||||
_web3Wallet.core.pairing.onPairingCreate.subscribe(_onPairingCreate);
|
||||
_web3Wallet.core.pairing.onPairingDelete.subscribe(_onPairingDelete);
|
||||
_web3Wallet.core.pairing.onPairingExpire.subscribe(_onPairingDelete);
|
||||
_web3Wallet.pairings.onSync.subscribe(_onPairingsSync);
|
||||
_web3Wallet.onSessionProposal.subscribe(_onSessionProposal);
|
||||
_web3Wallet.onSessionProposalError.subscribe(_onSessionProposalError);
|
||||
_web3Wallet.onSessionConnect.subscribe(_onSessionConnect);
|
||||
_web3Wallet.onAuthRequest.subscribe(_onAuthRequest);
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> init() async {
|
||||
// Await the initialization of the web3wallet
|
||||
log('Intializing web3wallet');
|
||||
if (!isInitialized) {
|
||||
try {
|
||||
await _web3Wallet.init();
|
||||
log('Initialized');
|
||||
isInitialized = true;
|
||||
} catch (e) {
|
||||
log('Experimentallllll: $e');
|
||||
isInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
_refreshPairings();
|
||||
|
||||
final newSessions = _web3Wallet.sessions.getAll();
|
||||
sessions.addAll(newSessions);
|
||||
|
||||
final newAuthRequests = _web3Wallet.completeRequests.getAll();
|
||||
auth.addAll(newAuthRequests);
|
||||
|
||||
for (final cId in EVMChainId.values) {
|
||||
EvmChainServiceImpl(
|
||||
reference: cId,
|
||||
appStore: appStore,
|
||||
wcKeyService: walletKeyService,
|
||||
bottomSheetService: _bottomSheetHandler,
|
||||
wallet: _web3Wallet,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
FutureOr<void> onDispose() {
|
||||
log('web3wallet dispose');
|
||||
_web3Wallet.core.pairing.onPairingInvalid.unsubscribe(_onPairingInvalid);
|
||||
_web3Wallet.pairings.onSync.unsubscribe(_onPairingsSync);
|
||||
_web3Wallet.onSessionProposal.unsubscribe(_onSessionProposal);
|
||||
_web3Wallet.onSessionProposalError.unsubscribe(_onSessionProposalError);
|
||||
_web3Wallet.onSessionConnect.unsubscribe(_onSessionConnect);
|
||||
_web3Wallet.onAuthRequest.unsubscribe(_onAuthRequest);
|
||||
_web3Wallet.core.pairing.onPairingDelete.unsubscribe(_onPairingDelete);
|
||||
_web3Wallet.core.pairing.onPairingExpire.unsubscribe(_onPairingDelete);
|
||||
}
|
||||
|
||||
Web3Wallet getWeb3Wallet() {
|
||||
return _web3Wallet;
|
||||
}
|
||||
|
||||
void _onPairingsSync(StoreSyncEvent? args) {
|
||||
if (args != null) {
|
||||
_refreshPairings();
|
||||
}
|
||||
}
|
||||
|
||||
void _onPairingDelete(PairingEvent? event) {
|
||||
_refreshPairings();
|
||||
}
|
||||
|
||||
@action
|
||||
void _refreshPairings() {
|
||||
pairings.clear();
|
||||
final allPairings = _web3Wallet.pairings.getAll();
|
||||
pairings.addAll(allPairings);
|
||||
}
|
||||
|
||||
Future<void> _onSessionProposalError(SessionProposalErrorEvent? args) async {
|
||||
log(args.toString());
|
||||
}
|
||||
|
||||
void _onSessionProposal(SessionProposalEvent? args) async {
|
||||
if (args != null) {
|
||||
final Widget modalWidget = Web3RequestModal(
|
||||
child: ConnectionRequestWidget(
|
||||
wallet: _web3Wallet,
|
||||
sessionProposal: SessionRequestModel(request: args.params),
|
||||
),
|
||||
);
|
||||
// show the bottom sheet
|
||||
final bool? isApproved = await _bottomSheetHandler.queueBottomSheet(
|
||||
widget: modalWidget,
|
||||
) as bool?;
|
||||
|
||||
if (isApproved != null && isApproved) {
|
||||
_web3Wallet.approveSession(
|
||||
id: args.id,
|
||||
namespaces: args.params.generatedNamespaces!,
|
||||
);
|
||||
} else {
|
||||
_web3Wallet.rejectSession(
|
||||
id: args.id,
|
||||
reason: Errors.getSdkError(
|
||||
Errors.USER_REJECTED,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
void _onPairingInvalid(PairingInvalidEvent? args) {
|
||||
log('Pairing Invalid Event: $args');
|
||||
_bottomSheetHandler.queueBottomSheet(
|
||||
isModalDismissible: true,
|
||||
widget: BottomSheetMessageDisplayWidget(message: '${S.current.pairingInvalidEvent}: $args'),
|
||||
);
|
||||
}
|
||||
|
||||
void _onPairingCreate(PairingEvent? args) {
|
||||
log('Pairing Create Event: $args');
|
||||
}
|
||||
|
||||
@action
|
||||
void _onSessionConnect(SessionConnect? args) {
|
||||
if (args != null) {
|
||||
sessions.add(args.session);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> _onAuthRequest(AuthRequest? args) async {
|
||||
if (args != null) {
|
||||
List<ChainKeyModel> chainKeys = walletKeyService.getKeysForChain('eip155:1');
|
||||
// Create the message to be signed
|
||||
final String iss = 'did:pkh:eip155:1:${chainKeys.first.publicKey}';
|
||||
|
||||
final Widget modalWidget = Web3RequestModal(
|
||||
child: ConnectionRequestWidget(
|
||||
wallet: _web3Wallet,
|
||||
authRequest: AuthRequestModel(iss: iss, request: args),
|
||||
),
|
||||
);
|
||||
final bool? isAuthenticated = await _bottomSheetHandler.queueBottomSheet(
|
||||
widget: modalWidget,
|
||||
) as bool?;
|
||||
|
||||
if (isAuthenticated != null && isAuthenticated) {
|
||||
final String message = _web3Wallet.formatAuthMessage(
|
||||
iss: iss,
|
||||
cacaoPayload: CacaoRequestPayload.fromPayloadParams(
|
||||
args.payloadParams,
|
||||
),
|
||||
);
|
||||
|
||||
final String sig = EthSigUtil.signPersonalMessage(
|
||||
message: Uint8List.fromList(message.codeUnits),
|
||||
privateKey: chainKeys.first.privateKey,
|
||||
);
|
||||
|
||||
await _web3Wallet.respondAuthRequest(
|
||||
id: args.id,
|
||||
iss: iss,
|
||||
signature: CacaoSignature(
|
||||
t: CacaoSignature.EIP191,
|
||||
s: sig,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await _web3Wallet.respondAuthRequest(
|
||||
id: args.id,
|
||||
iss: iss,
|
||||
error: Errors.getSdkError(
|
||||
Errors.USER_REJECTED_AUTH,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> disconnectSession(String topic) async {
|
||||
final session = sessions.firstWhere((element) => element.pairingTopic == topic);
|
||||
|
||||
await _web3Wallet.core.pairing.disconnect(topic: topic);
|
||||
await _web3Wallet.disconnectSession(
|
||||
topic: session.topic, reason: Errors.getSdkError(Errors.USER_DISCONNECTED));
|
||||
}
|
||||
|
||||
@action
|
||||
List<SessionData> getSessionsForPairingInfo(PairingInfo pairing) {
|
||||
return sessions.where((element) => element.pairingTopic == pairing.topic).toList();
|
||||
}
|
||||
}
|
124
lib/di.dart
124
lib/di.dart
|
@ -3,10 +3,12 @@ import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
|
|||
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
|
||||
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
|
||||
import 'package:cake_wallet/core/yat_service.dart';
|
||||
import 'package:cake_wallet/entities/background_tasks.dart';
|
||||
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
|
||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
||||
import 'package:cake_wallet/entities/receive_page_option.dart';
|
||||
|
@ -80,7 +82,6 @@ import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.da
|
|||
import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/pow_node_create_or_edit_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
|
||||
import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart';
|
||||
|
@ -418,6 +419,10 @@ Future<void> setup({
|
|||
}
|
||||
if (appStore.wallet != null) {
|
||||
authStore.allowed();
|
||||
|
||||
if (appStore.wallet!.type == WalletType.ethereum) {
|
||||
getIt.get<Web3WalletService>().init();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -438,6 +443,10 @@ Future<void> setup({
|
|||
} else {
|
||||
if (appStore.wallet != null) {
|
||||
authStore.allowed();
|
||||
|
||||
if (appStore.wallet!.type == WalletType.ethereum) {
|
||||
getIt.get<Web3WalletService>().init();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -457,11 +466,28 @@ Future<void> setup({
|
|||
}, closable: false);
|
||||
}, instanceName: 'login');
|
||||
|
||||
getIt.registerSingleton<BottomSheetService>(BottomSheetServiceImpl());
|
||||
|
||||
final appStore = getIt.get<AppStore>();
|
||||
|
||||
getIt.registerLazySingleton<WalletConnectKeyService>(() => KeyServiceImpl(appStore.wallet!));
|
||||
|
||||
getIt.registerLazySingleton<Web3WalletService>(() {
|
||||
final Web3WalletService web3WalletService = Web3WalletService(
|
||||
getIt.get<BottomSheetService>(),
|
||||
getIt.get<WalletConnectKeyService>(),
|
||||
appStore,
|
||||
);
|
||||
web3WalletService.create();
|
||||
return web3WalletService;
|
||||
});
|
||||
|
||||
getIt.registerFactory(() => BalancePage(
|
||||
dashboardViewModel: getIt.get<DashboardViewModel>(),
|
||||
settingsStore: getIt.get<SettingsStore>()));
|
||||
|
||||
getIt.registerFactory<DashboardPage>(() => DashboardPage(
|
||||
bottomSheetService: getIt.get<BottomSheetService>(),
|
||||
balancePage: getIt.get<BalancePage>(),
|
||||
dashboardViewModel: getIt.get<DashboardViewModel>(),
|
||||
addressListViewModel: getIt.get<WalletAddressListViewModel>(),
|
||||
|
@ -478,6 +504,7 @@ Future<void> setup({
|
|||
});
|
||||
getIt.registerFactoryParam<DesktopDashboardPage, GlobalKey<NavigatorState>, void>(
|
||||
(desktopKey, _) => DesktopDashboardPage(
|
||||
bottomSheetService: getIt.get<BottomSheetService>(),
|
||||
balancePage: getIt.get<BalancePage>(),
|
||||
dashboardViewModel: getIt.get<DashboardViewModel>(),
|
||||
addressListViewModel: getIt.get<WalletAddressListViewModel>(),
|
||||
|
@ -603,37 +630,22 @@ Future<void> setup({
|
|||
editingWallet: editingWallet);
|
||||
});
|
||||
|
||||
// getIt.registerFactory(() {
|
||||
// final wallet = getIt.get<AppStore>().wallet!;
|
||||
|
||||
// if (wallet.type == WalletType.monero || wallet.type == WalletType.haven) {
|
||||
// return MoneroAccountListViewModel(wallet);
|
||||
// }
|
||||
|
||||
// if (wallet.type == WalletType.nano || wallet.type == WalletType.banano) {
|
||||
// return NanoAccountListViewModel(wallet);
|
||||
// }
|
||||
|
||||
// throw Exception(
|
||||
// 'Unexpected wallet type: ${wallet.type} for generate Nano/Monero AccountListViewModel');
|
||||
// });
|
||||
|
||||
getIt.registerFactory(() {
|
||||
getIt.registerFactory<NanoAccountListViewModel>(() {
|
||||
final wallet = getIt.get<AppStore>().wallet!;
|
||||
if (wallet.type == WalletType.nano || wallet.type == WalletType.banano) {
|
||||
return NanoAccountListViewModel(wallet);
|
||||
}
|
||||
throw Exception(
|
||||
'Unexpected wallet type: ${wallet.type} for generate Nano/Monero AccountListViewModel');
|
||||
'Unexpected wallet type: ${wallet.type} for generate Nano/Banano AccountListViewModel');
|
||||
});
|
||||
|
||||
getIt.registerFactory(() {
|
||||
getIt.registerFactory<MoneroAccountListViewModel>(() {
|
||||
final wallet = getIt.get<AppStore>().wallet!;
|
||||
if (wallet.type == WalletType.monero || wallet.type == WalletType.haven) {
|
||||
return MoneroAccountListViewModel(wallet);
|
||||
}
|
||||
throw Exception(
|
||||
'Unexpected wallet type: ${wallet.type} for generate Nano/Monero AccountListViewModel');
|
||||
'Unexpected wallet type: ${wallet.type} for generate Monero AccountListViewModel');
|
||||
});
|
||||
|
||||
getIt.registerFactory(
|
||||
|
@ -729,7 +741,13 @@ Future<void> setup({
|
|||
return PowNodeListViewModel(_powNodeSource, appStore);
|
||||
});
|
||||
|
||||
getIt.registerFactory(() => ConnectionSyncPage(getIt.get<DashboardViewModel>()));
|
||||
getIt.registerFactory(() {
|
||||
final wallet = getIt.get<AppStore>().wallet;
|
||||
return ConnectionSyncPage(
|
||||
getIt.get<DashboardViewModel>(),
|
||||
wallet?.type == WalletType.ethereum ? getIt.get<Web3WalletService>() : null,
|
||||
);
|
||||
});
|
||||
|
||||
getIt.registerFactory(
|
||||
() => SecurityBackupPage(getIt.get<SecuritySettingsViewModel>(), getIt.get<AuthService>()));
|
||||
|
@ -740,11 +758,13 @@ Future<void> setup({
|
|||
|
||||
getIt.registerFactory(() => OtherSettingsPage(getIt.get<OtherSettingsViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => NanoChangeRepPage());
|
||||
getIt.registerFactory(() => NanoChangeRepPage(getIt.get<AppStore>().wallet!));
|
||||
|
||||
getIt.registerFactoryParam<NodeCreateOrEditViewModel, WalletType?, void>((WalletType? type, _) =>
|
||||
NodeCreateOrEditViewModel(
|
||||
_nodeSource, type ?? getIt.get<AppStore>().wallet!.type, getIt.get<SettingsStore>()));
|
||||
getIt.registerFactoryParam<NodeCreateOrEditViewModel, WalletType?, bool?>(
|
||||
(WalletType? type, bool? isPow) => NodeCreateOrEditViewModel(
|
||||
(isPow ?? false) ? _powNodeSource : _nodeSource,
|
||||
type ?? getIt.get<AppStore>().wallet!.type,
|
||||
getIt.get<SettingsStore>()));
|
||||
|
||||
getIt.registerFactoryParam<PowNodeCreateOrEditViewModel, WalletType?, void>(
|
||||
(WalletType? type, _) => PowNodeCreateOrEditViewModel(
|
||||
|
@ -752,7 +772,13 @@ Future<void> setup({
|
|||
|
||||
getIt.registerFactoryParam<NodeCreateOrEditPage, Node?, bool?>(
|
||||
(Node? editingNode, bool? isSelected) => NodeCreateOrEditPage(
|
||||
nodeCreateOrEditViewModel: getIt.get<NodeCreateOrEditViewModel>(),
|
||||
nodeCreateOrEditViewModel: getIt.get<NodeCreateOrEditViewModel>(param2: false),
|
||||
editingNode: editingNode,
|
||||
isSelected: isSelected));
|
||||
|
||||
getIt.registerFactoryParam<PowNodeCreateOrEditPage, Node?, bool?>(
|
||||
(Node? editingNode, bool? isSelected) => PowNodeCreateOrEditPage(
|
||||
nodeCreateOrEditViewModel: getIt.get<NodeCreateOrEditViewModel>(param2: true),
|
||||
editingNode: editingNode,
|
||||
isSelected: isSelected));
|
||||
|
||||
|
@ -844,10 +870,9 @@ Future<void> setup({
|
|||
(type, _) => WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: type)));
|
||||
|
||||
getIt.registerFactoryParam<WalletRestoreChooseDerivationViewModel, List<DerivationInfo>, void>(
|
||||
(derivations, _) =>
|
||||
WalletRestoreChooseDerivationViewModel(derivationInfos: derivations));
|
||||
(derivations, _) => WalletRestoreChooseDerivationViewModel(derivationInfos: derivations));
|
||||
|
||||
getIt.registerFactoryParam<WalletRestoreChooseDerivationPage, dynamic, void>(
|
||||
getIt.registerFactoryParam<WalletRestoreChooseDerivationPage, List<DerivationInfo>, void>(
|
||||
(credentials, _) =>
|
||||
WalletRestoreChooseDerivationPage(getIt.get<WalletRestoreChooseDerivationViewModel>(
|
||||
param1: credentials,
|
||||
|
@ -915,7 +940,7 @@ Future<void> setup({
|
|||
wallet: wallet!);
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<BuyWebViewPage, List, void>((List args, _) {
|
||||
getIt.registerFactoryParam<BuyWebViewPage, List<dynamic>, void>((List<dynamic> args, _) {
|
||||
final url = args.first as String;
|
||||
final buyViewModel = args[1] as BuyViewModel;
|
||||
|
||||
|
@ -936,16 +961,15 @@ Future<void> setup({
|
|||
|
||||
getIt.registerFactory(() => SupportPage(getIt.get<SupportViewModel>()));
|
||||
|
||||
getIt.registerFactory(() =>
|
||||
SupportChatPage(
|
||||
getIt.get<SupportViewModel>(), secureStorage: getIt.get<FlutterSecureStorage>()));
|
||||
getIt.registerFactory(() => SupportChatPage(getIt.get<SupportViewModel>(),
|
||||
secureStorage: getIt.get<FlutterSecureStorage>()));
|
||||
|
||||
getIt.registerFactory(() => SupportOtherLinksPage(getIt.get<SupportViewModel>()));
|
||||
|
||||
getIt.registerFactory(() {
|
||||
final wallet = getIt.get<AppStore>().wallet;
|
||||
|
||||
return UnspentCoinsListViewModel(wallet: wallet!, unspentCoinsInfo: _unspentCoinsInfoSource!);
|
||||
return UnspentCoinsListViewModel(wallet: wallet!, unspentCoinsInfo: _unspentCoinsInfoSource);
|
||||
});
|
||||
|
||||
getIt.registerFactory(() =>
|
||||
|
@ -956,7 +980,7 @@ Future<void> setup({
|
|||
(item, model) =>
|
||||
UnspentCoinsDetailsViewModel(unspentCoinsItem: item, unspentCoinsListViewModel: model));
|
||||
|
||||
getIt.registerFactoryParam<UnspentCoinsDetailsPage, List, void>((List args, _) {
|
||||
getIt.registerFactoryParam<UnspentCoinsDetailsPage, List<dynamic>, void>((List<dynamic> args, _) {
|
||||
final item = args.first as UnspentCoinsItem;
|
||||
final unspentCoinsListViewModel = args[1] as UnspentCoinsListViewModel;
|
||||
|
||||
|
@ -967,8 +991,8 @@ Future<void> setup({
|
|||
|
||||
getIt.registerFactory(() => YatService());
|
||||
|
||||
getIt.registerFactory(() => AddressResolver(
|
||||
yatService: getIt.get<YatService>(), walletType: getIt.get<AppStore>().wallet!.type));
|
||||
getIt.registerFactory(() =>
|
||||
AddressResolver(yatService: getIt.get<YatService>(), wallet: getIt.get<AppStore>().wallet!));
|
||||
|
||||
getIt.registerFactoryParam<FullscreenQRPage, QrViewData, void>(
|
||||
(QrViewData viewData, _) => FullscreenQRPage(qrViewData: viewData));
|
||||
|
@ -1009,7 +1033,7 @@ Future<void> setup({
|
|||
|
||||
getIt.registerFactory(() => IoniaLoginPage(getIt.get<IoniaAuthViewModel>()));
|
||||
|
||||
getIt.registerFactoryParam<IoniaVerifyIoniaOtp, List, void>((List args, _) {
|
||||
getIt.registerFactoryParam<IoniaVerifyIoniaOtp, List<dynamic>, void>((List<dynamic> args, _) {
|
||||
final email = args.first as String;
|
||||
final isSignIn = args[1] as bool;
|
||||
|
||||
|
@ -1018,13 +1042,14 @@ Future<void> setup({
|
|||
|
||||
getIt.registerFactory(() => IoniaWelcomePage());
|
||||
|
||||
getIt.registerFactoryParam<IoniaBuyGiftCardPage, List, void>((List args, _) {
|
||||
getIt.registerFactoryParam<IoniaBuyGiftCardPage, List<dynamic>, void>((List<dynamic> args, _) {
|
||||
final merchant = args.first as IoniaMerchant;
|
||||
|
||||
return IoniaBuyGiftCardPage(getIt.get<IoniaBuyCardViewModel>(param1: merchant));
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<IoniaBuyGiftCardDetailPage, List, void>((List args, _) {
|
||||
getIt.registerFactoryParam<IoniaBuyGiftCardDetailPage, List<dynamic>, void>(
|
||||
(List<dynamic> args, _) {
|
||||
final amount = args.first as double;
|
||||
final merchant = args.last as IoniaMerchant;
|
||||
return IoniaBuyGiftCardDetailPage(
|
||||
|
@ -1037,7 +1062,7 @@ Future<void> setup({
|
|||
ioniaService: getIt.get<IoniaService>(), giftCard: giftCard);
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<IoniaCustomTipViewModel, List, void>((List args, _) {
|
||||
getIt.registerFactoryParam<IoniaCustomTipViewModel, List<dynamic>, void>((List<dynamic> args, _) {
|
||||
final amount = args[0] as double;
|
||||
final merchant = args[1] as IoniaMerchant;
|
||||
final tip = args[2] as IoniaTip;
|
||||
|
@ -1050,7 +1075,7 @@ Future<void> setup({
|
|||
return IoniaGiftCardDetailPage(getIt.get<IoniaGiftCardDetailsViewModel>(param1: giftCard));
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<IoniaMoreOptionsPage, List, void>((List args, _) {
|
||||
getIt.registerFactoryParam<IoniaMoreOptionsPage, List<dynamic>, void>((List<dynamic> args, _) {
|
||||
final giftCard = args.first as IoniaGiftCard;
|
||||
|
||||
return IoniaMoreOptionsPage(giftCard);
|
||||
|
@ -1060,13 +1085,13 @@ Future<void> setup({
|
|||
(IoniaGiftCard giftCard, _) =>
|
||||
IoniaCustomRedeemViewModel(giftCard: giftCard, ioniaService: getIt.get<IoniaService>()));
|
||||
|
||||
getIt.registerFactoryParam<IoniaCustomRedeemPage, List, void>((List args, _) {
|
||||
getIt.registerFactoryParam<IoniaCustomRedeemPage, List<dynamic>, void>((List<dynamic> args, _) {
|
||||
final giftCard = args.first as IoniaGiftCard;
|
||||
|
||||
return IoniaCustomRedeemPage(getIt.get<IoniaCustomRedeemViewModel>(param1: giftCard));
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<IoniaCustomTipPage, List, void>((List args, _) {
|
||||
getIt.registerFactoryParam<IoniaCustomTipPage, List<dynamic>, void>((List<dynamic> args, _) {
|
||||
return IoniaCustomTipPage(getIt.get<IoniaCustomTipViewModel>(param1: args));
|
||||
});
|
||||
|
||||
|
@ -1133,9 +1158,12 @@ Future<void> setup({
|
|||
),
|
||||
);
|
||||
|
||||
getIt.registerFactory<ManageNodesPage>(() => ManageNodesPage(getIt.get<NodeListViewModel>()));
|
||||
getIt.registerFactory<ManagePowNodesPage>(
|
||||
() => ManagePowNodesPage(getIt.get<PowNodeListViewModel>()));
|
||||
getIt.registerFactoryParam<ManageNodesPage, bool, void>((bool isPow, _) {
|
||||
if (isPow) {
|
||||
return ManageNodesPage(isPow, powNodeListViewModel: getIt.get<PowNodeListViewModel>());
|
||||
}
|
||||
return ManageNodesPage(isPow, nodeListViewModel: getIt.get<NodeListViewModel>());
|
||||
});
|
||||
|
||||
_isSetupFinished = true;
|
||||
}
|
||||
|
|
|
@ -26,8 +26,8 @@ const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
|
|||
const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
|
||||
const havenDefaultNodeUri = 'nodes.havenprotocol.org:443';
|
||||
const ethereumDefaultNodeUri = 'ethereum.publicnode.com';
|
||||
const nanoDefaultNodeUri = 'rpc.nano.to:443';
|
||||
const nanoDefaultPowNodeUri = 'rpc.nano.to:443';
|
||||
const nanoDefaultNodeUri = 'rpc.nano.to';
|
||||
const nanoDefaultPowNodeUri = 'rpc.nano.to';
|
||||
|
||||
Future<void> defaultSettingsMigration(
|
||||
{required int version,
|
||||
|
@ -52,6 +52,8 @@ Future<void> defaultSettingsMigration(
|
|||
|
||||
await sharedPreferences.setBool(PreferencesKey.isNewInstall, isNewInstall);
|
||||
|
||||
await sharedPreferences.setBool(PreferencesKey.isNewInstall, isNewInstall);
|
||||
|
||||
final currentVersion =
|
||||
sharedPreferences.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) ?? 0;
|
||||
|
||||
|
@ -161,10 +163,10 @@ Future<void> defaultSettingsMigration(
|
|||
break;
|
||||
case 22:
|
||||
await addNanoNodeList(nodes: nodes);
|
||||
await addNanoPowNodeList(nodes: powNodes);
|
||||
await changeNanoCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
|
||||
await changeNanoCurrentPowNodeToDefault(
|
||||
sharedPreferences: sharedPreferences, nodes: powNodes);
|
||||
await resetPowToDefault(powNodes);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -516,7 +518,7 @@ Future<void> checkCurrentNodes(
|
|||
final currentNanoNodeServer =
|
||||
nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId);
|
||||
final currentNanoPowNodeServer =
|
||||
nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId);
|
||||
powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId);
|
||||
|
||||
if (currentMoneroNode == null) {
|
||||
final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
|
||||
|
@ -551,7 +553,7 @@ Future<void> checkCurrentNodes(
|
|||
}
|
||||
|
||||
if (currentNanoNodeServer == null) {
|
||||
final node = Node(uri: nanoDefaultNodeUri, type: WalletType.nano);
|
||||
final node = Node(uri: nanoDefaultNodeUri, useSSL: true, type: WalletType.nano);
|
||||
await nodeSource.add(node);
|
||||
await sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, node.key as int);
|
||||
}
|
||||
|
@ -560,7 +562,7 @@ Future<void> checkCurrentNodes(
|
|||
Node? node = powNodeSource.values
|
||||
.firstWhereOrNull((node) => node.uri.toString() == nanoDefaultPowNodeUri);
|
||||
if (node == null) {
|
||||
node = Node(uri: nanoDefaultPowNodeUri, type: WalletType.nano);
|
||||
node = Node(uri: nanoDefaultPowNodeUri, useSSL: true, type: WalletType.nano);
|
||||
await powNodeSource.add(node);
|
||||
}
|
||||
await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, node.key as int);
|
||||
|
@ -636,6 +638,15 @@ Future<void> addNanoNodeList({required Box<Node> nodes}) async {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> addNanoPowNodeList({required Box<Node> nodes}) async {
|
||||
final nodeList = await loadDefaultNanoPowNodes();
|
||||
for (var node in nodeList) {
|
||||
if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
|
||||
await nodes.add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> changeNanoCurrentNodeToDefault(
|
||||
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
|
||||
final node = getNanoDefaultNode(nodes: nodes);
|
||||
|
|
46
lib/entities/ens_record.dart
Normal file
46
lib/entities/ens_record.dart
Normal file
|
@ -0,0 +1,46 @@
|
|||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:ens_dart/ens_dart.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:web3dart/web3dart.dart';
|
||||
|
||||
class EnsRecord {
|
||||
static Future<String> fetchEnsAddress(String name, {WalletBase? wallet}) async {
|
||||
Web3Client? _client;
|
||||
|
||||
if (wallet != null && wallet.type == WalletType.ethereum) {
|
||||
_client = ethereum!.getWeb3Client(wallet);
|
||||
}
|
||||
|
||||
if (_client == null) {
|
||||
_client = Web3Client("https://ethereum.publicnode.com", Client());
|
||||
}
|
||||
|
||||
try {
|
||||
final ens = Ens(client: _client);
|
||||
|
||||
if (wallet != null) {
|
||||
switch (wallet.type) {
|
||||
case WalletType.monero:
|
||||
return await ens.withName(name).getCoinAddress(CoinType.XMR);
|
||||
case WalletType.bitcoin:
|
||||
return await ens.withName(name).getCoinAddress(CoinType.BTC);
|
||||
case WalletType.litecoin:
|
||||
return await ens.withName(name).getCoinAddress(CoinType.LTC);
|
||||
case WalletType.haven:
|
||||
return await ens.withName(name).getCoinAddress(CoinType.XHV);
|
||||
case WalletType.ethereum:
|
||||
default:
|
||||
return (await ens.withName(name).getAddress()).hex;
|
||||
}
|
||||
}
|
||||
|
||||
final addr = await ens.withName(name).getAddress();
|
||||
return addr.hex;
|
||||
} catch (e) {
|
||||
print(e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +1,23 @@
|
|||
import 'package:cake_wallet/core/address_validator.dart';
|
||||
import 'package:cake_wallet/core/yat_service.dart';
|
||||
import 'package:cake_wallet/entities/ens_record.dart';
|
||||
import 'package:cake_wallet/entities/openalias_record.dart';
|
||||
import 'package:cake_wallet/entities/parsed_address.dart';
|
||||
import 'package:cake_wallet/entities/unstoppable_domain_address.dart';
|
||||
import 'package:cake_wallet/entities/emoji_string_extension.dart';
|
||||
import 'package:cake_wallet/mastodon/mastodon_api.dart';
|
||||
import 'package:cake_wallet/twitter/twitter_api.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/entities/fio_address_provider.dart';
|
||||
|
||||
class AddressResolver {
|
||||
AddressResolver({required this.yatService, required this.walletType});
|
||||
AddressResolver({required this.yatService, required this.wallet}) : walletType = wallet.type;
|
||||
|
||||
final YatService yatService;
|
||||
final WalletType walletType;
|
||||
final WalletBase wallet;
|
||||
|
||||
static const unstoppableDomains = [
|
||||
'crypto',
|
||||
|
@ -63,13 +67,47 @@ class AddressResolver {
|
|||
});
|
||||
final userTweetsText = subString.toString();
|
||||
final addressFromPinnedTweet =
|
||||
extractAddressByType(raw: userTweetsText, type: CryptoCurrency.fromString(ticker));
|
||||
extractAddressByType(raw: userTweetsText, type: CryptoCurrency.fromString(ticker));
|
||||
|
||||
if (addressFromPinnedTweet != null) {
|
||||
return ParsedAddress.fetchTwitterAddress(address: addressFromPinnedTweet, name: text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (text.startsWith('@') && text.contains('@', 1) && text.contains('.', 1)) {
|
||||
final subText = text.substring(1);
|
||||
final hostNameIndex = subText.indexOf('@');
|
||||
final hostName = subText.substring(hostNameIndex + 1);
|
||||
final userName = subText.substring(0, hostNameIndex);
|
||||
|
||||
final mastodonUser =
|
||||
await MastodonAPI.lookupUserByUserName(userName: userName, apiHost: hostName);
|
||||
|
||||
if (mastodonUser != null) {
|
||||
String? addressFromBio =
|
||||
extractAddressByType(raw: mastodonUser.note, type: CryptoCurrency.fromString(ticker));
|
||||
|
||||
if (addressFromBio != null) {
|
||||
return ParsedAddress.fetchMastodonAddress(address: addressFromBio, name: text);
|
||||
} else {
|
||||
final pinnedPosts =
|
||||
await MastodonAPI.getPinnedPosts(userId: mastodonUser.id, apiHost: hostName);
|
||||
|
||||
if (pinnedPosts.isNotEmpty) {
|
||||
final userPinnedPostsText = pinnedPosts.map((item) => item.content).join('\n');
|
||||
String? addressFromPinnedPost = extractAddressByType(
|
||||
raw: userPinnedPostsText, type: CryptoCurrency.fromString(ticker));
|
||||
|
||||
if (addressFromPinnedPost != null) {
|
||||
return ParsedAddress.fetchMastodonAddress(
|
||||
address: addressFromPinnedPost, name: text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!text.startsWith('@') && text.contains('@') && !text.contains('.')) {
|
||||
final bool isFioRegistered = await FioAddressProvider.checkAvail(text);
|
||||
if (isFioRegistered) {
|
||||
|
@ -96,6 +134,13 @@ class AddressResolver {
|
|||
return ParsedAddress.fetchUnstoppableDomainAddress(address: address, name: text);
|
||||
}
|
||||
|
||||
if (text.endsWith(".eth")) {
|
||||
final address = await EnsRecord.fetchEnsAddress(text, wallet: wallet);
|
||||
if (address.isNotEmpty && address != "0x0000000000000000000000000000000000000000") {
|
||||
return ParsedAddress.fetchEnsAddress(name: text, address: address);
|
||||
}
|
||||
}
|
||||
|
||||
if (formattedName.contains(".")) {
|
||||
final txtRecord = await OpenaliasRecord.lookupOpenAliasRecord(formattedName);
|
||||
if (txtRecord != null) {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import 'package:cake_wallet/entities/openalias_record.dart';
|
||||
import 'package:cake_wallet/entities/yat_record.dart';
|
||||
|
||||
enum ParseFrom { unstoppableDomains, openAlias, yatRecord, fio, notParsed, twitter, contact }
|
||||
|
||||
enum ParseFrom { unstoppableDomains, openAlias, yatRecord, fio, notParsed, twitter, ens, contact, mastodon }
|
||||
|
||||
class ParsedAddress {
|
||||
ParsedAddress({
|
||||
|
@ -69,6 +70,14 @@ class ParsedAddress {
|
|||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.fetchMastodonAddress({required String address, required String name}){
|
||||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.mastodon
|
||||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.fetchContactAddress({required String address, required String name}){
|
||||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
|
@ -77,8 +86,17 @@ class ParsedAddress {
|
|||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.fetchEnsAddress({required String address, required String name}) {
|
||||
return ParsedAddress(
|
||||
addresses: [address],
|
||||
name: name,
|
||||
parseFrom: ParseFrom.ens,
|
||||
);
|
||||
}
|
||||
|
||||
final List<String> addresses;
|
||||
final String name;
|
||||
final String description;
|
||||
final ParseFrom parseFrom;
|
||||
|
||||
}
|
||||
|
|
|
@ -19,8 +19,7 @@ class PreferencesKey {
|
|||
static const disableSellKey = 'disable_sell';
|
||||
static const defaultBuyProvider = 'default_buy_provider';
|
||||
static const currentFiatApiModeKey = 'current_fiat_api_mode';
|
||||
static const allowBiometricalAuthenticationKey =
|
||||
'allow_biometrical_authentication';
|
||||
static const allowBiometricalAuthenticationKey = 'allow_biometrical_authentication';
|
||||
static const useTOTP2FA = 'use_totp_2fa';
|
||||
static const failedTotpTokenTrials = 'failed_token_trials';
|
||||
static const disableExchangeKey = 'disable_exchange';
|
||||
|
@ -58,8 +57,7 @@ class PreferencesKey {
|
|||
static const clearnetDonationLink = 'clearnet_donation_link';
|
||||
static const onionDonationLink = 'onion_donation_link';
|
||||
static const lastSeenAppVersion = 'last_seen_app_version';
|
||||
static const shouldShowMarketPlaceInDashboard =
|
||||
'should_show_marketplace_in_dashboard';
|
||||
static const shouldShowMarketPlaceInDashboard = 'should_show_marketplace_in_dashboard';
|
||||
static const isNewInstall = 'is_new_install';
|
||||
static const shouldRequireTOTP2FAForAccessingWallet =
|
||||
'should_require_totp_2fa_for_accessing_wallets';
|
||||
|
|
|
@ -33,6 +33,20 @@ class CWEthereum extends Ethereum {
|
|||
@override
|
||||
String getAddress(WalletBase wallet) => (wallet as EthereumWallet).walletAddresses.address;
|
||||
|
||||
@override
|
||||
String getPrivateKey(WalletBase wallet) {
|
||||
final privateKeyHolder = (wallet as EthereumWallet).ethPrivateKey;
|
||||
String stringKey = bytesToHex(privateKeyHolder.privateKey);
|
||||
return stringKey;
|
||||
}
|
||||
|
||||
@override
|
||||
String getPublicKey(WalletBase wallet) {
|
||||
final privateKeyInUnitInt = (wallet as EthereumWallet).ethPrivateKey;
|
||||
final publicKey = privateKeyInUnitInt.address.hex;
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
@override
|
||||
TransactionPriority getDefaultTransactionPriority() => EthereumTransactionPriority.medium;
|
||||
|
||||
|
@ -131,4 +145,9 @@ class CWEthereum extends Ethereum {
|
|||
void updateEtherscanUsageState(WalletBase wallet, bool isEnabled) {
|
||||
(wallet as EthereumWallet).updateEtherscanUsageState(isEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Web3Client? getWeb3Client(WalletBase wallet) {
|
||||
return (wallet as EthereumWallet).getWeb3Client();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,6 @@ import 'package:cake_wallet/src/screens/root/root.dart';
|
|||
import 'package:uni_links/uni_links.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/wallet_type_utils.dart';
|
||||
import 'package:cw_core/cake_hive.dart';
|
||||
|
||||
final navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
@ -317,26 +316,26 @@ class _Home extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _HomeState extends State<_Home> {
|
||||
@override
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
if(!ResponsiveLayoutUtil.instance.isMobile){
|
||||
_setOrientation(context);
|
||||
if (!ResponsiveLayoutUtil.instance.isMobile) {
|
||||
_setOrientation(context);
|
||||
}
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
|
||||
void _setOrientation(BuildContext context){
|
||||
void _setOrientation(BuildContext context) {
|
||||
final orientation = MediaQuery.of(context).orientation;
|
||||
final width = MediaQuery.of(context).size.width;
|
||||
final height = MediaQuery.of(context).size.height;
|
||||
if (orientation == Orientation.portrait && width < height) {
|
||||
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
||||
SystemChrome.setPreferredOrientations(
|
||||
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
||||
} else if (orientation == Orientation.landscape && width > height) {
|
||||
SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
|
||||
SystemChrome.setPreferredOrientations(
|
||||
[DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
63
lib/mastodon/mastodon_api.dart
Normal file
63
lib/mastodon/mastodon_api.dart
Normal file
|
@ -0,0 +1,63 @@
|
|||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:cake_wallet/mastodon/mastodon_user.dart';
|
||||
|
||||
class MastodonAPI {
|
||||
static const httpsScheme = 'https';
|
||||
static const userPath = '/api/v1/accounts/lookup';
|
||||
static const statusesPath = '/api/v1/accounts/:id/statuses';
|
||||
|
||||
static Future<MastodonUser?> lookupUserByUserName(
|
||||
{required String userName, required String apiHost}) async {
|
||||
try {
|
||||
final queryParams = {'acct': userName};
|
||||
|
||||
final uri = Uri(
|
||||
scheme: httpsScheme,
|
||||
host: apiHost,
|
||||
path: userPath,
|
||||
queryParameters: queryParams,
|
||||
);
|
||||
|
||||
final response = await http.get(uri);
|
||||
|
||||
if (response.statusCode != 200) return null;
|
||||
|
||||
final Map<String, dynamic> responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||
|
||||
return MastodonUser.fromJson(responseJSON);
|
||||
} catch (e) {
|
||||
print('Error in lookupUserByUserName: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<List<PinnedPost>> getPinnedPosts({
|
||||
required String userId,
|
||||
required String apiHost,
|
||||
}) async {
|
||||
try {
|
||||
final queryParams = {'pinned': 'true'};
|
||||
|
||||
final uri = Uri(
|
||||
scheme: httpsScheme,
|
||||
host: apiHost,
|
||||
path: statusesPath.replaceAll(':id', userId),
|
||||
queryParameters: queryParams,
|
||||
);
|
||||
|
||||
final response = await http.get(uri);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Unexpected HTTP status: ${response.statusCode}');
|
||||
}
|
||||
|
||||
final List<dynamic> responseJSON = json.decode(response.body) as List<dynamic>;
|
||||
|
||||
return responseJSON.map((json) => PinnedPost.fromJson(json as Map<String, dynamic>)).toList();
|
||||
} catch (e) {
|
||||
print('Error in getPinnedPosts: $e');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
36
lib/mastodon/mastodon_user.dart
Normal file
36
lib/mastodon/mastodon_user.dart
Normal file
|
@ -0,0 +1,36 @@
|
|||
class MastodonUser {
|
||||
String id;
|
||||
String username;
|
||||
String acct;
|
||||
String note;
|
||||
|
||||
MastodonUser({
|
||||
required this.id,
|
||||
required this.username,
|
||||
required this.acct,
|
||||
required this.note,
|
||||
});
|
||||
|
||||
factory MastodonUser.fromJson(Map<String, dynamic> json) {
|
||||
return MastodonUser(
|
||||
id: json['id'] as String,
|
||||
username: json['username'] as String,
|
||||
acct: json['acct'] as String,
|
||||
note: json['note'] as String,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PinnedPost {
|
||||
final String id;
|
||||
final String content;
|
||||
|
||||
PinnedPost({required this.id, required this.content});
|
||||
|
||||
factory PinnedPost.fromJson(Map<String, dynamic> json) {
|
||||
return PinnedPost(
|
||||
id: json['id'] as String,
|
||||
content: json['content'] as String,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -79,10 +79,6 @@ class CWNano extends Nano {
|
|||
return NanoWalletService(walletInfoSource);
|
||||
}
|
||||
|
||||
String getTransactionAddress(Object wallet, int accountIndex, int addressIndex) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, String> getKeys(Object wallet) {
|
||||
final nanoWallet = wallet as NanoWallet;
|
||||
|
@ -109,7 +105,6 @@ class CWNano extends Nano {
|
|||
required String mnemonic,
|
||||
DerivationType? derivationType,
|
||||
}) {
|
||||
|
||||
if (derivationType == null) {
|
||||
// figure out the derivation type as best we can, otherwise set it to "unknown"
|
||||
if (mnemonic.split(" ").length == 12) {
|
||||
|
@ -134,7 +129,6 @@ class CWNano extends Nano {
|
|||
required String seedKey,
|
||||
DerivationType? derivationType,
|
||||
}) {
|
||||
|
||||
if (derivationType == null) {
|
||||
// figure out the derivation type as best we can, otherwise set it to "unknown"
|
||||
if (seedKey.length == 64) {
|
||||
|
@ -152,14 +146,6 @@ class CWNano extends Nano {
|
|||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TransactionHistoryBase getTransactionHistory(Object wallet) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
void onStartup() {}
|
||||
|
||||
@override
|
||||
Object createNanoTransactionCredentials(List<Output> outputs) {
|
||||
return NanoTransactionCredentials(
|
||||
|
@ -179,10 +165,335 @@ class CWNano extends Nano {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> setLabelAccount(Object wallet,
|
||||
{required int accountIndex, required String label}) async {
|
||||
final nanoWallet = wallet as NanoWallet;
|
||||
await nanoWallet.walletAddresses.accountList
|
||||
.setLabelAccount(accountIndex: accountIndex, label: label);
|
||||
Future<void> changeRep(Object wallet, String address) async {
|
||||
return (wallet as NanoWallet).changeRep(address);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateTransactions(Object wallet) async {
|
||||
return (wallet as NanoWallet).updateTransactions();
|
||||
}
|
||||
|
||||
@override
|
||||
BigInt getTransactionAmountRaw(TransactionInfo transactionInfo) {
|
||||
return (transactionInfo as NanoTransactionInfo).amountRaw;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// number util:
|
||||
|
||||
static const int maxDecimalDigits = 6; // Max digits after decimal
|
||||
BigInt rawPerNano = BigInt.parse("1000000000000000000000000000000");
|
||||
BigInt rawPerNyano = BigInt.parse("1000000000000000000000000");
|
||||
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
|
||||
///
|
||||
@override
|
||||
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 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";
|
||||
}
|
||||
|
||||
@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 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();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AccountInfoResponse?> getInfoFromSeedOrMnemonic(
|
||||
DerivationType derivationType, {
|
||||
String? seedKey,
|
||||
String? mnemonic,
|
||||
required Node node,
|
||||
}) async {
|
||||
NanoClient nanoClient = NanoClient();
|
||||
nanoClient.connect(node);
|
||||
late String publicAddress;
|
||||
|
||||
if (seedKey != null) {
|
||||
if (derivationType == DerivationType.bip39) {
|
||||
publicAddress = await hdSeedToAddress(seedKey, 0);
|
||||
} else if (derivationType == DerivationType.nano) {
|
||||
publicAddress = await seedToAddress(seedKey, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (derivationType == DerivationType.bip39) {
|
||||
if (mnemonic != null) {
|
||||
seedKey = await hdMnemonicListToSeed(mnemonic.split(' '));
|
||||
publicAddress = await hdSeedToAddress(seedKey, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (derivationType == DerivationType.nano) {
|
||||
if (mnemonic != null) {
|
||||
seedKey = await mnemonicToSeed(mnemonic);
|
||||
publicAddress = await seedToAddress(seedKey, 0);
|
||||
}
|
||||
}
|
||||
|
||||
AccountInfoResponse? accountInfo = await nanoClient.getAccountInfo(publicAddress);
|
||||
if (accountInfo == null) {
|
||||
accountInfo = AccountInfoResponse(frontier: "", balance: "0", representative: "", confirmationHeight: 0);
|
||||
}
|
||||
accountInfo.address = publicAddress;
|
||||
return accountInfo;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<DerivationType>> compareDerivationMethods({
|
||||
String? mnemonic,
|
||||
String? privateKey,
|
||||
required Node node,
|
||||
}) async {
|
||||
String? seedKey = privateKey;
|
||||
|
||||
if (mnemonic?.split(' ').length == 12) {
|
||||
return [DerivationType.bip39];
|
||||
}
|
||||
if (seedKey?.length == 128) {
|
||||
return [DerivationType.bip39];
|
||||
} else if (seedKey?.length == 64) {
|
||||
return [DerivationType.nano];
|
||||
}
|
||||
|
||||
late String publicAddressStandard;
|
||||
late String publicAddressBip39;
|
||||
|
||||
try {
|
||||
NanoClient nanoClient = NanoClient();
|
||||
nanoClient.connect(node);
|
||||
|
||||
if (mnemonic != null) {
|
||||
seedKey = await hdMnemonicListToSeed(mnemonic.split(' '));
|
||||
publicAddressBip39 = await hdSeedToAddress(seedKey, 0);
|
||||
|
||||
seedKey = await mnemonicToSeed(mnemonic);
|
||||
publicAddressStandard = await seedToAddress(seedKey, 0);
|
||||
} else if (seedKey != null) {
|
||||
try {
|
||||
publicAddressBip39 = await hdSeedToAddress(seedKey, 0);
|
||||
} catch (e) {
|
||||
return [DerivationType.nano];
|
||||
}
|
||||
try {
|
||||
publicAddressStandard = await seedToAddress(seedKey, 0);
|
||||
} catch (e) {
|
||||
return [DerivationType.bip39];
|
||||
}
|
||||
}
|
||||
|
||||
// check if account has a history:
|
||||
AccountInfoResponse? bip39Info;
|
||||
AccountInfoResponse? standardInfo;
|
||||
|
||||
try {
|
||||
bip39Info = await nanoClient.getAccountInfo(publicAddressBip39);
|
||||
} catch (e) {
|
||||
bip39Info = null;
|
||||
}
|
||||
try {
|
||||
standardInfo = await nanoClient.getAccountInfo(publicAddressStandard);
|
||||
} catch (e) {
|
||||
standardInfo = null;
|
||||
}
|
||||
|
||||
// one of these is *probably* null:
|
||||
if (bip39Info == null && standardInfo != null) {
|
||||
return [DerivationType.nano];
|
||||
} else if (standardInfo == null && bip39Info != null) {
|
||||
return [DerivationType.bip39];
|
||||
}
|
||||
|
||||
// we don't know for sure:
|
||||
return [DerivationType.nano, DerivationType.bip39];
|
||||
} catch (e) {
|
||||
return [DerivationType.unknown];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -201,8 +201,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
|
||||
case Routes.restoreWalletChooseDerivation:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) =>
|
||||
getIt.get<WalletRestoreChooseDerivationPage>(param1: settings.arguments as List<DerivationInfo>));
|
||||
builder: (_) => getIt.get<WalletRestoreChooseDerivationPage>(
|
||||
param1: settings.arguments as List<DerivationInfo>));
|
||||
|
||||
case Routes.sweepingWalletPage:
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<SweepingWalletPage>());
|
||||
|
@ -248,7 +248,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
return CupertinoPageRoute<void>(builder: (_) => DisclaimerPage(isReadOnly: true));
|
||||
|
||||
case Routes.changeRep:
|
||||
return CupertinoPageRoute<void>(builder: (_) => NanoChangeRepPage());
|
||||
return CupertinoPageRoute<void>(builder: (_) => getIt.get<NanoChangeRepPage>());
|
||||
|
||||
case Routes.seedLanguage:
|
||||
final args = settings.arguments as List<dynamic>;
|
||||
|
@ -521,7 +521,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => AdvancedPrivacySettingsPage(
|
||||
getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
|
||||
getIt.get<NodeCreateOrEditViewModel>(param1: type),
|
||||
getIt.get<NodeCreateOrEditViewModel>(param1: type, param2: false),
|
||||
));
|
||||
|
||||
case Routes.anonPayInvoicePage:
|
||||
|
@ -585,7 +585,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
);
|
||||
|
||||
case Routes.manageNodes:
|
||||
return MaterialPageRoute<void>(builder: (_) => getIt.get<ManageNodesPage>());
|
||||
return MaterialPageRoute<void>(builder: (_) => getIt.get<ManageNodesPage>(param1: false));
|
||||
|
||||
case Routes.managePowNodes:
|
||||
return MaterialPageRoute<void>(builder: (_) => getIt.get<ManageNodesPage>(param1: true));
|
||||
|
||||
case Routes.managePowNodes:
|
||||
return MaterialPageRoute<void>(builder: (_) => getIt.get<ManagePowNodesPage>());
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import 'dart:async';
|
||||
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/entities/main_actions.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/market_place_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart';
|
||||
import 'package:cake_wallet/src/widgets/gradient_background.dart';
|
||||
import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart';
|
||||
import 'package:cake_wallet/utils/device_info.dart';
|
||||
|
@ -35,12 +38,14 @@ import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
|||
|
||||
class DashboardPage extends StatelessWidget {
|
||||
DashboardPage({
|
||||
required this.bottomSheetService,
|
||||
required this.balancePage,
|
||||
required this.dashboardViewModel,
|
||||
required this.addressListViewModel,
|
||||
});
|
||||
|
||||
final BalancePage balancePage;
|
||||
final BottomSheetService bottomSheetService;
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final WalletAddressListViewModel addressListViewModel;
|
||||
|
||||
|
@ -55,12 +60,14 @@ class DashboardPage extends StatelessWidget {
|
|||
} else {
|
||||
return _DashboardPageView(
|
||||
balancePage: balancePage,
|
||||
bottomSheetService: bottomSheetService,
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
addressListViewModel: addressListViewModel,
|
||||
);
|
||||
}
|
||||
} else if (ResponsiveLayoutUtil.instance.shouldRenderMobileUI()) {
|
||||
return _DashboardPageView(
|
||||
bottomSheetService: bottomSheetService,
|
||||
balancePage: balancePage,
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
addressListViewModel: addressListViewModel,
|
||||
|
@ -76,6 +83,7 @@ class DashboardPage extends StatelessWidget {
|
|||
|
||||
class _DashboardPageView extends BasePage {
|
||||
_DashboardPageView({
|
||||
required this.bottomSheetService,
|
||||
required this.balancePage,
|
||||
required this.dashboardViewModel,
|
||||
required this.addressListViewModel,
|
||||
|
@ -126,6 +134,7 @@ class _DashboardPageView extends BasePage {
|
|||
}
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final BottomSheetService bottomSheetService;
|
||||
final WalletAddressListViewModel addressListViewModel;
|
||||
|
||||
int get initialPage => dashboardViewModel.shouldShowMarketPlaceInDashboard ? 1 : 0;
|
||||
|
@ -158,102 +167,106 @@ class _DashboardPageView extends BasePage {
|
|||
|
||||
return SafeArea(
|
||||
minimum: EdgeInsets.only(bottom: 24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Observer(
|
||||
builder: (context) {
|
||||
return PageView.builder(
|
||||
controller: controller,
|
||||
itemCount: pages.length,
|
||||
itemBuilder: (context, index) => pages[index],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(bottom: 24, top: 10),
|
||||
child: Observer(
|
||||
builder: (context) {
|
||||
return ExcludeSemantics(
|
||||
child: SmoothPageIndicator(
|
||||
child: BottomSheetListener(
|
||||
bottomSheetService: bottomSheetService,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Observer(
|
||||
builder: (context) {
|
||||
return PageView.builder(
|
||||
controller: controller,
|
||||
count: pages.length,
|
||||
effect: ColorTransitionEffect(
|
||||
spacing: 6.0,
|
||||
radius: 6.0,
|
||||
dotWidth: 6.0,
|
||||
dotHeight: 6.0,
|
||||
dotColor: Theme.of(context).indicatorColor,
|
||||
activeDotColor: Theme.of(context)
|
||||
.extension<DashboardPageTheme>()!
|
||||
.indicatorDotTheme
|
||||
.activeIndicatorColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: pages.length,
|
||||
itemBuilder: (context, index) => pages[index],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) {
|
||||
return ClipRect(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(left: 16, right: 16),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(50.0),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
|
||||
width: 1,
|
||||
Padding(
|
||||
padding: EdgeInsets.only(bottom: 24, top: 10),
|
||||
child: Observer(
|
||||
builder: (context) {
|
||||
return ExcludeSemantics(
|
||||
child: SmoothPageIndicator(
|
||||
controller: controller,
|
||||
count: pages.length,
|
||||
effect: ColorTransitionEffect(
|
||||
spacing: 6.0,
|
||||
radius: 6.0,
|
||||
dotWidth: 6.0,
|
||||
dotHeight: 6.0,
|
||||
dotColor: Theme.of(context).indicatorColor,
|
||||
activeDotColor: Theme.of(context)
|
||||
.extension<DashboardPageTheme>()!
|
||||
.indicatorDotTheme
|
||||
.activeIndicatorColor,
|
||||
),
|
||||
color:
|
||||
Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) {
|
||||
return ClipRect(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(left: 16, right: 16),
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 32, right: 32),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: MainActions.all
|
||||
.where((element) => element.canShow?.call(dashboardViewModel) ?? true)
|
||||
.map(
|
||||
(action) => Semantics(
|
||||
button: true,
|
||||
enabled: (action.isEnabled?.call(dashboardViewModel) ?? true),
|
||||
child: ActionButton(
|
||||
image: Image.asset(
|
||||
action.image,
|
||||
height: 24,
|
||||
width: 24,
|
||||
color: action.isEnabled?.call(dashboardViewModel) ?? true
|
||||
? Theme.of(context)
|
||||
.extension<DashboardPageTheme>()!
|
||||
.mainActionsIconColor
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(50.0),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
|
||||
width: 1,
|
||||
),
|
||||
color: Theme.of(context)
|
||||
.extension<SyncIndicatorTheme>()!
|
||||
.syncedBackgroundColor,
|
||||
),
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 32, right: 32),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: MainActions.all
|
||||
.where((element) => element.canShow?.call(dashboardViewModel) ?? true)
|
||||
.map(
|
||||
(action) => Semantics(
|
||||
button: true,
|
||||
enabled: (action.isEnabled?.call(dashboardViewModel) ?? true),
|
||||
child: ActionButton(
|
||||
image: Image.asset(
|
||||
action.image,
|
||||
height: 24,
|
||||
width: 24,
|
||||
color: action.isEnabled?.call(dashboardViewModel) ?? true
|
||||
? Theme.of(context)
|
||||
.extension<DashboardPageTheme>()!
|
||||
.mainActionsIconColor
|
||||
: Theme.of(context)
|
||||
.extension<BalancePageTheme>()!
|
||||
.labelTextColor,
|
||||
),
|
||||
title: action.name(context),
|
||||
onClick: () async =>
|
||||
await action.onTap(context, dashboardViewModel),
|
||||
textColor: action.isEnabled?.call(dashboardViewModel) ?? true
|
||||
? null
|
||||
: Theme.of(context)
|
||||
.extension<BalancePageTheme>()!
|
||||
.labelTextColor,
|
||||
),
|
||||
title: action.name(context),
|
||||
onClick: () async =>
|
||||
await action.onTap(context, dashboardViewModel),
|
||||
textColor: action.isEnabled?.call(dashboardViewModel) ?? true
|
||||
? null
|
||||
: Theme.of(context)
|
||||
.extension<BalancePageTheme>()!
|
||||
.labelTextColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import 'dart:async';
|
||||
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/release_notes/release_notes_screen.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart';
|
||||
import 'package:cake_wallet/src/screens/yat_emoji_id.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
|
@ -19,12 +21,14 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||
class DesktopDashboardPage extends StatelessWidget {
|
||||
DesktopDashboardPage({
|
||||
required this.balancePage,
|
||||
required this.bottomSheetService,
|
||||
required this.dashboardViewModel,
|
||||
required this.addressListViewModel,
|
||||
required this.desktopKey,
|
||||
});
|
||||
|
||||
final BalancePage balancePage;
|
||||
final BottomSheetService bottomSheetService;
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final WalletAddressListViewModel addressListViewModel;
|
||||
final GlobalKey<NavigatorState> desktopKey;
|
||||
|
@ -36,31 +40,34 @@ class DesktopDashboardPage extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
_setEffects(context);
|
||||
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 400,
|
||||
child: balancePage,
|
||||
),
|
||||
Flexible(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 500),
|
||||
child: Navigator(
|
||||
key: desktopKey,
|
||||
initialRoute: Routes.desktop_actions,
|
||||
onGenerateRoute: (settings) => Router.createRoute(settings),
|
||||
onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) {
|
||||
return [
|
||||
navigator.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))!
|
||||
];
|
||||
},
|
||||
return BottomSheetListener(
|
||||
bottomSheetService: bottomSheetService,
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 400,
|
||||
child: balancePage,
|
||||
),
|
||||
Flexible(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 500),
|
||||
child: Navigator(
|
||||
key: desktopKey,
|
||||
initialRoute: Routes.desktop_actions,
|
||||
onGenerateRoute: (settings) => Router.createRoute(settings),
|
||||
onGenerateInitialRoutes: (NavigatorState navigator, String initialRouteName) {
|
||||
return [
|
||||
navigator.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))!
|
||||
];
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import 'package:cake_wallet/core/address_validator.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/nano/nano.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_nano/nano_wallet.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
@ -14,20 +15,14 @@ import 'package:cake_wallet/src/screens/base_page.dart';
|
|||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
|
||||
class NanoChangeRepPage extends BasePage {
|
||||
|
||||
NanoChangeRepPage()
|
||||
: _formKey = GlobalKey<FormState>(),
|
||||
NanoChangeRepPage(WalletBase wallet)
|
||||
: _wallet = wallet,
|
||||
_addressController = TextEditingController() {
|
||||
var wallet = getIt.get<AppStore>().wallet!;
|
||||
if (wallet is NanoWallet /*|| wallet is BananoWallet*/) {
|
||||
_addressController.text = wallet.representative;
|
||||
}
|
||||
_addressController.text = (wallet as NanoWallet).representative;
|
||||
}
|
||||
|
||||
final GlobalKey<FormState> _formKey;
|
||||
final TextEditingController _addressController;
|
||||
|
||||
// final CryptoCurrency type;
|
||||
final WalletBase _wallet;
|
||||
|
||||
@override
|
||||
String get title => S.current.change_rep;
|
||||
|
@ -80,14 +75,18 @@ class NanoChangeRepPage extends BasePage {
|
|||
|
||||
if (confirmed) {
|
||||
try {
|
||||
final wallet = getIt.get<AppStore>().wallet!;
|
||||
if (wallet is NanoWallet) {
|
||||
await wallet.changeRep(_addressController.text);
|
||||
}
|
||||
// TODO: show message saying success:
|
||||
|
||||
await nano!.changeRep(_wallet, _addressController.text);
|
||||
Navigator.of(context).pop();
|
||||
} catch (e) {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(context).error,
|
||||
alertContent: e.toString(),
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.pop(context));
|
||||
});
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:cake_wallet/themes/extensions/account_list_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
@ -21,11 +22,11 @@ class AccountTile extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = isCurrent
|
||||
? Theme.of(context).textTheme.titleSmall!.decorationColor!
|
||||
: Theme.of(context).textTheme.displayLarge!.decorationColor!;
|
||||
? Theme.of(context).extension<AccountListTheme>()!.currentAccountBackgroundColor
|
||||
: Theme.of(context).extension<AccountListTheme>()!.tilesBackgroundColor;
|
||||
final textColor = isCurrent
|
||||
? Theme.of(context).textTheme.titleSmall!.color!
|
||||
: Theme.of(context).textTheme.displayLarge!.color!;
|
||||
? Theme.of(context).extension<AccountListTheme>()!.currentAccountTextColor
|
||||
: Theme.of(context).extension<AccountListTheme>()!.tilesTextColor;
|
||||
|
||||
final Widget cell = GestureDetector(
|
||||
onTap: onTap,
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/src/screens/nodes/widgets/node_form.dart';
|
||||
import 'package:cake_wallet/src/screens/nodes/widgets/pow_node_form.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/pow_node_create_or_edit_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
@ -14,7 +13,6 @@ import 'package:cake_wallet/generated/i18n.dart';
|
|||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
|
||||
|
||||
class PowNodeCreateOrEditPage extends BasePage {
|
||||
PowNodeCreateOrEditPage({required this.nodeCreateOrEditViewModel,this.editingNode, this.isSelected})
|
||||
|
@ -81,7 +79,7 @@ class PowNodeCreateOrEditPage extends BasePage {
|
|||
),
|
||||
);
|
||||
|
||||
final PowNodeCreateOrEditViewModel nodeCreateOrEditViewModel;
|
||||
final NodeCreateOrEditViewModel nodeCreateOrEditViewModel;
|
||||
final Node? editingNode;
|
||||
final bool? isSelected;
|
||||
|
||||
|
@ -124,7 +122,7 @@ class PowNodeCreateOrEditPage extends BasePage {
|
|||
padding: EdgeInsets.only(left: 24, right: 24),
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.only(bottom: 24.0),
|
||||
content: PowNodeForm(
|
||||
content: NodeForm(
|
||||
formKey: _formKey,
|
||||
nodeViewModel: nodeCreateOrEditViewModel,
|
||||
editingNode: editingNode,
|
||||
|
|
|
@ -11,10 +11,12 @@ class NodeListRow extends StandardListRow {
|
|||
{required String title,
|
||||
required this.node,
|
||||
required void Function(BuildContext context) onTap,
|
||||
required bool isSelected})
|
||||
required bool isSelected,
|
||||
required this.isPow})
|
||||
: super(title: title, onTap: onTap, isSelected: isSelected);
|
||||
|
||||
final Node node;
|
||||
final bool isPow;
|
||||
|
||||
@override
|
||||
Widget buildLeading(BuildContext context) {
|
||||
|
@ -33,7 +35,7 @@ class NodeListRow extends StandardListRow {
|
|||
@override
|
||||
Widget buildTrailing(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.newNode,
|
||||
onTap: () => Navigator.of(context).pushNamed(isPow ? Routes.newPowNode : Routes.newNode,
|
||||
arguments: {'editingNode': node, 'isSelected': isSelected}),
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(10),
|
||||
|
|
|
@ -66,6 +66,7 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> {
|
|||
addressController.dispose();
|
||||
viewKeyController.dispose();
|
||||
privateKeyController.dispose();
|
||||
spendKeyController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
|
@ -5,11 +5,9 @@ import 'package:cake_wallet/store/app_store.dart';
|
|||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_wallet_service.dart';
|
||||
import 'package:cw_core/nano_account_info_response.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_nano/nano_util.dart';
|
||||
import 'package:cw_nano/nano_wallet_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:keyboard_actions/keyboard_actions.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
@ -242,7 +240,7 @@ class WalletRestorePage extends BasePage {
|
|||
}
|
||||
|
||||
// bip39:
|
||||
const validSeedLengths = [12, 15, 18, 21, 24];
|
||||
const validSeedLengths = [12, 18, 24];
|
||||
if (walletRestoreViewModel.type == WalletType.bitcoin &&
|
||||
!(validSeedLengths.contains(seedWords.length))) {
|
||||
return false;
|
||||
|
@ -306,6 +304,7 @@ class WalletRestorePage extends BasePage {
|
|||
var walletType = credentials["walletType"] as WalletType;
|
||||
var appStore = getIt.get<AppStore>();
|
||||
var node = appStore.settingsStore.getCurrentNode(walletType);
|
||||
|
||||
switch (walletType) {
|
||||
case WalletType.bitcoin:
|
||||
String? mnemonic = credentials['seed'] as String?;
|
||||
|
@ -314,38 +313,33 @@ class WalletRestorePage extends BasePage {
|
|||
case WalletType.nano:
|
||||
String? mnemonic = credentials['seed'] as String?;
|
||||
String? seedKey = credentials['private_key'] as String?;
|
||||
dynamic bip39Info = await NanoWalletService.getInfoFromSeedOrMnemonic(DerivationType.bip39,
|
||||
mnemonic: mnemonic, seedKey: seedKey, node: node);
|
||||
dynamic standardInfo = await NanoWalletService.getInfoFromSeedOrMnemonic(
|
||||
AccountInfoResponse? bip39Info = await nanoUtil!.getInfoFromSeedOrMnemonic(
|
||||
DerivationType.bip39,
|
||||
mnemonic: mnemonic,
|
||||
seedKey: seedKey,
|
||||
node: node);
|
||||
AccountInfoResponse? standardInfo = await nanoUtil!.getInfoFromSeedOrMnemonic(
|
||||
DerivationType.nano,
|
||||
mnemonic: mnemonic,
|
||||
seedKey: seedKey,
|
||||
node: node,
|
||||
);
|
||||
|
||||
if (standardInfo["balance"] != null) {
|
||||
if (standardInfo?.balance != null) {
|
||||
list.add(DerivationInfo(
|
||||
derivationType: DerivationType.nano,
|
||||
balance: NanoUtil.getRawAsUsableString(
|
||||
standardInfo["balance"] as String, NanoUtil.rawPerNano),
|
||||
address: standardInfo["address"] as String,
|
||||
height: int.tryParse(
|
||||
standardInfo["confirmation_height"] as String,
|
||||
) ??
|
||||
0,
|
||||
balance: nanoUtil!.getRawAsUsableString(standardInfo!.balance, nanoUtil!.rawPerNano),
|
||||
address: standardInfo.address!,
|
||||
height: standardInfo.confirmationHeight,
|
||||
));
|
||||
}
|
||||
|
||||
if (bip39Info["balance"] != null) {
|
||||
if (bip39Info?.balance != null) {
|
||||
list.add(DerivationInfo(
|
||||
derivationType: DerivationType.bip39,
|
||||
balance:
|
||||
NanoUtil.getRawAsUsableString(bip39Info["balance"] as String, NanoUtil.rawPerNano),
|
||||
address: bip39Info["address"] as String,
|
||||
height: int.tryParse(
|
||||
bip39Info["confirmation_height"] as String? ?? "",
|
||||
) ??
|
||||
0,
|
||||
balance: nanoUtil!.getRawAsUsableString(bip39Info!.balance, nanoUtil!.rawPerNano),
|
||||
address: bip39Info.address!,
|
||||
height: bip39Info.confirmationHeight,
|
||||
));
|
||||
}
|
||||
break;
|
||||
|
@ -399,6 +393,7 @@ class WalletRestorePage extends BasePage {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
DerivationInfo? derivationInfo;
|
||||
|
||||
if (derivationsWithHistory > 1) {
|
||||
|
@ -413,8 +408,6 @@ class WalletRestorePage extends BasePage {
|
|||
derivationPath: "m/0'/1",
|
||||
height: 0,
|
||||
);
|
||||
this.derivationType = derivationTypes[0];
|
||||
this.derivationPath = "m/0'/1";
|
||||
}
|
||||
|
||||
if (derivationInfo == null) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/core/totp_request_details.dart';
|
||||
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
||||
import 'package:cake_wallet/utils/device_info.dart';
|
||||
import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -97,8 +98,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!_isInactive &&
|
||||
widget.authenticationStore.state == AuthenticationState.allowed) {
|
||||
if (!_isInactive && widget.authenticationStore.state == AuthenticationState.allowed) {
|
||||
setState(() => _setInactive(true));
|
||||
}
|
||||
|
||||
|
@ -125,16 +125,15 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
|||
return;
|
||||
} else {
|
||||
final useTotp = widget.appStore.settingsStore.useTOTP2FA;
|
||||
final shouldUseTotp2FAToAccessWallets = widget.appStore
|
||||
.settingsStore.shouldRequireTOTP2FAForAccessingWallet;
|
||||
final shouldUseTotp2FAToAccessWallets =
|
||||
widget.appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet;
|
||||
if (useTotp && shouldUseTotp2FAToAccessWallets) {
|
||||
_reset();
|
||||
auth.close(
|
||||
route: Routes.totpAuthCodePage,
|
||||
arguments: TotpAuthArgumentsModel(
|
||||
onTotpAuthenticationFinished:
|
||||
(bool isAuthenticatedSuccessfully,
|
||||
TotpAuthCodePageState totpAuth) {
|
||||
(bool isAuthenticatedSuccessfully, TotpAuthCodePageState totpAuth) {
|
||||
if (!isAuthenticatedSuccessfully) {
|
||||
return;
|
||||
}
|
||||
|
@ -169,7 +168,10 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
|||
launchUri = null;
|
||||
}
|
||||
|
||||
return WillPopScope(onWillPop: () async => false, child: widget.child);
|
||||
return WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
void _reset() {
|
||||
|
|
|
@ -18,6 +18,11 @@ Future<String> extractAddressFromParsed(
|
|||
content = S.of(context).address_from_domain(parsedAddress.name);
|
||||
address = parsedAddress.addresses.first;
|
||||
break;
|
||||
case ParseFrom.ens:
|
||||
title = S.of(context).address_detected;
|
||||
content = S.of(context).extracted_address_content('${parsedAddress.name} (ENS)');
|
||||
address = parsedAddress.addresses.first;
|
||||
break;
|
||||
case ParseFrom.openAlias:
|
||||
title = S.of(context).address_detected;
|
||||
content = S.of(context).extracted_address_content('${parsedAddress.name} (OpenAlias)');
|
||||
|
@ -33,6 +38,11 @@ Future<String> extractAddressFromParsed(
|
|||
content = S.of(context).extracted_address_content('${parsedAddress.name} (Twitter)');
|
||||
address = parsedAddress.addresses.first;
|
||||
break;
|
||||
case ParseFrom.mastodon:
|
||||
title = S.of(context).address_detected;
|
||||
content = S.of(context).extracted_address_content('${parsedAddress.name} (Mastodon)');
|
||||
address = parsedAddress.addresses.first;
|
||||
break;
|
||||
case ParseFrom.yatRecord:
|
||||
if (parsedAddress.name.isEmpty) {
|
||||
title = S.of(context).yat_error;
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/wallet_connect_button.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart';
|
||||
import 'package:cake_wallet/utils/device_info.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/settings/sync_mode.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
|
@ -15,11 +18,12 @@ import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
|||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class ConnectionSyncPage extends BasePage {
|
||||
ConnectionSyncPage(this.dashboardViewModel);
|
||||
ConnectionSyncPage(this.dashboardViewModel, this.web3walletService);
|
||||
|
||||
@override
|
||||
String get title => S.current.connection_sync;
|
||||
|
||||
final Web3WalletService? web3walletService;
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
|
||||
@override
|
||||
|
@ -81,6 +85,20 @@ class ConnectionSyncPage extends BasePage {
|
|||
);
|
||||
},
|
||||
),
|
||||
if (dashboardViewModel.wallet.type == WalletType.ethereum) ...[
|
||||
WalletConnectTile(
|
||||
onTap: () async {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return WalletConnectConnectionsView(web3walletService: web3walletService!);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
||||
]
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -6,13 +6,17 @@ import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
|||
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class ManageNodesPage extends BasePage {
|
||||
ManageNodesPage(this.nodeListViewModel);
|
||||
ManageNodesPage(this.isPow, {this.nodeListViewModel, this.powNodeListViewModel})
|
||||
: assert((isPow && powNodeListViewModel != null) || (!isPow && nodeListViewModel != null));
|
||||
|
||||
final NodeListViewModel nodeListViewModel;
|
||||
final NodeListViewModel? nodeListViewModel;
|
||||
final PowNodeListViewModel? powNodeListViewModel;
|
||||
final bool isPow;
|
||||
|
||||
@override
|
||||
String get title => S.current.manage_nodes;
|
||||
|
@ -34,7 +38,8 @@ class ManageNodesPage extends BasePage {
|
|||
SizedBox(height: 20),
|
||||
Observer(
|
||||
builder: (BuildContext context) {
|
||||
int itemsCount = nodeListViewModel.nodes.length;
|
||||
int itemsCount =
|
||||
nodeListViewModel?.nodes.length ?? powNodeListViewModel!.nodes.length;
|
||||
return Flexible(
|
||||
child: SectionStandardList(
|
||||
sectionCount: 1,
|
||||
|
@ -43,12 +48,19 @@ class ManageNodesPage extends BasePage {
|
|||
itemBuilder: (_, index) {
|
||||
return Observer(
|
||||
builder: (context) {
|
||||
final node = nodeListViewModel.nodes[index];
|
||||
final isSelected = node.keyIndex == nodeListViewModel.currentNode.keyIndex;
|
||||
final node =
|
||||
nodeListViewModel?.nodes[index] ?? powNodeListViewModel!.nodes[index];
|
||||
late bool isSelected;
|
||||
if (isPow) {
|
||||
isSelected = node.keyIndex == powNodeListViewModel!.currentNode.keyIndex;
|
||||
} else {
|
||||
isSelected = node.keyIndex == nodeListViewModel!.currentNode.keyIndex;
|
||||
}
|
||||
final nodeListRow = NodeListRow(
|
||||
title: node.uriRaw,
|
||||
node: node,
|
||||
isSelected: isSelected,
|
||||
isPow: false,
|
||||
onTap: (_) async {
|
||||
if (isSelected) {
|
||||
return;
|
||||
|
@ -59,12 +71,17 @@ class ManageNodesPage extends BasePage {
|
|||
builder: (BuildContext context) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: S.of(context).change_current_node_title,
|
||||
alertContent: nodeListViewModel.getAlertContent(node.uriRaw),
|
||||
alertContent: nodeListViewModel?.getAlertContent(node.uriRaw) ??
|
||||
powNodeListViewModel!.getAlertContent(node.uriRaw),
|
||||
leftButtonText: S.of(context).cancel,
|
||||
rightButtonText: S.of(context).change,
|
||||
actionLeftButton: () => Navigator.of(context).pop(),
|
||||
actionRightButton: () async {
|
||||
await nodeListViewModel.setAsCurrent(node);
|
||||
if (isPow) {
|
||||
await powNodeListViewModel!.setAsCurrent(node);
|
||||
} else {
|
||||
await nodeListViewModel!.setAsCurrent(node);
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
|
|
46
lib/src/screens/settings/widgets/wallet_connect_button.dart
Normal file
46
lib/src/screens/settings/widgets/wallet_connect_button.dart
Normal file
|
@ -0,0 +1,46 @@
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class WalletConnectTile extends StatelessWidget {
|
||||
const WalletConnectTile({required this.onTap});
|
||||
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(24),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Image.asset(
|
||||
'assets/images/walletconnect_logo.png',
|
||||
height: 24,
|
||||
width: 24,
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Text(
|
||||
S.current.walletConnect,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
Image.asset(
|
||||
'assets/images/select_arrow.png',
|
||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_widget.dart';
|
||||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
||||
|
||||
import '../../../../core/wallet_connect/models/connection_model.dart';
|
||||
|
||||
class ConnectionWidgetBuilder {
|
||||
static List<ConnectionWidget> buildFromRequiredNamespaces(
|
||||
Map<String, RequiredNamespace> requiredNamespaces,
|
||||
) {
|
||||
final List<ConnectionWidget> views = [];
|
||||
for (final key in requiredNamespaces.keys) {
|
||||
RequiredNamespace ns = requiredNamespaces[key]!;
|
||||
final List<ConnectionModel> models = [];
|
||||
// If the chains property is present, add the chain data to the models
|
||||
if (ns.chains != null) {
|
||||
models.add(ConnectionModel(title: S.current.chains, elements: ns.chains!));
|
||||
}
|
||||
models.add(ConnectionModel(title: S.current.methods, elements: ns.methods));
|
||||
models.add(ConnectionModel(title: S.current.events, elements: ns.events));
|
||||
|
||||
views.add(ConnectionWidget(title: key, info: models));
|
||||
}
|
||||
|
||||
return views;
|
||||
}
|
||||
|
||||
static List<ConnectionWidget> buildFromNamespaces(
|
||||
String topic,
|
||||
Map<String, Namespace> namespaces,
|
||||
Web3Wallet web3wallet,
|
||||
) {
|
||||
final List<ConnectionWidget> views = [];
|
||||
for (final key in namespaces.keys) {
|
||||
final Namespace ns = namespaces[key]!;
|
||||
final List<ConnectionModel> models = [];
|
||||
// If the chains property is present, add the chain data to the models
|
||||
models.add(
|
||||
ConnectionModel(
|
||||
title: S.current.chains,
|
||||
elements: ns.accounts,
|
||||
),
|
||||
);
|
||||
models.add(ConnectionModel(
|
||||
title: S.current.methods,
|
||||
elements: ns.methods,
|
||||
));
|
||||
|
||||
Map<String, void Function()> actions = {};
|
||||
for (final String event in ns.events) {
|
||||
actions[event] = () async {
|
||||
final String chainId = NamespaceUtils.isValidChainId(key)
|
||||
? key
|
||||
: NamespaceUtils.getChainFromAccount(ns.accounts.first);
|
||||
await web3wallet.emitSessionEvent(
|
||||
topic: topic,
|
||||
chainId: chainId,
|
||||
event: SessionEventParams(name: event, data: '${S.current.event}: $event'),
|
||||
);
|
||||
};
|
||||
}
|
||||
models.add(
|
||||
ConnectionModel(title: S.current.events, elements: ns.events, elementActions: actions),
|
||||
);
|
||||
|
||||
views.add(ConnectionWidget(title: key, info: models));
|
||||
}
|
||||
|
||||
return views;
|
||||
}
|
||||
}
|
16
lib/src/screens/wallet_connect/utils/string_parsing.dart
Normal file
16
lib/src/screens/wallet_connect/utils/string_parsing.dart
Normal file
|
@ -0,0 +1,16 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:convert/convert.dart';
|
||||
|
||||
extension StringParsing on String {
|
||||
String get utf8Message {
|
||||
if (startsWith('0x')) {
|
||||
final List<int> decoded = hex.decode(
|
||||
substring(2),
|
||||
);
|
||||
return utf8.decode(decoded);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
142
lib/src/screens/wallet_connect/wc_connections_listing_view.dart
Normal file
142
lib/src/screens/wallet_connect/wc_connections_listing_view.dart
Normal file
|
@ -0,0 +1,142 @@
|
|||
import 'dart:developer';
|
||||
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
||||
import 'package:cake_wallet/entities/qr_scanner.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
|
||||
import 'widgets/pairing_item_widget.dart';
|
||||
import 'wc_pairing_detail_page.dart';
|
||||
|
||||
class WalletConnectConnectionsView extends StatelessWidget {
|
||||
final Web3WalletService web3walletService;
|
||||
|
||||
WalletConnectConnectionsView({required this.web3walletService, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WCPairingsWidget(web3walletService: web3walletService);
|
||||
}
|
||||
}
|
||||
|
||||
class WCPairingsWidget extends BasePage {
|
||||
WCPairingsWidget({required this.web3walletService, Key? key})
|
||||
: web3wallet = web3walletService.getWeb3Wallet();
|
||||
|
||||
final Web3Wallet web3wallet;
|
||||
final Web3WalletService web3walletService;
|
||||
|
||||
@override
|
||||
String get title => S.current.walletConnect;
|
||||
|
||||
Future<void> _onScanQrCode(BuildContext context, Web3Wallet web3Wallet) async {
|
||||
final String? uri = await presentQRScanner();
|
||||
|
||||
if (uri == null) return _invalidUriToast(context, S.current.nullURIError);
|
||||
|
||||
try {
|
||||
log('_onFoundUri: $uri');
|
||||
final Uri uriData = Uri.parse(uri);
|
||||
await web3Wallet.pair(uri: uriData);
|
||||
} on WalletConnectError catch (e) {
|
||||
await _invalidUriToast(context, e.message);
|
||||
} catch (e) {
|
||||
await _invalidUriToast(context, e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _invalidUriToast(BuildContext context, String message) async {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(context).error,
|
||||
alertContent: message,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: Navigator.of(context).pop,
|
||||
alertBarrierDismissible: false,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (context) {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(height: 24),
|
||||
Text(
|
||||
S.current.connectWalletPrompt,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
PrimaryButton(
|
||||
text: S.current.newConnection,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
onPressed: () => _onScanQrCode(context, web3wallet),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 48),
|
||||
Expanded(
|
||||
child: Visibility(
|
||||
visible: web3walletService.pairings.isEmpty,
|
||||
child: Center(
|
||||
child: Text(
|
||||
S.current.activeConnectionsPrompt,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
replacement: ListView.builder(
|
||||
itemCount: web3walletService.pairings.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final pairing = web3walletService.pairings[index];
|
||||
return PairingItemWidget(
|
||||
key: ValueKey(pairing.topic),
|
||||
pairing: pairing,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => WalletConnectPairingDetailsPage(
|
||||
pairing: pairing,
|
||||
web3walletService: web3walletService,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 48),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
186
lib/src/screens/wallet_connect/wc_pairing_detail_page.dart
Normal file
186
lib/src/screens/wallet_connect/wc_pairing_detail_page.dart
Normal file
|
@ -0,0 +1,186 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
||||
|
||||
import 'utils/namespace_model_builder.dart';
|
||||
|
||||
class WalletConnectPairingDetailsPage extends StatefulWidget {
|
||||
final PairingInfo pairing;
|
||||
final Web3WalletService web3walletService;
|
||||
|
||||
const WalletConnectPairingDetailsPage({
|
||||
required this.pairing,
|
||||
required this.web3walletService,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
WalletConnectPairingDetailsPageState createState() => WalletConnectPairingDetailsPageState();
|
||||
}
|
||||
|
||||
class WalletConnectPairingDetailsPageState extends State<WalletConnectPairingDetailsPage> {
|
||||
List<Widget> sessionWidgets = [];
|
||||
late String expiryDate;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initDateTime();
|
||||
initSessions();
|
||||
}
|
||||
|
||||
void initDateTime() {
|
||||
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(widget.pairing.expiry * 1000);
|
||||
int year = dateTime.year;
|
||||
int month = dateTime.month;
|
||||
int day = dateTime.day;
|
||||
|
||||
expiryDate = '$year-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
void initSessions() {
|
||||
List<SessionData> sessions = widget.web3walletService.getSessionsForPairingInfo(widget.pairing);
|
||||
|
||||
for (final SessionData session in sessions) {
|
||||
List<Widget> namespaceWidget = ConnectionWidgetBuilder.buildFromNamespaces(
|
||||
session.topic,
|
||||
session.namespaces,
|
||||
widget.web3walletService.getWeb3Wallet(),
|
||||
);
|
||||
// Loop through and add the namespace widgets, but put 20 pixels between each one
|
||||
for (int i = 0; i < namespaceWidget.length; i++) {
|
||||
sessionWidgets.add(namespaceWidget[i]);
|
||||
if (i != namespaceWidget.length - 1) {
|
||||
sessionWidgets.add(const SizedBox(height: 20.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WCCDetailsWidget(
|
||||
widget.pairing,
|
||||
expiryDate,
|
||||
sessionWidgets,
|
||||
widget.web3walletService,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class WCCDetailsWidget extends BasePage {
|
||||
WCCDetailsWidget(
|
||||
this.pairing,
|
||||
this.expiryDate,
|
||||
this.sessionWidgets,
|
||||
this.web3walletService,
|
||||
);
|
||||
|
||||
final PairingInfo pairing;
|
||||
final String expiryDate;
|
||||
final List<Widget> sessionWidgets;
|
||||
final Web3WalletService web3walletService;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: SingleChildScrollView(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: CircleAvatar(
|
||||
backgroundImage: (pairing.peerMetadata!.icons.isNotEmpty
|
||||
? NetworkImage(pairing.peerMetadata!.icons[0])
|
||||
: const AssetImage('assets/images/default_icon.png'))
|
||||
as ImageProvider<Object>,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
Text(
|
||||
pairing.peerMetadata!.name,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
Text(
|
||||
pairing.peerMetadata!.url,
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
Text(
|
||||
'${S.current.expiresOn}: $expiryDate',
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: sessionWidgets,
|
||||
),
|
||||
const SizedBox(height: 20.0),
|
||||
PrimaryButton(
|
||||
onPressed: () =>
|
||||
_onDeleteButtonPressed(context, pairing.peerMetadata!.name, web3walletService),
|
||||
text: S.current.delete,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onDeleteButtonPressed(
|
||||
BuildContext context, String dAppName, Web3WalletService web3walletService) async {
|
||||
bool confirmed = false;
|
||||
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext dialogContext) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: S.of(context).delete,
|
||||
alertContent: '${S.current.deleteConnectionConfirmationPrompt} $dAppName?',
|
||||
leftButtonText: S.of(context).cancel,
|
||||
rightButtonText: S.of(context).delete,
|
||||
actionLeftButton: () => Navigator.of(dialogContext).pop(),
|
||||
actionRightButton: () {
|
||||
confirmed = true;
|
||||
Navigator.of(dialogContext).pop();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
if (confirmed) {
|
||||
try {
|
||||
await web3walletService.disconnectSession(pairing.topic);
|
||||
|
||||
Navigator.of(context).pop();
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../core/wallet_connect/models/connection_model.dart';
|
||||
|
||||
class ConnectionItemWidget extends StatelessWidget {
|
||||
const ConnectionItemWidget({required this.model, Key? key}) : super(key: key);
|
||||
|
||||
final ConnectionModel model;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).cardColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
padding: const EdgeInsets.all(8),
|
||||
margin: const EdgeInsetsDirectional.only(top: 8),
|
||||
child: Visibility(
|
||||
visible: model.elements != null,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
model.title ?? '',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (model.elements != null)
|
||||
Wrap(
|
||||
spacing: 4,
|
||||
runSpacing: 4,
|
||||
direction: Axis.horizontal,
|
||||
children: model.elements!
|
||||
.map((e) => _ModelElementWidget(model: model, modelElement: e))
|
||||
.toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
replacement: _NoModelElementWidget(model: model),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NoModelElementWidget extends StatelessWidget {
|
||||
const _NoModelElementWidget({required this.model});
|
||||
|
||||
final ConnectionModel model;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text(
|
||||
model.text!,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ModelElementWidget extends StatelessWidget {
|
||||
const _ModelElementWidget({
|
||||
required this.model,
|
||||
required this.modelElement,
|
||||
});
|
||||
|
||||
final ConnectionModel model;
|
||||
final String modelElement;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: model.elementActions != null ? model.elementActions![modelElement] : null,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(
|
||||
modelElement,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
maxLines: 10,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
|
||||
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
|
||||
import '../../../../core/wallet_connect/models/auth_request_model.dart';
|
||||
import '../../../../core/wallet_connect/models/connection_model.dart';
|
||||
import '../../../../core/wallet_connect/models/session_request_model.dart';
|
||||
import '../utils/namespace_model_builder.dart';
|
||||
import 'connection_widget.dart';
|
||||
|
||||
class ConnectionRequestWidget extends StatefulWidget {
|
||||
const ConnectionRequestWidget({
|
||||
required this.wallet,
|
||||
this.authRequest,
|
||||
this.sessionProposal,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
final Web3Wallet wallet;
|
||||
final AuthRequestModel? authRequest;
|
||||
final SessionRequestModel? sessionProposal;
|
||||
|
||||
@override
|
||||
State<ConnectionRequestWidget> createState() => _ConnectionRequestWidgetState();
|
||||
}
|
||||
|
||||
class _ConnectionRequestWidgetState extends State<ConnectionRequestWidget> {
|
||||
ConnectionMetadata? metadata;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Get the connection metadata
|
||||
metadata = widget.authRequest?.request.requester ?? widget.sessionProposal?.request.proposer;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (metadata == null) {
|
||||
return Text(
|
||||
S.current.error,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return _ConnectionMetadataDisplayWidget(
|
||||
metadata: metadata,
|
||||
authRequest: widget.authRequest,
|
||||
sessionProposal: widget.sessionProposal,
|
||||
wallet: widget.wallet,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ConnectionMetadataDisplayWidget extends StatelessWidget {
|
||||
const _ConnectionMetadataDisplayWidget({
|
||||
required this.metadata,
|
||||
required this.wallet,
|
||||
this.authRequest,
|
||||
required this.sessionProposal,
|
||||
});
|
||||
|
||||
final ConnectionMetadata? metadata;
|
||||
final Web3Wallet wallet;
|
||||
final AuthRequestModel? authRequest;
|
||||
final SessionRequestModel? sessionProposal;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Color.fromARGB(255, 18, 18, 19),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
metadata!.metadata.name,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Text(
|
||||
S.current.wouoldLikeToConnect,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
metadata!.metadata.url,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Visibility(
|
||||
visible: authRequest != null,
|
||||
child: _AuthRequestWidget(wallet: wallet, authRequest: authRequest),
|
||||
|
||||
//If authRequest is null, sessionProposal is not null.
|
||||
replacement: _SessionProposalWidget(sessionProposal: sessionProposal!),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AuthRequestWidget extends StatelessWidget {
|
||||
const _AuthRequestWidget({required this.wallet, this.authRequest});
|
||||
|
||||
final Web3Wallet wallet;
|
||||
final AuthRequestModel? authRequest;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final model = ConnectionModel(
|
||||
text: wallet.formatAuthMessage(
|
||||
iss: 'did:pkh:eip155:1:${authRequest!.iss}',
|
||||
cacaoPayload: CacaoRequestPayload.fromPayloadParams(
|
||||
authRequest!.request.payloadParams,
|
||||
),
|
||||
),
|
||||
);
|
||||
return ConnectionWidget(
|
||||
title: S.current.message,
|
||||
info: [model],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SessionProposalWidget extends StatelessWidget {
|
||||
const _SessionProposalWidget({required this.sessionProposal});
|
||||
|
||||
final SessionRequestModel sessionProposal;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Create the connection models using the required and optional namespaces provided by the proposal data
|
||||
// The key is the title and the list of values is the data
|
||||
final List<ConnectionWidget> views = ConnectionWidgetBuilder.buildFromRequiredNamespaces(
|
||||
sessionProposal.request.requiredNamespaces,
|
||||
);
|
||||
|
||||
return Column(children: views);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../core/wallet_connect/models/connection_model.dart';
|
||||
import 'connection_item_widget.dart';
|
||||
|
||||
class ConnectionWidget extends StatelessWidget {
|
||||
const ConnectionWidget({required this.title, required this.info, super.key});
|
||||
|
||||
final String title;
|
||||
final List<ConnectionModel> info;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColorLight,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 8),
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
...info.map((e) => ConnectionItemWidget(model: e)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BottomSheetMessageDisplayWidget extends StatelessWidget {
|
||||
final String message;
|
||||
final bool isError;
|
||||
|
||||
const BottomSheetMessageDisplayWidget({super.key, required this.message, this.isError = true});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
isError ? S.current.error : S.current.successful,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
message,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../../core/wallet_connect/models/bottom_sheet_queue_item_model.dart';
|
||||
|
||||
class BottomSheetListener extends StatefulWidget {
|
||||
final BottomSheetService bottomSheetService;
|
||||
final Widget child;
|
||||
|
||||
const BottomSheetListener({
|
||||
required this.child,
|
||||
required this.bottomSheetService,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
BottomSheetListenerState createState() => BottomSheetListenerState();
|
||||
}
|
||||
|
||||
class BottomSheetListenerState extends State<BottomSheetListener> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.bottomSheetService.currentSheet.addListener(_showBottomSheet);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.bottomSheetService.currentSheet.removeListener(_showBottomSheet);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _showBottomSheet() async {
|
||||
if (widget.bottomSheetService.currentSheet.value != null) {
|
||||
BottomSheetQueueItemModel item = widget.bottomSheetService.currentSheet.value!;
|
||||
final value = await showModalBottomSheet(
|
||||
context: context,
|
||||
isDismissible: item.isModalDismissible,
|
||||
backgroundColor: Color.fromARGB(0, 0, 0, 0),
|
||||
isScrollControlled: true,
|
||||
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.9),
|
||||
builder: (context) {
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Color.fromARGB(255, 18, 18, 19),
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
margin: const EdgeInsets.all(16),
|
||||
child: item.widget,
|
||||
);
|
||||
},
|
||||
);
|
||||
item.completer.complete(value);
|
||||
widget.bottomSheetService.resetCurrentSheet();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => widget.child;
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Web3RequestModal extends StatelessWidget {
|
||||
const Web3RequestModal({required this.child, this.onAccept, this.onReject, super.key});
|
||||
|
||||
final Widget child;
|
||||
final VoidCallback? onAccept;
|
||||
final VoidCallback? onReject;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
child,
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
onPressed: onReject ?? () => Navigator.of(context).pop(false),
|
||||
text: S.current.reject,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
textColor: Theme.of(context).colorScheme.onError,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
onPressed: onAccept ?? () => Navigator.of(context).pop(true),
|
||||
text: S.current.approve,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:walletconnect_flutter_v2/apis/core/pairing/utils/pairing_models.dart';
|
||||
|
||||
class PairingItemWidget extends StatelessWidget {
|
||||
const PairingItemWidget({required this.pairing, required this.onTap, super.key});
|
||||
|
||||
final PairingInfo pairing;
|
||||
final void Function() onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
PairingMetadata? metadata = pairing.peerMetadata;
|
||||
if (metadata == null) {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
|
||||
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(pairing.expiry * 1000);
|
||||
int year = dateTime.year;
|
||||
int month = dateTime.month;
|
||||
int day = dateTime.day;
|
||||
|
||||
String expiryDate =
|
||||
'$year-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}';
|
||||
|
||||
return ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundImage: (metadata.icons.isNotEmpty
|
||||
? NetworkImage(metadata.icons[0])
|
||||
: const AssetImage(
|
||||
'assets/images/default_icon.png',
|
||||
)) as ImageProvider<Object>,
|
||||
),
|
||||
title: Text(
|
||||
metadata.name,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
metadata.url,
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${S.current.expiresOn}: $expiryDate',
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: Container(
|
||||
height: 40,
|
||||
width: 44,
|
||||
padding: EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsBackgroundColor,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.edit,
|
||||
size: 14,
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
|
||||
),
|
||||
),
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:cw_nano/nano_util.dart';
|
||||
import 'package:cake_wallet/nano/nano.dart';
|
||||
|
||||
class PaymentRequest {
|
||||
PaymentRequest(this.address, this.amount, this.note, this.scheme);
|
||||
|
@ -16,10 +16,12 @@ class PaymentRequest {
|
|||
scheme = uri.scheme;
|
||||
}
|
||||
|
||||
if (address.contains("nano")) {
|
||||
amount = NanoUtil.getRawAsUsableString(amount, NanoUtil.rawPerNano);
|
||||
} else if (address.contains("ban")) {
|
||||
amount = NanoUtil.getRawAsUsableString(amount, NanoUtil.rawPerBanano);
|
||||
if (nano != null) {
|
||||
if (address.contains("nano")) {
|
||||
amount = nanoUtil!.getRawAsUsableString(amount, nanoUtil!.rawPerNano);
|
||||
} else if (address.contains("ban")) {
|
||||
amount = nanoUtil!.getRawAsUsableString(amount, nanoUtil!.rawPerBanano);
|
||||
}
|
||||
}
|
||||
|
||||
return PaymentRequest(address, amount, note, scheme);
|
||||
|
|
|
@ -284,7 +284,6 @@ abstract class DashboardViewModelBase with Store {
|
|||
|
||||
Map<String, List<FilterItem>> filterItems;
|
||||
|
||||
|
||||
BuyProviderType get defaultBuyProvider => settingsStore.defaultBuyProvider;
|
||||
|
||||
bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled;
|
||||
|
|
|
@ -93,11 +93,10 @@ class TransactionListItem extends ActionListItem with Keyable {
|
|||
price: price);
|
||||
break;
|
||||
case WalletType.nano:
|
||||
final nanoTransaction = transaction as NanoTransactionInfo;
|
||||
amount = calculateFiatAmountRaw(
|
||||
cryptoAmount:
|
||||
NanoUtil.getRawAsDecimal(nanoTransaction.amountRaw.toString(), NanoUtil.rawPerNano)
|
||||
.toDouble(),
|
||||
cryptoAmount: nanoUtil!
|
||||
.getRawAsDecimal(nano!.getTransactionAmountRaw(transaction).toString(), nanoUtil!.rawPerNano)
|
||||
.toDouble(),
|
||||
price: price);
|
||||
break;
|
||||
case WalletType.ethereum:
|
||||
|
|
|
@ -86,13 +86,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
|||
super(appStore: appStore) {
|
||||
_useTorOnly = _settingsStore.exchangeStatus == ExchangeApiMode.torOnly;
|
||||
_setProviders();
|
||||
const excludeDepositCurrencies = [CryptoCurrency.btt, CryptoCurrency.nano];
|
||||
const excludeDepositCurrencies = [CryptoCurrency.btt];
|
||||
const excludeReceiveCurrencies = [
|
||||
CryptoCurrency.xlm,
|
||||
CryptoCurrency.xrp,
|
||||
CryptoCurrency.bnb,
|
||||
CryptoCurrency.btt,
|
||||
CryptoCurrency.nano
|
||||
CryptoCurrency.btt
|
||||
];
|
||||
_initialPairBasedOnWallet();
|
||||
|
||||
|
|
|
@ -2,12 +2,9 @@ import 'package:cake_wallet/nano/nano.dart';
|
|||
import 'package:cw_core/nano_account.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
// import 'package:cw_nano/nano_account_list.dart';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
|
||||
|
||||
part 'nano_account_edit_or_create_view_model.g.dart';
|
||||
|
||||
|
@ -16,9 +13,7 @@ class NanoAccountEditOrCreateViewModel = NanoAccountEditOrCreateViewModelBase
|
|||
|
||||
abstract class NanoAccountEditOrCreateViewModelBase with Store {
|
||||
NanoAccountEditOrCreateViewModelBase(this._nanoAccountList,
|
||||
/*this._bananoAccountList,*/
|
||||
{required WalletBase wallet,
|
||||
NanoAccount? accountListItem})
|
||||
{required WalletBase wallet, NanoAccount? accountListItem})
|
||||
: state = InitialExecutionState(),
|
||||
isEdit = accountListItem != null,
|
||||
label = accountListItem?.label ?? '',
|
||||
|
@ -34,7 +29,6 @@ abstract class NanoAccountEditOrCreateViewModelBase with Store {
|
|||
String label;
|
||||
|
||||
final NanoAccountList _nanoAccountList;
|
||||
// final BananoAccountList? _bananoAccountList;
|
||||
final NanoAccount? _accountListItem;
|
||||
final WalletBase _wallet;
|
||||
|
||||
|
@ -42,10 +36,6 @@ abstract class NanoAccountEditOrCreateViewModelBase with Store {
|
|||
if (_wallet.type == WalletType.nano) {
|
||||
await saveNano();
|
||||
}
|
||||
|
||||
// if (_wallet.type == WalletType.banano) {
|
||||
// await saveBanano();
|
||||
// }
|
||||
}
|
||||
|
||||
Future<void> saveNano() async {
|
||||
|
@ -53,7 +43,8 @@ abstract class NanoAccountEditOrCreateViewModelBase with Store {
|
|||
state = IsExecutingState();
|
||||
|
||||
if (_accountListItem != null) {
|
||||
await _nanoAccountList.setLabelAccount(_wallet, accountIndex: _accountListItem!.id, label: label);
|
||||
await _nanoAccountList.setLabelAccount(_wallet,
|
||||
accountIndex: _accountListItem!.id, label: label);
|
||||
} else {
|
||||
await _nanoAccountList.addAccount(_wallet, label: label);
|
||||
}
|
||||
|
@ -64,26 +55,4 @@ abstract class NanoAccountEditOrCreateViewModelBase with Store {
|
|||
state = FailureState(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Future<void> saveBanano() async {
|
||||
// if (!(_wallet.type == WalletType.banano)) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// try {
|
||||
// state = IsExecutingState();
|
||||
|
||||
// if (_accountListItem != null) {
|
||||
// await _bananoAccountList!
|
||||
// .setLabelAccount(_wallet, accountIndex: _accountListItem!.id, label: label);
|
||||
// } else {
|
||||
// await _bananoAccountList!.addAccount(_wallet, label: label);
|
||||
// }
|
||||
|
||||
// await _wallet.save();
|
||||
// state = ExecutedSuccessfullyState();
|
||||
// } catch (e) {
|
||||
// state = FailureState(e.toString());
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -36,17 +36,6 @@ abstract class NanoAccountListViewModelBase with Store {
|
|||
.toList();
|
||||
}
|
||||
|
||||
// if (_wallet.type == WalletType.banano) {
|
||||
// return banano
|
||||
// !.getAccountList(_wallet)
|
||||
// .accounts.map((acc) => AccountListItem(
|
||||
// label: acc.label,
|
||||
// id: acc.id,
|
||||
// balance: acc.balance,
|
||||
// isSelected: acc.id == banano!.getCurrentAccount(_wallet).id))
|
||||
// .toList();
|
||||
// }
|
||||
|
||||
throw Exception('Unexpected wallet type: ${_wallet.type}');
|
||||
}
|
||||
|
||||
|
@ -61,13 +50,5 @@ abstract class NanoAccountListViewModelBase with Store {
|
|||
item.balance,
|
||||
);
|
||||
}
|
||||
|
||||
// if (_wallet.type == WalletType.banano) {
|
||||
// banano!.setCurrentAccount(
|
||||
// _wallet,
|
||||
// item.id,
|
||||
// item.label,
|
||||
// );
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
|
||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
|
@ -323,8 +322,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
await pendingTransaction!.commit();
|
||||
|
||||
if (walletType == WalletType.nano) {
|
||||
var wallet = getIt.get<AppStore>().wallet as NanoWallet?;
|
||||
wallet?.updateTransactions();
|
||||
nano!.updateTransactions(wallet);
|
||||
}
|
||||
|
||||
if (pendingTransaction!.id.isNotEmpty) {
|
||||
|
@ -432,7 +430,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
|
||||
String translateErrorMessage(String error, WalletType walletType, CryptoCurrency currency,) {
|
||||
if (walletType == WalletType.ethereum || walletType == WalletType.haven) {
|
||||
if (error.contains('gas required exceeds allowance (0)') || error.contains('insufficient funds for gas')) {
|
||||
if (error.contains('gas required exceeds allowance') || error.contains('insufficient funds for')) {
|
||||
return S.current.do_not_have_enough_gas_asset(currency.toString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ abstract class OtherSettingsViewModelBase with Store {
|
|||
final priority = _settingsStore.priority[_wallet.type];
|
||||
final priorities = priorityForWalletType(_wallet.type);
|
||||
|
||||
if (!priorities.contains(priority)) {
|
||||
if (!priorities.contains(priority) && priorities.isNotEmpty) {
|
||||
_settingsStore.priority[_wallet.type] = priorities.first;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -239,7 +239,7 @@ abstract class TransactionDetailsViewModelBase with Store {
|
|||
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
|
||||
// StandartListItem(title: S.current.confirmed, value: (tx.confirmations > 0).toString()),
|
||||
StandartListItem(title: S.current.confirmations, 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()),
|
||||
];
|
||||
|
|
|
@ -294,9 +294,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
_baseItems = [];
|
||||
|
||||
if (wallet.type == WalletType.monero ||
|
||||
wallet.type == WalletType.haven ||
|
||||
wallet.type == WalletType.haven /*||
|
||||
wallet.type == WalletType.nano ||
|
||||
wallet.type == WalletType.banano) {
|
||||
wallet.type == WalletType.banano*/) {
|
||||
_baseItems.add(WalletAccountListHeader());
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_wallet_service.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_nano/nano_util.dart';
|
||||
import 'package:cw_nano/nano_wallet_service.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
|
||||
|
||||
part 'wallet_restore_choose_derivation_view_model.g.dart';
|
||||
|
|
|
@ -2,10 +2,6 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
|||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/nano/nano.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_wallet_service.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_nano/nano_wallet.dart';
|
||||
import 'package:cw_nano/nano_wallet_service.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
|
@ -165,19 +161,16 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
|||
final seedKey = options['private_key'] as String?;
|
||||
final mnemonic = options['seed'] as String?;
|
||||
WalletType walletType = options['walletType'] as WalletType;
|
||||
AppStore appStore = getIt.get<AppStore>();
|
||||
Node node = appStore.settingsStore.getCurrentNode(walletType);
|
||||
var appStore = getIt.get<AppStore>();
|
||||
var node = appStore.settingsStore.getCurrentNode(walletType);
|
||||
|
||||
switch (type) {
|
||||
case WalletType.bitcoin:
|
||||
return bitcoin!.compareDerivationMethods(mnemonic: mnemonic!, node: node);
|
||||
// case WalletType.litecoin:
|
||||
// return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
|
||||
// name: name, mnemonic: seed, password: password);
|
||||
case WalletType.nano:
|
||||
return await NanoWalletService.compareDerivationMethods(
|
||||
return nanoUtil!.compareDerivationMethods(
|
||||
mnemonic: mnemonic,
|
||||
seedKey: seedKey,
|
||||
privateKey: seedKey,
|
||||
node: node,
|
||||
);
|
||||
default:
|
||||
|
|
|
@ -82,6 +82,12 @@ dependencies:
|
|||
shared_preferences_android: 2.0.17
|
||||
url_launcher_android: 6.0.24
|
||||
sensitive_clipboard: ^1.0.0
|
||||
walletconnect_flutter_v2: ^2.1.4
|
||||
eth_sig_util: ^0.0.9
|
||||
ens_dart:
|
||||
git:
|
||||
url: https://github.com/cake-tech/ens_dart.git
|
||||
ref: main
|
||||
bitcoin_flutter:
|
||||
git:
|
||||
url: https://github.com/cake-tech/bitcoin_flutter.git
|
||||
|
|
|
@ -695,8 +695,27 @@
|
|||
"default_buy_provider": "مزود شراء الافتراضي",
|
||||
"ask_each_time": "اسأل في كل مرة",
|
||||
"buy_provider_unavailable": "مزود حاليا غير متوفر.",
|
||||
|
||||
"signTransaction": " ﺔﻠﻣﺎﻌﻤﻟﺍ ﻊﻴﻗﻮﺗ",
|
||||
"errorGettingCredentials": "ﺩﺎﻤﺘﻋﻻﺍ ﺕﺎﻧﺎﻴﺑ ﻰﻠﻋ ﻝﻮﺼﺤﻟﺍ ءﺎﻨﺛﺃ ﺄﻄﺧ ﺙﺪﺣ :ﻞﺸﻓ",
|
||||
"errorSigningTransaction": "ﺔﻠﻣﺎﻌﻤﻟﺍ ﻊﻴﻗﻮﺗ ءﺎﻨﺛﺃ ﺄﻄﺧ ﺙﺪﺣ",
|
||||
"pairingInvalidEvent": "ﺢﻟﺎﺻ ﺮﻴﻏ ﺙﺪﺣ ﻥﺍﺮﻗﺇ",
|
||||
"chains": "ﻞﺳﻼﺴﻟﺍ",
|
||||
"methods": " ﻕﺮﻃُ",
|
||||
"events": "ﺙﺍﺪﺣﻷﺍ",
|
||||
"reject": "ﺾﻓﺮﻳ",
|
||||
"approve": "ﺪﻤﺘﻌﻳ",
|
||||
"expiresOn": "ﻲﻓ ﻪﺘﻴﺣﻼﺻ ﻲﻬﺘﻨﺗ",
|
||||
"walletConnect": "WalletConnect",
|
||||
"nullURIError": "ﻍﺭﺎﻓ (URI) ﻢﻈﺘﻨﻤﻟﺍ ﺩﺭﺍﻮﻤﻟﺍ ﻑﺮﻌﻣ",
|
||||
"connectWalletPrompt": "ﺕﻼﻣﺎﻌﻤﻟﺍ ءﺍﺮﺟﻹ WalletConnect ﻊﻣ ﻚﺘﻈﻔﺤﻣ ﻞﻴﺻﻮﺘﺑ ﻢﻗ",
|
||||
"newConnection": "ﺪﻳﺪﺟ ﻝﺎﺼﺗﺍ",
|
||||
"activeConnectionsPrompt": "ﺎﻨﻫ ﺔﻄﺸﻨﻟﺍ ﺕﻻﺎﺼﺗﻻﺍ ﺮﻬﻈﺘﺳ",
|
||||
"deleteConnectionConfirmationPrompt": "ـﺑ ﻝﺎﺼﺗﻻﺍ ﻑﺬﺣ ﺪﻳﺮﺗ ﻚﻧﺃ ﺪﻛﺄﺘﻣ ﺖﻧﺃ ﻞﻫ",
|
||||
"event": "ﺙﺪﺣ",
|
||||
"successful": "ﺢﺟﺎﻧ",
|
||||
"wouoldLikeToConnect": "ﻝﺎﺼﺗﻻﺍ ﻲﻓ ﺐﻏﺮﺗ",
|
||||
"message": "ﺔﻟﺎﺳﺭ",
|
||||
"do_not_have_enough_gas_asset": "ليس لديك ما يكفي من ${currency} لإجراء معاملة وفقًا لشروط شبكة blockchain الحالية. أنت بحاجة إلى المزيد من ${currency} لدفع رسوم شبكة blockchain، حتى لو كنت ترسل أصلًا مختلفًا.",
|
||||
"totp_auth_url": " TOTP ﺔﻗﺩﺎﺼﻤﻟ URL ﻥﺍﻮﻨﻋ"
|
||||
"totp_auth_url": "TOTP ﺔﻗﺩﺎﺼﻤﻟ URL ﻥﺍﻮﻨﻋ",
|
||||
"awaitDAppProcessing": ".ﺔﺠﻟﺎﻌﻤﻟﺍ ﻦﻣ dApp ﻲﻬﺘﻨﻳ ﻰﺘﺣ ﺭﺎﻈﺘﻧﻻﺍ ﻰﺟﺮﻳ"
|
||||
}
|
||||
|
||||
|
|
|
@ -691,6 +691,27 @@
|
|||
"default_buy_provider": "Доставчик по подразбиране купува",
|
||||
"ask_each_time": "Питайте всеки път",
|
||||
"buy_provider_unavailable": "Понастоящем доставчик не е наличен.",
|
||||
"signTransaction": "Подпишете транзакция",
|
||||
"errorGettingCredentials": "Неуспешно: Грешка при получаване на идентификационни данни",
|
||||
"errorSigningTransaction": "Възникна грешка при подписване на транзакция",
|
||||
"pairingInvalidEvent": "Невалидно събитие при сдвояване",
|
||||
"chains": "Вериги",
|
||||
"methods": "Методи",
|
||||
"events": "събития",
|
||||
"reject": "Отхвърляне",
|
||||
"approve": "Одобряване",
|
||||
"expiresOn": "Изтича на",
|
||||
"walletConnect": "WalletConnect",
|
||||
"nullURIError": "URI е нула",
|
||||
"connectWalletPrompt": "Свържете портфейла си с WalletConnect, за да извършвате транзакции",
|
||||
"newConnection": "Нова връзка",
|
||||
"activeConnectionsPrompt": "Тук ще се появят активни връзки",
|
||||
"deleteConnectionConfirmationPrompt": "Сигурни ли сте, че искате да изтриете връзката към",
|
||||
"event": "Събитие",
|
||||
"successful": "Успешен",
|
||||
"wouoldLikeToConnect": "иска да се свърже",
|
||||
"message": "Съобщение",
|
||||
"do_not_have_enough_gas_asset": "Нямате достатъчно ${currency}, за да извършите транзакция с текущите условия на блокчейн мрежата. Имате нужда от повече ${currency}, за да платите таксите за блокчейн мрежа, дори ако изпращате различен актив.",
|
||||
"totp_auth_url": "TOTP AUTH URL"
|
||||
"totp_auth_url": "TOTP AUTH URL",
|
||||
"awaitDAppProcessing": "Моля, изчакайте dApp да завърши обработката."
|
||||
}
|
||||
|
|
|
@ -691,6 +691,27 @@
|
|||
"default_buy_provider": "Výchozí poskytovatel nákupu",
|
||||
"ask_each_time": "Zeptejte se pokaždé",
|
||||
"buy_provider_unavailable": "Poskytovatel aktuálně nedostupný.",
|
||||
"signTransaction": "Podepsat transakci",
|
||||
"errorGettingCredentials": "Selhalo: Chyba při získávání přihlašovacích údajů",
|
||||
"errorSigningTransaction": "Při podepisování transakce došlo k chybě",
|
||||
"pairingInvalidEvent": "Neplatná událost párování",
|
||||
"chains": "Řetězy",
|
||||
"methods": "Metody",
|
||||
"events": "Události",
|
||||
"reject": "Odmítnout",
|
||||
"approve": "Schvalovat",
|
||||
"expiresOn": "Vyprší dne",
|
||||
"walletConnect": "WalletConnect",
|
||||
"nullURIError": "URI je nulové",
|
||||
"connectWalletPrompt": "Propojte svou peněženku s WalletConnect a provádějte transakce",
|
||||
"newConnection": "Nové připojení",
|
||||
"activeConnectionsPrompt": "Zde se zobrazí aktivní připojení",
|
||||
"deleteConnectionConfirmationPrompt": "Jste si jisti, že chcete smazat připojení k?",
|
||||
"event": "událost",
|
||||
"successful": "Úspěšný",
|
||||
"wouoldLikeToConnect": "by se chtělo připojit",
|
||||
"message": "Zpráva",
|
||||
"do_not_have_enough_gas_asset": "Nemáte dostatek ${currency} k provedení transakce s aktuálními podmínkami blockchainové sítě. K placení poplatků za blockchainovou síť potřebujete více ${currency}, i když posíláte jiné aktivum.",
|
||||
"totp_auth_url": "URL AUTH TOTP"
|
||||
"totp_auth_url": "URL AUTH TOTP",
|
||||
"awaitDAppProcessing": "Počkejte, až dApp dokončí zpracování."
|
||||
}
|
||||
|
|
|
@ -699,6 +699,27 @@
|
|||
"default_buy_provider": "Standard-Kaufanbieter",
|
||||
"ask_each_time": "Jedes Mal fragen",
|
||||
"buy_provider_unavailable": "Anbieter derzeit nicht verfügbar.",
|
||||
"signTransaction": "Transaktion unterzeichnen",
|
||||
"errorGettingCredentials": "Fehlgeschlagen: Fehler beim Abrufen der Anmeldeinformationen",
|
||||
"errorSigningTransaction": "Beim Signieren der Transaktion ist ein Fehler aufgetreten",
|
||||
"pairingInvalidEvent": "Paarung ungültiges Ereignis",
|
||||
"chains": "Ketten",
|
||||
"methods": "Methoden",
|
||||
"events": "Veranstaltungen",
|
||||
"reject": "Ablehnen",
|
||||
"approve": "Genehmigen",
|
||||
"expiresOn": "Läuft aus am",
|
||||
"walletConnect": "WalletConnect",
|
||||
"nullURIError": "URI ist null",
|
||||
"connectWalletPrompt": "Verbinden Sie Ihr Wallet mit WalletConnect, um Transaktionen durchzuführen",
|
||||
"newConnection": "Neue Verbindung",
|
||||
"activeConnectionsPrompt": "Hier werden aktive Verbindungen angezeigt",
|
||||
"deleteConnectionConfirmationPrompt": "Sind Sie sicher, dass Sie die Verbindung zu löschen möchten?",
|
||||
"event": "Ereignis",
|
||||
"successful": "Erfolgreich",
|
||||
"wouoldLikeToConnect": "möchte mich gerne vernetzen",
|
||||
"message": "Nachricht",
|
||||
"do_not_have_enough_gas_asset": "Sie verfügen nicht über genügend ${currency}, um eine Transaktion unter den aktuellen Bedingungen des Blockchain-Netzwerks durchzuführen. Sie benötigen mehr ${currency}, um die Gebühren für das Blockchain-Netzwerk zu bezahlen, auch wenn Sie einen anderen Vermögenswert senden.",
|
||||
"totp_auth_url": "TOTP-Auth-URL"
|
||||
"totp_auth_url": "TOTP-Auth-URL",
|
||||
"awaitDAppProcessing": "Bitte warten Sie, bis die dApp die Verarbeitung abgeschlossen hat."
|
||||
}
|
||||
|
|
|
@ -700,6 +700,27 @@
|
|||
"ask_each_time": "Ask each time",
|
||||
"robinhood_option_description": "Buy and transfer instantly using your debit card, bank account, or Robinhood balance. USA only.",
|
||||
"buy_provider_unavailable": "Provider currently unavailable.",
|
||||
"signTransaction": "Sign Transaction",
|
||||
"errorGettingCredentials": "Failed: Error while getting credentials",
|
||||
"errorSigningTransaction": "An error has occured while signing transaction",
|
||||
"pairingInvalidEvent": "Pairing Invalid Event",
|
||||
"chains": "Chains",
|
||||
"methods": "Methods",
|
||||
"events": "Events",
|
||||
"reject": "Reject",
|
||||
"approve": "Approve",
|
||||
"expiresOn": "Expires on",
|
||||
"walletConnect": "WalletConnect",
|
||||
"nullURIError": "URI is null",
|
||||
"connectWalletPrompt": "Connect your wallet with WalletConnect to make transactions",
|
||||
"newConnection": "New Connection",
|
||||
"activeConnectionsPrompt": "Active connections will appear here",
|
||||
"deleteConnectionConfirmationPrompt": "Are you sure that you want to delete the connection to",
|
||||
"event": "Event",
|
||||
"successful": "Successful",
|
||||
"wouoldLikeToConnect": "would like to connect",
|
||||
"message": "Message",
|
||||
"do_not_have_enough_gas_asset": "You do not have enough ${currency} to make a transaction with the current blockchain network conditions. You need more ${currency} to pay blockchain network fees, even if you are sending a different asset.",
|
||||
"totp_auth_url": "TOTP AUTH URL"
|
||||
"totp_auth_url": "TOTP AUTH URL",
|
||||
"awaitDAppProcessing": "Kindly wait for the dApp to finish processing."
|
||||
}
|
||||
|
|
|
@ -699,6 +699,27 @@
|
|||
"default_buy_provider": "Proveedor de compra predeterminado",
|
||||
"ask_each_time": "Pregunta cada vez",
|
||||
"buy_provider_unavailable": "Proveedor actualmente no disponible.",
|
||||
"signTransaction": "Firmar transacción",
|
||||
"errorGettingCredentials": "Error: error al obtener las credenciales",
|
||||
"errorSigningTransaction": "Se ha producido un error al firmar la transacción.",
|
||||
"pairingInvalidEvent": "Evento de emparejamiento no válido",
|
||||
"chains": "Cadenas",
|
||||
"methods": "Métodos",
|
||||
"events": "Eventos",
|
||||
"reject": "Rechazar",
|
||||
"approve": "Aprobar",
|
||||
"expiresOn": "Expira el",
|
||||
"walletConnect": "MonederoConectar",
|
||||
"nullURIError": "URI es nula",
|
||||
"connectWalletPrompt": "Conecte su billetera con WalletConnect para realizar transacciones",
|
||||
"newConnection": "Nueva conexión",
|
||||
"activeConnectionsPrompt": "Las conexiones activas aparecerán aquí",
|
||||
"deleteConnectionConfirmationPrompt": "¿Está seguro de que desea eliminar la conexión a",
|
||||
"event": "Evento",
|
||||
"successful": "Exitoso",
|
||||
"wouoldLikeToConnect": "quisiera conectar",
|
||||
"message": "Mensaje",
|
||||
"do_not_have_enough_gas_asset": "No tienes suficiente ${currency} para realizar una transacción con las condiciones actuales de la red blockchain. Necesita más ${currency} para pagar las tarifas de la red blockchain, incluso si envía un activo diferente.",
|
||||
"totp_auth_url": "URL de autenticación TOTP"
|
||||
"totp_auth_url": "URL de autenticación TOTP",
|
||||
"awaitDAppProcessing": "Espere a que la dApp termine de procesarse."
|
||||
}
|
||||
|
|
|
@ -699,6 +699,27 @@
|
|||
"default_buy_provider": "Fournisseur d'achat par défaut",
|
||||
"ask_each_time": "Demandez à chaque fois",
|
||||
"buy_provider_unavailable": "Fournisseur actuellement indisponible.",
|
||||
"signTransaction": "Signer une transaction",
|
||||
"errorGettingCredentials": "Échec : erreur lors de l'obtention des informations d'identification",
|
||||
"errorSigningTransaction": "Une erreur s'est produite lors de la signature de la transaction",
|
||||
"pairingInvalidEvent": "Événement de couplage non valide",
|
||||
"chains": "Chaînes",
|
||||
"methods": "Méthodes",
|
||||
"events": "Événements",
|
||||
"reject": "Rejeter",
|
||||
"approve": "Approuver",
|
||||
"expiresOn": "Expire le",
|
||||
"walletConnect": "PortefeuilleConnect",
|
||||
"nullURIError": "L'URI est nul",
|
||||
"connectWalletPrompt": "Connectez votre portefeuille avec WalletConnect pour effectuer des transactions",
|
||||
"newConnection": "Nouvelle connexion",
|
||||
"activeConnectionsPrompt": "Les connexions actives apparaîtront ici",
|
||||
"deleteConnectionConfirmationPrompt": "Êtes-vous sûr de vouloir supprimer la connexion à",
|
||||
"event": "Événement",
|
||||
"successful": "Réussi",
|
||||
"wouoldLikeToConnect": "je voudrais me connecter",
|
||||
"message": "Message",
|
||||
"do_not_have_enough_gas_asset": "Vous n'avez pas assez de ${currency} pour effectuer une transaction avec les conditions actuelles du réseau blockchain. Vous avez besoin de plus de ${currency} pour payer les frais du réseau blockchain, même si vous envoyez un actif différent.",
|
||||
"totp_auth_url": "URL D'AUTORISATION TOTP"
|
||||
"totp_auth_url": "URL D'AUTORISATION TOTP",
|
||||
"awaitDAppProcessing": "Veuillez attendre que le dApp termine le traitement."
|
||||
}
|
||||
|
|
|
@ -677,6 +677,27 @@
|
|||
"default_buy_provider": "Tsohuwar Siyarwa",
|
||||
"ask_each_time": "Tambaya kowane lokaci",
|
||||
"buy_provider_unavailable": "Mai ba da kyauta a halin yanzu babu.",
|
||||
"signTransaction": "Sa hannu Ma'amala",
|
||||
"errorGettingCredentials": "Ba a yi nasara ba: Kuskure yayin samun takaddun shaida",
|
||||
"errorSigningTransaction": "An sami kuskure yayin sanya hannu kan ciniki",
|
||||
"pairingInvalidEvent": "Haɗa Lamarin mara inganci",
|
||||
"chains": "Sarkoki",
|
||||
"methods": "Hanyoyin",
|
||||
"events": "Abubuwan da suka faru",
|
||||
"reject": "Ƙi",
|
||||
"approve": "Amincewa",
|
||||
"expiresOn": "Yana ƙarewa",
|
||||
"walletConnect": "WalletConnect",
|
||||
"nullURIError": "URI banza ne",
|
||||
"connectWalletPrompt": "Haɗa walat ɗin ku tare da WalletConnect don yin ma'amala",
|
||||
"newConnection": "Sabuwar Haɗi",
|
||||
"activeConnectionsPrompt": "Haɗin kai mai aiki zai bayyana a nan",
|
||||
"deleteConnectionConfirmationPrompt": "Shin kun tabbata cewa kuna son share haɗin zuwa",
|
||||
"event": "Lamarin",
|
||||
"successful": "Nasara",
|
||||
"wouoldLikeToConnect": "ina son haɗi",
|
||||
"message": "Sako",
|
||||
"do_not_have_enough_gas_asset": "Ba ku da isassun ${currency} don yin ma'amala tare da yanayin cibiyar sadarwar blockchain na yanzu. Kuna buƙatar ƙarin ${currency} don biyan kuɗaɗen cibiyar sadarwar blockchain, koda kuwa kuna aika wata kadara daban.",
|
||||
"totp_auth_url": "TOTP AUTH URL"
|
||||
"totp_auth_url": "TOTP AUTH URL",
|
||||
"awaitDAppProcessing": "Da fatan za a jira dApp ya gama aiki."
|
||||
}
|
||||
|
|
|
@ -699,6 +699,27 @@
|
|||
"default_buy_provider": "डिफ़ॉल्ट खरीद प्रदाता",
|
||||
"ask_each_time": "हर बार पूछें",
|
||||
"buy_provider_unavailable": "वर्तमान में प्रदाता अनुपलब्ध है।",
|
||||
"signTransaction": "लेन-देन पर हस्ताक्षर करें",
|
||||
"errorGettingCredentials": "विफल: क्रेडेंशियल प्राप्त करते समय त्रुटि",
|
||||
"errorSigningTransaction": "लेन-देन पर हस्ताक्षर करते समय एक त्रुटि उत्पन्न हुई है",
|
||||
"pairingInvalidEvent": "अमान्य ईवेंट युग्मित करना",
|
||||
"chains": "चेन",
|
||||
"methods": "तरीकों",
|
||||
"events": "आयोजन",
|
||||
"reject": "अस्वीकार करना",
|
||||
"approve": "मंज़ूरी देना",
|
||||
"expiresOn": "पर समय सीमा समाप्त",
|
||||
"walletConnect": "वॉलेटकनेक्ट",
|
||||
"nullURIError": "यूआरआई शून्य है",
|
||||
"connectWalletPrompt": "लेन-देन करने के लिए अपने वॉलेट को वॉलेटकनेक्ट से कनेक्ट करें",
|
||||
"newConnection": "नया कनेक्शन",
|
||||
"activeConnectionsPrompt": "सक्रिय कनेक्शन यहां दिखाई देंगे",
|
||||
"deleteConnectionConfirmationPrompt": "क्या आप वाकई कनेक्शन हटाना चाहते हैं?",
|
||||
"event": "आयोजन",
|
||||
"successful": "सफल",
|
||||
"wouoldLikeToConnect": "जुड़ना चाहेंगे",
|
||||
"message": "संदेश",
|
||||
"do_not_have_enough_gas_asset": "वर्तमान ब्लॉकचेन नेटवर्क स्थितियों में लेनदेन करने के लिए आपके पास पर्याप्त ${currency} नहीं है। ब्लॉकचेन नेटवर्क शुल्क का भुगतान करने के लिए आपको अधिक ${currency} की आवश्यकता है, भले ही आप एक अलग संपत्ति भेज रहे हों।",
|
||||
"totp_auth_url": "TOTP प्रामाणिक यूआरएल"
|
||||
"totp_auth_url": "TOTP प्रामाणिक यूआरएल",
|
||||
"awaitDAppProcessing": "कृपया डीएपी की प्रोसेसिंग पूरी होने तक प्रतीक्षा करें।"
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue