Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Bridge screen #762

Merged
merged 22 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4667c9f
Add bridge screen, Fix ContactsScreen typo
zaelgohary Nov 20, 2024
f4e09a0
Add logs
zaelgohary Nov 21, 2024
2615562
Fix swapToStellar, remove logs, edit amount validation, remove static…
zaelgohary Nov 21, 2024
7920a99
Edit swap widget, add chain logos
zaelgohary Nov 25, 2024
86b7ed9
Remove memo from bridge confirmation, edit validation, add getMemo
zaelgohary Nov 26, 2024
00c7ec6
Edit swap design
zaelgohary Nov 26, 2024
a91883c
Replace fixed spaces with dynamic, edit text & swap icon style
zaelgohary Nov 27, 2024
9da2dba
Edit swap icon bg
zaelgohary Nov 27, 2024
b38a691
Revert error in stellar service
zaelgohary Nov 27, 2024
e4f9dc7
Add stellar icon, edit icons width & height
zaelgohary Nov 27, 2024
387143f
Edit icons in balance tile
zaelgohary Nov 27, 2024
a415b81
Add bridge to flags, rename TransactionType, refactor swap_transactio…
zaelgohary Nov 28, 2024
31a72ea
Remove tf_chain & tft_icon, rename best_size
zaelgohary Nov 28, 2024
170f219
Rename hideDeposit
zaelgohary Nov 28, 2024
f413735
Merge branch 'development' of https://github.com/threefoldtech/threef…
zaelgohary Dec 4, 2024
11d0b82
Use query client in getMemo, refactor brodge, make memo optional
zaelgohary Dec 4, 2024
c156131
Merge branch 'development' of https://github.com/threefoldtech/threef…
zaelgohary Dec 4, 2024
06fb896
Edit err
zaelgohary Dec 4, 2024
eeecca3
simple refactor
zaelgohary Dec 4, 2024
fe94c3c
Block bridge, refactor bridge
zaelgohary Dec 10, 2024
98f1bfe
Fix async suspension issue
zaelgohary Dec 10, 2024
788fa07
Seperate conditions
zaelgohary Dec 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added app/assets/stellar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
AhmedHanafy725 marked this conversation as resolved.
Show resolved Hide resolved
File renamed without changes
Binary file removed app/assets/tft_icon.png
Binary file not shown.
2 changes: 1 addition & 1 deletion app/lib/apps/news/news_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ class _NewsScreenState extends State<NewsScreen> {
Row(
children: [
Image.asset(
'assets/tft_icon.png',
'assets/tf_chain.png',
color: Theme.of(context).colorScheme.onSurface,
height: 20,
width: 20,
Expand Down
3 changes: 3 additions & 0 deletions app/lib/helpers/flags.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ class Flags {
Globals().refreshBalance = int.parse(
(await Flags().getFlagValueByFeatureName('refresh-balance'))
.toString());

Globals().bridgeTFTAddress =
(await Flags().getFlagValueByFeatureName('bridge-address'))!;
}

Future<bool> hasFlagValueByFeatureName(String name) async {
Expand Down
1 change: 1 addition & 0 deletions app/lib/helpers/globals.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class Globals {
String chainUrl = '';
String gridproxyUrl = '';
String activationUrl = '';
String bridgeTFTAddress = '';
String relayUrl = '';
String termsAndConditionsUrl = '';
String newsUrl = '';
Expand Down
2 changes: 2 additions & 0 deletions app/lib/models/wallet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ enum WalletType { NATIVE, IMPORTED }

enum ChainType { Stellar, TFChain }

enum BridgeOperation { Withdraw, Deposit }

class Wallet {
Wallet({
required this.name,
Expand Down
304 changes: 304 additions & 0 deletions app/lib/screens/wallets/bridge.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:threebotlogin/helpers/globals.dart';
import 'package:threebotlogin/helpers/transaction_helpers.dart';
import 'package:threebotlogin/models/wallet.dart';
import 'package:threebotlogin/providers/wallets_provider.dart';
import 'package:threebotlogin/screens/wallets/contacts.dart';
import 'package:threebotlogin/services/stellar_service.dart';
import 'package:threebotlogin/widgets/wallets/bridge_confirmation.dart';
import 'package:threebotlogin/widgets/wallets/swap_transaction_widget.dart';
import 'package:validators/validators.dart';
import 'package:threebotlogin/services/stellar_service.dart' as Stellar;
import 'package:threebotlogin/services/tfchain_service.dart' as TFChain;

class WalletBridgeScreen extends StatefulWidget {
const WalletBridgeScreen(
{super.key, required this.wallet, required this.allWallets});
final Wallet wallet;
final List<Wallet> allWallets;

@override
State<WalletBridgeScreen> createState() => _WalletBridgeScreenState();
}

class _WalletBridgeScreenState extends State<WalletBridgeScreen> {
final fromController = TextEditingController();
final toController = TextEditingController();
final amountController = TextEditingController();
BridgeOperation transactionType = BridgeOperation.Withdraw;
late bool isWithdraw;
String? toAddressError;
String? amountError;
bool reloadBalance = true;

_WalletBridgeScreenState() {
isWithdraw = true;
}
AhmedHanafy725 marked this conversation as resolved.
Show resolved Hide resolved

@override
void initState() {
fromController.text = widget.wallet.tfchainAddress;
_reloadBalances();
super.initState();
}

@override
void dispose() {
fromController.dispose();
toController.dispose();
amountController.dispose();
reloadBalance = false;
super.dispose();
}

_loadTFChainBalance() async {
final chainUrl = Globals().chainUrl;
final balance =
await TFChain.getBalance(chainUrl, widget.wallet.tfchainAddress);
widget.wallet.tfchainBalance =
balance.toString() == '0.0' ? '0' : balance.toString();
setState(() {});
}

_loadStellarBalance() async {
widget.wallet.stellarBalance =
(await Stellar.getBalance(widget.wallet.stellarSecret)).toString();
setState(() {});
}

_reloadBalances() async {
if (!reloadBalance) return;
final refreshBalance = Globals().refreshBalance;
final WalletsNotifier walletRef =
ProviderScope.containerOf(context, listen: false)
.read(walletsNotifier.notifier);
final wallet = walletRef.getUpdatedWallet(widget.wallet.name)!;
widget.wallet.tfchainBalance = wallet.tfchainBalance;
widget.wallet.stellarBalance = wallet.stellarBalance;
setState(() {});
await Future.delayed(Duration(seconds: refreshBalance));
await _reloadBalances();
}

onTransactionChange(BridgeOperation type) {
transactionType = type;
isWithdraw = transactionType == BridgeOperation.Withdraw ? true : false;
fromController.text = isWithdraw
? widget.wallet.tfchainAddress
: widget.wallet.stellarAddress;
toController.text = '';
toAddressError = null;
amountError = null;
setState(() {});
}

Future<bool> _validateToAddress() async {
final toAddress = toController.text.trim();
toAddressError = null;
if (toAddress.isEmpty) {
toAddressError = "Address can't be empty";
return false;
}

if (!isWithdraw) {
if (toAddress.length != 48) {
toAddressError = 'Address length should be 48 characters';
return false;
}
final twinId = await TFChain.getTwinIdByQueryClient(toAddress);
if (twinId == 0) {
toAddressError = 'Address must have a twin ID';
return false;
}
}

if (isWithdraw) {
if (!isValidStellarAddress(toAddress)) {
AhmedHanafy725 marked this conversation as resolved.
Show resolved Hide resolved
AhmedHanafy725 marked this conversation as resolved.
Show resolved Hide resolved
toAddressError = 'Invaild Stellar address';
AhmedHanafy725 marked this conversation as resolved.
Show resolved Hide resolved
return false;
}
final toAddrBalance = await Stellar.getBalanceByAccountId(toAddress);
if (toAddrBalance == '-1') {
toAddressError = 'Address must be active and have TFT trustline';
return false;
}
}
return true;
}

bool _validateAmount() {
final amount = amountController.text.trim();
amountError = null;

if (amount.isEmpty) {
amountError = "Amount can't be empty";
return false;
}
if (!isFloat(amount)) {
amountError = 'Amount should have numeric values only';
return false;
}
if (double.parse(amount) < 2) {
amountError = "Amount can't be less than 2";
return false;
}
if (isWithdraw) {
if (double.parse(amount) > double.parse(widget.wallet.tfchainBalance)) {
amountError = "Amount shouldn't be more than the wallet balance";
return false;
}
}

if (!isWithdraw) {
if (double.parse(amount) > double.parse(widget.wallet.stellarBalance)) {
amountError = "Amount shouldn't be more than the wallet balance";
return false;
}
}
return true;
}

Future<bool> _validate() async {
final validAddress = await _validateToAddress();
final validAmount = _validateAmount();
setState(() {});
return validAddress && validAmount;
}

void _selectToAddress(String address) {
toController.text = address;
setState(() {});
}

@override
Widget build(BuildContext context) {
String balance = isWithdraw
? widget.wallet.tfchainBalance
: widget.wallet.stellarBalance;
final bool disableDeposit = widget.wallet.stellarBalance == '-1';

if (disableDeposit && !isWithdraw) {
onTransactionChange(BridgeOperation.Withdraw);
}

return Scaffold(
appBar: AppBar(title: const Text('Bridge')),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(children: [
const SizedBox(height: 10),
SwapTransactionWidget(
bridgeOperation: transactionType,
onTransactionChange: onTransactionChange,
disableDeposit: disableDeposit),
const SizedBox(height: 20),
ListTile(
title: TextField(
readOnly: true,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onSurface,
),
controller: fromController,
decoration: InputDecoration(
labelText: 'From (name: ${widget.wallet.name})',
)),
),
const SizedBox(height: 10),
ListTile(
title: TextField(
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onSurface,
),
controller: toController,
decoration: InputDecoration(
labelText: 'To',
errorText: toAddressError,
suffixIcon: IconButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ContactsScreen(
chainType: isWithdraw
? ChainType.Stellar
: ChainType.TFChain,
currentWalletAddress: fromController.text,
wallets: isWithdraw
? widget.allWallets
.where((w) =>
double.parse(w.stellarBalance) >=
0)
.toList()
: widget.allWallets,
onSelectToAddress: _selectToAddress),
));
},
icon: const Icon(Icons.person)))),
),
const SizedBox(height: 10),
ListTile(
title: TextField(
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onSurface,
),
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
controller: amountController,
decoration: InputDecoration(
labelText: 'Amount (Balance: ${formatAmount(balance)})',
hintText: '100',
suffixText: 'TFT',
errorText: amountError)),
subtitle: Text('Max Fee: ${!isWithdraw ? 1.1 : 1.01} TFT'),
),
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10),
child: ElevatedButton(
onPressed: () async {
if (await _validate()) {
await _bridge_confirmation();
}
},
style: ElevatedButton.styleFrom(),
child: SizedBox(
width: double.infinity,
child: Text(
'Submit',
style: Theme.of(context).textTheme.titleLarge!.copyWith(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
),
),
),
]),
),
),
);
}

_bridge_confirmation() async {
final memoText =
isWithdraw ? await TFChain.getMemo(fromController.text.trim()) : null;
AhmedHanafy725 marked this conversation as resolved.
Show resolved Hide resolved
showModalBottomSheet(
isScrollControlled: true,
useSafeArea: true,
isDismissible: false,
constraints: const BoxConstraints(maxWidth: double.infinity),
context: context,
builder: (ctx) => BridgeConfirmationWidget(
bridgeOperation: transactionType,
secret: !isWithdraw
? widget.wallet.stellarSecret
: widget.wallet.tfchainSecret,
from: fromController.text.trim(),
to: toController.text.trim(),
amount: amountController.text.trim(),
memo: memoText,
reloadBalance:
!isWithdraw ? _loadStellarBalance : _loadTFChainBalance,
AhmedHanafy725 marked this conversation as resolved.
Show resolved Hide resolved
));
}
}
8 changes: 4 additions & 4 deletions app/lib/screens/wallets/contacts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import 'package:threebotlogin/services/contact_service.dart';
import 'package:threebotlogin/widgets/wallets/add_edit_contact.dart';
import 'package:threebotlogin/widgets/wallets/contacts_widget.dart';

class ContractsScreen extends StatefulWidget {
const ContractsScreen(
class ContactsScreen extends StatefulWidget {
const ContactsScreen(
{super.key,
required this.chainType,
required this.currentWalletAddress,
Expand All @@ -19,10 +19,10 @@ class ContractsScreen extends StatefulWidget {
final void Function(String address) onSelectToAddress;

@override
State<ContractsScreen> createState() => _ContractsScreenState();
State<ContactsScreen> createState() => _ContactsScreenState();
}

class _ContractsScreenState extends State<ContractsScreen> {
class _ContactsScreenState extends State<ContactsScreen> {
List<PkidContact> myWalletContacts = [];
List<PkidContact> myPkidContacts = [];

Expand Down
14 changes: 6 additions & 8 deletions app/lib/screens/wallets/send.dart
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,12 @@ class _WalletSendScreenState extends State<WalletSendScreen> {
if (wallet != null && wallet.stellarBalance == '-1') {
toAddressError = 'Wallet not activated on stellar';
return false;
}else {
} else {
final balance = await Stellar.getBalanceByAccountId(toAddress);
if (balance == '-1') {
toAddressError = 'Wallet not activated on stellar';
return false;
}


if (balance == '-1') {
toAddressError = 'Wallet not activated on stellar';
return false;
}
}
}
return true;
Expand Down Expand Up @@ -274,7 +272,7 @@ class _WalletSendScreenState extends State<WalletSendScreen> {
suffixIcon: IconButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ContractsScreen(
builder: (context) => ContactsScreen(
chainType: chainType,
currentWalletAddress:
fromController.text,
Expand Down
Loading
Loading