From d1870ba8b87dbe918c0667f588f4376a802a8406 Mon Sep 17 00:00:00 2001
From: Adegoke David <64401859+Blazebrain@users.noreply.github.com>
Date: Fri, 3 May 2024 19:00:05 +0100
Subject: [PATCH] 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
---
.github/workflows/pr_test_build.yml | 2 +
.gitignore | 3 +
android/app/src/main/AndroidManifestBase.xml | 3 +
assets/tron_node_list.yml | 4 +
cw_core/lib/crypto_currency.dart | 2 +
cw_core/lib/currency_for_wallet_type.dart | 5 +-
cw_core/lib/hive_type_ids.dart | 1 +
cw_core/lib/node.dart | 2 +
cw_core/lib/wallet_type.dart | 16 +-
cw_tron/.gitignore | 30 +
cw_tron/.metadata | 10 +
cw_tron/CHANGELOG.md | 3 +
cw_tron/LICENSE | 1 +
cw_tron/README.md | 39 ++
cw_tron/analysis_options.yaml | 4 +
cw_tron/lib/cw_tron.dart | 7 +
cw_tron/lib/default_tron_tokens.dart | 103 ++++
cw_tron/lib/file.dart | 39 ++
cw_tron/lib/pending_tron_transaction.dart | 33 +
cw_tron/lib/tron_abi.dart | 436 +++++++++++++
cw_tron/lib/tron_balance.dart | 34 ++
cw_tron/lib/tron_client.dart | 574 ++++++++++++++++++
cw_tron/lib/tron_exception.dart | 16 +
cw_tron/lib/tron_http_provider.dart | 41 ++
cw_tron/lib/tron_token.dart | 80 +++
cw_tron/lib/tron_transaction_credentials.dart | 12 +
cw_tron/lib/tron_transaction_history.dart | 80 +++
cw_tron/lib/tron_transaction_info.dart | 93 +++
cw_tron/lib/tron_transaction_model.dart | 205 +++++++
cw_tron/lib/tron_wallet.dart | 560 +++++++++++++++++
cw_tron/lib/tron_wallet_addresses.dart | 36 ++
.../lib/tron_wallet_creation_credentials.dart | 29 +
cw_tron/lib/tron_wallet_service.dart | 148 +++++
cw_tron/pubspec.yaml | 33 +
cw_tron/test/cw_tron_test.dart | 12 +
how_to_add_new_wallet_type.md | 300 +++++++++
ios/Runner/InfoBase.plist | 30 +
lib/core/address_validator.dart | 2 +
lib/core/seed_validator.dart | 3 +
lib/di.dart | 3 +
lib/entities/default_settings_migration.dart | 41 +-
lib/entities/node_list.dart | 20 +-
lib/entities/preferences_key.dart | 1 +
lib/entities/priority_for_wallet_type.dart | 3 +-
lib/entities/provider_types.dart | 13 +
lib/main.dart | 2 +-
lib/reactions/fiat_rate_update.dart | 6 +
lib/reactions/on_current_wallet_change.dart | 14 +-
.../desktop_wallet_selection_dropdown.dart | 3 +
.../dashboard/pages/transactions_page.dart | 30 +-
.../dashboard/widgets/menu_widget.dart | 6 +-
.../screens/wallet_list/wallet_list_page.dart | 3 +
lib/store/settings_store.dart | 15 +
lib/tron/cw_tron.dart | 114 ++++
.../advanced_privacy_settings_view_model.dart | 1 +
.../dashboard/balance_view_model.dart | 7 +-
.../dashboard/home_settings_view_model.dart | 33 +-
.../dashboard/transaction_list_item.dart | 46 ++
.../exchange/exchange_trade_view_model.dart | 7 +-
.../exchange/exchange_view_model.dart | 4 +
.../node_create_or_edit_view_model.dart | 1 +
.../node_list/node_list_view_model.dart | 3 +
.../restore/restore_from_qr_vm.dart | 9 +-
.../restore/wallet_restore_from_qr_code.dart | 11 +
lib/view_model/send/output.dart | 22 +-
.../send/send_template_view_model.dart | 3 +-
lib/view_model/send/send_view_model.dart | 19 +-
.../settings/other_settings_view_model.dart | 5 +-
.../transaction_details_view_model.dart | 54 +-
.../wallet_address_list_view_model.dart | 26 +
lib/view_model/wallet_keys_view_model.dart | 5 +-
lib/view_model/wallet_new_vm.dart | 4 +
lib/view_model/wallet_restore_view_model.dart | 17 +-
model_generator.sh | 1 +
pubspec_base.yaml | 1 +
scripts/android/pubspec_gen.sh | 2 +-
scripts/ios/app_config.sh | 2 +-
scripts/macos/app_config.sh | 2 +-
tool/configure.dart | 96 ++-
tool/generate_secrets_config.dart | 18 +-
tool/import_secrets_config.dart | 14 +
tool/utils/secret_key.dart | 4 +
82 files changed, 3660 insertions(+), 62 deletions(-)
create mode 100644 assets/tron_node_list.yml
create mode 100644 cw_tron/.gitignore
create mode 100644 cw_tron/.metadata
create mode 100644 cw_tron/CHANGELOG.md
create mode 100644 cw_tron/LICENSE
create mode 100644 cw_tron/README.md
create mode 100644 cw_tron/analysis_options.yaml
create mode 100644 cw_tron/lib/cw_tron.dart
create mode 100644 cw_tron/lib/default_tron_tokens.dart
create mode 100644 cw_tron/lib/file.dart
create mode 100644 cw_tron/lib/pending_tron_transaction.dart
create mode 100644 cw_tron/lib/tron_abi.dart
create mode 100644 cw_tron/lib/tron_balance.dart
create mode 100644 cw_tron/lib/tron_client.dart
create mode 100644 cw_tron/lib/tron_exception.dart
create mode 100644 cw_tron/lib/tron_http_provider.dart
create mode 100644 cw_tron/lib/tron_token.dart
create mode 100644 cw_tron/lib/tron_transaction_credentials.dart
create mode 100644 cw_tron/lib/tron_transaction_history.dart
create mode 100644 cw_tron/lib/tron_transaction_info.dart
create mode 100644 cw_tron/lib/tron_transaction_model.dart
create mode 100644 cw_tron/lib/tron_wallet.dart
create mode 100644 cw_tron/lib/tron_wallet_addresses.dart
create mode 100644 cw_tron/lib/tron_wallet_creation_credentials.dart
create mode 100644 cw_tron/lib/tron_wallet_service.dart
create mode 100644 cw_tron/pubspec.yaml
create mode 100644 cw_tron/test/cw_tron_test.dart
create mode 100644 how_to_add_new_wallet_type.md
create mode 100644 lib/tron/cw_tron.dart
diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml
index dc231df42..46924cb35 100644
--- a/.github/workflows/pr_test_build.yml
+++ b/.github/workflows/pr_test_build.yml
@@ -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: |
diff --git a/.gitignore b/.gitignore
index 6f2d0a182..f1e5b6da3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml
index eea9b5521..485f049e8 100644
--- a/android/app/src/main/AndroidManifestBase.xml
+++ b/android/app/src/main/AndroidManifestBase.xml
@@ -67,6 +67,9 @@
+
+
+
with Serializable implemen
CryptoCurrency.kaspa,
CryptoCurrency.digibyte,
CryptoCurrency.usdtSol,
+ CryptoCurrency.usdcTrc20,
];
static const havenCurrencies = [
@@ -217,6 +218,7 @@ class CryptoCurrency extends EnumerableItem with Serializable 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 _rawCurrencyMap =
diff --git a/cw_core/lib/currency_for_wallet_type.dart b/cw_core/lib/currency_for_wallet_type.dart
index 58ee37669..92e78b2e6 100644
--- a/cw_core/lib/currency_for_wallet_type.dart
+++ b/cw_core/lib/currency_for_wallet_type.dart
@@ -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');
}
}
diff --git a/cw_core/lib/hive_type_ids.dart b/cw_core/lib/hive_type_ids.dart
index e0896bab1..e3332a043 100644
--- a/cw_core/lib/hive_type_ids.dart
+++ b/cw_core/lib/hive_type_ids.dart
@@ -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;
diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart
index 9d0806851..1195b6819 100644
--- a/cw_core/lib/node.dart
+++ b/cw_core/lib/node.dart
@@ -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;
diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart
index a63ddf37c..e846093d0 100644
--- a/cw_core/lib/wallet_type.dart
+++ b/cw_core/lib/wallet_type.dart
@@ -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');
diff --git a/cw_tron/.gitignore b/cw_tron/.gitignore
new file mode 100644
index 000000000..96486fd93
--- /dev/null
+++ b/cw_tron/.gitignore
@@ -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/
diff --git a/cw_tron/.metadata b/cw_tron/.metadata
new file mode 100644
index 000000000..fa347fc6a
--- /dev/null
+++ b/cw_tron/.metadata
@@ -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
diff --git a/cw_tron/CHANGELOG.md b/cw_tron/CHANGELOG.md
new file mode 100644
index 000000000..41cc7d819
--- /dev/null
+++ b/cw_tron/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.0.1
+
+* TODO: Describe initial release.
diff --git a/cw_tron/LICENSE b/cw_tron/LICENSE
new file mode 100644
index 000000000..ba75c69f7
--- /dev/null
+++ b/cw_tron/LICENSE
@@ -0,0 +1 @@
+TODO: Add your license here.
diff --git a/cw_tron/README.md b/cw_tron/README.md
new file mode 100644
index 000000000..02fe8ecab
--- /dev/null
+++ b/cw_tron/README.md
@@ -0,0 +1,39 @@
+
+
+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.
diff --git a/cw_tron/analysis_options.yaml b/cw_tron/analysis_options.yaml
new file mode 100644
index 000000000..a5744c1cf
--- /dev/null
+++ b/cw_tron/analysis_options.yaml
@@ -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
diff --git a/cw_tron/lib/cw_tron.dart b/cw_tron/lib/cw_tron.dart
new file mode 100644
index 000000000..6981fccba
--- /dev/null
+++ b/cw_tron/lib/cw_tron.dart
@@ -0,0 +1,7 @@
+library cw_tron;
+
+/// A Calculator.
+class Calculator {
+ /// Returns [value] plus 1.
+ int addOne(int value) => value + 1;
+}
diff --git a/cw_tron/lib/default_tron_tokens.dart b/cw_tron/lib/default_tron_tokens.dart
new file mode 100644
index 000000000..ad70f28cd
--- /dev/null
+++ b/cw_tron/lib/default_tron_tokens.dart
@@ -0,0 +1,103 @@
+import 'package:cw_core/crypto_currency.dart';
+import 'package:cw_tron/tron_token.dart';
+
+class DefaultTronTokens {
+ final List _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 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();
+}
diff --git a/cw_tron/lib/file.dart b/cw_tron/lib/file.dart
new file mode 100644
index 000000000..8fd236ec3
--- /dev/null
+++ b/cw_tron/lib/file.dart
@@ -0,0 +1,39 @@
+import 'dart:io';
+import 'package:cw_core/key.dart';
+import 'package:encrypt/encrypt.dart' as encrypt;
+
+Future 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 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 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);
+}
diff --git a/cw_tron/lib/pending_tron_transaction.dart b/cw_tron/lib/pending_tron_transaction.dart
new file mode 100644
index 000000000..b6d064b31
--- /dev/null
+++ b/cw_tron/lib/pending_tron_transaction.dart
@@ -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 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 commit() async => await sendTransaction();
+
+ @override
+ String get feeFormatted => fee;
+
+ @override
+ String get hex => bytesToHex(signedTransaction);
+
+ @override
+ String get id => '';
+}
diff --git a/cw_tron/lib/tron_abi.dart b/cw_tron/lib/tron_abi.dart
new file mode 100644
index 000000000..fdb998636
--- /dev/null
+++ b/cw_tron/lib/tron_abi.dart
@@ -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"
+ }
+];
diff --git a/cw_tron/lib/tron_balance.dart b/cw_tron/lib/tron_balance.dart
new file mode 100644
index 000000000..5b2ba3fa7
--- /dev/null
+++ b/cw_tron/lib/tron_balance.dart
@@ -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);
+ }
+ }
+}
diff --git a/cw_tron/lib/tron_client.dart b/cw_tron/lib/tron_client.dart
new file mode 100644
index 000000000..f03a8abce
--- /dev/null
+++ b/cw_tron/lib/tron_client.dart
@@ -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> 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;
+
+ if (response.statusCode >= 200 &&
+ response.statusCode < 300 &&
+ jsonResponse['status'] != false) {
+ return (jsonResponse['data'] as List).map((e) {
+ return TronTransactionModel.fromJson(e as Map);
+ }).toList();
+ }
+
+ return [];
+ } catch (e, s) {
+ log('Error getting tx: ${e.toString()}\n ${s.toString()}');
+ return [];
+ }
+ }
+
+ Future> 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;
+
+ if (response.statusCode >= 200 &&
+ response.statusCode < 300 &&
+ jsonResponse['status'] != false) {
+ return (jsonResponse['data'] as List).map((e) {
+ return TronTRC20TransactionModel.fromJson(e as Map);
+ }).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 getBalance(TronAddress address) async {
+ try {
+ final accountDetails = await _provider!.request(TronRequestGetAccount(address: address));
+
+ return accountDetails?.balance ?? BigInt.zero;
+ } catch (_) {
+ return BigInt.zero;
+ }
+ }
+
+ Future 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 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 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 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 _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 _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 sendTransaction({
+ required TransactionRaw rawTransaction,
+ required List 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 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 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 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;
+ }
+ }
+}
diff --git a/cw_tron/lib/tron_exception.dart b/cw_tron/lib/tron_exception.dart
new file mode 100644
index 000000000..13b98c024
--- /dev/null
+++ b/cw_tron/lib/tron_exception.dart
@@ -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;
+}
\ No newline at end of file
diff --git a/cw_tron/lib/tron_http_provider.dart b/cw_tron/lib/tron_http_provider.dart
new file mode 100644
index 000000000..193a3dbdd
--- /dev/null
+++ b/cw_tron/lib/tron_http_provider.dart
@@ -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