mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 11:39:22 +00:00
CW-525-Add-Tron-Wallet (#1327)
* chore: Initial setup for Tron Wallet * feat: Create Tron Wallet base flow implemented, keys, address, receive, restore and proxy classes all setup * feat: Display seed and key within the app * feat: Activate restore from key and seed for Tron wallet * feat: Add icon for tron wallet in wallet listing page * feat: Activate display of receive address for tron * feat: Fetch and display tron balance, sending transaction flow setup, fee limit calculation setup * feat: Implement sending of native tron, setup sending of trc20 tokens * chore: Rename function * Delete lib/tron/tron.dart * feat: Activate exchange for tron and its tokens, implement balance display for trc20 tokens and setup secrets configuration for tron * feat: Implement tron token management, add, remove, delete, and get tokens in home settings view, also minor cleanup * feat: Activate buy and sell for tron * feat: Implement restore from QR, transactions history listing for both native transactions and trc20 transactions * feat: Activate send all and do some minor cleanups * chore: Fix some lint infos and warnings * chore: Adjust configurations * ci: Modify CI to create and add secrets for node * fix: Fixes made while self reviewing the PR for this feature * feat: Add guide for adding new wallet types, and add fixes to requested changes * fix: Handle exceptions gracefully * fix: Alternative for trc20 estimated fee * fix: Fixes to display of amount and fee, removing clashes * fix: Fee calculation WIP * fix: Fix issue with handling of send all flow and display of amount and fee values before broadcasting transaction * fix: PR review fixes and fix merge conflicts * fix: Modify fetching assetOfTransaction [skip ci] * fix: Move tron settings migration to 33
This commit is contained in:
parent
e4fd534949
commit
d1870ba8b8
82 changed files with 3660 additions and 62 deletions
2
.github/workflows/pr_test_build.yml
vendored
2
.github/workflows/pr_test_build.yml
vendored
|
@ -113,6 +113,7 @@ jobs:
|
|||
touch lib/.secrets.g.dart
|
||||
touch cw_evm/lib/.secrets.g.dart
|
||||
touch cw_solana/lib/.secrets.g.dart
|
||||
touch cw_tron/lib/.secrets.g.dart
|
||||
echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart
|
||||
echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart
|
||||
echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart
|
||||
|
@ -150,6 +151,7 @@ jobs:
|
|||
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
|
||||
echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
|
||||
echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
|
||||
|
||||
- name: Rename app
|
||||
run: |
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -94,9 +94,11 @@ android/app/key.jks
|
|||
**/tool/.evm-secrets-config.json
|
||||
**/tool/.ethereum-secrets-config.json
|
||||
**/tool/.solana-secrets-config.json
|
||||
**/tool/.tron-secrets-config.json
|
||||
**/lib/.secrets.g.dart
|
||||
**/cw_evm/lib/.secrets.g.dart
|
||||
**/cw_solana/lib/.secrets.g.dart
|
||||
**/cw_tron/lib/.secrets.g.dart
|
||||
|
||||
vendor/
|
||||
|
||||
|
@ -132,6 +134,7 @@ lib/bitcoin_cash/bitcoin_cash.dart
|
|||
lib/nano/nano.dart
|
||||
lib/polygon/polygon.dart
|
||||
lib/solana/solana.dart
|
||||
lib/tron/tron.dart
|
||||
|
||||
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
|
||||
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png
|
||||
|
|
|
@ -67,6 +67,9 @@
|
|||
<data android:scheme="polygon-wallet" />
|
||||
<data android:scheme="polygon_wallet" />
|
||||
<data android:scheme="solana-wallet" />
|
||||
<data android:scheme="tron" />
|
||||
<data android:scheme="tron-wallet" />
|
||||
<data android:scheme="tron_wallet" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<meta-data
|
||||
|
|
4
assets/tron_node_list.yml
Normal file
4
assets/tron_node_list.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
-
|
||||
uri: api.trongrid.io
|
||||
is_default: true
|
||||
useSSL: true
|
|
@ -103,6 +103,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
|||
CryptoCurrency.kaspa,
|
||||
CryptoCurrency.digibyte,
|
||||
CryptoCurrency.usdtSol,
|
||||
CryptoCurrency.usdcTrc20,
|
||||
];
|
||||
|
||||
static const havenCurrencies = [
|
||||
|
@ -217,6 +218,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
|||
static const kaspa = CryptoCurrency(title: 'KAS', fullName: 'Kaspa', raw: 89, name: 'kas', iconPath: 'assets/images/kaspa_icon.png', decimals: 8);
|
||||
static const digibyte = CryptoCurrency(title: 'DGB', fullName: 'DigiByte', raw: 90, name: 'dgb', iconPath: 'assets/images/digibyte.png', decimals: 8);
|
||||
static const usdtSol = CryptoCurrency(title: 'USDT', tag: 'SOL', fullName: 'USDT Tether', raw: 91, name: 'usdtsol', iconPath: 'assets/images/usdt_icon.png', decimals: 6);
|
||||
static const usdcTrc20 = CryptoCurrency(title: 'USDC', tag: 'TRX', fullName: 'USDC Coin', raw: 92, name: 'usdctrc20', iconPath: 'assets/images/usdc_icon.png', decimals: 6);
|
||||
|
||||
|
||||
static final Map<int, CryptoCurrency> _rawCurrencyMap =
|
||||
|
|
|
@ -23,7 +23,10 @@ CryptoCurrency currencyForWalletType(WalletType type) {
|
|||
return CryptoCurrency.maticpoly;
|
||||
case WalletType.solana:
|
||||
return CryptoCurrency.sol;
|
||||
case WalletType.tron:
|
||||
return CryptoCurrency.trx;
|
||||
default:
|
||||
throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
|
||||
throw Exception(
|
||||
'Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,3 +16,4 @@ const POW_NODE_TYPE_ID = 14;
|
|||
const DERIVATION_TYPE_TYPE_ID = 15;
|
||||
const SPL_TOKEN_TYPE_ID = 16;
|
||||
const DERIVATION_INFO_TYPE_ID = 17;
|
||||
const TRON_TOKEN_TYPE_ID = 18;
|
||||
|
|
|
@ -94,6 +94,7 @@ class Node extends HiveObject with Keyable {
|
|||
case WalletType.ethereum:
|
||||
case WalletType.polygon:
|
||||
case WalletType.solana:
|
||||
case WalletType.tron:
|
||||
return Uri.https(uriRaw, path ?? '');
|
||||
default:
|
||||
throw Exception('Unexpected type ${type.toString()} for Node uri');
|
||||
|
@ -152,6 +153,7 @@ class Node extends HiveObject with Keyable {
|
|||
case WalletType.ethereum:
|
||||
case WalletType.polygon:
|
||||
case WalletType.solana:
|
||||
case WalletType.tron:
|
||||
return requestElectrumServer();
|
||||
default:
|
||||
return false;
|
||||
|
|
|
@ -15,6 +15,7 @@ const walletTypes = [
|
|||
WalletType.banano,
|
||||
WalletType.polygon,
|
||||
WalletType.solana,
|
||||
WalletType.tron,
|
||||
];
|
||||
|
||||
@HiveType(typeId: WALLET_TYPE_TYPE_ID)
|
||||
|
@ -50,7 +51,10 @@ enum WalletType {
|
|||
polygon,
|
||||
|
||||
@HiveField(10)
|
||||
solana
|
||||
solana,
|
||||
|
||||
@HiveField(11)
|
||||
tron
|
||||
}
|
||||
|
||||
int serializeToInt(WalletType type) {
|
||||
|
@ -75,6 +79,8 @@ int serializeToInt(WalletType type) {
|
|||
return 8;
|
||||
case WalletType.solana:
|
||||
return 9;
|
||||
case WalletType.tron:
|
||||
return 10;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
@ -102,6 +108,8 @@ WalletType deserializeFromInt(int raw) {
|
|||
return WalletType.polygon;
|
||||
case 9:
|
||||
return WalletType.solana;
|
||||
case 10:
|
||||
return WalletType.tron;
|
||||
default:
|
||||
throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
|
||||
}
|
||||
|
@ -129,6 +137,8 @@ String walletTypeToString(WalletType type) {
|
|||
return 'Polygon';
|
||||
case WalletType.solana:
|
||||
return 'Solana';
|
||||
case WalletType.tron:
|
||||
return 'Tron';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
@ -156,6 +166,8 @@ String walletTypeToDisplayName(WalletType type) {
|
|||
return 'Polygon (MATIC)';
|
||||
case WalletType.solana:
|
||||
return 'Solana (SOL)';
|
||||
case WalletType.tron:
|
||||
return 'Tron (TRX)';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
@ -183,6 +195,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
|
|||
return CryptoCurrency.maticpoly;
|
||||
case WalletType.solana:
|
||||
return CryptoCurrency.sol;
|
||||
case WalletType.tron:
|
||||
return CryptoCurrency.trx;
|
||||
default:
|
||||
throw Exception(
|
||||
'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
|
||||
|
|
30
cw_tron/.gitignore
vendored
Normal file
30
cw_tron/.gitignore
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
10
cw_tron/.metadata
Normal file
10
cw_tron/.metadata
Normal file
|
@ -0,0 +1,10 @@
|
|||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
channel: stable
|
||||
|
||||
project_type: package
|
3
cw_tron/CHANGELOG.md
Normal file
3
cw_tron/CHANGELOG.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## 0.0.1
|
||||
|
||||
* TODO: Describe initial release.
|
1
cw_tron/LICENSE
Normal file
1
cw_tron/LICENSE
Normal file
|
@ -0,0 +1 @@
|
|||
TODO: Add your license here.
|
39
cw_tron/README.md
Normal file
39
cw_tron/README.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
<!--
|
||||
This README describes the package. If you publish this package to pub.dev,
|
||||
this README's contents appear on the landing page for your package.
|
||||
|
||||
For information about how to write a good package README, see the guide for
|
||||
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
|
||||
|
||||
For general information about developing packages, see the Dart guide for
|
||||
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
|
||||
and the Flutter guide for
|
||||
[developing packages and plugins](https://flutter.dev/developing-packages).
|
||||
-->
|
||||
|
||||
TODO: Put a short description of the package here that helps potential users
|
||||
know whether this package might be useful for them.
|
||||
|
||||
## Features
|
||||
|
||||
TODO: List what your package can do. Maybe include images, gifs, or videos.
|
||||
|
||||
## Getting started
|
||||
|
||||
TODO: List prerequisites and provide or point to information on how to
|
||||
start using the package.
|
||||
|
||||
## Usage
|
||||
|
||||
TODO: Include short and useful examples for package users. Add longer examples
|
||||
to `/example` folder.
|
||||
|
||||
```dart
|
||||
const like = 'sample';
|
||||
```
|
||||
|
||||
## Additional information
|
||||
|
||||
TODO: Tell users more about the package: where to find more information, how to
|
||||
contribute to the package, how to file issues, what response they can expect
|
||||
from the package authors, and more.
|
4
cw_tron/analysis_options.yaml
Normal file
4
cw_tron/analysis_options.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
7
cw_tron/lib/cw_tron.dart
Normal file
7
cw_tron/lib/cw_tron.dart
Normal file
|
@ -0,0 +1,7 @@
|
|||
library cw_tron;
|
||||
|
||||
/// A Calculator.
|
||||
class Calculator {
|
||||
/// Returns [value] plus 1.
|
||||
int addOne(int value) => value + 1;
|
||||
}
|
103
cw_tron/lib/default_tron_tokens.dart
Normal file
103
cw_tron/lib/default_tron_tokens.dart
Normal file
|
@ -0,0 +1,103 @@
|
|||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_tron/tron_token.dart';
|
||||
|
||||
class DefaultTronTokens {
|
||||
final List<TronToken> _defaultTokens = [
|
||||
TronToken(
|
||||
name: "Tether USD",
|
||||
symbol: "USDT",
|
||||
contractAddress: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
|
||||
decimal: 6,
|
||||
enabled: true,
|
||||
),
|
||||
TronToken(
|
||||
name: "USD Coin",
|
||||
symbol: "USDC",
|
||||
contractAddress: "TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8",
|
||||
decimal: 6,
|
||||
enabled: true,
|
||||
),
|
||||
TronToken(
|
||||
name: "Bitcoin",
|
||||
symbol: "BTC",
|
||||
contractAddress: "TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9",
|
||||
decimal: 8,
|
||||
enabled: true,
|
||||
),
|
||||
TronToken(
|
||||
name: "Ethereum",
|
||||
symbol: "ETH",
|
||||
contractAddress: "TRFe3hT5oYhjSZ6f3ji5FJ7YCfrkWnHRvh",
|
||||
decimal: 18,
|
||||
enabled: true,
|
||||
),
|
||||
TronToken(
|
||||
name: "Wrapped BTC",
|
||||
symbol: "WBTC",
|
||||
contractAddress: "TXpw8XeWYeTUd4quDskoUqeQPowRh4jY65",
|
||||
decimal: 8,
|
||||
enabled: true,
|
||||
),
|
||||
TronToken(
|
||||
name: "Dogecoin",
|
||||
symbol: "DOGE",
|
||||
contractAddress: "THbVQp8kMjStKNnf2iCY6NEzThKMK5aBHg",
|
||||
decimal: 8,
|
||||
enabled: true,
|
||||
),
|
||||
TronToken(
|
||||
name: "JUST Stablecoin",
|
||||
symbol: "USDJ",
|
||||
contractAddress: "TMwFHYXLJaRUPeW6421aqXL4ZEzPRFGkGT",
|
||||
decimal: 18,
|
||||
enabled: false,
|
||||
),
|
||||
TronToken(
|
||||
name: "SUN",
|
||||
symbol: "SUN",
|
||||
contractAddress: "TSSMHYeV2uE9qYH95DqyoCuNCzEL1NvU3S",
|
||||
decimal: 18,
|
||||
enabled: false,
|
||||
),
|
||||
TronToken(
|
||||
name: "Wrapped TRX",
|
||||
symbol: "WTRX",
|
||||
contractAddress: "TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR",
|
||||
decimal: 6,
|
||||
enabled: false,
|
||||
),
|
||||
TronToken(
|
||||
name: "BitTorent",
|
||||
symbol: "BTT",
|
||||
contractAddress: "TAFjULxiVgT4qWk6UZwjqwZXTSaGaqnVp4",
|
||||
decimal: 18,
|
||||
enabled: false,
|
||||
),
|
||||
TronToken(
|
||||
name: "BUSD Token",
|
||||
symbol: "BUSD",
|
||||
contractAddress: "TMz2SWatiAtZVVcH2ebpsbVtYwUPT9EdjH",
|
||||
decimal: 18,
|
||||
enabled: false,
|
||||
),
|
||||
TronToken(
|
||||
name: "HTX",
|
||||
symbol: "HTX",
|
||||
contractAddress: "TUPM7K8REVzD2UdV4R5fe5M8XbnR2DdoJ6",
|
||||
decimal: 18,
|
||||
enabled: false,
|
||||
),
|
||||
];
|
||||
|
||||
List<TronToken> get initialTronTokens => _defaultTokens.map((token) {
|
||||
String? iconPath;
|
||||
try {
|
||||
iconPath = CryptoCurrency.all
|
||||
.firstWhere((element) =>
|
||||
element.title.toUpperCase() == token.symbol.split(".").first.toUpperCase())
|
||||
.iconPath;
|
||||
} catch (_) {}
|
||||
|
||||
return TronToken.copyWith(token, iconPath, 'TRX');
|
||||
}).toList();
|
||||
}
|
39
cw_tron/lib/file.dart
Normal file
39
cw_tron/lib/file.dart
Normal file
|
@ -0,0 +1,39 @@
|
|||
import 'dart:io';
|
||||
import 'package:cw_core/key.dart';
|
||||
import 'package:encrypt/encrypt.dart' as encrypt;
|
||||
|
||||
Future<void> write(
|
||||
{required String path,
|
||||
required String password,
|
||||
required String data}) async {
|
||||
final keys = extractKeys(password);
|
||||
final key = encrypt.Key.fromBase64(keys.first);
|
||||
final iv = encrypt.IV.fromBase64(keys.last);
|
||||
final encrypted = await encode(key: key, iv: iv, data: data);
|
||||
final f = File(path);
|
||||
f.writeAsStringSync(encrypted);
|
||||
}
|
||||
|
||||
Future<void> writeData(
|
||||
{required String path,
|
||||
required String password,
|
||||
required String data}) async {
|
||||
final keys = extractKeys(password);
|
||||
final key = encrypt.Key.fromBase64(keys.first);
|
||||
final iv = encrypt.IV.fromBase64(keys.last);
|
||||
final encrypted = await encode(key: key, iv: iv, data: data);
|
||||
final f = File(path);
|
||||
f.writeAsStringSync(encrypted);
|
||||
}
|
||||
|
||||
Future<String> read({required String path, required String password}) async {
|
||||
final file = File(path);
|
||||
|
||||
if (!file.existsSync()) {
|
||||
file.createSync();
|
||||
}
|
||||
|
||||
final encrypted = file.readAsStringSync();
|
||||
|
||||
return decode(password: password, data: encrypted);
|
||||
}
|
33
cw_tron/lib/pending_tron_transaction.dart
Normal file
33
cw_tron/lib/pending_tron_transaction.dart
Normal file
|
@ -0,0 +1,33 @@
|
|||
|
||||
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:web3dart/crypto.dart';
|
||||
|
||||
class PendingTronTransaction with PendingTransaction {
|
||||
final Function sendTransaction;
|
||||
final List<int> signedTransaction;
|
||||
final String fee;
|
||||
final String amount;
|
||||
|
||||
PendingTronTransaction({
|
||||
required this.sendTransaction,
|
||||
required this.signedTransaction,
|
||||
required this.fee,
|
||||
required this.amount,
|
||||
});
|
||||
|
||||
@override
|
||||
String get amountFormatted => amount;
|
||||
|
||||
@override
|
||||
Future<void> commit() async => await sendTransaction();
|
||||
|
||||
@override
|
||||
String get feeFormatted => fee;
|
||||
|
||||
@override
|
||||
String get hex => bytesToHex(signedTransaction);
|
||||
|
||||
@override
|
||||
String get id => '';
|
||||
}
|
436
cw_tron/lib/tron_abi.dart
Normal file
436
cw_tron/lib/tron_abi.dart
Normal file
|
@ -0,0 +1,436 @@
|
|||
final trc20Abi = [
|
||||
{"inputs": [], "stateMutability": "nonpayable", "type": "constructor"},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": true, "internalType": "address", "name": "owner", "type": "address"},
|
||||
{"indexed": true, "internalType": "address", "name": "spender", "type": "address"},
|
||||
{"indexed": false, "internalType": "uint256", "name": "value", "type": "uint256"}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": false, "internalType": "uint256", "name": "total", "type": "uint256"},
|
||||
{"indexed": true, "internalType": "uint16", "name": "order_id", "type": "uint16"},
|
||||
{"indexed": true, "internalType": "address", "name": "buyer", "type": "address"},
|
||||
{"indexed": true, "internalType": "address", "name": "seller", "type": "address"},
|
||||
{"indexed": false, "internalType": "address", "name": "contract_address", "type": "address"}
|
||||
],
|
||||
"name": "OrderPaid",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": true, "internalType": "address", "name": "previousOwner", "type": "address"},
|
||||
{"indexed": true, "internalType": "address", "name": "newOwner", "type": "address"}
|
||||
],
|
||||
"name": "OwnershipTransferred",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": false, "internalType": "address", "name": "token", "type": "address"},
|
||||
{"indexed": false, "internalType": "bool", "name": "active", "type": "bool"}
|
||||
],
|
||||
"name": "TokenUpdate",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": true, "internalType": "address", "name": "from", "type": "address"},
|
||||
{"indexed": true, "internalType": "address", "name": "to", "type": "address"},
|
||||
{"indexed": false, "internalType": "uint256", "name": "value", "type": "uint256"}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": false, "internalType": "string", "name": "username", "type": "string"},
|
||||
{"indexed": true, "internalType": "address", "name": "seller", "type": "address"}
|
||||
],
|
||||
"name": "UserRegistred",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": true, "internalType": "uint16", "name": "order_id", "type": "uint16"},
|
||||
{"indexed": true, "internalType": "address", "name": "buyer", "type": "address"},
|
||||
{"indexed": false, "internalType": "address", "name": "seller", "type": "address"}
|
||||
],
|
||||
"name": "WBuyer",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": true, "internalType": "uint16", "name": "order_id", "type": "uint16"},
|
||||
{"indexed": true, "internalType": "address", "name": "seller", "type": "address"},
|
||||
{"indexed": false, "internalType": "address", "name": "buyer", "type": "address"}
|
||||
],
|
||||
"name": "WSeller",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "CONTRACTPERCENTAGE",
|
||||
"outputs": [
|
||||
{"internalType": "uint8", "name": "", "type": "uint8"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "uint16", "name": "order_id", "type": "uint16"},
|
||||
{"internalType": "uint256", "name": "order_total", "type": "uint256"},
|
||||
{"internalType": "address", "name": "contractAddress", "type": "address"},
|
||||
{"internalType": "address", "name": "seller", "type": "address"}
|
||||
],
|
||||
"name": "PayWithTokens",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "TOKENINCREAMENT",
|
||||
"outputs": [
|
||||
{"internalType": "uint16", "name": "", "type": "uint16"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "", "type": "address"}
|
||||
],
|
||||
"name": "_signer",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "", "type": "bool"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "", "type": "address"}
|
||||
],
|
||||
"name": "_tokens",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "active", "type": "bool"},
|
||||
{"internalType": "uint16", "name": "token", "type": "uint16"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "", "type": "address"}
|
||||
],
|
||||
"name": "_users",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "active", "type": "bool"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "owner", "type": "address"},
|
||||
{"internalType": "address", "name": "spender", "type": "address"}
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{"internalType": "uint256", "name": "", "type": "uint256"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "spender", "type": "address"},
|
||||
{"internalType": "uint256", "name": "amount", "type": "uint256"}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "", "type": "bool"}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "account", "type": "address"}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{"internalType": "uint256", "name": "", "type": "uint256"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "token", "type": "address"}
|
||||
],
|
||||
"name": "balanceOfContract",
|
||||
"outputs": [
|
||||
{"internalType": "uint256", "name": "", "type": "uint256"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "uint256", "name": "amount", "type": "uint256"}
|
||||
],
|
||||
"name": "burn",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "account", "type": "address"},
|
||||
{"internalType": "uint256", "name": "amount", "type": "uint256"}
|
||||
],
|
||||
"name": "burnFrom",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "uint256", "name": "value", "type": "uint256"},
|
||||
{"internalType": "address", "name": "_contractAddress", "type": "address"}
|
||||
],
|
||||
"name": "contractWithdraw",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [
|
||||
{"internalType": "uint8", "name": "", "type": "uint8"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "spender", "type": "address"},
|
||||
{"internalType": "uint256", "name": "subtractedValue", "type": "uint256"}
|
||||
],
|
||||
"name": "decreaseAllowance",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "", "type": "bool"}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "spender", "type": "address"},
|
||||
{"internalType": "uint256", "name": "addedValue", "type": "uint256"}
|
||||
],
|
||||
"name": "increaseAllowance",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "", "type": "bool"}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "to", "type": "address"},
|
||||
{"internalType": "uint256", "name": "amount", "type": "uint256"}
|
||||
],
|
||||
"name": "mint",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{"internalType": "string", "name": "", "type": "string"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "owner",
|
||||
"outputs": [
|
||||
{"internalType": "address", "name": "", "type": "address"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "token", "type": "address"},
|
||||
{"internalType": "uint256", "name": "value", "type": "uint256"}
|
||||
],
|
||||
"name": "payToContract",
|
||||
"outputs": [],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "uint16", "name": "order_id", "type": "uint16"},
|
||||
{"internalType": "address", "name": "seller", "type": "address"}
|
||||
],
|
||||
"name": "payWithNativeToken",
|
||||
"outputs": [],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "string", "name": "username", "type": "string"}
|
||||
],
|
||||
"name": "regiserUser",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "renounceOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "uint16", "name": "id", "type": "uint16"},
|
||||
{"internalType": "address", "name": "buyer", "type": "address"},
|
||||
{"internalType": "address", "name": "seller", "type": "address"}
|
||||
],
|
||||
"name": "selectOrder",
|
||||
"outputs": [
|
||||
{"internalType": "uint232", "name": "", "type": "uint232"},
|
||||
{"internalType": "uint16", "name": "", "type": "uint16"},
|
||||
{"internalType": "uint8", "name": "", "type": "uint8"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{"internalType": "string", "name": "", "type": "string"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "signer", "type": "address"}
|
||||
],
|
||||
"name": "toggleSigner",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "tokenAddress", "type": "address"}
|
||||
],
|
||||
"name": "toggleToken",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{"internalType": "uint256", "name": "", "type": "uint256"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "to", "type": "address"},
|
||||
{"internalType": "uint256", "name": "amount", "type": "uint256"}
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "", "type": "bool"}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "from", "type": "address"},
|
||||
{"internalType": "address", "name": "to", "type": "address"},
|
||||
{"internalType": "uint256", "name": "amount", "type": "uint256"}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "", "type": "bool"}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "newOwner", "type": "address"}
|
||||
],
|
||||
"name": "transferOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "uint8", "name": "newPercentage", "type": "uint8"}
|
||||
],
|
||||
"name": "updateContractPercentage",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address[]", "name": "buyer", "type": "address[]"},
|
||||
{"internalType": "bytes[]", "name": "signature", "type": "bytes[]"},
|
||||
{"internalType": "uint16[]", "name": "order_id", "type": "uint16[]"},
|
||||
{"internalType": "address", "name": "contractAddress", "type": "address"}
|
||||
],
|
||||
"name": "widthrawForSellers",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "seller", "type": "address"},
|
||||
{"internalType": "bytes", "name": "signature", "type": "bytes"},
|
||||
{"internalType": "uint16", "name": "order_id", "type": "uint16"},
|
||||
{"internalType": "address", "name": "contractAddress", "type": "address"}
|
||||
],
|
||||
"name": "widthrowForBuyers",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
];
|
34
cw_tron/lib/tron_balance.dart
Normal file
34
cw_tron/lib/tron_balance.dart
Normal file
|
@ -0,0 +1,34 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:cw_core/balance.dart';
|
||||
import 'package:on_chain/on_chain.dart';
|
||||
|
||||
class TronBalance extends Balance {
|
||||
TronBalance(this.balance) : super(balance.toInt(), balance.toInt());
|
||||
|
||||
final BigInt balance;
|
||||
|
||||
@override
|
||||
String get formattedAdditionalBalance => TronHelper.fromSun(balance);
|
||||
|
||||
@override
|
||||
String get formattedAvailableBalance => TronHelper.fromSun(balance);
|
||||
|
||||
String toJSON() => json.encode({
|
||||
'balance': balance.toString(),
|
||||
});
|
||||
|
||||
static TronBalance? fromJSON(String? jsonSource) {
|
||||
if (jsonSource == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final decoded = json.decode(jsonSource) as Map;
|
||||
|
||||
try {
|
||||
return TronBalance(BigInt.parse(decoded['balance']));
|
||||
} catch (e) {
|
||||
return TronBalance(BigInt.zero);
|
||||
}
|
||||
}
|
||||
}
|
574
cw_tron/lib/tron_client.dart
Normal file
574
cw_tron/lib/tron_client.dart
Normal file
|
@ -0,0 +1,574 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_tron/pending_tron_transaction.dart';
|
||||
import 'package:cw_tron/tron_abi.dart';
|
||||
import 'package:cw_tron/tron_balance.dart';
|
||||
import 'package:cw_tron/tron_http_provider.dart';
|
||||
import 'package:cw_tron/tron_token.dart';
|
||||
import 'package:cw_tron/tron_transaction_model.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:http/http.dart';
|
||||
import '.secrets.g.dart' as secrets;
|
||||
import 'package:on_chain/on_chain.dart';
|
||||
|
||||
class TronClient {
|
||||
final httpClient = Client();
|
||||
TronProvider? _provider;
|
||||
// This is an internal tracker, so we don't have to "refetch".
|
||||
int _nativeTxEstimatedFee = 0;
|
||||
|
||||
int get chainId => 1000;
|
||||
|
||||
Future<List<TronTransactionModel>> fetchTransactions(String address,
|
||||
{String? contractAddress}) async {
|
||||
try {
|
||||
final response = await httpClient.get(
|
||||
Uri.https(
|
||||
"api.trongrid.io",
|
||||
"/v1/accounts/$address/transactions",
|
||||
{
|
||||
"only_confirmed": "true",
|
||||
"limit": "200",
|
||||
},
|
||||
),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'TRON-PRO-API-KEY': secrets.tronGridApiKey,
|
||||
},
|
||||
);
|
||||
final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
|
||||
|
||||
if (response.statusCode >= 200 &&
|
||||
response.statusCode < 300 &&
|
||||
jsonResponse['status'] != false) {
|
||||
return (jsonResponse['data'] as List).map((e) {
|
||||
return TronTransactionModel.fromJson(e as Map<String, dynamic>);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
return [];
|
||||
} catch (e, s) {
|
||||
log('Error getting tx: ${e.toString()}\n ${s.toString()}');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<TronTRC20TransactionModel>> fetchTrc20ExcludedTransactions(String address) async {
|
||||
try {
|
||||
final response = await httpClient.get(
|
||||
Uri.https(
|
||||
"api.trongrid.io",
|
||||
"/v1/accounts/$address/transactions/trc20",
|
||||
{
|
||||
"only_confirmed": "true",
|
||||
"limit": "200",
|
||||
},
|
||||
),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'TRON-PRO-API-KEY': secrets.tronGridApiKey,
|
||||
},
|
||||
);
|
||||
final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
|
||||
|
||||
if (response.statusCode >= 200 &&
|
||||
response.statusCode < 300 &&
|
||||
jsonResponse['status'] != false) {
|
||||
return (jsonResponse['data'] as List).map((e) {
|
||||
return TronTRC20TransactionModel.fromJson(e as Map<String, dynamic>);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
return [];
|
||||
} catch (e, s) {
|
||||
log('Error getting trc20 tx: ${e.toString()}\n ${s.toString()}');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
bool connect(Node node) {
|
||||
try {
|
||||
final formattedUrl = '${node.isSSL ? 'https' : 'http'}://${node.uriRaw}';
|
||||
_provider = TronProvider(TronHTTPProvider(url: formattedUrl));
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<BigInt> getBalance(TronAddress address) async {
|
||||
try {
|
||||
final accountDetails = await _provider!.request(TronRequestGetAccount(address: address));
|
||||
|
||||
return accountDetails?.balance ?? BigInt.zero;
|
||||
} catch (_) {
|
||||
return BigInt.zero;
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> getFeeLimit(
|
||||
TransactionRaw rawTransaction,
|
||||
TronAddress address,
|
||||
TronAddress receiverAddress, {
|
||||
int energyUsed = 0,
|
||||
bool isEstimatedFeeFlow = false,
|
||||
}) async {
|
||||
try {
|
||||
// Get the tron chain parameters.
|
||||
final chainParams = await _provider!.request(TronRequestGetChainParameters());
|
||||
|
||||
final bandWidthInSun = chainParams.getTransactionFee!;
|
||||
log('BandWidth In Sun: $bandWidthInSun');
|
||||
|
||||
final energyInSun = chainParams.getEnergyFee!;
|
||||
log('Energy In Sun: $energyInSun');
|
||||
|
||||
log(
|
||||
'Create Account Fee In System Contract for Chain: ${chainParams.getCreateNewAccountFeeInSystemContract!}',
|
||||
);
|
||||
log('Create Account Fee for Chain: ${chainParams.getCreateAccountFee}');
|
||||
|
||||
final fakeTransaction = Transaction(
|
||||
rawData: rawTransaction,
|
||||
signature: [Uint8List(65)],
|
||||
);
|
||||
|
||||
// Calculate the total size of the fake transaction, considering the required network overhead.
|
||||
final transactionSize = fakeTransaction.length + 64;
|
||||
|
||||
// Assign the calculated size to the variable representing the required bandwidth.
|
||||
int neededBandWidth = transactionSize;
|
||||
log('Initial Needed Bandwidth: $neededBandWidth');
|
||||
|
||||
int neededEnergy = energyUsed;
|
||||
log('Initial Needed Energy: $neededEnergy');
|
||||
|
||||
// Fetch account resources to assess the available bandwidth and energy
|
||||
final accountResource =
|
||||
await _provider!.request(TronRequestGetAccountResource(address: address));
|
||||
|
||||
neededEnergy -= accountResource.howManyEnergy.toInt();
|
||||
log('Account resource energy: ${accountResource.howManyEnergy.toInt()}');
|
||||
log('Needed Energy after deducting from account resource energy: $neededEnergy');
|
||||
|
||||
// Deduct the bandwidth from the account's available bandwidth.
|
||||
final BigInt accountBandWidth = accountResource.howManyBandwIth;
|
||||
log('Account resource bandwidth: ${accountResource.howManyBandwIth.toInt()}');
|
||||
|
||||
if (accountBandWidth >= BigInt.from(neededBandWidth) && !isEstimatedFeeFlow) {
|
||||
log('Account has more bandwidth than required');
|
||||
neededBandWidth = 0;
|
||||
}
|
||||
|
||||
if (neededEnergy < 0) {
|
||||
neededEnergy = 0;
|
||||
}
|
||||
|
||||
final energyBurn = neededEnergy * energyInSun.toInt();
|
||||
log('Energy Burn: $energyBurn');
|
||||
|
||||
final bandWidthBurn = neededBandWidth * bandWidthInSun;
|
||||
log('Bandwidth Burn: $bandWidthBurn');
|
||||
|
||||
int totalBurn = energyBurn + bandWidthBurn;
|
||||
log('Total Burn: $totalBurn');
|
||||
|
||||
/// If there is a note (memo), calculate the memo fee.
|
||||
if (rawTransaction.data != null) {
|
||||
totalBurn += chainParams.getMemoFee!;
|
||||
}
|
||||
|
||||
// Check if receiver's account is active
|
||||
final receiverAccountInfo =
|
||||
await _provider!.request(TronRequestGetAccount(address: receiverAddress));
|
||||
|
||||
/// Calculate the resources required to create a new account.
|
||||
if (receiverAccountInfo == null) {
|
||||
totalBurn += chainParams.getCreateNewAccountFeeInSystemContract!;
|
||||
|
||||
totalBurn += (chainParams.getCreateAccountFee! * bandWidthInSun);
|
||||
}
|
||||
|
||||
log('Final total burn: $totalBurn');
|
||||
|
||||
return totalBurn;
|
||||
} catch (_) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> getEstimatedFee(TronAddress ownerAddress) async {
|
||||
const constantAmount = '1000';
|
||||
// Fetch the latest Tron block
|
||||
final block = await _provider!.request(TronRequestGetNowBlock());
|
||||
|
||||
// Create the transfer contract
|
||||
final contract = TransferContract(
|
||||
amount: TronHelper.toSun(constantAmount),
|
||||
ownerAddress: ownerAddress,
|
||||
toAddress: ownerAddress,
|
||||
);
|
||||
|
||||
// Prepare the contract parameter for the transaction.
|
||||
final parameter = Any(typeUrl: contract.typeURL, value: contract);
|
||||
|
||||
// Create a TransactionContract object with the contract type and parameter.
|
||||
final transactionContract =
|
||||
TransactionContract(type: contract.contractType, parameter: parameter);
|
||||
|
||||
// Set the transaction expiration time (maximum 24 hours)
|
||||
final expireTime = DateTime.now().toUtc().add(const Duration(hours: 24));
|
||||
|
||||
// Create a raw transaction
|
||||
TransactionRaw rawTransaction = TransactionRaw(
|
||||
refBlockBytes: block.blockHeader.rawData.refBlockBytes,
|
||||
refBlockHash: block.blockHeader.rawData.refBlockHash,
|
||||
expiration: BigInt.from(expireTime.millisecondsSinceEpoch),
|
||||
contract: [transactionContract],
|
||||
timestamp: block.blockHeader.rawData.timestamp,
|
||||
);
|
||||
|
||||
final estimatedFee = await getFeeLimit(
|
||||
rawTransaction,
|
||||
ownerAddress,
|
||||
ownerAddress,
|
||||
isEstimatedFeeFlow: true,
|
||||
);
|
||||
|
||||
_nativeTxEstimatedFee = estimatedFee;
|
||||
|
||||
return estimatedFee;
|
||||
}
|
||||
|
||||
Future<int> getTRCEstimatedFee(TronAddress ownerAddress) async {
|
||||
String contractAddress = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t';
|
||||
String constantAmount =
|
||||
'0'; // We're using 0 as the base amount here as we get an error when balance is zero i.e for new wallets.
|
||||
final contract = ContractABI.fromJson(trc20Abi, isTron: true);
|
||||
|
||||
final function = contract.functionFromName("transfer");
|
||||
|
||||
/// address /// amount
|
||||
final transferparams = [
|
||||
ownerAddress,
|
||||
TronHelper.toSun(constantAmount),
|
||||
];
|
||||
|
||||
final contractAddr = TronAddress(contractAddress);
|
||||
|
||||
final request = await _provider!.request(
|
||||
TronRequestTriggerConstantContract(
|
||||
ownerAddress: ownerAddress,
|
||||
contractAddress: contractAddr,
|
||||
data: function.encodeHex(transferparams),
|
||||
),
|
||||
);
|
||||
|
||||
if (!request.isSuccess) {
|
||||
log("Tron TRC20 error: ${request.error} \n ${request.respose}");
|
||||
}
|
||||
|
||||
final feeLimit = await getFeeLimit(
|
||||
request.transactionRaw!,
|
||||
ownerAddress,
|
||||
ownerAddress,
|
||||
energyUsed: request.energyUsed ?? 0,
|
||||
isEstimatedFeeFlow: true,
|
||||
);
|
||||
return feeLimit;
|
||||
}
|
||||
|
||||
Future<PendingTronTransaction> signTransaction({
|
||||
required TronPrivateKey ownerPrivKey,
|
||||
required String toAddress,
|
||||
required String amount,
|
||||
required CryptoCurrency currency,
|
||||
required BigInt tronBalance,
|
||||
required bool sendAll,
|
||||
}) async {
|
||||
// Get the owner tron address from the key
|
||||
final ownerAddress = ownerPrivKey.publicKey().toAddress();
|
||||
|
||||
// Define the receiving Tron address for the transaction.
|
||||
final receiverAddress = TronAddress(toAddress);
|
||||
|
||||
bool isNativeTransaction = currency == CryptoCurrency.trx;
|
||||
|
||||
String totalAmount;
|
||||
TransactionRaw rawTransaction;
|
||||
if (isNativeTransaction) {
|
||||
if (sendAll) {
|
||||
final accountResource =
|
||||
await _provider!.request(TronRequestGetAccountResource(address: ownerAddress));
|
||||
|
||||
final availableBandWidth = accountResource.howManyBandwIth.toInt();
|
||||
|
||||
// 269 is the current middle ground for bandwidth per transaction
|
||||
if (availableBandWidth >= 269) {
|
||||
totalAmount = amount;
|
||||
} else {
|
||||
final amountInSun = TronHelper.toSun(amount).toInt();
|
||||
|
||||
// 5000 added here is a buffer since we're working with "estimated" value of the fee.
|
||||
final result = amountInSun - (_nativeTxEstimatedFee + 5000);
|
||||
|
||||
totalAmount = TronHelper.fromSun(BigInt.from(result));
|
||||
}
|
||||
} else {
|
||||
totalAmount = amount;
|
||||
}
|
||||
rawTransaction = await _signNativeTransaction(
|
||||
ownerAddress,
|
||||
receiverAddress,
|
||||
totalAmount,
|
||||
tronBalance,
|
||||
sendAll,
|
||||
);
|
||||
} else {
|
||||
final tokenAddress = (currency as TronToken).contractAddress;
|
||||
totalAmount = amount;
|
||||
rawTransaction = await _signTrcTokenTransaction(
|
||||
ownerAddress,
|
||||
receiverAddress,
|
||||
totalAmount,
|
||||
tokenAddress,
|
||||
tronBalance,
|
||||
);
|
||||
}
|
||||
|
||||
final signature = ownerPrivKey.sign(rawTransaction.toBuffer());
|
||||
|
||||
sendTx() async => await sendTransaction(
|
||||
rawTransaction: rawTransaction,
|
||||
signature: signature,
|
||||
);
|
||||
|
||||
return PendingTronTransaction(
|
||||
signedTransaction: signature,
|
||||
amount: totalAmount,
|
||||
fee: TronHelper.fromSun(rawTransaction.feeLimit ?? BigInt.zero),
|
||||
sendTransaction: sendTx,
|
||||
);
|
||||
}
|
||||
|
||||
Future<TransactionRaw> _signNativeTransaction(
|
||||
TronAddress ownerAddress,
|
||||
TronAddress receiverAddress,
|
||||
String amount,
|
||||
BigInt tronBalance,
|
||||
bool sendAll,
|
||||
) async {
|
||||
// This is introduce to server as a limit in cases where feeLimit is 0
|
||||
// The transaction signing will fail if the feeLimit is explicitly 0.
|
||||
int defaultFeeLimit = 100000;
|
||||
|
||||
final block = await _provider!.request(TronRequestGetNowBlock());
|
||||
// Create the transfer contract
|
||||
final contract = TransferContract(
|
||||
amount: TronHelper.toSun(amount),
|
||||
ownerAddress: ownerAddress,
|
||||
toAddress: receiverAddress,
|
||||
);
|
||||
|
||||
// Prepare the contract parameter for the transaction.
|
||||
final parameter = Any(typeUrl: contract.typeURL, value: contract);
|
||||
|
||||
// Create a TransactionContract object with the contract type and parameter.
|
||||
final transactionContract =
|
||||
TransactionContract(type: contract.contractType, parameter: parameter);
|
||||
|
||||
// Set the transaction expiration time (maximum 24 hours)
|
||||
final expireTime = DateTime.now().toUtc().add(const Duration(hours: 24));
|
||||
|
||||
// Create a raw transaction
|
||||
TransactionRaw rawTransaction = TransactionRaw(
|
||||
refBlockBytes: block.blockHeader.rawData.refBlockBytes,
|
||||
refBlockHash: block.blockHeader.rawData.refBlockHash,
|
||||
expiration: BigInt.from(expireTime.millisecondsSinceEpoch),
|
||||
contract: [transactionContract],
|
||||
timestamp: block.blockHeader.rawData.timestamp,
|
||||
);
|
||||
|
||||
final feeLimit = await getFeeLimit(rawTransaction, ownerAddress, receiverAddress);
|
||||
final feeLimitToUse = feeLimit != 0 ? feeLimit : defaultFeeLimit;
|
||||
final tronBalanceInt = tronBalance.toInt();
|
||||
|
||||
if (feeLimit > tronBalanceInt) {
|
||||
throw Exception(
|
||||
'You don\'t have enough TRX to cover the transaction fee for this transaction. Kindly top up.',
|
||||
);
|
||||
}
|
||||
|
||||
rawTransaction = rawTransaction.copyWith(
|
||||
feeLimit: BigInt.from(feeLimitToUse),
|
||||
);
|
||||
|
||||
return rawTransaction;
|
||||
}
|
||||
|
||||
Future<TransactionRaw> _signTrcTokenTransaction(
|
||||
TronAddress ownerAddress,
|
||||
TronAddress receiverAddress,
|
||||
String amount,
|
||||
String contractAddress,
|
||||
BigInt tronBalance,
|
||||
) async {
|
||||
final contract = ContractABI.fromJson(trc20Abi, isTron: true);
|
||||
|
||||
final function = contract.functionFromName("transfer");
|
||||
|
||||
/// address /// amount
|
||||
final transferparams = [
|
||||
receiverAddress,
|
||||
TronHelper.toSun(amount),
|
||||
];
|
||||
|
||||
final contractAddr = TronAddress(contractAddress);
|
||||
|
||||
final request = await _provider!.request(
|
||||
TronRequestTriggerConstantContract(
|
||||
ownerAddress: ownerAddress,
|
||||
contractAddress: contractAddr,
|
||||
data: function.encodeHex(transferparams),
|
||||
),
|
||||
);
|
||||
|
||||
if (!request.isSuccess) {
|
||||
log("Tron TRC20 error: ${request.error} \n ${request.respose}");
|
||||
}
|
||||
|
||||
final feeLimit = await getFeeLimit(
|
||||
request.transactionRaw!,
|
||||
ownerAddress,
|
||||
receiverAddress,
|
||||
energyUsed: request.energyUsed ?? 0,
|
||||
);
|
||||
|
||||
final tronBalanceInt = tronBalance.toInt();
|
||||
|
||||
if (feeLimit > tronBalanceInt) {
|
||||
throw Exception(
|
||||
'You don\'t have enough TRX to cover the transaction fee for this transaction. Kindly top up.',
|
||||
);
|
||||
}
|
||||
|
||||
final rawTransaction = request.transactionRaw!.copyWith(
|
||||
feeLimit: BigInt.from(feeLimit),
|
||||
);
|
||||
|
||||
return rawTransaction;
|
||||
}
|
||||
|
||||
Future<String> sendTransaction({
|
||||
required TransactionRaw rawTransaction,
|
||||
required List<int> signature,
|
||||
}) async {
|
||||
try {
|
||||
final transaction = Transaction(rawData: rawTransaction, signature: [signature]);
|
||||
|
||||
final raw = BytesUtils.toHexString(transaction.toBuffer());
|
||||
|
||||
final txBroadcastResult = await _provider!.request(TronRequestBroadcastHex(transaction: raw));
|
||||
|
||||
if (txBroadcastResult.isSuccess) {
|
||||
return txBroadcastResult.txId!;
|
||||
} else {
|
||||
throw Exception(txBroadcastResult.error);
|
||||
}
|
||||
} catch (e) {
|
||||
log('Send block Exception: ${e.toString()}');
|
||||
throw Exception(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<TronBalance> fetchTronTokenBalances(String userAddress, String contractAddress) async {
|
||||
try {
|
||||
final ownerAddress = TronAddress(userAddress);
|
||||
|
||||
final tokenAddress = TronAddress(contractAddress);
|
||||
|
||||
final contract = ContractABI.fromJson(trc20Abi, isTron: true);
|
||||
|
||||
final function = contract.functionFromName("balanceOf");
|
||||
|
||||
final request = await _provider!.request(
|
||||
TronRequestTriggerConstantContract.fromMethod(
|
||||
ownerAddress: ownerAddress,
|
||||
contractAddress: tokenAddress,
|
||||
function: function,
|
||||
params: [ownerAddress],
|
||||
),
|
||||
);
|
||||
|
||||
final outputResult = request.outputResult?.first ?? BigInt.zero;
|
||||
|
||||
return TronBalance(outputResult);
|
||||
} catch (_) {
|
||||
return TronBalance(BigInt.zero);
|
||||
}
|
||||
}
|
||||
|
||||
Future<TronToken?> getTronToken(String contractAddress, String userAddress) async {
|
||||
try {
|
||||
final tokenAddress = TronAddress(contractAddress);
|
||||
|
||||
final ownerAddress = TronAddress(userAddress);
|
||||
|
||||
final contract = ContractABI.fromJson(trc20Abi, isTron: true);
|
||||
|
||||
final name =
|
||||
(await getTokenDetail(contract, "name", ownerAddress, tokenAddress) as String?) ?? '';
|
||||
|
||||
final symbol =
|
||||
(await getTokenDetail(contract, "symbol", ownerAddress, tokenAddress) as String?) ?? '';
|
||||
|
||||
final decimal =
|
||||
(await getTokenDetail(contract, "decimals", ownerAddress, tokenAddress) as BigInt?) ??
|
||||
BigInt.zero;
|
||||
|
||||
return TronToken(
|
||||
name: name,
|
||||
symbol: symbol,
|
||||
contractAddress: contractAddress,
|
||||
decimal: decimal.toInt(),
|
||||
);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<dynamic> getTokenDetail(
|
||||
ContractABI contract,
|
||||
String functionName,
|
||||
TronAddress ownerAddress,
|
||||
TronAddress tokenAddress,
|
||||
) async {
|
||||
final function = contract.functionFromName(functionName);
|
||||
|
||||
try {
|
||||
final request = await _provider!.request(
|
||||
TronRequestTriggerConstantContract.fromMethod(
|
||||
ownerAddress: ownerAddress,
|
||||
contractAddress: tokenAddress,
|
||||
function: function,
|
||||
params: [],
|
||||
),
|
||||
);
|
||||
|
||||
final outputResult = request.outputResult?.first;
|
||||
|
||||
return outputResult;
|
||||
} catch (_) {
|
||||
log('Erorr fetching detail: ${_.toString()}');
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
16
cw_tron/lib/tron_exception.dart
Normal file
16
cw_tron/lib/tron_exception.dart
Normal file
|
@ -0,0 +1,16 @@
|
|||
import 'package:cw_core/crypto_currency.dart';
|
||||
|
||||
class TronMnemonicIsIncorrectException implements Exception {
|
||||
@override
|
||||
String toString() =>
|
||||
'Tron mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
|
||||
}
|
||||
class TronTransactionCreationException implements Exception {
|
||||
final String exceptionMessage;
|
||||
|
||||
TronTransactionCreationException(CryptoCurrency currency)
|
||||
: exceptionMessage = 'Wrong balance. Not enough ${currency.title} on your balance.';
|
||||
|
||||
@override
|
||||
String toString() => exceptionMessage;
|
||||
}
|
41
cw_tron/lib/tron_http_provider.dart
Normal file
41
cw_tron/lib/tron_http_provider.dart
Normal file
|
@ -0,0 +1,41 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:on_chain/tron/tron.dart';
|
||||
import '.secrets.g.dart' as secrets;
|
||||
|
||||
class TronHTTPProvider implements TronServiceProvider {
|
||||
TronHTTPProvider(
|
||||
{required this.url,
|
||||
http.Client? client,
|
||||
this.defaultRequestTimeout = const Duration(seconds: 30)})
|
||||
: client = client ?? http.Client();
|
||||
@override
|
||||
final String url;
|
||||
final http.Client client;
|
||||
final Duration defaultRequestTimeout;
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> get(TronRequestDetails params, [Duration? timeout]) async {
|
||||
final response = await client.get(Uri.parse(params.url(url)), headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'TRON-PRO-API-KEY': secrets.tronGridApiKey,
|
||||
}).timeout(timeout ?? defaultRequestTimeout);
|
||||
final data = json.decode(response.body) as Map<String, dynamic>;
|
||||
return data;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> post(TronRequestDetails params, [Duration? timeout]) async {
|
||||
final response = await client
|
||||
.post(Uri.parse(params.url(url)),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'TRON-PRO-API-KEY': secrets.tronGridApiKey,
|
||||
},
|
||||
body: params.toRequestBody())
|
||||
.timeout(timeout ?? defaultRequestTimeout);
|
||||
final data = json.decode(response.body) as Map<String, dynamic>;
|
||||
return data;
|
||||
}
|
||||
}
|
80
cw_tron/lib/tron_token.dart
Normal file
80
cw_tron/lib/tron_token.dart
Normal file
|
@ -0,0 +1,80 @@
|
|||
// ignore_for_file: annotate_overrides, overridden_fields
|
||||
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/hive_type_ids.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'tron_token.g.dart';
|
||||
|
||||
@HiveType(typeId: TronToken.typeId)
|
||||
class TronToken extends CryptoCurrency with HiveObjectMixin {
|
||||
@HiveField(0)
|
||||
final String name;
|
||||
|
||||
@HiveField(1)
|
||||
final String symbol;
|
||||
|
||||
@HiveField(2)
|
||||
final String contractAddress;
|
||||
|
||||
@HiveField(3)
|
||||
final int decimal;
|
||||
|
||||
@HiveField(4, defaultValue: true)
|
||||
bool _enabled;
|
||||
|
||||
@HiveField(5)
|
||||
final String? iconPath;
|
||||
|
||||
@HiveField(6)
|
||||
final String? tag;
|
||||
|
||||
bool get enabled => _enabled;
|
||||
|
||||
set enabled(bool value) => _enabled = value;
|
||||
|
||||
TronToken({
|
||||
required this.name,
|
||||
required this.symbol,
|
||||
required this.contractAddress,
|
||||
required this.decimal,
|
||||
bool enabled = true,
|
||||
this.iconPath,
|
||||
this.tag = 'TRX',
|
||||
}) : _enabled = enabled,
|
||||
super(
|
||||
name: symbol.toLowerCase(),
|
||||
title: symbol.toUpperCase(),
|
||||
fullName: name,
|
||||
tag: tag,
|
||||
iconPath: iconPath,
|
||||
decimals: decimal);
|
||||
|
||||
TronToken.copyWith(TronToken other, String? icon, String? tag)
|
||||
: name = other.name,
|
||||
symbol = other.symbol,
|
||||
contractAddress = other.contractAddress,
|
||||
decimal = other.decimal,
|
||||
_enabled = other.enabled,
|
||||
tag = tag ?? other.tag,
|
||||
iconPath = icon ?? other.iconPath,
|
||||
super(
|
||||
name: other.name,
|
||||
title: other.symbol.toUpperCase(),
|
||||
fullName: other.name,
|
||||
tag: tag ?? other.tag,
|
||||
iconPath: icon ?? other.iconPath,
|
||||
decimals: other.decimal,
|
||||
);
|
||||
|
||||
static const typeId = TRON_TOKEN_TYPE_ID;
|
||||
static const boxName = 'TronTokens';
|
||||
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
(other is TronToken && other.contractAddress == contractAddress) ||
|
||||
(other is CryptoCurrency && other.title == title);
|
||||
|
||||
@override
|
||||
int get hashCode => contractAddress.hashCode;
|
||||
}
|
12
cw_tron/lib/tron_transaction_credentials.dart
Normal file
12
cw_tron/lib/tron_transaction_credentials.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/output_info.dart';
|
||||
|
||||
class TronTransactionCredentials {
|
||||
TronTransactionCredentials(
|
||||
this.outputs, {
|
||||
required this.currency,
|
||||
});
|
||||
|
||||
final List<OutputInfo> outputs;
|
||||
final CryptoCurrency currency;
|
||||
}
|
80
cw_tron/lib/tron_transaction_history.dart
Normal file
80
cw_tron/lib/tron_transaction_history.dart
Normal file
|
@ -0,0 +1,80 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:core';
|
||||
import 'dart:developer';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_evm/file.dart';
|
||||
import 'package:cw_tron/tron_transaction_info.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
|
||||
part 'tron_transaction_history.g.dart';
|
||||
|
||||
class TronTransactionHistory = TronTransactionHistoryBase with _$TronTransactionHistory;
|
||||
|
||||
abstract class TronTransactionHistoryBase extends TransactionHistoryBase<TronTransactionInfo>
|
||||
with Store {
|
||||
TronTransactionHistoryBase({required this.walletInfo, required String password})
|
||||
: _password = password {
|
||||
transactions = ObservableMap<String, TronTransactionInfo>();
|
||||
}
|
||||
|
||||
String _password;
|
||||
|
||||
final WalletInfo walletInfo;
|
||||
|
||||
Future<void> init() async => await _load();
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
String transactionsHistoryFileNameForWallet = 'tron_transactions.json';
|
||||
try {
|
||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
String path = '$dirPath/$transactionsHistoryFileNameForWallet';
|
||||
final transactionMaps = transactions.map((key, value) => MapEntry(key, value.toJson()));
|
||||
final data = json.encode({'transactions': transactionMaps});
|
||||
await writeData(path: path, password: _password, data: data);
|
||||
} catch (e, s) {
|
||||
log('Error while saving ${walletInfo.type.name} transaction history: ${e.toString()}');
|
||||
log(s.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void addOne(TronTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||
|
||||
@override
|
||||
void addMany(Map<String, TronTransactionInfo> transactions) =>
|
||||
this.transactions.addAll(transactions);
|
||||
|
||||
Future<Map<String, dynamic>> _read() async {
|
||||
String transactionsHistoryFileNameForWallet = 'tron_transactions.json';
|
||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
String path = '$dirPath/$transactionsHistoryFileNameForWallet';
|
||||
final content = await read(path: path, password: _password);
|
||||
if (content.isEmpty) {
|
||||
return {};
|
||||
}
|
||||
return json.decode(content) as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
try {
|
||||
final content = await _read();
|
||||
final txs = content['transactions'] as Map<String, dynamic>? ?? {};
|
||||
|
||||
for (var entry in txs.entries) {
|
||||
final val = entry.value;
|
||||
|
||||
if (val is Map<String, dynamic>) {
|
||||
final tx = TronTransactionInfo.fromJson(val);
|
||||
_update(tx);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void _update(TronTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||
}
|
93
cw_tron/lib/tron_transaction_info.dart
Normal file
93
cw_tron/lib/tron_transaction_info.dart
Normal file
|
@ -0,0 +1,93 @@
|
|||
import 'package:cw_core/format_amount.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:on_chain/on_chain.dart' as onchain;
|
||||
import 'package:on_chain/tron/tron.dart';
|
||||
|
||||
class TronTransactionInfo extends TransactionInfo {
|
||||
TronTransactionInfo({
|
||||
required this.id,
|
||||
required this.tronAmount,
|
||||
required this.txFee,
|
||||
required this.direction,
|
||||
required this.blockTime,
|
||||
required this.to,
|
||||
required this.from,
|
||||
required this.isPending,
|
||||
this.tokenSymbol = 'TRX',
|
||||
}) : amount = tronAmount.toInt();
|
||||
|
||||
final String id;
|
||||
final String? to;
|
||||
final String? from;
|
||||
final int amount;
|
||||
final BigInt tronAmount;
|
||||
final String tokenSymbol;
|
||||
final DateTime blockTime;
|
||||
final bool isPending;
|
||||
final int? txFee;
|
||||
final TransactionDirection direction;
|
||||
|
||||
factory TronTransactionInfo.fromJson(Map<String, dynamic> data) {
|
||||
return TronTransactionInfo(
|
||||
id: data['id'] as String,
|
||||
tronAmount: BigInt.parse(data['tronAmount']),
|
||||
txFee: data['txFee'],
|
||||
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
||||
blockTime: DateTime.fromMillisecondsSinceEpoch(data['blockTime'] as int),
|
||||
tokenSymbol: data['tokenSymbol'] as String,
|
||||
to: data['to'],
|
||||
from: data['from'],
|
||||
isPending: data['isPending'],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'tronAmount': tronAmount.toString(),
|
||||
'txFee': txFee,
|
||||
'direction': direction.index,
|
||||
'blockTime': blockTime.millisecondsSinceEpoch,
|
||||
'tokenSymbol': tokenSymbol,
|
||||
'to': to,
|
||||
'from': from,
|
||||
'isPending': isPending,
|
||||
};
|
||||
|
||||
@override
|
||||
DateTime get date => blockTime;
|
||||
|
||||
String? _fiatAmount;
|
||||
|
||||
@override
|
||||
String amountFormatted() {
|
||||
String formattedAmount = _rawAmountAsString(tronAmount);
|
||||
|
||||
return '$formattedAmount $tokenSymbol';
|
||||
}
|
||||
|
||||
@override
|
||||
String fiatAmount() => _fiatAmount ?? '';
|
||||
|
||||
@override
|
||||
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
||||
|
||||
@override
|
||||
String feeFormatted() {
|
||||
final formattedFee = onchain.TronHelper.fromSun(BigInt.from(txFee ?? 0));
|
||||
|
||||
return '$formattedFee TRX';
|
||||
}
|
||||
|
||||
String _rawAmountAsString(BigInt amount) {
|
||||
String formattedAmount = TronHelper.fromSun(amount);
|
||||
|
||||
if (formattedAmount.length >= 8) {
|
||||
formattedAmount = formattedAmount.substring(0, 8);
|
||||
}
|
||||
|
||||
return formattedAmount;
|
||||
}
|
||||
|
||||
String rawTronAmount() => _rawAmountAsString(tronAmount);
|
||||
}
|
205
cw_tron/lib/tron_transaction_model.dart
Normal file
205
cw_tron/lib/tron_transaction_model.dart
Normal file
|
@ -0,0 +1,205 @@
|
|||
import 'package:blockchain_utils/hex/hex.dart';
|
||||
import 'package:on_chain/on_chain.dart';
|
||||
|
||||
class TronTRC20TransactionModel extends TronTransactionModel {
|
||||
String? transactionId;
|
||||
|
||||
String? tokenSymbol;
|
||||
|
||||
int? timestamp;
|
||||
|
||||
@override
|
||||
String? from;
|
||||
|
||||
@override
|
||||
String? to;
|
||||
|
||||
String? value;
|
||||
|
||||
@override
|
||||
String get hash => transactionId!;
|
||||
|
||||
@override
|
||||
DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp ?? 0);
|
||||
|
||||
@override
|
||||
BigInt? get amount => BigInt.parse(value ?? '0');
|
||||
|
||||
@override
|
||||
int? get fee => 0;
|
||||
|
||||
TronTRC20TransactionModel({
|
||||
this.transactionId,
|
||||
this.tokenSymbol,
|
||||
this.timestamp,
|
||||
this.from,
|
||||
this.to,
|
||||
this.value,
|
||||
});
|
||||
|
||||
TronTRC20TransactionModel.fromJson(Map<String, dynamic> json) {
|
||||
transactionId = json['transaction_id'];
|
||||
tokenSymbol = json['token_info'] != null ? json['token_info']['symbol'] : null;
|
||||
timestamp = json['block_timestamp'];
|
||||
from = json['from'];
|
||||
to = json['to'];
|
||||
value = json['value'];
|
||||
}
|
||||
}
|
||||
|
||||
class TronTransactionModel {
|
||||
List<Ret>? ret;
|
||||
String? txID;
|
||||
int? blockTimestamp;
|
||||
List<Contract>? contracts;
|
||||
|
||||
/// Getters to extract out the needed/useful information directly from the model params
|
||||
/// Without having to go through extra steps in the methods that use this model.
|
||||
bool get isError {
|
||||
if (ret?.first.contractRet == null) return true;
|
||||
|
||||
return ret?.first.contractRet != "SUCCESS";
|
||||
}
|
||||
|
||||
String get hash => txID!;
|
||||
|
||||
DateTime get date => DateTime.fromMillisecondsSinceEpoch(blockTimestamp ?? 0);
|
||||
|
||||
String? get from => contracts?.first.parameter?.value?.ownerAddress;
|
||||
|
||||
String? get to => contracts?.first.parameter?.value?.receiverAddress;
|
||||
|
||||
BigInt? get amount => contracts?.first.parameter?.value?.txAmount;
|
||||
|
||||
int? get fee => ret?.first.fee;
|
||||
|
||||
String? get contractAddress => contracts?.first.parameter?.value?.contractAddress;
|
||||
|
||||
TronTransactionModel({
|
||||
this.ret,
|
||||
this.txID,
|
||||
this.blockTimestamp,
|
||||
this.contracts,
|
||||
});
|
||||
|
||||
TronTransactionModel.fromJson(Map<String, dynamic> json) {
|
||||
if (json['ret'] != null) {
|
||||
ret = <Ret>[];
|
||||
json['ret'].forEach((v) {
|
||||
ret!.add(Ret.fromJson(v));
|
||||
});
|
||||
}
|
||||
txID = json['txID'];
|
||||
blockTimestamp = json['block_timestamp'];
|
||||
contracts = json['raw_data'] != null
|
||||
? (json['raw_data']['contract'] as List)
|
||||
.map((e) => Contract.fromJson(e as Map<String, dynamic>))
|
||||
.toList()
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
class Ret {
|
||||
String? contractRet;
|
||||
int? fee;
|
||||
|
||||
Ret({this.contractRet, this.fee});
|
||||
|
||||
Ret.fromJson(Map<String, dynamic> json) {
|
||||
contractRet = json['contractRet'];
|
||||
fee = json['fee'];
|
||||
}
|
||||
}
|
||||
|
||||
class Contract {
|
||||
Parameter? parameter;
|
||||
String? type;
|
||||
|
||||
Contract({this.parameter, this.type});
|
||||
|
||||
Contract.fromJson(Map<String, dynamic> json) {
|
||||
parameter = json['parameter'] != null ? Parameter.fromJson(json['parameter']) : null;
|
||||
type = json['type'];
|
||||
}
|
||||
}
|
||||
|
||||
class Parameter {
|
||||
Value? value;
|
||||
String? typeUrl;
|
||||
|
||||
Parameter({this.value, this.typeUrl});
|
||||
|
||||
Parameter.fromJson(Map<String, dynamic> json) {
|
||||
value = json['value'] != null ? Value.fromJson(json['value']) : null;
|
||||
typeUrl = json['type_url'];
|
||||
}
|
||||
}
|
||||
|
||||
class Value {
|
||||
String? data;
|
||||
String? ownerAddress;
|
||||
String? contractAddress;
|
||||
int? amount;
|
||||
String? toAddress;
|
||||
String? assetName;
|
||||
|
||||
//Getters to extract address for tron transactions
|
||||
/// If the contract address is null, it returns the toAddress
|
||||
/// If it's not null, it decodes the data field and gets the receiver address.
|
||||
String? get receiverAddress {
|
||||
if (contractAddress == null) return toAddress;
|
||||
|
||||
if (data == null) return null;
|
||||
|
||||
return _decodeAddressFromEncodedDataField(data!);
|
||||
}
|
||||
|
||||
//Getters to extract amount for tron transactions
|
||||
/// If the contract address is null, it returns the amount
|
||||
/// If it's not null, it decodes the data field and gets the tx amount.
|
||||
BigInt? get txAmount {
|
||||
if (contractAddress == null) return BigInt.from(amount ?? 0);
|
||||
|
||||
if (data == null) return null;
|
||||
|
||||
return _decodeAmountInvolvedFromEncodedDataField(data!);
|
||||
}
|
||||
|
||||
Value(
|
||||
{this.data,
|
||||
this.ownerAddress,
|
||||
this.contractAddress,
|
||||
this.amount,
|
||||
this.toAddress,
|
||||
this.assetName});
|
||||
|
||||
Value.fromJson(Map<String, dynamic> json) {
|
||||
data = json['data'];
|
||||
ownerAddress = json['owner_address'];
|
||||
contractAddress = json['contract_address'];
|
||||
amount = json['amount'];
|
||||
toAddress = json['to_address'];
|
||||
assetName = json['asset_name'];
|
||||
}
|
||||
|
||||
/// To get the address from the encoded data field
|
||||
String _decodeAddressFromEncodedDataField(String output) {
|
||||
// To get the receiver address from the encoded params
|
||||
output = output.replaceFirst('0x', '').substring(8);
|
||||
final abiCoder = ABICoder.fromType('address');
|
||||
final decoded = abiCoder.decode(AbiParameter.bytes, hex.decode(output));
|
||||
final tronAddress = TronAddress.fromEthAddress((decoded.result as ETHAddress).toBytes());
|
||||
|
||||
return tronAddress.toString();
|
||||
}
|
||||
|
||||
/// To get the amount from the encoded data field
|
||||
BigInt _decodeAmountInvolvedFromEncodedDataField(String output) {
|
||||
output = output.replaceFirst('0x', '').substring(72);
|
||||
final amountAbiCoder = ABICoder.fromType('uint256');
|
||||
final decodedA = amountAbiCoder.decode(AbiParameter.uint256, hex.decode(output));
|
||||
final amount = decodedA.result as BigInt;
|
||||
|
||||
return amount;
|
||||
}
|
||||
}
|
560
cw_tron/lib/tron_wallet.dart
Normal file
560
cw_tron/lib/tron_wallet.dart
Normal file
|
@ -0,0 +1,560 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_core/cake_hive.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_tron/default_tron_tokens.dart';
|
||||
import 'package:cw_tron/file.dart';
|
||||
import 'package:cw_tron/tron_abi.dart';
|
||||
import 'package:cw_tron/tron_balance.dart';
|
||||
import 'package:cw_tron/tron_client.dart';
|
||||
import 'package:cw_tron/tron_exception.dart';
|
||||
import 'package:cw_tron/tron_token.dart';
|
||||
import 'package:cw_tron/tron_transaction_credentials.dart';
|
||||
import 'package:cw_tron/tron_transaction_history.dart';
|
||||
import 'package:cw_tron/tron_transaction_info.dart';
|
||||
import 'package:cw_tron/tron_wallet_addresses.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:on_chain/on_chain.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
part 'tron_wallet.g.dart';
|
||||
|
||||
class TronWallet = TronWalletBase with _$TronWallet;
|
||||
|
||||
abstract class TronWalletBase
|
||||
extends WalletBase<TronBalance, TronTransactionHistory, TronTransactionInfo> with Store {
|
||||
TronWalletBase({
|
||||
required WalletInfo walletInfo,
|
||||
String? mnemonic,
|
||||
String? privateKey,
|
||||
required String password,
|
||||
TronBalance? initialBalance,
|
||||
}) : syncStatus = const NotConnectedSyncStatus(),
|
||||
_password = password,
|
||||
_mnemonic = mnemonic,
|
||||
_hexPrivateKey = privateKey,
|
||||
_client = TronClient(),
|
||||
walletAddresses = TronWalletAddresses(walletInfo),
|
||||
balance = ObservableMap<CryptoCurrency, TronBalance>.of(
|
||||
{CryptoCurrency.trx: initialBalance ?? TronBalance(BigInt.zero)},
|
||||
),
|
||||
super(walletInfo) {
|
||||
this.walletInfo = walletInfo;
|
||||
transactionHistory = TronTransactionHistory(walletInfo: walletInfo, password: password);
|
||||
|
||||
if (!CakeHive.isAdapterRegistered(TronToken.typeId)) {
|
||||
CakeHive.registerAdapter(TronTokenAdapter());
|
||||
}
|
||||
|
||||
sharedPrefs.complete(SharedPreferences.getInstance());
|
||||
}
|
||||
|
||||
final String? _mnemonic;
|
||||
final String? _hexPrivateKey;
|
||||
final String _password;
|
||||
|
||||
late final Box<TronToken> tronTokensBox;
|
||||
|
||||
late final TronPrivateKey _tronPrivateKey;
|
||||
|
||||
late final TronPublicKey _tronPublicKey;
|
||||
|
||||
TronPublicKey get tronPublicKey => _tronPublicKey;
|
||||
|
||||
TronPrivateKey get tronPrivateKey => _tronPrivateKey;
|
||||
|
||||
late String _tronAddress;
|
||||
|
||||
late TronClient _client;
|
||||
|
||||
Timer? _transactionsUpdateTimer;
|
||||
|
||||
@override
|
||||
WalletAddresses walletAddresses;
|
||||
|
||||
@observable
|
||||
String? nativeTxEstimatedFee;
|
||||
|
||||
@observable
|
||||
String? trc20EstimatedFee;
|
||||
|
||||
@override
|
||||
@observable
|
||||
SyncStatus syncStatus;
|
||||
|
||||
@override
|
||||
@observable
|
||||
late ObservableMap<CryptoCurrency, TronBalance> balance;
|
||||
|
||||
Completer<SharedPreferences> sharedPrefs = Completer();
|
||||
|
||||
Future<void> init() async {
|
||||
await initTronTokensBox();
|
||||
|
||||
await walletAddresses.init();
|
||||
await transactionHistory.init();
|
||||
_tronPrivateKey = await getPrivateKey(
|
||||
mnemonic: _mnemonic,
|
||||
privateKey: _hexPrivateKey,
|
||||
password: _password,
|
||||
);
|
||||
|
||||
_tronPublicKey = _tronPrivateKey.publicKey();
|
||||
|
||||
_tronAddress = _tronPublicKey.toAddress().toString();
|
||||
|
||||
walletAddresses.address = _tronAddress;
|
||||
|
||||
await save();
|
||||
}
|
||||
|
||||
static Future<TronWallet> open({
|
||||
required String name,
|
||||
required String password,
|
||||
required WalletInfo walletInfo,
|
||||
}) async {
|
||||
final path = await pathForWallet(name: name, type: walletInfo.type);
|
||||
final jsonSource = await read(path: path, password: password);
|
||||
final data = json.decode(jsonSource) as Map;
|
||||
final mnemonic = data['mnemonic'] as String?;
|
||||
final privateKey = data['private_key'] as String?;
|
||||
final balance = TronBalance.fromJSON(data['balance'] as String) ?? TronBalance(BigInt.zero);
|
||||
|
||||
return TronWallet(
|
||||
walletInfo: walletInfo,
|
||||
password: password,
|
||||
mnemonic: mnemonic,
|
||||
privateKey: privateKey,
|
||||
initialBalance: balance,
|
||||
);
|
||||
}
|
||||
|
||||
void addInitialTokens() {
|
||||
final initialTronTokens = DefaultTronTokens().initialTronTokens;
|
||||
|
||||
for (var token in initialTronTokens) {
|
||||
tronTokensBox.put(token.contractAddress, token);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initTronTokensBox() async {
|
||||
final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${TronToken.boxName}";
|
||||
|
||||
tronTokensBox = await CakeHive.openBox<TronToken>(boxName);
|
||||
}
|
||||
|
||||
String idFor(String name, WalletType type) => '${walletTypeToString(type).toLowerCase()}_$name';
|
||||
|
||||
Future<TronPrivateKey> getPrivateKey({
|
||||
String? mnemonic,
|
||||
String? privateKey,
|
||||
required String password,
|
||||
}) async {
|
||||
assert(mnemonic != null || privateKey != null);
|
||||
|
||||
if (privateKey != null) {
|
||||
return TronPrivateKey(privateKey);
|
||||
}
|
||||
|
||||
final seed = bip39.mnemonicToSeed(mnemonic!);
|
||||
|
||||
// Derive a TRON private key from the seed
|
||||
final bip44 = Bip44.fromSeed(seed, Bip44Coins.tron);
|
||||
|
||||
final childKey = bip44.deriveDefaultPath;
|
||||
|
||||
return TronPrivateKey.fromBytes(childKey.privateKey.raw);
|
||||
}
|
||||
|
||||
@override
|
||||
int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0;
|
||||
|
||||
@override
|
||||
Future<void> changePassword(String password) {
|
||||
throw UnimplementedError("changePassword");
|
||||
}
|
||||
|
||||
@override
|
||||
void close() {
|
||||
_transactionsUpdateTimer?.cancel();
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> connectToNode({required Node node}) async {
|
||||
try {
|
||||
syncStatus = ConnectingSyncStatus();
|
||||
|
||||
final isConnected = _client.connect(node);
|
||||
|
||||
if (!isConnected) {
|
||||
throw Exception("${walletInfo.type.name.toUpperCase()} Node connection failed");
|
||||
}
|
||||
|
||||
_getEstimatedFees();
|
||||
_setTransactionUpdateTimer();
|
||||
|
||||
syncStatus = ConnectedSyncStatus();
|
||||
} catch (e) {
|
||||
syncStatus = FailedSyncStatus();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _getEstimatedFees() async {
|
||||
final nativeFee = await _getNativeTxFee();
|
||||
nativeTxEstimatedFee = TronHelper.fromSun(BigInt.from(nativeFee));
|
||||
|
||||
final trc20Fee = await _getTrc20TxFee();
|
||||
trc20EstimatedFee = TronHelper.fromSun(BigInt.from(trc20Fee));
|
||||
|
||||
log('Native Estimated Fee: $nativeTxEstimatedFee');
|
||||
log('TRC20 Estimated Fee: $trc20EstimatedFee');
|
||||
}
|
||||
|
||||
Future<int> _getNativeTxFee() async {
|
||||
try {
|
||||
final fee = await _client.getEstimatedFee(_tronPublicKey.toAddress());
|
||||
return fee;
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> _getTrc20TxFee() async {
|
||||
try {
|
||||
final trc20fee = await _client.getTRCEstimatedFee(_tronPublicKey.toAddress());
|
||||
return trc20fee;
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> startSync() async {
|
||||
try {
|
||||
syncStatus = AttemptingSyncStatus();
|
||||
await _updateBalance();
|
||||
await fetchTransactions();
|
||||
fetchTrc20ExcludedTransactions();
|
||||
|
||||
syncStatus = SyncedSyncStatus();
|
||||
} catch (e) {
|
||||
syncStatus = FailedSyncStatus();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||
final tronCredentials = credentials as TronTransactionCredentials;
|
||||
|
||||
final outputs = tronCredentials.outputs;
|
||||
|
||||
final hasMultiDestination = outputs.length > 1;
|
||||
|
||||
final CryptoCurrency transactionCurrency =
|
||||
balance.keys.firstWhere((element) => element.title == tronCredentials.currency.title);
|
||||
|
||||
final walletBalanceForCurrency = balance[transactionCurrency]!.balance;
|
||||
|
||||
BigInt totalAmount = BigInt.zero;
|
||||
bool shouldSendAll = false;
|
||||
if (hasMultiDestination) {
|
||||
if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
|
||||
throw TronTransactionCreationException(transactionCurrency);
|
||||
}
|
||||
|
||||
final totalAmountFromCredentials =
|
||||
outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0));
|
||||
|
||||
totalAmount = BigInt.from(totalAmountFromCredentials);
|
||||
|
||||
if (walletBalanceForCurrency < totalAmount) {
|
||||
throw TronTransactionCreationException(transactionCurrency);
|
||||
}
|
||||
} else {
|
||||
final output = outputs.first;
|
||||
|
||||
shouldSendAll = output.sendAll;
|
||||
|
||||
if (shouldSendAll) {
|
||||
totalAmount = walletBalanceForCurrency;
|
||||
} else {
|
||||
final totalOriginalAmount = double.parse(output.cryptoAmount ?? '0.0');
|
||||
totalAmount = TronHelper.toSun(totalOriginalAmount.toString());
|
||||
}
|
||||
|
||||
if (walletBalanceForCurrency < totalAmount || totalAmount < BigInt.zero) {
|
||||
throw TronTransactionCreationException(transactionCurrency);
|
||||
}
|
||||
}
|
||||
|
||||
final tronBalance = balance[CryptoCurrency.trx]?.balance ?? BigInt.zero;
|
||||
|
||||
final pendingTransaction = await _client.signTransaction(
|
||||
ownerPrivKey: _tronPrivateKey,
|
||||
toAddress: tronCredentials.outputs.first.isParsedAddress
|
||||
? tronCredentials.outputs.first.extractedAddress!
|
||||
: tronCredentials.outputs.first.address,
|
||||
amount: TronHelper.fromSun(totalAmount),
|
||||
currency: transactionCurrency,
|
||||
tronBalance: tronBalance,
|
||||
sendAll: shouldSendAll,
|
||||
);
|
||||
|
||||
return pendingTransaction;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, TronTransactionInfo>> fetchTransactions() async {
|
||||
final address = _tronAddress;
|
||||
|
||||
final transactions = await _client.fetchTransactions(address);
|
||||
|
||||
final Map<String, TronTransactionInfo> result = {};
|
||||
|
||||
final contract = ContractABI.fromJson(trc20Abi, isTron: true);
|
||||
|
||||
final ownerAddress = TronAddress(_tronAddress);
|
||||
|
||||
for (var transactionModel in transactions) {
|
||||
if (transactionModel.isError) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String? tokenSymbol;
|
||||
if (transactionModel.contractAddress != null) {
|
||||
final tokenAddress = TronAddress(transactionModel.contractAddress!);
|
||||
|
||||
tokenSymbol = (await _client.getTokenDetail(
|
||||
contract,
|
||||
"symbol",
|
||||
ownerAddress,
|
||||
tokenAddress,
|
||||
) as String?) ??
|
||||
'';
|
||||
}
|
||||
|
||||
result[transactionModel.hash] = TronTransactionInfo(
|
||||
id: transactionModel.hash,
|
||||
tronAmount: transactionModel.amount ?? BigInt.zero,
|
||||
direction: TronAddress(transactionModel.from!, visible: false).toAddress() == address
|
||||
? TransactionDirection.outgoing
|
||||
: TransactionDirection.incoming,
|
||||
blockTime: transactionModel.date,
|
||||
txFee: transactionModel.fee,
|
||||
tokenSymbol: tokenSymbol ?? "TRX",
|
||||
to: transactionModel.to,
|
||||
from: transactionModel.from,
|
||||
isPending: false,
|
||||
);
|
||||
}
|
||||
|
||||
transactionHistory.addMany(result);
|
||||
|
||||
await transactionHistory.save();
|
||||
|
||||
return transactionHistory.transactions;
|
||||
}
|
||||
|
||||
Future<void> fetchTrc20ExcludedTransactions() async {
|
||||
final address = _tronAddress;
|
||||
|
||||
final transactions = await _client.fetchTrc20ExcludedTransactions(address);
|
||||
|
||||
final Map<String, TronTransactionInfo> result = {};
|
||||
|
||||
for (var transactionModel in transactions) {
|
||||
if (transactionHistory.transactions.containsKey(transactionModel.hash)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result[transactionModel.hash] = TronTransactionInfo(
|
||||
id: transactionModel.hash,
|
||||
tronAmount: transactionModel.amount ?? BigInt.zero,
|
||||
direction: transactionModel.from! == address
|
||||
? TransactionDirection.outgoing
|
||||
: TransactionDirection.incoming,
|
||||
blockTime: transactionModel.date,
|
||||
txFee: transactionModel.fee,
|
||||
tokenSymbol: transactionModel.tokenSymbol ?? "TRX",
|
||||
to: transactionModel.to,
|
||||
from: transactionModel.from,
|
||||
isPending: false,
|
||||
);
|
||||
}
|
||||
|
||||
transactionHistory.addMany(result);
|
||||
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
@override
|
||||
Object get keys => throw UnimplementedError("keys");
|
||||
|
||||
@override
|
||||
Future<void> rescan({required int height}) {
|
||||
throw UnimplementedError("rescan");
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
await walletAddresses.updateAddressesInBox();
|
||||
final path = await makePath();
|
||||
await write(path: path, password: _password, data: toJSON());
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
@override
|
||||
String? get seed => _mnemonic;
|
||||
|
||||
@override
|
||||
String get privateKey => _tronPrivateKey.toHex();
|
||||
|
||||
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
||||
|
||||
String toJSON() => json.encode({
|
||||
'mnemonic': _mnemonic,
|
||||
'private_key': privateKey,
|
||||
'balance': balance[currency]!.toJSON(),
|
||||
});
|
||||
|
||||
Future<void> _updateBalance() async {
|
||||
balance[currency] = await _fetchTronBalance();
|
||||
|
||||
await _fetchTronTokenBalances();
|
||||
await save();
|
||||
}
|
||||
|
||||
Future<TronBalance> _fetchTronBalance() async {
|
||||
final balance = await _client.getBalance(_tronPublicKey.toAddress());
|
||||
return TronBalance(balance);
|
||||
}
|
||||
|
||||
Future<void> _fetchTronTokenBalances() async {
|
||||
for (var token in tronTokensBox.values) {
|
||||
try {
|
||||
if (token.enabled) {
|
||||
balance[token] = await _client.fetchTronTokenBalances(
|
||||
_tronAddress,
|
||||
token.contractAddress,
|
||||
);
|
||||
} else {
|
||||
balance.remove(token);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void>? updateBalance() async => await _updateBalance();
|
||||
|
||||
List<TronToken> get tronTokenCurrencies => tronTokensBox.values.toList();
|
||||
|
||||
Future<void> addTronToken(TronToken token) async {
|
||||
String? iconPath;
|
||||
try {
|
||||
iconPath = CryptoCurrency.all
|
||||
.firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
|
||||
.iconPath;
|
||||
} catch (_) {}
|
||||
|
||||
final newToken = TronToken(
|
||||
name: token.name,
|
||||
symbol: token.symbol,
|
||||
contractAddress: token.contractAddress,
|
||||
decimal: token.decimal,
|
||||
enabled: token.enabled,
|
||||
tag: token.tag ?? "TRX",
|
||||
iconPath: iconPath,
|
||||
);
|
||||
|
||||
await tronTokensBox.put(newToken.contractAddress, newToken);
|
||||
|
||||
if (newToken.enabled) {
|
||||
balance[newToken] = await _client.fetchTronTokenBalances(
|
||||
_tronAddress,
|
||||
newToken.contractAddress,
|
||||
);
|
||||
} else {
|
||||
balance.remove(newToken);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteTronToken(TronToken token) async {
|
||||
await token.delete();
|
||||
|
||||
balance.remove(token);
|
||||
await _removeTokenTransactionsInHistory(token);
|
||||
_updateBalance();
|
||||
}
|
||||
|
||||
Future<void> _removeTokenTransactionsInHistory(TronToken token) async {
|
||||
transactionHistory.transactions.removeWhere((key, value) => value.tokenSymbol == token.title);
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
Future<TronToken?> getTronToken(String contractAddress) async =>
|
||||
await _client.getTronToken(contractAddress, _tronAddress);
|
||||
|
||||
@override
|
||||
Future<void> renameWalletFiles(String newWalletName) async {
|
||||
String transactionHistoryFileNameForWallet = 'tron_transactions.json';
|
||||
|
||||
final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
|
||||
final currentWalletFile = File(currentWalletPath);
|
||||
|
||||
final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
|
||||
final currentTransactionsFile = File('$currentDirPath/$transactionHistoryFileNameForWallet');
|
||||
|
||||
// Copies current wallet files into new wallet name's dir and files
|
||||
if (currentWalletFile.existsSync()) {
|
||||
final newWalletPath = await pathForWallet(name: newWalletName, type: type);
|
||||
await currentWalletFile.copy(newWalletPath);
|
||||
}
|
||||
if (currentTransactionsFile.existsSync()) {
|
||||
final newDirPath = await pathForWalletDir(name: newWalletName, type: type);
|
||||
await currentTransactionsFile.copy('$newDirPath/$transactionHistoryFileNameForWallet');
|
||||
}
|
||||
|
||||
// Delete old name's dir and files
|
||||
await Directory(currentDirPath).delete(recursive: true);
|
||||
}
|
||||
|
||||
void _setTransactionUpdateTimer() {
|
||||
if (_transactionsUpdateTimer?.isActive ?? false) {
|
||||
_transactionsUpdateTimer!.cancel();
|
||||
}
|
||||
|
||||
_transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 20), (_) async {
|
||||
_updateBalance();
|
||||
await fetchTransactions();
|
||||
fetchTrc20ExcludedTransactions();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
String signMessage(String message, {String? address}) =>
|
||||
_tronPrivateKey.signPersonalMessage(ascii.encode(message));
|
||||
|
||||
String getTronBase58AddressFromHex(String hexAddress) {
|
||||
return TronAddress(hexAddress).toAddress();
|
||||
}
|
||||
}
|
36
cw_tron/lib/tron_wallet_addresses.dart
Normal file
36
cw_tron/lib/tron_wallet_addresses.dart
Normal file
|
@ -0,0 +1,36 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'tron_wallet_addresses.g.dart';
|
||||
|
||||
class TronWalletAddresses = TronWalletAddressesBase with _$TronWalletAddresses;
|
||||
|
||||
abstract class TronWalletAddressesBase extends WalletAddresses with Store {
|
||||
TronWalletAddressesBase(WalletInfo walletInfo)
|
||||
: address = '',
|
||||
super(walletInfo);
|
||||
|
||||
@override
|
||||
@observable
|
||||
String address;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
address = walletInfo.address;
|
||||
await updateAddressesInBox();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateAddressesInBox() async {
|
||||
try {
|
||||
addressesMap.clear();
|
||||
addressesMap[address] = '';
|
||||
await saveAddressesInBox();
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
29
cw_tron/lib/tron_wallet_creation_credentials.dart
Normal file
29
cw_tron/lib/tron_wallet_creation_credentials.dart
Normal file
|
@ -0,0 +1,29 @@
|
|||
import 'package:cw_core/wallet_credentials.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
|
||||
class TronNewWalletCredentials extends WalletCredentials {
|
||||
TronNewWalletCredentials({required String name, WalletInfo? walletInfo})
|
||||
: super(name: name, walletInfo: walletInfo);
|
||||
}
|
||||
|
||||
class TronRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||
TronRestoreWalletFromSeedCredentials(
|
||||
{required String name,
|
||||
required String password,
|
||||
required this.mnemonic,
|
||||
WalletInfo? walletInfo})
|
||||
: super(name: name, password: password, walletInfo: walletInfo);
|
||||
|
||||
final String mnemonic;
|
||||
}
|
||||
|
||||
class TronRestoreWalletFromPrivateKey extends WalletCredentials {
|
||||
TronRestoreWalletFromPrivateKey(
|
||||
{required String name,
|
||||
required String password,
|
||||
required this.privateKey,
|
||||
WalletInfo? walletInfo})
|
||||
: super(name: name, password: password, walletInfo: walletInfo);
|
||||
|
||||
final String privateKey;
|
||||
}
|
148
cw_tron/lib/tron_wallet_service.dart
Normal file
148
cw_tron/lib/tron_wallet_service.dart
Normal file
|
@ -0,0 +1,148 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
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_tron/tron_client.dart';
|
||||
import 'package:cw_tron/tron_exception.dart';
|
||||
import 'package:cw_tron/tron_wallet.dart';
|
||||
import 'package:cw_tron/tron_wallet_creation_credentials.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
class TronWalletService extends WalletService<TronNewWalletCredentials,
|
||||
TronRestoreWalletFromSeedCredentials, TronRestoreWalletFromPrivateKey> {
|
||||
TronWalletService(this.walletInfoSource, {required this.client});
|
||||
|
||||
late TronClient client;
|
||||
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
|
||||
@override
|
||||
WalletType getType() => WalletType.tron;
|
||||
|
||||
@override
|
||||
Future<TronWallet> create(
|
||||
TronNewWalletCredentials credentials, {
|
||||
bool? isTestnet,
|
||||
}) async {
|
||||
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
||||
|
||||
final mnemonic = bip39.generateMnemonic(strength: strength);
|
||||
|
||||
final wallet = TronWallet(
|
||||
walletInfo: credentials.walletInfo!,
|
||||
mnemonic: mnemonic,
|
||||
password: credentials.password!,
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TronWallet> openWallet(String name, String password) async {
|
||||
final walletInfo =
|
||||
walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
|
||||
|
||||
try {
|
||||
final wallet = await TronWalletBase.open(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
await wallet.save();
|
||||
saveBackup(name);
|
||||
return wallet;
|
||||
} catch (_) {
|
||||
await restoreWalletFilesFromBackup(name);
|
||||
|
||||
final wallet = await TronWalletBase.open(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
await wallet.save();
|
||||
return wallet;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TronWallet> restoreFromKeys(
|
||||
TronRestoreWalletFromPrivateKey credentials, {
|
||||
bool? isTestnet,
|
||||
}) async {
|
||||
final wallet = TronWallet(
|
||||
password: credentials.password!,
|
||||
privateKey: credentials.privateKey,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TronWallet> restoreFromSeed(
|
||||
TronRestoreWalletFromSeedCredentials credentials, {
|
||||
bool? isTestnet,
|
||||
}) async {
|
||||
if (!bip39.validateMnemonic(credentials.mnemonic)) {
|
||||
throw TronMnemonicIsIncorrectException();
|
||||
}
|
||||
|
||||
final wallet = TronWallet(
|
||||
password: credentials.password!,
|
||||
mnemonic: credentials.mnemonic,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> rename(String currentName, String password, String newName) async {
|
||||
final currentWalletInfo = walletInfoSource.values
|
||||
.firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
|
||||
final currentWallet = await TronWalletBase.open(
|
||||
password: password, name: currentName, walletInfo: currentWalletInfo);
|
||||
|
||||
await currentWallet.renameWalletFiles(newName);
|
||||
await saveBackup(newName);
|
||||
|
||||
final newWalletInfo = currentWalletInfo;
|
||||
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
||||
newWalletInfo.name = newName;
|
||||
|
||||
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isWalletExit(String name) async =>
|
||||
File(await pathForWallet(name: name, type: getType())).existsSync();
|
||||
|
||||
@override
|
||||
Future<void> remove(String wallet) async {
|
||||
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
|
||||
final walletInfo = walletInfoSource.values
|
||||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
|
||||
await walletInfoSource.delete(walletInfo.key);
|
||||
}
|
||||
}
|
33
cw_tron/pubspec.yaml
Normal file
33
cw_tron/pubspec.yaml
Normal file
|
@ -0,0 +1,33 @@
|
|||
name: cw_tron
|
||||
description: A new Flutter package project.
|
||||
version: 0.0.1
|
||||
publish_to: none
|
||||
homepage: https://cakewallet.com
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.6 <4.0.0'
|
||||
flutter: ">=1.17.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
cw_core:
|
||||
path: ../cw_core
|
||||
cw_evm:
|
||||
path: ../cw_evm
|
||||
on_chain: ^3.0.1
|
||||
blockchain_utils: ^2.1.1
|
||||
mobx: ^2.3.0+1
|
||||
bip39: ^1.0.6
|
||||
hive: ^2.2.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^2.0.0
|
||||
build_runner: ^2.3.3
|
||||
mobx_codegen: ^2.1.1
|
||||
hive_generator: ^1.1.3
|
||||
flutter:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
12
cw_tron/test/cw_tron_test.dart
Normal file
12
cw_tron/test/cw_tron_test.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:cw_tron/cw_tron.dart';
|
||||
|
||||
void main() {
|
||||
test('adds one to input values', () {
|
||||
final calculator = Calculator();
|
||||
expect(calculator.addOne(2), 3);
|
||||
expect(calculator.addOne(-7), -6);
|
||||
expect(calculator.addOne(0), 1);
|
||||
});
|
||||
}
|
300
how_to_add_new_wallet_type.md
Normal file
300
how_to_add_new_wallet_type.md
Normal file
|
@ -0,0 +1,300 @@
|
|||
# Guide to adding a new wallet type in Cake Wallet
|
||||
|
||||
## Wallet Integration
|
||||
|
||||
**N:B** Throughout this guide, `walletx` refers to the specific wallet type you want to add. If you're adding `BNB` to CakeWallet, then `walletx` for you here is `bnb`.
|
||||
|
||||
**Core Folder/Files Setup**
|
||||
- Idenitify your core component/package (major project component), which would power the integration e.g web3dart, solana, onchain etc
|
||||
- Add a new entry to `WalletType` class in `cw_core/wallet_type.dart`.
|
||||
- Fill out the necessary information int he various functions in the files, concerning the wallet name, the native currency type, symbol etc.
|
||||
- Go to `cw_core/lib/currency_for_wallet_type.dart`, in the `currencyForWalletType` function, add a case for `walletx`, returning the native cryptocurrency for `walletx`.
|
||||
- If the cryptocurrency for walletx is not available among the default cryptocurrencies, add a new cryptocurrency entry in `cw_core/lib/cryptocurrency.dart`.
|
||||
- Add the newly created cryptocurrency name to the list named `all` in this file.
|
||||
- Create a package for the wallet specific integration, name it. `cw_walletx`
|
||||
- Add the following initial common files and replicate to fit the wallet
|
||||
- walletx_transaction_history.dart
|
||||
- walletx_transaction_info.dart
|
||||
- walletx_mnemonics_exception.dart
|
||||
- walletx_tokens.dart
|
||||
- walletx_wallet_service.dart:
|
||||
- walletx_wallet.dart
|
||||
- etc.
|
||||
|
||||
- Add the code to run the code generation needed for the files in the `cw_walletx` package to the `model_generator.sh` script
|
||||
|
||||
cd cw_walletx && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
|
||||
- Add the relevant dev_dependencies for generating the files also
|
||||
- build_runner
|
||||
- mobx_codegen
|
||||
- hive_generator
|
||||
|
||||
**WalletX Proxy Setup**
|
||||
|
||||
A `Proxy` class is used to communicate with the specific wallet package we have. Instead of directly making use of methods and parameters in `cw_walletx` within the `lib` directory, we use a proxy to access these data. All important functions, calls and interactions we want to make with our `cw_walletx` package would be defined and done through the proxy class. The class would define the import
|
||||
|
||||
- Create a proxy folder titled `walletx` to handle the wallet operations. It would contain 2 files: `cw_walletx.dart` and `walletx.dart`.
|
||||
- `cw_walletx.dart` file would hold an implementation class containing major operations to be done in the lib directory. It serves as the link between the cw_walletx package and the rest of the codebase(lib directory files and folders).
|
||||
- `walletx.dart` would contain the abstract class highlighting the methods that would bring the functionalities and features in the `cw_walletx` package to the rest of the `lib` directory.
|
||||
- Add `walletx.dart` to `.gitignore` as we won’t be pushing it: `lib/tron/tron.dart`.
|
||||
- `walletx.dart` would always be generated based on the configure files we would be setting up in the next step.
|
||||
|
||||
**Configuration Files Setup**
|
||||
- Before we populate the field, head over to `tool/configure.dart` to setup the necessary configurations for the `walletx` proxy.
|
||||
- Define the output path, it’ll follow the format `lib/walletx/walletx.dart`.
|
||||
- Add the variable to check if `walletx` is to be activated
|
||||
- Define the function that would generate the abstract class for the proxy.(We will flesh out this function in the next steps).
|
||||
- Add the defined variable in step 2 to the `generatePubspec` and `generateWalletTypes`.
|
||||
- Next, modify the following functions:
|
||||
- generatePubspec function
|
||||
1. Add the parameters to the method params (i.e required bool hasWalletX)
|
||||
2. Define a variable to hold the entry for the pubspec.yaml file
|
||||
|
||||
const cwWalletX = """
|
||||
cw_tron:
|
||||
path: ./cw_walletx
|
||||
""";
|
||||
|
||||
3. Add an if block that takes in the passed parameter and adds the defined variable(inn the previous step) to the list of outputs
|
||||
|
||||
if (hasWalletX) {
|
||||
output += '\n$cwWalletX’;
|
||||
}
|
||||
|
||||
- generateWalletTypes function
|
||||
1. Add the parameters to the method params (i.e required bool hasWalletX)
|
||||
2. Add an if block to add the wallet type to the list of outputs this function generates
|
||||
|
||||
if (hasWalletX) {
|
||||
outputContent += '\tWalletType.walletx,\n’;
|
||||
}
|
||||
|
||||
- Head over to `scripts/android/pubspec.sh` script, and modify the `CONFIG_ARGS` under `$CAKEWALLET`. Add `"—walletx”` to the end of the passed in params.
|
||||
- Repeat this in `scripts/ios/app_config.sh` and `scripts/macos/app_config.sh`
|
||||
- Open a terminal and cd into `scripts/android/`. Run the following commands to run setup configuration scripts(proxy class, add walletx to list of wallet types and add cw_walletx to pubspec).
|
||||
|
||||
source ./app_env.sh cakewallet
|
||||
|
||||
./app_config.sh
|
||||
|
||||
cd cw_walletx && flutter pub get && flutter packages pub run build_runner build
|
||||
|
||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
|
||||
Moving forward, our interactions with the cw_walletx package would be through the proxy class and its methods.
|
||||
|
||||
**Pre-Wallet Creation for WalletX**
|
||||
- Go to `di.dart` and locate the block to `registerWalletService`. In this, add the case to handle creating the WalletXWalletService
|
||||
|
||||
case WalletType.walletx:
|
||||
return walletx!.createWalletXWalletService(_walletInfoSource);
|
||||
|
||||
- Go to `lib/view_model/wallet_new_vm.dart`, in the getCredentials method, which gets the new wallet credentials for walletX add the case for the new wallet
|
||||
|
||||
case WalletType.walletx:
|
||||
return walletx!.createWalletXNewWalletCredentials(name: name);
|
||||
|
||||
**Node Setup**
|
||||
- Before we can be able to successfully create a new wallet of wallet type walletx we need to setup the node that the wallet would use:
|
||||
- In the assets directory, create a new file and name it `walletx_node_list.yml`. This yml file would contain the details for nodes to be used for walletX. An example structure for each node entry
|
||||
|
||||
uri: "api.nodeurl.io"
|
||||
is_default: true
|
||||
useSSL: true
|
||||
|
||||
You can add as many node entries as desired.
|
||||
|
||||
- Add the path to the yml file created to the `pubspec_base.yaml` file (`“assets/walletx_node_list.yml”`)
|
||||
- Go to `lib/entities/node_list.dart`, add a function to load the node entries we made in `walletx_node_list.yml` for walletx.
|
||||
- Name your function `loadDefaultWalletXNodes()`. The function would handle loading the yml file as a string and parsing it into a Node Object to be used within the app. Here’s a template for the function.
|
||||
|
||||
Future<List<Node>> loadDefaultWalletXNodes() async {
|
||||
final nodesRaw = await rootBundle.loadString('assets/tron_node_list.yml');
|
||||
final loadedNodes = loadYaml(nodesRaw) as YamlList;
|
||||
final nodes = <Node>[];
|
||||
for (final raw in loadedNodes) {
|
||||
if (raw is Map) {
|
||||
final node = Node.fromMap(Map<String, Object>.from(raw));
|
||||
node.type = WalletType.tron;
|
||||
nodes.add(node);
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
- Inside the `resetToDefault` function, call the function you created and add the result to the nodes result variable.
|
||||
- Go to `lib/entities/default_settings_migration.dart` file, we’ll be adding the following to the file.
|
||||
- At the top of the file, after the imports, define the default nodeUrl for wallet-name.
|
||||
- Next, write a function to fetch the node for this default uri you added above.
|
||||
|
||||
Node? getWalletXDefaultNode({required Box<Node> nodes}) {
|
||||
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == walletXDefaultNodeUri) ??
|
||||
nodes.values.firstWhereOrNull((node) => node.type == WalletType.walletx);
|
||||
}
|
||||
|
||||
- Next, write a function that will add the list of nodes we declared in the `walletx_node_list.yml` file to the Nodes Box, to be used in the app. Here’s the format for this function
|
||||
|
||||
Future<void> addWalletXNodeList({required Box<Node> nodes}) async {
|
||||
final nodeList = await loadDefaultWalletXNodes();
|
||||
for (var node in nodeList) {
|
||||
if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
|
||||
await nodes.add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- Next, we’ll write the function to change walletX current node to default. An handy function we would make use of later on. Add a new preference key in `lib/entities/preference_key.dart` with the format `PreferencesKey.currentWalletXNodeIdKey`, we’ll use it to identify the current node id.
|
||||
|
||||
Future<void> changeWalletXCurrentNodeToDefault(
|
||||
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
|
||||
final node = getWalletXDefaultNode(nodes: nodes);
|
||||
final nodeId = node?.key as int? ?? 0;
|
||||
await sharedPreferences.setInt(PreferencesKey.currentWalletXNodeIdKey, nodeId);
|
||||
}
|
||||
|
||||
- Next, in the `defaultSettingsMigration` function at the top of the file, add a new case to handle both `addWalletXNodeList` and `changeWalletXCurrentNodeToDefault`
|
||||
|
||||
case “next-number-increment”:
|
||||
await addWalletXNodeList(nodes: nodes);
|
||||
await changeWalletXCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
|
||||
break;
|
||||
|
||||
- Next, increase the `initialMigrationVersion` number in `main.dart` to be the new case entry number you entered in the step above for the `defaultSettingsMigration` function.
|
||||
- Next, go to `lib/view_model/node_list/node_list_view_model.dart`
|
||||
- In the `reset` function, add a case for walletX:
|
||||
|
||||
case WalletType.tron:
|
||||
node = getTronDefaultNode(nodes: _nodeSource)!;
|
||||
break;
|
||||
|
||||
- Lastly, go to `cw_core/lib/node.dart`,
|
||||
- In the uri getter, add a case to handle the uri setup for walletX. If the node uses http, return `Uri.http`, if not, return `Uri.https`
|
||||
|
||||
case WalletType.walletX:
|
||||
return Uri.https(uriRaw, ‘’);
|
||||
|
||||
- Also, in the `requestNode` method, add a case for `WalletType.walletx`
|
||||
- Next is the modifications to `lib/store/settings_store.dart` file:
|
||||
- In the `load` function, create a variable to fetch the currentWalletxNodeId using the `PreferencesKey.currentWalletXNodeIdKey` we created earlier.
|
||||
- Create another variable `walletXNode` which gets the walletx node using the nodeId variable assigned in the step above.
|
||||
- Add a check to see if walletXNode is not null, if it’s not null, assign the created tronNode variable to the nodeMap with a type of walletX
|
||||
|
||||
final walletXNode = nodeSource.get(walletXNodeId);
|
||||
final walletXNodeId = sharedPreferences.getInt(PreferencesKey.currentWalletXNodeIdKey);
|
||||
if (walletXNode != null) {
|
||||
nodes[WalletType.walletx] = walletXNode;
|
||||
}
|
||||
|
||||
- Repeat the steps above in the `reload` function
|
||||
- Next, add a case for walletX in the `_saveCurrentNode` function.
|
||||
|
||||
- Run the following commands after to generate modified files in cw_core and lib
|
||||
|
||||
cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
|
||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
|
||||
- Lastly, before we run the app to test what we’ve done so far,
|
||||
- Go to `lib/src/dashboard/widgets/menu_widget.dart` and add an icon for walletX to be used within the app.
|
||||
- Go to `lib/src/screens/wallet_list/wallet_list_page.dart` and add an icon for walletx, add a case for walletx also in the `imageFor` method.
|
||||
- Do the same thing in `lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart`
|
||||
|
||||
- One last thing before we can create a wallet for walletx, go to `lib/view_model/wallet_new_vm.dart`
|
||||
- Modify the `seedPhraseWordsLength` getter by adding a case for `WalletType.walletx`
|
||||
|
||||
Now you can run the codebase and successfully create a wallet for type walletX successfully.
|
||||
|
||||
**Display Seeds/Keys**
|
||||
- Next, we want to set up our wallet to display the seeds and/or keys in the security page of the app.
|
||||
- Go to `lib/view_model/wallet_keys_view_model.dart`
|
||||
- Modify the `populateItems` function by adding a case for `WalletType.walletx` in it.
|
||||
- Now your seeds and/or keys should display when you go to Security and Backup -> Show seed/keys page within the app.
|
||||
|
||||
**Restore Wallet**
|
||||
- Go to `lib/core/seed_validator.dart`
|
||||
- In the `getWordList` method, add a case to handle `WalletType.walletx` which would return the word list to be used to validate the passed in seeds.
|
||||
- Next, go to `lib/restore_view_model.dart`
|
||||
- Modify the `hasRestoreFromPrivateKey` to reflect if walletx supports restore from Key
|
||||
- Add a switch case to handle the various restore modes that walletX supports
|
||||
- Modify the `getCredential` method to handle the restore flows for `WalletType.walletx`
|
||||
- Run the build_runner code generation command
|
||||
|
||||
**Receive**
|
||||
- Go to `lib/view_model/wallet_address_list/wallet_address_list_view_model.dart`
|
||||
- Create an implementation of `PaymentUri` for type WalletX.
|
||||
- In the uri getter, add a case for `WalletType.walletx` returning the implementation class for `PaymentUri`
|
||||
- Modify the `addressList` getter to return the address/addresses for walletx
|
||||
|
||||
**Balance Screen**
|
||||
- Go to `lib/view_model/dashboard/balance_view_model.dart`
|
||||
- Modify the function to adjust the way the balance is being display on the app: `isHomeScreenSettingsEnabled`
|
||||
- Add a case to the `availableBalanceLabel` getter to modify the text being displayed (Available or confirmed)
|
||||
- Same for `additionalBalanceLabel`
|
||||
- Next, go to `lib/reactions/fiat_rate_update.dart`
|
||||
- Modify the `startFiatRateUpdate` function and add a check for `WalletType.walletx` to return all the token currencies
|
||||
- Next, go to `lib/reactions/on_current_wallet_change.dart`
|
||||
- Modify the `startCurrentWalletChangeReaction` function and add a check for `WalletType.walletx` to return all the token currencies
|
||||
- Lastly, go to `lib/view_model/dashboard/transaction_list_item.dart`
|
||||
- In the `formattedFiatAmount` getter, add a case to handle the fiat amount conversion for `WalletType.walletx`
|
||||
|
||||
**Send ViewModel**
|
||||
- Go to `lib/view_model/send/send_view_model.dart`
|
||||
- Modify the `_credentials` function to reflect `WalletType.walletx`
|
||||
- Modify `hasMultipleTokens` to reflect wallets
|
||||
|
||||
**Exchange**
|
||||
- Go to lib/view_model/exchange/exchange_view_model.dart
|
||||
- First, add a case for WalletType.walletx in the `initialPairBasedOnWallet` method.
|
||||
- If WalletX supports tokens, go to `lib/view_model/exchange/exchange_trade_view_model.dart`
|
||||
- Modify the `_checkIfCanSend` method by creating a `_isWalletXToken` that checks if the from currency is WalletX and if its tag is for walletx
|
||||
- Add `_isWalletXToken` to the return logic for the method.
|
||||
|
||||
**Secrets**
|
||||
- Create a json file named `wallet-secrets-config.json` and put an empty curly bracket “{}” in it
|
||||
- Add a new entry to `tool/utils/secret_key.dart` for walletx
|
||||
- Modify the `tool/generate_secrets_config.dart` file for walletx, don’t forget to call `secrets.clear()` before adding a new set of generation logic
|
||||
- Modify the `tool/import_secrets_config.dart` file for walletx
|
||||
- In the `.gitignore` file, add `**/tool/.walletx-secrets-config.json` and `**/cw_walletx/lib/.secrets.g.dart`
|
||||
|
||||
**HomeSettings: WalletX Tokens Display and Management**
|
||||
- Go to `lib/view_model/dashboard/home_settings_view_model.dart`
|
||||
- Modify the `_updateTokensList` method to add all walletx tokens if the wallet type is `WalletType.walletx`.
|
||||
- Modify the `getTokenAddressBasedOnWallet` method to include a case to fetch the address for a WalletX token.
|
||||
- Modify the `getToken` method to return a specific walletx token
|
||||
- Modify the `addToken`, `deleteToken` and `changeTokenAvailability` methods to handle cases where the walletType is walletx
|
||||
|
||||
**Buy and Sell WalletX**
|
||||
- Go to `lib/entities/provider_types.dart`
|
||||
- Add a case for `WalletType.walletx` in the `getAvailableBuyProviderTypes` method. Return a list of providers that support buying WalletX.
|
||||
- Add a case for `WalletType.walletx` in the `getAvailableSellProviderTypes` method. Return a list of providers that support selling WalletX.
|
||||
|
||||
**Restore QR setup**
|
||||
- Go to `lib/view_model/restore/wallet_restore_from_qr_code.dart`
|
||||
- Add the scheme for walletx in `_walletTypeMap`
|
||||
- Also modify `_determineWalletRestoreMode` to include a case for walletx
|
||||
- Go to `lib/view_model/restore/restore_from_qr_vm.dart`
|
||||
- Modify `getCredentialsFromRestoredWallet` method
|
||||
- Go to `lib/core/address_validator.dart`
|
||||
- Modify the `getAddressFromStringPattern` method to add a case for `WalletType.walletx`
|
||||
- Add the scheme for walletx for both Android in `AndroidManifestBase.xml` and iOS in `InfoBase.plist`
|
||||
|
||||
**Transaction History**
|
||||
- Go to `lib/view_model/transaction_details_view_model.dart`
|
||||
- Add a case for `WalletType.walletx` to add the items to be displayed on the detailed view
|
||||
- Modify the `_explorerUrl` method to add the blockchain explorer link for WalletX in order to view the more info on a transaction
|
||||
- Modify the `_explorerDescription` to display the name of the explorer
|
||||
|
||||
|
||||
|
||||
|
||||
# Points to note when adding the new wallet type
|
||||
|
||||
1. if it has tokens (ex. ERC20, SPL, etc...) make sure to add that to this function `_checkIfCanSend` in `exchange_trade_view_model.dart`
|
||||
2. Check On/Off ramp providers that support the new wallet currency and add them accordingly in `provider_types.dart`
|
||||
3. Add support for wallet uri scheme to restore from QR for both Android in `AndroidManifestBase.xml` and iOS in `InfoBase.plist`
|
||||
4. Make sure no imports are using the wallet internal package files directly, instead use the proxy layers that is created in the main lib `lib/cw_ethereum.dart` for example. (i.e try building Monero.com if you get compilation errors, then you probably missed something)
|
||||
5.
|
||||
|
||||
|
||||
Copyright (C) 2018-2023 Cake Labs LLC
|
|
@ -190,6 +190,36 @@
|
|||
<string>solana-wallet</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>tron</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>tron</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>tron-wallet</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>tron-wallet</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>tron_wallet</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>tron_wallet</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
|
|
|
@ -294,6 +294,8 @@ class AddressValidator extends TextValidator {
|
|||
'|([^0-9a-zA-Z]|^)q[0-9a-zA-Z]{42}([^0-9a-zA-Z]|\$)';
|
||||
case CryptoCurrency.sol:
|
||||
return '([^0-9a-zA-Z]|^)[1-9A-HJ-NP-Za-km-z]{43,44}([^0-9a-zA-Z]|\$)';
|
||||
case CryptoCurrency.trx:
|
||||
return '^(T|t)[1-9A-HJ-NP-Za-km-z]{33}\$';
|
||||
default:
|
||||
if (type.tag == CryptoCurrency.eth.title) {
|
||||
return '0x[0-9a-zA-Z]{42}';
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:cake_wallet/core/validator.dart';
|
|||
import 'package:cake_wallet/entities/mnemonic_item.dart';
|
||||
import 'package:cake_wallet/polygon/polygon.dart';
|
||||
import 'package:cake_wallet/solana/solana.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/nano/nano.dart';
|
||||
|
@ -40,6 +41,8 @@ class SeedValidator extends Validator<MnemonicItem> {
|
|||
return polygon!.getPolygonWordList(language);
|
||||
case WalletType.solana:
|
||||
return solana!.getSolanaWordList(language);
|
||||
case WalletType.tron:
|
||||
return tron!.getTronWordList(language);
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import 'package:cake_wallet/core/yat_service.dart';
|
|||
import 'package:cake_wallet/entities/background_tasks.dart';
|
||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
|
||||
import 'package:cw_core/receive_page_option.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
|
@ -873,6 +874,8 @@ Future<void> setup({
|
|||
return polygon!.createPolygonWalletService(_walletInfoSource);
|
||||
case WalletType.solana:
|
||||
return solana!.createSolanaWalletService(_walletInfoSource);
|
||||
case WalletType.tron:
|
||||
return tron!.createTronWalletService(_walletInfoSource);
|
||||
default:
|
||||
throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002';
|
|||
const nanoDefaultNodeUri = 'rpc.nano.to';
|
||||
const nanoDefaultPowNodeUri = 'rpc.nano.to';
|
||||
const solanaDefaultNodeUri = 'rpc.ankr.com';
|
||||
const tronDefaultNodeUri = 'api.trongrid.io';
|
||||
const newCakeWalletBitcoinUri = 'btc-electrum.cakewallet.com:50002';
|
||||
|
||||
Future<void> defaultSettingsMigration(
|
||||
|
@ -207,23 +208,22 @@ Future<void> defaultSettingsMigration(
|
|||
case 28:
|
||||
await _updateMoneroPriority(sharedPreferences);
|
||||
break;
|
||||
|
||||
case 29:
|
||||
await changeDefaultBitcoinNode(nodes, sharedPreferences);
|
||||
break;
|
||||
|
||||
case 30:
|
||||
await disableServiceStatusFiatDisabled(sharedPreferences);
|
||||
break;
|
||||
|
||||
case 31:
|
||||
await updateNanoNodeList(nodes: nodes);
|
||||
break;
|
||||
|
||||
case 32:
|
||||
await updateBtcNanoWalletInfos(walletInfoSource);
|
||||
break;
|
||||
|
||||
case 33:
|
||||
await addTronNodeList(nodes: nodes);
|
||||
await changeTronCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -478,6 +478,11 @@ Node? getSolanaDefaultNode({required Box<Node> nodes}) {
|
|||
nodes.values.firstWhereOrNull((node) => node.type == WalletType.solana);
|
||||
}
|
||||
|
||||
Node? getTronDefaultNode({required Box<Node> nodes}) {
|
||||
return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == tronDefaultNodeUri) ??
|
||||
nodes.values.firstWhereOrNull((node) => node.type == WalletType.tron);
|
||||
}
|
||||
|
||||
Future<void> insecureStorageMigration({
|
||||
required SharedPreferences sharedPreferences,
|
||||
required FlutterSecureStorage secureStorage,
|
||||
|
@ -809,6 +814,7 @@ Future<void> checkCurrentNodes(
|
|||
final currentBitcoinCashNodeId =
|
||||
sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
|
||||
final currentSolanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey);
|
||||
final currentTronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
|
||||
final currentMoneroNode =
|
||||
nodeSource.values.firstWhereOrNull((node) => node.key == currentMoneroNodeId);
|
||||
final currentBitcoinElectrumServer =
|
||||
|
@ -829,6 +835,8 @@ Future<void> checkCurrentNodes(
|
|||
nodeSource.values.firstWhereOrNull((node) => node.key == currentBitcoinCashNodeId);
|
||||
final currentSolanaNodeServer =
|
||||
nodeSource.values.firstWhereOrNull((node) => node.key == currentSolanaNodeId);
|
||||
final currentTronNodeServer =
|
||||
nodeSource.values.firstWhereOrNull((node) => node.key == currentTronNodeId);
|
||||
if (currentMoneroNode == null) {
|
||||
final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
|
||||
await nodeSource.add(newCakeWalletNode);
|
||||
|
@ -894,6 +902,12 @@ Future<void> checkCurrentNodes(
|
|||
await nodeSource.add(node);
|
||||
await sharedPreferences.setInt(PreferencesKey.currentSolanaNodeIdKey, node.key as int);
|
||||
}
|
||||
|
||||
if (currentTronNodeServer == null) {
|
||||
final node = Node(uri: tronDefaultNodeUri, type: WalletType.tron);
|
||||
await nodeSource.add(node);
|
||||
await sharedPreferences.setInt(PreferencesKey.currentTronNodeIdKey, node.key as int);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> resetBitcoinElectrumServer(
|
||||
|
@ -1022,3 +1036,20 @@ Future<void> changeSolanaCurrentNodeToDefault(
|
|||
|
||||
await sharedPreferences.setInt(PreferencesKey.currentSolanaNodeIdKey, nodeId);
|
||||
}
|
||||
|
||||
Future<void> addTronNodeList({required Box<Node> nodes}) async {
|
||||
final nodeList = await loadDefaultTronNodes();
|
||||
for (var node in nodeList) {
|
||||
if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
|
||||
await nodes.add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> changeTronCurrentNodeToDefault(
|
||||
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
|
||||
final node = getTronDefaultNode(nodes: nodes);
|
||||
final nodeId = node?.key as int? ?? 0;
|
||||
|
||||
await sharedPreferences.setInt(PreferencesKey.currentTronNodeIdKey, nodeId);
|
||||
}
|
||||
|
|
|
@ -166,6 +166,23 @@ Future<List<Node>> loadDefaultSolanaNodes() async {
|
|||
return nodes;
|
||||
}
|
||||
|
||||
Future<List<Node>> loadDefaultTronNodes() async {
|
||||
final nodesRaw = await rootBundle.loadString('assets/tron_node_list.yml');
|
||||
final loadedNodes = loadYaml(nodesRaw) as YamlList;
|
||||
final nodes = <Node>[];
|
||||
|
||||
for (final raw in loadedNodes) {
|
||||
if (raw is Map) {
|
||||
final node = Node.fromMap(Map<String, Object>.from(raw));
|
||||
|
||||
node.type = WalletType.tron;
|
||||
nodes.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
Future<void> resetToDefault(Box<Node> nodeSource) async {
|
||||
final moneroNodes = await loadDefaultNodes();
|
||||
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
|
||||
|
@ -176,6 +193,7 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
|
|||
final nanoNodes = await loadDefaultNanoNodes();
|
||||
final polygonNodes = await loadDefaultPolygonNodes();
|
||||
final solanaNodes = await loadDefaultSolanaNodes();
|
||||
final tronNodes = await loadDefaultTronNodes();
|
||||
|
||||
final nodes = moneroNodes +
|
||||
bitcoinElectrumServerList +
|
||||
|
@ -185,7 +203,7 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
|
|||
bitcoinCashElectrumServerList +
|
||||
nanoNodes +
|
||||
polygonNodes +
|
||||
solanaNodes;
|
||||
solanaNodes + tronNodes;
|
||||
|
||||
await nodeSource.clear();
|
||||
await nodeSource.addAll(nodes);
|
||||
|
|
|
@ -14,6 +14,7 @@ class PreferencesKey {
|
|||
static const currentFiatCurrencyKey = 'current_fiat_currency';
|
||||
static const currentBitcoinCashNodeIdKey = 'current_node_id_bch';
|
||||
static const currentSolanaNodeIdKey = 'current_node_id_sol';
|
||||
static const currentTronNodeIdKey = 'current_node_id_trx';
|
||||
static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
|
||||
static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
|
||||
static const shouldSaveRecipientAddressKey = 'save_recipient_address';
|
||||
|
|
|
@ -23,10 +23,11 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
|
|||
return bitcoinCash!.getTransactionPriorities();
|
||||
case WalletType.polygon:
|
||||
return polygon!.getTransactionPriorities();
|
||||
// no such thing for nano/banano/solana:
|
||||
// no such thing for nano/banano/solana/tron:
|
||||
case WalletType.nano:
|
||||
case WalletType.banano:
|
||||
case WalletType.solana:
|
||||
case WalletType.tron:
|
||||
return [];
|
||||
default:
|
||||
return [];
|
||||
|
|
|
@ -69,6 +69,13 @@ class ProvidersHelper {
|
|||
return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood, ProviderType.moonpay];
|
||||
case WalletType.solana:
|
||||
return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood];
|
||||
case WalletType.tron:
|
||||
return [
|
||||
ProviderType.askEachTime,
|
||||
ProviderType.onramper,
|
||||
ProviderType.robinhood,
|
||||
ProviderType.moonpay,
|
||||
];
|
||||
case WalletType.none:
|
||||
case WalletType.haven:
|
||||
return [];
|
||||
|
@ -96,6 +103,12 @@ class ProvidersHelper {
|
|||
ProviderType.robinhood,
|
||||
ProviderType.moonpay,
|
||||
];
|
||||
case WalletType.tron:
|
||||
return [
|
||||
ProviderType.askEachTime,
|
||||
ProviderType.robinhood,
|
||||
ProviderType.moonpay,
|
||||
];
|
||||
case WalletType.monero:
|
||||
case WalletType.nano:
|
||||
case WalletType.banano:
|
||||
|
|
|
@ -167,7 +167,7 @@ Future<void> initializeAppConfigs() async {
|
|||
transactionDescriptions: transactionDescriptions,
|
||||
secureStorage: secureStorage,
|
||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||
initialMigrationVersion: 32,
|
||||
initialMigrationVersion: 33,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import 'package:cake_wallet/solana/solana.dart';
|
|||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
@ -53,6 +54,11 @@ Future<void> startFiatRateUpdate(
|
|||
solana!.getSPLTokenCurrencies(appStore.wallet!).where((element) => element.enabled);
|
||||
}
|
||||
|
||||
if (appStore.wallet!.type == WalletType.tron) {
|
||||
currencies =
|
||||
tron!.getTronTokenCurrencies(appStore.wallet!).where((element) => element.enabled);
|
||||
}
|
||||
|
||||
|
||||
if (currencies != null) {
|
||||
for (final currency in currencies) {
|
||||
|
|
|
@ -4,8 +4,8 @@ import 'package:cake_wallet/entities/update_haven_rate.dart';
|
|||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/polygon/polygon.dart';
|
||||
import 'package:cake_wallet/solana/solana.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
import 'package:cw_core/balance.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
|
@ -70,8 +70,10 @@ void startCurrentWalletChangeReaction(
|
|||
.get<SharedPreferences>()
|
||||
.setInt(PreferencesKey.currentWalletType, serializeToInt(wallet.type));
|
||||
|
||||
if (wallet.type == WalletType.monero || wallet.type == WalletType.bitcoin ||
|
||||
wallet.type == WalletType.litecoin || wallet.type == WalletType.bitcoinCash ) {
|
||||
if (wallet.type == WalletType.monero ||
|
||||
wallet.type == WalletType.bitcoin ||
|
||||
wallet.type == WalletType.litecoin ||
|
||||
wallet.type == WalletType.bitcoinCash) {
|
||||
_setAutoGenerateSubaddressStatus(wallet, settingsStore);
|
||||
}
|
||||
|
||||
|
@ -124,7 +126,11 @@ void startCurrentWalletChangeReaction(
|
|||
currencies =
|
||||
solana!.getSPLTokenCurrencies(appStore.wallet!).where((element) => element.enabled);
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.tron) {
|
||||
currencies =
|
||||
tron!.getTronTokenCurrencies(appStore.wallet!).where((element) => element.enabled);
|
||||
}
|
||||
|
||||
if (currencies != null) {
|
||||
for (final currency in currencies) {
|
||||
() async {
|
||||
|
|
|
@ -38,6 +38,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
|||
final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
|
||||
final bananoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
|
||||
final solanaIcon = Image.asset('assets/images/sol_icon.png', height: 24, width: 24);
|
||||
final tronIcon = Image.asset('assets/images/trx_icon.png', height: 24, width: 24);
|
||||
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
|
||||
|
||||
Image _newWalletImage(BuildContext context) => Image.asset(
|
||||
|
@ -156,6 +157,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
|||
return polygonIcon;
|
||||
case WalletType.solana:
|
||||
return solanaIcon;
|
||||
case WalletType.tron:
|
||||
return tronIcon;
|
||||
default:
|
||||
return nonWalletTypeIcon;
|
||||
}
|
||||
|
|
|
@ -82,21 +82,27 @@ class TransactionsPage extends StatelessWidget {
|
|||
}
|
||||
|
||||
if (item is TransactionListItem) {
|
||||
if (item.hasTokens && item.assetOfTransaction == null) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
final transaction = item.transaction;
|
||||
|
||||
return Observer(
|
||||
builder: (_) => TransactionRow(
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.transactionDetails, arguments: transaction),
|
||||
direction: transaction.direction,
|
||||
formattedDate: DateFormat('HH:mm').format(transaction.date),
|
||||
formattedAmount: item.formattedCryptoAmount,
|
||||
formattedFiatAmount:
|
||||
dashboardViewModel.balanceViewModel.isFiatDisabled
|
||||
? ''
|
||||
: item.formattedFiatAmount,
|
||||
isPending: transaction.isPending,
|
||||
title: item.formattedTitle + item.formattedStatus));
|
||||
builder: (_) => TransactionRow(
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.transactionDetails, arguments: transaction),
|
||||
direction: transaction.direction,
|
||||
formattedDate: DateFormat('HH:mm').format(transaction.date),
|
||||
formattedAmount: item.formattedCryptoAmount,
|
||||
formattedFiatAmount:
|
||||
dashboardViewModel.balanceViewModel.isFiatDisabled
|
||||
? ''
|
||||
: item.formattedFiatAmount,
|
||||
isPending: transaction.isPending,
|
||||
title: item.formattedTitle + item.formattedStatus,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (item is AnonpayTransactionListItem) {
|
||||
|
|
|
@ -34,7 +34,8 @@ class MenuWidgetState extends State<MenuWidget> {
|
|||
this.bananoIcon = Image.asset('assets/images/nano_icon.png'),
|
||||
this.bitcoinCashIcon = Image.asset('assets/images/bch_icon.png'),
|
||||
this.polygonIcon = Image.asset('assets/images/matic_icon.png'),
|
||||
this.solanaIcon = Image.asset('assets/images/sol_icon.png');
|
||||
this.solanaIcon = Image.asset('assets/images/sol_icon.png'),
|
||||
this.tronIcon = Image.asset('assets/images/trx_icon.png');
|
||||
|
||||
final largeScreen = 731;
|
||||
|
||||
|
@ -57,6 +58,7 @@ class MenuWidgetState extends State<MenuWidget> {
|
|||
Image bananoIcon;
|
||||
Image polygonIcon;
|
||||
Image solanaIcon;
|
||||
Image tronIcon;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -226,6 +228,8 @@ class MenuWidgetState extends State<MenuWidget> {
|
|||
return polygonIcon;
|
||||
case WalletType.solana:
|
||||
return solanaIcon;
|
||||
case WalletType.tron:
|
||||
return tronIcon;
|
||||
default:
|
||||
throw Exception('No icon for ${type.toString()}');
|
||||
}
|
||||
|
|
|
@ -104,6 +104,7 @@ class WalletListBodyState extends State<WalletListBody> {
|
|||
final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
|
||||
final polygonIcon = Image.asset('assets/images/matic_icon.png', height: 24, width: 24);
|
||||
final solanaIcon = Image.asset('assets/images/sol_icon.png', height: 24, width: 24);
|
||||
final tronIcon = Image.asset('assets/images/trx_icon.png', height: 24, width: 24);
|
||||
final scrollController = ScrollController();
|
||||
final double tileHeight = 60;
|
||||
Flushbar<void>? _progressBar;
|
||||
|
@ -316,6 +317,8 @@ class WalletListBodyState extends State<WalletListBody> {
|
|||
return polygonIcon;
|
||||
case WalletType.solana:
|
||||
return solanaIcon;
|
||||
case WalletType.tron:
|
||||
return tronIcon;
|
||||
default:
|
||||
return nonWalletTypeIcon;
|
||||
}
|
||||
|
|
|
@ -872,6 +872,7 @@ abstract class SettingsStoreBase with Store {
|
|||
final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
|
||||
final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
|
||||
final solanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey);
|
||||
final tronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
|
||||
final moneroNode = nodeSource.get(nodeId);
|
||||
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
|
||||
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
|
||||
|
@ -882,6 +883,7 @@ abstract class SettingsStoreBase with Store {
|
|||
final nanoNode = nodeSource.get(nanoNodeId);
|
||||
final nanoPowNode = powNodeSource.get(nanoPowNodeId);
|
||||
final solanaNode = nodeSource.get(solanaNodeId);
|
||||
final tronNode = nodeSource.get(tronNodeId);
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
final deviceName = await _getDeviceName() ?? '';
|
||||
final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
|
||||
|
@ -944,6 +946,10 @@ abstract class SettingsStoreBase with Store {
|
|||
nodes[WalletType.solana] = solanaNode;
|
||||
}
|
||||
|
||||
if (tronNode != null) {
|
||||
nodes[WalletType.tron] = tronNode;
|
||||
}
|
||||
|
||||
final savedSyncMode = SyncMode.all.firstWhere((element) {
|
||||
return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 0);
|
||||
});
|
||||
|
@ -1238,6 +1244,7 @@ abstract class SettingsStoreBase with Store {
|
|||
final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
|
||||
final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
|
||||
final solanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey);
|
||||
final tronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
|
||||
final moneroNode = nodeSource.get(nodeId);
|
||||
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
|
||||
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
|
||||
|
@ -1247,6 +1254,7 @@ abstract class SettingsStoreBase with Store {
|
|||
final bitcoinCashNode = nodeSource.get(bitcoinCashElectrumServerId);
|
||||
final nanoNode = nodeSource.get(nanoNodeId);
|
||||
final solanaNode = nodeSource.get(solanaNodeId);
|
||||
final tronNode = nodeSource.get(tronNodeId);
|
||||
if (moneroNode != null) {
|
||||
nodes[WalletType.monero] = moneroNode;
|
||||
}
|
||||
|
@ -1283,6 +1291,10 @@ abstract class SettingsStoreBase with Store {
|
|||
nodes[WalletType.solana] = solanaNode;
|
||||
}
|
||||
|
||||
if (tronNode != null) {
|
||||
nodes[WalletType.tron] = tronNode;
|
||||
}
|
||||
|
||||
// MIGRATED:
|
||||
|
||||
useTOTP2FA = await SecureKey.getBool(
|
||||
|
@ -1413,6 +1425,9 @@ abstract class SettingsStoreBase with Store {
|
|||
case WalletType.solana:
|
||||
await _sharedPreferences.setInt(PreferencesKey.currentSolanaNodeIdKey, node.key as int);
|
||||
break;
|
||||
case WalletType.tron:
|
||||
await _sharedPreferences.setInt(PreferencesKey.currentTronNodeIdKey, node.key as int);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
114
lib/tron/cw_tron.dart
Normal file
114
lib/tron/cw_tron.dart
Normal file
|
@ -0,0 +1,114 @@
|
|||
part of 'tron.dart';
|
||||
|
||||
class CWTron extends Tron {
|
||||
@override
|
||||
List<String> getTronWordList(String language) => EVMChainMnemonics.englishWordlist;
|
||||
|
||||
WalletService createTronWalletService(Box<WalletInfo> walletInfoSource) =>
|
||||
TronWalletService(walletInfoSource, client: TronClient());
|
||||
|
||||
@override
|
||||
WalletCredentials createTronNewWalletCredentials({
|
||||
required String name,
|
||||
WalletInfo? walletInfo,
|
||||
}) =>
|
||||
TronNewWalletCredentials(name: name, walletInfo: walletInfo);
|
||||
|
||||
@override
|
||||
WalletCredentials createTronRestoreWalletFromSeedCredentials({
|
||||
required String name,
|
||||
required String mnemonic,
|
||||
required String password,
|
||||
}) =>
|
||||
TronRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic);
|
||||
|
||||
@override
|
||||
WalletCredentials createTronRestoreWalletFromPrivateKey({
|
||||
required String name,
|
||||
required String privateKey,
|
||||
required String password,
|
||||
}) =>
|
||||
TronRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
|
||||
|
||||
@override
|
||||
String getAddress(WalletBase wallet) => (wallet as TronWallet).walletAddresses.address;
|
||||
|
||||
Object createTronTransactionCredentials(
|
||||
List<Output> outputs, {
|
||||
required CryptoCurrency currency,
|
||||
}) =>
|
||||
TronTransactionCredentials(
|
||||
outputs
|
||||
.map(
|
||||
(out) => OutputInfo(
|
||||
fiatAmount: out.fiatAmount,
|
||||
cryptoAmount: out.cryptoAmount,
|
||||
address: out.address,
|
||||
note: out.note,
|
||||
sendAll: out.sendAll,
|
||||
extractedAddress: out.extractedAddress,
|
||||
isParsedAddress: out.isParsedAddress,
|
||||
formattedCryptoAmount: out.formattedCryptoAmount,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
currency: currency,
|
||||
);
|
||||
|
||||
@override
|
||||
List<TronToken> getTronTokenCurrencies(WalletBase wallet) =>
|
||||
(wallet as TronWallet).tronTokenCurrencies;
|
||||
|
||||
@override
|
||||
Future<void> addTronToken(WalletBase wallet, CryptoCurrency token, String contractAddress) async {
|
||||
final tronToken = TronToken(
|
||||
name: token.name,
|
||||
symbol: token.title,
|
||||
contractAddress: contractAddress,
|
||||
decimal: token.decimals,
|
||||
enabled: token.enabled,
|
||||
iconPath: token.iconPath,
|
||||
);
|
||||
await (wallet as TronWallet).addTronToken(tronToken);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteTronToken(WalletBase wallet, CryptoCurrency token) async =>
|
||||
await (wallet as TronWallet).deleteTronToken(token as TronToken);
|
||||
|
||||
@override
|
||||
Future<TronToken?> getTronToken(WalletBase wallet, String contractAddress) async =>
|
||||
(wallet as TronWallet).getTronToken(contractAddress);
|
||||
|
||||
@override
|
||||
double getTransactionAmountRaw(TransactionInfo transactionInfo) {
|
||||
final amount = (transactionInfo as TronTransactionInfo).rawTronAmount();
|
||||
return double.parse(amount);
|
||||
}
|
||||
|
||||
@override
|
||||
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
|
||||
transaction as TronTransactionInfo;
|
||||
if (transaction.tokenSymbol == CryptoCurrency.trx.title) {
|
||||
return CryptoCurrency.trx;
|
||||
}
|
||||
|
||||
wallet as TronWallet;
|
||||
return wallet.tronTokenCurrencies.firstWhere(
|
||||
(element) => transaction.tokenSymbol.toLowerCase() == element.symbol.toLowerCase());
|
||||
}
|
||||
|
||||
@override
|
||||
String getTokenAddress(CryptoCurrency asset) => (asset as TronToken).contractAddress;
|
||||
|
||||
@override
|
||||
String getTronBase58Address(String hexAddress, WalletBase wallet) =>
|
||||
(wallet as TronWallet).getTronBase58AddressFromHex(hexAddress);
|
||||
|
||||
@override
|
||||
String? getTronNativeEstimatedFee(WalletBase wallet) =>
|
||||
(wallet as TronWallet).nativeTxEstimatedFee;
|
||||
|
||||
@override
|
||||
String? getTronTRC20EstimatedFee(WalletBase wallet) => (wallet as TronWallet).trc20EstimatedFee;
|
||||
}
|
|
@ -38,6 +38,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
|
|||
case WalletType.bitcoinCash:
|
||||
case WalletType.polygon:
|
||||
case WalletType.solana:
|
||||
case WalletType.tron:
|
||||
return true;
|
||||
case WalletType.monero:
|
||||
case WalletType.none:
|
||||
|
|
|
@ -80,7 +80,9 @@ abstract class BalanceViewModelBase with Store {
|
|||
|
||||
@computed
|
||||
bool get isHomeScreenSettingsEnabled =>
|
||||
isEVMCompatibleChain(wallet.type) || wallet.type == WalletType.solana;
|
||||
isEVMCompatibleChain(wallet.type) ||
|
||||
wallet.type == WalletType.solana ||
|
||||
wallet.type == WalletType.tron;
|
||||
|
||||
@computed
|
||||
bool get hasAccounts => wallet.type == WalletType.monero;
|
||||
|
@ -126,6 +128,7 @@ abstract class BalanceViewModelBase with Store {
|
|||
case WalletType.nano:
|
||||
case WalletType.banano:
|
||||
case WalletType.solana:
|
||||
case WalletType.tron:
|
||||
return S.current.xmr_available_balance;
|
||||
default:
|
||||
return S.current.confirmed;
|
||||
|
@ -140,6 +143,7 @@ abstract class BalanceViewModelBase with Store {
|
|||
case WalletType.ethereum:
|
||||
case WalletType.polygon:
|
||||
case WalletType.solana:
|
||||
case WalletType.tron:
|
||||
return S.current.xmr_full_balance;
|
||||
case WalletType.nano:
|
||||
case WalletType.banano:
|
||||
|
@ -287,6 +291,7 @@ abstract class BalanceViewModelBase with Store {
|
|||
case WalletType.ethereum:
|
||||
case WalletType.polygon:
|
||||
case WalletType.solana:
|
||||
case WalletType.tron:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart';
|
|||
import 'package:cake_wallet/polygon/polygon.dart';
|
||||
import 'package:cake_wallet/solana/solana.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
|
@ -79,6 +80,10 @@ abstract class HomeSettingsViewModelBase with Store {
|
|||
);
|
||||
}
|
||||
|
||||
if (_balanceViewModel.wallet.type == WalletType.tron) {
|
||||
await tron!.addTronToken(_balanceViewModel.wallet, token, contractAddress);
|
||||
}
|
||||
|
||||
_updateTokensList();
|
||||
_updateFiatPrices(token);
|
||||
}
|
||||
|
@ -96,6 +101,9 @@ abstract class HomeSettingsViewModelBase with Store {
|
|||
await solana!.deleteSPLToken(_balanceViewModel.wallet, token);
|
||||
}
|
||||
|
||||
if (_balanceViewModel.wallet.type == WalletType.tron) {
|
||||
await tron!.deleteTronToken(_balanceViewModel.wallet, token);
|
||||
}
|
||||
_updateTokensList();
|
||||
}
|
||||
|
||||
|
@ -112,6 +120,10 @@ abstract class HomeSettingsViewModelBase with Store {
|
|||
return await solana!.getSPLToken(_balanceViewModel.wallet, contractAddress);
|
||||
}
|
||||
|
||||
if (_balanceViewModel.wallet.type == WalletType.tron) {
|
||||
return await tron!.getTronToken(_balanceViewModel.wallet, contractAddress);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -143,6 +155,11 @@ abstract class HomeSettingsViewModelBase with Store {
|
|||
solana!.addSPLToken(_balanceViewModel.wallet, token, address);
|
||||
}
|
||||
|
||||
if (_balanceViewModel.wallet.type == WalletType.tron) {
|
||||
final address = tron!.getTokenAddress(token);
|
||||
tron!.addTronToken(_balanceViewModel.wallet, token, address);
|
||||
}
|
||||
|
||||
_refreshTokensList();
|
||||
}
|
||||
|
||||
|
@ -189,6 +206,14 @@ abstract class HomeSettingsViewModelBase with Store {
|
|||
.toList()
|
||||
..sort(_sortFunc));
|
||||
}
|
||||
|
||||
if (_balanceViewModel.wallet.type == WalletType.tron) {
|
||||
tokens.addAll(tron!
|
||||
.getTronTokenCurrencies(_balanceViewModel.wallet)
|
||||
.where((element) => _matchesSearchText(element))
|
||||
.toList()
|
||||
..sort(_sortFunc));
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -207,7 +232,7 @@ abstract class HomeSettingsViewModelBase with Store {
|
|||
bool _matchesSearchText(CryptoCurrency asset) {
|
||||
final address = getTokenAddressBasedOnWallet(asset);
|
||||
|
||||
// The homes settings would only be displayed for either of Ethereum, Polygon or Solana Wallets.
|
||||
// The homes settings would only be displayed for either of Tron, Ethereum, Polygon or Solana Wallets.
|
||||
if (address == null) return false;
|
||||
|
||||
return searchText.isEmpty ||
|
||||
|
@ -217,6 +242,10 @@ abstract class HomeSettingsViewModelBase with Store {
|
|||
}
|
||||
|
||||
String? getTokenAddressBasedOnWallet(CryptoCurrency asset) {
|
||||
if (_balanceViewModel.wallet.type == WalletType.tron) {
|
||||
return tron!.getTokenAddress(asset);
|
||||
}
|
||||
|
||||
if (_balanceViewModel.wallet.type == WalletType.solana) {
|
||||
return solana!.getTokenAddress(asset);
|
||||
}
|
||||
|
@ -229,7 +258,7 @@ abstract class HomeSettingsViewModelBase with Store {
|
|||
return polygon!.getTokenAddress(asset);
|
||||
}
|
||||
|
||||
// We return null if it's neither Polygin, Ethereum or Solana wallet (which is actually impossible because we only display home settings for either of these three wallets).
|
||||
// We return null if it's neither Tron, Polygon, Ethereum or Solana wallet (which is actually impossible because we only display home settings for either of these three wallets).
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,10 @@ import 'package:cake_wallet/ethereum/ethereum.dart';
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/nano/nano.dart';
|
||||
import 'package:cake_wallet/polygon/polygon.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/solana/solana.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
|
@ -34,6 +37,11 @@ class TransactionListItem extends ActionListItem with Keyable {
|
|||
@override
|
||||
dynamic get keyIndex => transaction.id;
|
||||
|
||||
bool get hasTokens =>
|
||||
isEVMCompatibleChain(balanceViewModel.wallet.type) ||
|
||||
balanceViewModel.wallet.type == WalletType.solana ||
|
||||
balanceViewModel.wallet.type == WalletType.tron;
|
||||
|
||||
String get formattedCryptoAmount {
|
||||
return displayMode == BalanceDisplayMode.hiddenBalance ? '---' : transaction.amountFormatted();
|
||||
}
|
||||
|
@ -63,6 +71,34 @@ class TransactionListItem extends ActionListItem with Keyable {
|
|||
return transaction.isPending ? S.current.pending : '';
|
||||
}
|
||||
|
||||
CryptoCurrency? get assetOfTransaction {
|
||||
try {
|
||||
if (balanceViewModel.wallet.type == WalletType.ethereum) {
|
||||
final asset = ethereum!.assetOfTransaction(balanceViewModel.wallet, transaction);
|
||||
return asset;
|
||||
}
|
||||
|
||||
if (balanceViewModel.wallet.type == WalletType.polygon) {
|
||||
final asset = polygon!.assetOfTransaction(balanceViewModel.wallet, transaction);
|
||||
return asset;
|
||||
}
|
||||
|
||||
if (balanceViewModel.wallet.type == WalletType.solana) {
|
||||
final asset = solana!.assetOfTransaction(balanceViewModel.wallet, transaction);
|
||||
return asset;
|
||||
}
|
||||
|
||||
if (balanceViewModel.wallet.type == WalletType.tron) {
|
||||
final asset = tron!.assetOfTransaction(balanceViewModel.wallet, transaction);
|
||||
return asset;
|
||||
}
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
String get formattedFiatAmount {
|
||||
var amount = '';
|
||||
|
||||
|
@ -114,6 +150,16 @@ class TransactionListItem extends ActionListItem with Keyable {
|
|||
price: price,
|
||||
);
|
||||
break;
|
||||
|
||||
case WalletType.tron:
|
||||
final asset = tron!.assetOfTransaction(balanceViewModel.wallet, transaction);
|
||||
final price = balanceViewModel.fiatConvertationStore.prices[asset];
|
||||
final cryptoAmount = tron!.getTransactionAmountRaw(transaction);
|
||||
amount = calculateFiatAmountRaw(
|
||||
cryptoAmount: cryptoAmount,
|
||||
price: price,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -178,6 +178,10 @@ abstract class ExchangeTradeViewModelBase with Store {
|
|||
wallet.currency == CryptoCurrency.maticpoly &&
|
||||
tradesStore.trade!.from.tag == CryptoCurrency.maticpoly.tag;
|
||||
|
||||
bool _isTronToken() =>
|
||||
wallet.currency == CryptoCurrency.trx &&
|
||||
tradesStore.trade!.from.tag == CryptoCurrency.trx.title;
|
||||
|
||||
bool _isSplToken() =>
|
||||
wallet.currency == CryptoCurrency.sol &&
|
||||
tradesStore.trade!.from.tag == CryptoCurrency.sol.title;
|
||||
|
@ -186,6 +190,7 @@ abstract class ExchangeTradeViewModelBase with Store {
|
|||
tradesStore.trade!.provider == ExchangeProviderDescription.xmrto ||
|
||||
_isEthToken() ||
|
||||
_isPolygonToken() ||
|
||||
_isSplToken();
|
||||
_isSplToken() ||
|
||||
_isTronToken();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -676,6 +676,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
|||
depositCurrency = CryptoCurrency.sol;
|
||||
receiveCurrency = CryptoCurrency.xmr;
|
||||
break;
|
||||
case WalletType.tron:
|
||||
depositCurrency = CryptoCurrency.trx;
|
||||
receiveCurrency = CryptoCurrency.xmr;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -76,6 +76,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
|
|||
case WalletType.solana:
|
||||
case WalletType.banano:
|
||||
case WalletType.nano:
|
||||
case WalletType.tron:
|
||||
return true;
|
||||
case WalletType.none:
|
||||
case WalletType.monero:
|
||||
|
|
|
@ -82,6 +82,9 @@ abstract class NodeListViewModelBase with Store {
|
|||
case WalletType.solana:
|
||||
node = getSolanaDefaultNode(nodes: _nodeSource)!;
|
||||
break;
|
||||
case WalletType.tron:
|
||||
node = getTronDefaultNode(nodes: _nodeSource)!;
|
||||
break;
|
||||
default:
|
||||
throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}');
|
||||
}
|
||||
|
|
|
@ -4,13 +4,14 @@ import 'package:cake_wallet/ethereum/ethereum.dart';
|
|||
import 'package:cake_wallet/nano/nano.dart';
|
||||
import 'package:cake_wallet/polygon/polygon.dart';
|
||||
import 'package:cake_wallet/solana/solana.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
|
||||
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cake_wallet/core/generate_wallet_password.dart';
|
||||
import 'package:cake_wallet/core/wallet_creation_service.dart';
|
||||
import 'package:cw_core/wallet_credentials.dart';
|
||||
|
@ -86,6 +87,9 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
|
|||
case WalletType.solana:
|
||||
return solana!.createSolanaRestoreWalletFromPrivateKey(
|
||||
name: name, password: password, privateKey: restoreWallet.privateKey!);
|
||||
case WalletType.tron:
|
||||
return tron!.createTronRestoreWalletFromPrivateKey(
|
||||
name: name, password: password, privateKey: restoreWallet.privateKey!);
|
||||
default:
|
||||
throw Exception('Unexpected type: ${restoreWallet.type.toString()}');
|
||||
}
|
||||
|
@ -130,6 +134,9 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
|
|||
case WalletType.solana:
|
||||
return solana!.createSolanaRestoreWalletFromSeedCredentials(
|
||||
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
|
||||
case WalletType.tron:
|
||||
return tron!.createTronRestoreWalletFromSeedCredentials(
|
||||
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
|
||||
default:
|
||||
throw Exception('Unexpected type: ${type.toString()}');
|
||||
}
|
||||
|
|
|
@ -33,6 +33,9 @@ class WalletRestoreFromQRCode {
|
|||
'bitcoincash-wallet': WalletType.bitcoinCash,
|
||||
'bitcoincash_wallet': WalletType.bitcoinCash,
|
||||
'solana-wallet': WalletType.solana,
|
||||
'tron': WalletType.tron,
|
||||
'tron-wallet': WalletType.tron,
|
||||
'tron_wallet': WalletType.tron,
|
||||
};
|
||||
|
||||
static bool _containsAssetSpecifier(String code) => _extractWalletType(code) != null;
|
||||
|
@ -184,6 +187,14 @@ class WalletRestoreFromQRCode {
|
|||
return WalletRestoreMode.keys;
|
||||
}
|
||||
|
||||
if (type == WalletType.tron && credentials.containsKey('private_key')) {
|
||||
final privateKey = credentials['private_key'] as String;
|
||||
if (privateKey.isEmpty) {
|
||||
throw Exception('Unexpected restore mode: private_key');
|
||||
}
|
||||
return WalletRestoreMode.keys;
|
||||
}
|
||||
|
||||
throw Exception('Unexpected restore mode: restore params are invalid');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import 'package:cake_wallet/polygon/polygon.dart';
|
|||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/solana/solana.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
@ -117,6 +118,17 @@ abstract class OutputBase with Store {
|
|||
@computed
|
||||
double get estimatedFee {
|
||||
try {
|
||||
if (_wallet.type == WalletType.tron) {
|
||||
if (cryptoCurrencyHandler() == CryptoCurrency.trx) {
|
||||
final nativeEstimatedFee = tron!.getTronNativeEstimatedFee(_wallet) ?? 0;
|
||||
return double.parse(nativeEstimatedFee.toString());
|
||||
} else {
|
||||
final trc20EstimatedFee = tron!.getTronTRC20EstimatedFee(_wallet) ?? 0;
|
||||
return double.parse(trc20EstimatedFee.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (_wallet.type == WalletType.solana) {
|
||||
return solana!.getEstimateFees(_wallet) ?? 0.0;
|
||||
}
|
||||
|
@ -163,8 +175,11 @@ abstract class OutputBase with Store {
|
|||
@computed
|
||||
String get estimatedFeeFiatAmount {
|
||||
try {
|
||||
final currency =
|
||||
isEVMCompatibleChain(_wallet.type) ? _wallet.currency : cryptoCurrencyHandler();
|
||||
final currency = (isEVMCompatibleChain(_wallet.type) ||
|
||||
_wallet.type == WalletType.solana ||
|
||||
_wallet.type == WalletType.tron)
|
||||
? _wallet.currency
|
||||
: cryptoCurrencyHandler();
|
||||
final fiat = calculateFiatAmountRaw(
|
||||
price: _fiatConversationStore.prices[currency]!, cryptoAmount: estimatedFee);
|
||||
return fiat;
|
||||
|
@ -269,6 +284,9 @@ abstract class OutputBase with Store {
|
|||
case WalletType.solana:
|
||||
maximumFractionDigits = 12;
|
||||
break;
|
||||
case WalletType.tron:
|
||||
maximumFractionDigits = 12;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -53,7 +53,8 @@ abstract class SendTemplateViewModelBase with Store {
|
|||
_wallet.type != WalletType.haven &&
|
||||
_wallet.type != WalletType.ethereum &&
|
||||
_wallet.type != WalletType.polygon &&
|
||||
_wallet.type != WalletType.solana;
|
||||
_wallet.type != WalletType.solana &&
|
||||
_wallet.type != WalletType.tron;
|
||||
|
||||
@computed
|
||||
CryptoCurrency get cryptoCurrency => _wallet.currency;
|
||||
|
|
|
@ -12,6 +12,7 @@ import 'package:cake_wallet/polygon/polygon.dart';
|
|||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/solana/solana.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
||||
import 'package:cw_core/exceptions.dart';
|
||||
|
@ -50,7 +51,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
void onWalletChange(wallet) {
|
||||
currencies = wallet.balance.keys.toList();
|
||||
selectedCryptoCurrency = wallet.currency;
|
||||
hasMultipleTokens = isEVMCompatibleChain(wallet.type) || wallet.type == WalletType.solana;
|
||||
hasMultipleTokens = isEVMCompatibleChain(wallet.type) ||
|
||||
wallet.type == WalletType.solana ||
|
||||
wallet.type == WalletType.tron;
|
||||
}
|
||||
|
||||
SendViewModelBase(
|
||||
|
@ -64,7 +67,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
currencies = appStore.wallet!.balance.keys.toList(),
|
||||
selectedCryptoCurrency = appStore.wallet!.currency,
|
||||
hasMultipleTokens = isEVMCompatibleChain(appStore.wallet!.type) ||
|
||||
appStore.wallet!.type == WalletType.solana,
|
||||
appStore.wallet!.type == WalletType.solana ||
|
||||
appStore.wallet!.type == WalletType.tron,
|
||||
outputs = ObservableList<Output>(),
|
||||
_settingsStore = appStore.settingsStore,
|
||||
fiatFromSettings = appStore.settingsStore.fiatCurrency,
|
||||
|
@ -110,6 +114,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
@computed
|
||||
bool get isBatchSending => outputs.length > 1;
|
||||
|
||||
bool get shouldDisplaySendALL => walletType != WalletType.solana || walletType != WalletType.tron;
|
||||
|
||||
@computed
|
||||
String get pendingTransactionFiatAmount {
|
||||
if (pendingTransaction == null) {
|
||||
|
@ -236,7 +242,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
bool get hasFeesPriority =>
|
||||
wallet.type != WalletType.nano &&
|
||||
wallet.type != WalletType.banano &&
|
||||
wallet.type != WalletType.solana;
|
||||
wallet.type != WalletType.solana &&
|
||||
wallet.type != WalletType.tron;
|
||||
|
||||
@observable
|
||||
CryptoCurrency selectedCryptoCurrency;
|
||||
|
||||
|
@ -423,7 +431,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
Object _credentials() {
|
||||
final priority = _settingsStore.priority[wallet.type];
|
||||
|
||||
if (priority == null && wallet.type != WalletType.nano && wallet.type != WalletType.banano && wallet.type != WalletType.solana) {
|
||||
if (priority == null && wallet.type != WalletType.nano && wallet.type != WalletType.banano && wallet.type != WalletType.solana &&
|
||||
wallet.type != WalletType.tron) {
|
||||
throw Exception('Priority is null for wallet type: ${wallet.type}');
|
||||
}
|
||||
|
||||
|
@ -453,6 +462,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
case WalletType.solana:
|
||||
return solana!
|
||||
.createSolanaTransactionCredentials(outputs, currency: selectedCryptoCurrency);
|
||||
case WalletType.tron:
|
||||
return tron!.createTronTransactionCredentials(outputs, currency: selectedCryptoCurrency);
|
||||
default:
|
||||
throw Exception('Unexpected wallet type: ${wallet.type}');
|
||||
}
|
||||
|
|
|
@ -56,8 +56,9 @@ abstract class OtherSettingsViewModelBase with Store {
|
|||
_wallet.type == WalletType.nano || _wallet.type == WalletType.banano;
|
||||
|
||||
@computed
|
||||
bool get displayTransactionPriority =>
|
||||
!(changeRepresentativeEnabled || _wallet.type == WalletType.solana);
|
||||
bool get displayTransactionPriority => !(changeRepresentativeEnabled ||
|
||||
_wallet.type == WalletType.solana ||
|
||||
_wallet.type == WalletType.tron);
|
||||
|
||||
@computed
|
||||
bool get isEnabledBuyAction => !_settingsStore.disableBuy && _wallet.type != WalletType.haven;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
|
||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||
|
@ -14,10 +18,7 @@ import 'package:cake_wallet/utils/date_formatter.dart';
|
|||
import 'package:cake_wallet/view_model/send/send_view_model.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:intl/src/intl/date_format.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
@ -71,6 +72,9 @@ abstract class TransactionDetailsViewModelBase with Store {
|
|||
case WalletType.solana:
|
||||
_addSolanaListItems(tx, dateFormat);
|
||||
break;
|
||||
case WalletType.tron:
|
||||
_addTronListItems(tx, dateFormat);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -160,6 +164,8 @@ abstract class TransactionDetailsViewModelBase with Store {
|
|||
return 'https://polygonscan.com/tx/${txId}';
|
||||
case WalletType.solana:
|
||||
return 'https://solscan.io/tx/${txId}';
|
||||
case WalletType.tron:
|
||||
return 'https://tronscan.org/#/transaction/${txId}';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
@ -186,6 +192,8 @@ abstract class TransactionDetailsViewModelBase with Store {
|
|||
return S.current.view_transaction_on + 'polygonscan.com';
|
||||
case WalletType.solana:
|
||||
return S.current.view_transaction_on + 'solscan.io';
|
||||
case WalletType.tron:
|
||||
return S.current.view_transaction_on + 'tronscan.org';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
@ -339,20 +347,19 @@ abstract class TransactionDetailsViewModelBase with Store {
|
|||
transactionInfo.inputAddresses?.length ?? 1,
|
||||
transactionInfo.outputAddresses?.length ?? 1);
|
||||
|
||||
RBFListItems.add(StandartListItem(
|
||||
title: S.current.old_fee,
|
||||
value: tx.feeFormatted() ?? '0.0'));
|
||||
RBFListItems.add(StandartListItem(title: S.current.old_fee, value: tx.feeFormatted() ?? '0.0'));
|
||||
|
||||
final priorities = priorityForWalletType(wallet.type);
|
||||
final selectedItem = priorities.indexOf(sendViewModel.transactionPriority);
|
||||
final customItem = priorities.firstWhereOrNull(
|
||||
(element) => element == sendViewModel.bitcoinTransactionPriorityCustom);
|
||||
final customItem = priorities
|
||||
.firstWhereOrNull((element) => element == sendViewModel.bitcoinTransactionPriorityCustom);
|
||||
final customItemIndex = customItem != null ? priorities.indexOf(customItem) : null;
|
||||
final maxCustomFeeRate = sendViewModel.maxCustomFeeRate?.toDouble();
|
||||
|
||||
RBFListItems.add(StandardPickerListItem(
|
||||
title: S.current.estimated_new_fee,
|
||||
value: bitcoin!.formatterBitcoinAmountToString(amount: newFee) + ' ${walletTypeToCryptoCurrency(wallet.type)}',
|
||||
value: bitcoin!.formatterBitcoinAmountToString(amount: newFee) +
|
||||
' ${walletTypeToCryptoCurrency(wallet.type)}',
|
||||
items: priorityForWalletType(wallet.type),
|
||||
customValue: settingsStore.customBitcoinFeeRate.toDouble(),
|
||||
maxValue: maxCustomFeeRate,
|
||||
|
@ -378,6 +385,27 @@ abstract class TransactionDetailsViewModelBase with Store {
|
|||
}
|
||||
}
|
||||
|
||||
void _addTronListItems(TransactionInfo tx, DateFormat dateFormat) {
|
||||
final _items = [
|
||||
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.transaction_details_amount, value: tx.amountFormatted()),
|
||||
if (tx.feeFormatted()?.isNotEmpty ?? false)
|
||||
StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
|
||||
if (showRecipientAddress && tx.to != null)
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_recipient_address,
|
||||
value: tron!.getTronBase58Address(tx.to!, wallet)),
|
||||
if (tx.from != null)
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_source_address,
|
||||
value: tron!.getTronBase58Address(tx.from!, wallet)),
|
||||
];
|
||||
|
||||
items.addAll(_items);
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> _checkForRBF() async {
|
||||
if (wallet.type == WalletType.bitcoin &&
|
||||
|
@ -392,10 +420,10 @@ abstract class TransactionDetailsViewModelBase with Store {
|
|||
newFee = priority == bitcoin!.getBitcoinTransactionPriorityCustom() && value != null
|
||||
? bitcoin!.getEstimatedFeeWithFeeRate(wallet, value.round(), transactionInfo.amount)
|
||||
: bitcoin!.getFeeAmountForPriority(
|
||||
wallet,
|
||||
priority,
|
||||
transactionInfo.inputAddresses?.length ?? 1,
|
||||
transactionInfo.outputAddresses?.length ?? 1);
|
||||
wallet,
|
||||
priority,
|
||||
transactionInfo.inputAddresses?.length ?? 1,
|
||||
transactionInfo.outputAddresses?.length ?? 1);
|
||||
|
||||
return bitcoin!.formatterBitcoinAmountToString(amount: newFee);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import 'package:cake_wallet/store/app_store.dart';
|
|||
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/store/yat/yat_store.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cake_wallet/utils/list_item.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart';
|
||||
|
@ -175,6 +176,21 @@ class SolanaURI extends PaymentURI {
|
|||
}
|
||||
}
|
||||
|
||||
class TronURI extends PaymentURI {
|
||||
TronURI({required String amount, required String address})
|
||||
: super(amount: amount, address: address);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var base = 'tron:' + address;
|
||||
if (amount.isNotEmpty) {
|
||||
base += '?amount=${amount.replaceAll(',', '.')}';
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store {
|
||||
WalletAddressListViewModelBase({
|
||||
required AppStore appStore,
|
||||
|
@ -273,6 +289,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
return SolanaURI(amount: amount, address: address.address);
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.tron) {
|
||||
return TronURI(amount: amount, address: address.address);
|
||||
}
|
||||
|
||||
throw Exception('Unexpected type: ${type.toString()}');
|
||||
}
|
||||
|
||||
|
@ -348,6 +368,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.tron) {
|
||||
final primaryAddress = tron!.getAddress(wallet);
|
||||
|
||||
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
|
||||
}
|
||||
|
||||
if (searchText.isNotEmpty) {
|
||||
return ObservableList.of(addressList.where((item) {
|
||||
if (item is WalletAddressListItem) {
|
||||
|
|
|
@ -118,7 +118,8 @@ abstract class WalletKeysViewModelBase with Store {
|
|||
}
|
||||
|
||||
if (isEVMCompatibleChain(_appStore.wallet!.type) ||
|
||||
_appStore.wallet!.type == WalletType.solana) {
|
||||
_appStore.wallet!.type == WalletType.solana ||
|
||||
_appStore.wallet!.type == WalletType.tron) {
|
||||
items.addAll([
|
||||
if (_appStore.wallet!.privateKey != null)
|
||||
StandartListItem(title: S.current.private_key, value: _appStore.wallet!.privateKey!),
|
||||
|
@ -175,6 +176,8 @@ abstract class WalletKeysViewModelBase with Store {
|
|||
return 'polygon-wallet';
|
||||
case WalletType.solana:
|
||||
return 'solana-wallet';
|
||||
case WalletType.tron:
|
||||
return 'tron-wallet';
|
||||
default:
|
||||
throw Exception('Unexpected wallet type: ${_appStore.wallet!.toString()}');
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
||||
import 'package:cake_wallet/solana/solana.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
|
@ -43,6 +44,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
|
|||
return 16;
|
||||
}
|
||||
return 25;
|
||||
case WalletType.tron:
|
||||
case WalletType.solana:
|
||||
case WalletType.polygon:
|
||||
case WalletType.ethereum:
|
||||
|
@ -79,6 +81,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
|
|||
return polygon!.createPolygonNewWalletCredentials(name: name);
|
||||
case WalletType.solana:
|
||||
return solana!.createSolanaNewWalletCredentials(name: name);
|
||||
case WalletType.tron:
|
||||
return tron!.createTronNewWalletCredentials(name: name);
|
||||
default:
|
||||
throw Exception('Unexpected type: ${type.toString()}');
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:cw_core/nano_account_info_response.dart';
|
|||
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
||||
import 'package:cake_wallet/polygon/polygon.dart';
|
||||
import 'package:cake_wallet/solana/solana.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
|
@ -34,7 +35,8 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
|||
type == WalletType.polygon ||
|
||||
type == WalletType.nano ||
|
||||
type == WalletType.banano ||
|
||||
type == WalletType.solana,
|
||||
type == WalletType.solana ||
|
||||
type == WalletType.tron,
|
||||
isButtonEnabled = false,
|
||||
mode = WalletRestoreMode.seed,
|
||||
super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true) {
|
||||
|
@ -48,6 +50,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
|||
case WalletType.nano:
|
||||
case WalletType.banano:
|
||||
case WalletType.solana:
|
||||
case WalletType.tron:
|
||||
availableModes = [WalletRestoreMode.seed, WalletRestoreMode.keys];
|
||||
break;
|
||||
default:
|
||||
|
@ -127,6 +130,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
|||
mnemonic: seed,
|
||||
password: password,
|
||||
);
|
||||
case WalletType.tron:
|
||||
return tron!.createTronRestoreWalletFromSeedCredentials(
|
||||
name: name,
|
||||
mnemonic: seed,
|
||||
password: password,
|
||||
);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -185,6 +194,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
|||
password: password,
|
||||
privateKey: options['private_key'] as String,
|
||||
);
|
||||
case WalletType.tron:
|
||||
return tron!.createTronRestoreWalletFromPrivateKey(
|
||||
name: name,
|
||||
password: password,
|
||||
privateKey: options['private_key'] as String,
|
||||
);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ cd cw_haven && flutter pub get && flutter packages pub run build_runner build --
|
|||
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
cd cw_solana && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
cd cw_tron && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
cd cw_ethereum && flutter pub get && cd ..
|
||||
cd cw_polygon && flutter pub get && cd ..
|
||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
|
|
|
@ -158,6 +158,7 @@ flutter:
|
|||
- assets/nano_pow_node_list.yml
|
||||
- assets/polygon_node_list.yml
|
||||
- assets/solana_node_list.yml
|
||||
- assets/tron_node_list.yml
|
||||
- assets/text/
|
||||
- assets/faq/
|
||||
- assets/animation/
|
||||
|
|
|
@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in
|
|||
CONFIG_ARGS="--monero"
|
||||
;;
|
||||
$CAKEWALLET)
|
||||
CONFIG_ARGS="--monero --bitcoin --haven --ethereum --polygon --nano --bitcoinCash --solana"
|
||||
CONFIG_ARGS="--monero --bitcoin --haven --ethereum --polygon --nano --bitcoinCash --solana --tron"
|
||||
;;
|
||||
$HAVEN)
|
||||
CONFIG_ARGS="--haven"
|
||||
|
|
|
@ -28,7 +28,7 @@ case $APP_IOS_TYPE in
|
|||
CONFIG_ARGS="--monero"
|
||||
;;
|
||||
$CAKEWALLET)
|
||||
CONFIG_ARGS="--monero --bitcoin --haven --ethereum --polygon --nano --bitcoinCash --solana"
|
||||
CONFIG_ARGS="--monero --bitcoin --haven --ethereum --polygon --nano --bitcoinCash --solana --tron"
|
||||
;;
|
||||
$HAVEN)
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ case $APP_MACOS_TYPE in
|
|||
$MONERO_COM)
|
||||
CONFIG_ARGS="--monero";;
|
||||
$CAKEWALLET)
|
||||
CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana";; #--haven
|
||||
CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron";; #--haven
|
||||
esac
|
||||
|
||||
cp -rf pubspec_description.yaml pubspec.yaml
|
||||
|
|
|
@ -8,6 +8,7 @@ const bitcoinCashOutputPath = 'lib/bitcoin_cash/bitcoin_cash.dart';
|
|||
const nanoOutputPath = 'lib/nano/nano.dart';
|
||||
const polygonOutputPath = 'lib/polygon/polygon.dart';
|
||||
const solanaOutputPath = 'lib/solana/solana.dart';
|
||||
const tronOutputPath = 'lib/tron/tron.dart';
|
||||
const walletTypesPath = 'lib/wallet_types.g.dart';
|
||||
const pubspecDefaultPath = 'pubspec_default.yaml';
|
||||
const pubspecOutputPath = 'pubspec.yaml';
|
||||
|
@ -23,6 +24,7 @@ Future<void> main(List<String> args) async {
|
|||
final hasBanano = args.contains('${prefix}banano');
|
||||
final hasPolygon = args.contains('${prefix}polygon');
|
||||
final hasSolana = args.contains('${prefix}solana');
|
||||
final hasTron = args.contains('${prefix}tron');
|
||||
|
||||
await generateBitcoin(hasBitcoin);
|
||||
await generateMonero(hasMonero);
|
||||
|
@ -32,6 +34,7 @@ Future<void> main(List<String> args) async {
|
|||
await generateNano(hasNano);
|
||||
await generatePolygon(hasPolygon);
|
||||
await generateSolana(hasSolana);
|
||||
await generateTron(hasTron);
|
||||
// await generateBanano(hasEthereum);
|
||||
|
||||
await generatePubspec(
|
||||
|
@ -44,6 +47,7 @@ Future<void> main(List<String> args) async {
|
|||
hasBitcoinCash: hasBitcoinCash,
|
||||
hasPolygon: hasPolygon,
|
||||
hasSolana: hasSolana,
|
||||
hasTron: hasTron,
|
||||
);
|
||||
await generateWalletTypes(
|
||||
hasMonero: hasMonero,
|
||||
|
@ -55,6 +59,7 @@ Future<void> main(List<String> args) async {
|
|||
hasBitcoinCash: hasBitcoinCash,
|
||||
hasPolygon: hasPolygon,
|
||||
hasSolana: hasSolana,
|
||||
hasTron: hasTron,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1024,6 +1029,79 @@ abstract class Solana {
|
|||
await outputFile.writeAsString(output);
|
||||
}
|
||||
|
||||
Future<void> generateTron(bool hasImplementation) async {
|
||||
final outputFile = File(tronOutputPath);
|
||||
const tronCommonHeaders = """
|
||||
import 'package:cake_wallet/view_model/send/output.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/output_info.dart';
|
||||
import 'package:cw_core/transaction_info.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_service.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
""";
|
||||
const tronCWHeaders = """
|
||||
import 'package:cw_evm/evm_chain_mnemonics.dart';
|
||||
import 'package:cw_tron/tron_transaction_credentials.dart';
|
||||
import 'package:cw_tron/tron_transaction_info.dart';
|
||||
import 'package:cw_tron/tron_wallet_creation_credentials.dart';
|
||||
|
||||
import 'package:cw_tron/tron_client.dart';
|
||||
import 'package:cw_tron/tron_token.dart';
|
||||
import 'package:cw_tron/tron_wallet.dart';
|
||||
import 'package:cw_tron/tron_wallet_service.dart';
|
||||
|
||||
""";
|
||||
const tronCwPart = "part 'cw_tron.dart';";
|
||||
const tronContent = """
|
||||
abstract class Tron {
|
||||
List<String> getTronWordList(String language);
|
||||
WalletService createTronWalletService(Box<WalletInfo> walletInfoSource);
|
||||
WalletCredentials createTronNewWalletCredentials({required String name, WalletInfo? walletInfo});
|
||||
WalletCredentials createTronRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password});
|
||||
WalletCredentials createTronRestoreWalletFromPrivateKey({required String name, required String privateKey, required String password});
|
||||
String getAddress(WalletBase wallet);
|
||||
|
||||
Object createTronTransactionCredentials(
|
||||
List<Output> outputs, {
|
||||
required CryptoCurrency currency,
|
||||
});
|
||||
|
||||
List<CryptoCurrency> getTronTokenCurrencies(WalletBase wallet);
|
||||
Future<void> addTronToken(WalletBase wallet, CryptoCurrency token, String contractAddress);
|
||||
Future<void> deleteTronToken(WalletBase wallet, CryptoCurrency token);
|
||||
Future<CryptoCurrency?> getTronToken(WalletBase wallet, String contractAddress);
|
||||
|
||||
double getTransactionAmountRaw(TransactionInfo transactionInfo);
|
||||
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction);
|
||||
String getTokenAddress(CryptoCurrency asset);
|
||||
String getTronBase58Address(String hexAddress, WalletBase wallet);
|
||||
|
||||
String? getTronNativeEstimatedFee(WalletBase wallet);
|
||||
String? getTronTRC20EstimatedFee(WalletBase wallet);
|
||||
}
|
||||
""";
|
||||
|
||||
const tronEmptyDefinition = 'Tron? tron;\n';
|
||||
const tronCWDefinition = 'Tron? tron = CWTron();\n';
|
||||
|
||||
final output = '$tronCommonHeaders\n' +
|
||||
(hasImplementation ? '$tronCWHeaders\n' : '\n') +
|
||||
(hasImplementation ? '$tronCwPart\n\n' : '\n') +
|
||||
(hasImplementation ? tronCWDefinition : tronEmptyDefinition) +
|
||||
'\n' +
|
||||
tronContent;
|
||||
|
||||
if (outputFile.existsSync()) {
|
||||
await outputFile.delete();
|
||||
}
|
||||
|
||||
await outputFile.writeAsString(output);
|
||||
}
|
||||
|
||||
Future<void> generatePubspec(
|
||||
{required bool hasMonero,
|
||||
required bool hasBitcoin,
|
||||
|
@ -1033,7 +1111,8 @@ Future<void> generatePubspec(
|
|||
required bool hasBanano,
|
||||
required bool hasBitcoinCash,
|
||||
required bool hasPolygon,
|
||||
required bool hasSolana}) async {
|
||||
required bool hasSolana,
|
||||
required bool hasTron}) async {
|
||||
const cwCore = """
|
||||
cw_core:
|
||||
path: ./cw_core
|
||||
|
@ -1082,6 +1161,10 @@ Future<void> generatePubspec(
|
|||
cw_evm:
|
||||
path: ./cw_evm
|
||||
""";
|
||||
const cwTron = """
|
||||
cw_tron:
|
||||
path: ./cw_tron
|
||||
""";
|
||||
final inputFile = File(pubspecOutputPath);
|
||||
final inputText = await inputFile.readAsString();
|
||||
final inputLines = inputText.split('\n');
|
||||
|
@ -1121,6 +1204,10 @@ Future<void> generatePubspec(
|
|||
output += '\n$cwSolana';
|
||||
}
|
||||
|
||||
if (hasTron) {
|
||||
output += '\n$cwTron';
|
||||
}
|
||||
|
||||
if (hasHaven && !hasMonero) {
|
||||
output += '\n$cwSharedExternal\n$cwHaven';
|
||||
} else if (hasHaven) {
|
||||
|
@ -1152,7 +1239,8 @@ Future<void> generateWalletTypes(
|
|||
required bool hasBanano,
|
||||
required bool hasBitcoinCash,
|
||||
required bool hasPolygon,
|
||||
required bool hasSolana}) async {
|
||||
required bool hasSolana,
|
||||
required bool hasTron}) async {
|
||||
final walletTypesFile = File(walletTypesPath);
|
||||
|
||||
if (walletTypesFile.existsSync()) {
|
||||
|
@ -1191,6 +1279,10 @@ Future<void> generateWalletTypes(
|
|||
outputContent += '\tWalletType.solana,\n';
|
||||
}
|
||||
|
||||
if (hasTron) {
|
||||
outputContent += '\tWalletType.tron,\n';
|
||||
}
|
||||
|
||||
if (hasNano) {
|
||||
outputContent += '\tWalletType.nano,\n';
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'utils/utils.dart';
|
|||
const configPath = 'tool/.secrets-config.json';
|
||||
const evmChainsConfigPath = 'tool/.evm-secrets-config.json';
|
||||
const solanaConfigPath = 'tool/.solana-secrets-config.json';
|
||||
const tronConfigPath = 'tool/.tron-secrets-config.json';
|
||||
|
||||
Future<void> main(List<String> args) async => generateSecretsConfig(args);
|
||||
|
||||
|
@ -20,9 +21,10 @@ Future<void> generateSecretsConfig(List<String> args) async {
|
|||
final configFile = File(configPath);
|
||||
final evmChainsConfigFile = File(evmChainsConfigPath);
|
||||
final solanaConfigFile = File(solanaConfigPath);
|
||||
final tronConfigFile = File(tronConfigPath);
|
||||
|
||||
final secrets = <String, dynamic>{};
|
||||
|
||||
|
||||
secrets.addAll(extraInfo);
|
||||
secrets.removeWhere((key, dynamic value) {
|
||||
if (key.contains('--')) {
|
||||
|
@ -78,4 +80,18 @@ Future<void> generateSecretsConfig(List<String> args) async {
|
|||
secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
|
||||
|
||||
await solanaConfigFile.writeAsString(secretsJson);
|
||||
|
||||
secrets.clear();
|
||||
|
||||
SecretKey.tronSecrets.forEach((sec) {
|
||||
if (secrets[sec.name] != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
secrets[sec.name] = sec.generate();
|
||||
});
|
||||
|
||||
secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
|
||||
|
||||
await tronConfigFile.writeAsString(secretsJson);
|
||||
}
|
||||
|
|
|
@ -10,6 +10,9 @@ const evmChainsOutputPath = 'cw_evm/lib/.secrets.g.dart';
|
|||
|
||||
const solanaConfigPath = 'tool/.solana-secrets-config.json';
|
||||
const solanaOutputPath = 'cw_solana/lib/.secrets.g.dart';
|
||||
|
||||
const tronConfigPath = 'tool/.tron-secrets-config.json';
|
||||
const tronOutputPath = 'cw_tron/lib/.secrets.g.dart';
|
||||
Future<void> main(List<String> args) async => importSecretsConfig();
|
||||
|
||||
Future<void> importSecretsConfig() async {
|
||||
|
@ -29,6 +32,11 @@ Future<void> importSecretsConfig() async {
|
|||
final solanaOutput =
|
||||
solanaInput.keys.fold('', (String acc, String val) => acc + generateConst(val, solanaInput));
|
||||
|
||||
final tronOutputFile = File(tronOutputPath);
|
||||
final tronInput = json.decode(File(tronConfigPath).readAsStringSync()) as Map<String, dynamic>;
|
||||
final tronOutput =
|
||||
tronInput.keys.fold('', (String acc, String val) => acc + generateConst(val, tronInput));
|
||||
|
||||
if (outputFile.existsSync()) {
|
||||
await outputFile.delete();
|
||||
}
|
||||
|
@ -46,4 +54,10 @@ Future<void> importSecretsConfig() async {
|
|||
}
|
||||
|
||||
await solanaOutputFile.writeAsString(solanaOutput);
|
||||
|
||||
if (tronOutputFile.existsSync()) {
|
||||
await tronOutputFile.delete();
|
||||
}
|
||||
|
||||
await tronOutputFile.writeAsString(tronOutput);
|
||||
}
|
||||
|
|
|
@ -50,6 +50,10 @@ class SecretKey {
|
|||
SecretKey('ankrApiKey', () => ''),
|
||||
];
|
||||
|
||||
static final tronSecrets = [
|
||||
SecretKey('tronGridApiKey', () => ''),
|
||||
];
|
||||
|
||||
final String name;
|
||||
final String Function() generate;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue