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

Bridge Solana #811

Draft
wants to merge 24 commits into
base: development
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
773862d
Edit bridge design to include solana, refactor bridge
zaelgohary Dec 19, 2024
04753ae
Fix left & right chain labels, edit selectedChain
zaelgohary Dec 19, 2024
0a95b71
Merge branch 'development' of https://github.com/threefoldtech/threef…
zaelgohary Dec 22, 2024
02ef093
Fix swap styling, fix swapping, exclude left chain from right dropdown
zaelgohary Dec 22, 2024
f2650bc
Merge branch 'development' of https://github.com/threefoldtech/threef…
zaelgohary Jan 16, 2025
5c3b1e0
Merge branch 'development' of https://github.com/threefoldtech/threef…
zaelgohary Jan 20, 2025
c2982a7
Filter dropdowns based on selected chains, add selected chain default…
zaelgohary Jan 22, 2025
0db5400
Update current operation on chain change
zaelgohary Jan 22, 2025
eeaee3d
Add Solana validation, simple refactor
zaelgohary Jan 22, 2025
d50c402
Add Solana fee, edit validation
zaelgohary Jan 22, 2025
5ec5371
Remove unused enum
zaelgohary Jan 22, 2025
d99ee11
Merge branch 'development' of https://github.com/threefoldtech/threef…
zaelgohary Jan 26, 2025
97669c2
Fix fee & bridge validation
zaelgohary Jan 26, 2025
12b02ec
Fix fee & solana validation, add memoHash
zaelgohary Jan 27, 2025
1173b34
Revert precommit changes
zaelgohary Jan 28, 2025
a099099
Revert precommit changes, make memHash optional positional arg
zaelgohary Jan 28, 2025
937634f
Revert testnet changes
zaelgohary Jan 28, 2025
87efd37
Update bridge fee to 100
zaelgohary Jan 28, 2025
03085b6
Hide contact icon if solana chosen
zaelgohary Jan 28, 2025
c2e5481
Change to label in case of solana
zaelgohary Jan 28, 2025
9d6ac2c
Apply padding to max fee text
zaelgohary Jan 29, 2025
ffe28d7
Update stellar client ref
zaelgohary Jan 29, 2025
f2c68c8
Refactor swap & fee, prevent solana if tfchain chosen
zaelgohary Jan 30, 2025
4995f5f
Merge branch 'development' into development_bridge_solana
zaelgohary Jan 30, 2025
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/solana.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
108 changes: 69 additions & 39 deletions app/lib/screens/wallets/bridge.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:bs58/bs58.dart';
import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
Expand Down Expand Up @@ -32,6 +33,7 @@ class _WalletBridgeScreenState extends ConsumerState<WalletBridgeScreen> {
String? amountError;
bool reloadBalance = true;
List percentages = [25, 50, 75, 100];
bool isSolana = false;

@override
void initState() {
Expand Down Expand Up @@ -87,7 +89,9 @@ class _WalletBridgeScreenState extends ConsumerState<WalletBridgeScreen> {
toController.text = '';
toAddressError = null;
amountError = null;
fee = isWithdraw ? Decimal.parse('1.01') : Decimal.parse('1.1');
fee = isWithdraw
? Decimal.parse('1.01')
: (isSolana ? Decimal.parse('100') : Decimal.parse('1.1'));
setState(() {});
}

Expand All @@ -100,6 +104,13 @@ class _WalletBridgeScreenState extends ConsumerState<WalletBridgeScreen> {
}

if (!isWithdraw) {
if (isSolana) {
final isValidSolana = isValidSolanaAddress(toAddress);
if (!isValidSolana) {
toAddressError = 'Invaild Solana address';
}
return isValidSolana;
}
if (toAddress.length != 48) {
toAddressError = 'Address length should be 48 characters';
return false;
Expand All @@ -109,26 +120,31 @@ class _WalletBridgeScreenState extends ConsumerState<WalletBridgeScreen> {
toAddressError = 'Address must have a twin ID';
return false;
}
}

if (isWithdraw) {
if (!isValidStellarAddress(toAddress)) {
toAddressError = 'Invaild Stellar address';
return false;
}
if (toAddress == Globals().bridgeTFTAddress) {
toAddressError = "Bridge address can't be the destination";
return false;
}
final toAddrBalance = await Stellar.getBalanceByAccountId(toAddress);
if (toAddrBalance == '-1') {
toAddressError = 'Address must be active and have TFT trustline';
return false;
if (isWithdraw) {
if (!isValidStellarAddress(toAddress)) {
toAddressError = 'Invaild Stellar address';
return false;
}
if (toAddress == Globals().bridgeTFTAddress) {
toAddressError = "Bridge address can't be the destination";
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 isValidSolanaAddress(String address) {
final decodeBytes = base58.decode(address);
return decodeBytes.length == 32;
}

bool _validateAmount() {
final amount = amountController.text.trim();
amountError = null;
Expand Down Expand Up @@ -167,6 +183,12 @@ class _WalletBridgeScreenState extends ConsumerState<WalletBridgeScreen> {
setState(() {});
}

updateIsSolana(bool value) {
setState(() {
isSolana = value;
});
}

@override
Widget build(BuildContext context) {
List<Wallet> wallets = ref.read(walletsNotifier);
Expand All @@ -190,7 +212,8 @@ class _WalletBridgeScreenState extends ConsumerState<WalletBridgeScreen> {
SwapTransactionWidget(
bridgeOperation: transactionType,
onTransactionChange: onTransactionChange,
disableDeposit: disableDeposit),
disableDeposit: disableDeposit,
updateIsSolana: updateIsSolana),
const SizedBox(height: 20),
ListTile(
title: TextField(
Expand All @@ -211,27 +234,30 @@ class _WalletBridgeScreenState extends ConsumerState<WalletBridgeScreen> {
),
controller: toController,
decoration: InputDecoration(
labelText: 'To',
labelText: isSolana ? 'Associated Token Address' : '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
? wallets
.where((w) =>
double.parse(w.stellarBalance) >=
0)
.toList()
: wallets,
onSelectToAddress: _selectToAddress),
));
},
icon: const Icon(Icons.person)))),
suffixIcon: !isSolana
? IconButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ContactsScreen(
chainType: isWithdraw
? ChainType.Stellar
: ChainType.TFChain,
currentWalletAddress: fromController.text,
wallets: isWithdraw
? wallets
.where((w) =>
double.parse(
w.stellarBalance) >=
0)
.toList()
: wallets,
onSelectToAddress: _selectToAddress),
));
},
icon: const Icon(Icons.person))
: null)),
),
const SizedBox(height: 10),
ListTile(
Expand All @@ -247,7 +273,7 @@ class _WalletBridgeScreenState extends ConsumerState<WalletBridgeScreen> {
hintText: '100',
suffixText: 'TFT',
errorText: amountError)),
subtitle: Text('Max Fee: ${!isWithdraw ? 1.1 : 1.01} TFT'),
subtitle: Text('Max Fee: $fee TFT'),
),
const SizedBox(height: 10),
if (isBiggerThanFee)
Expand Down Expand Up @@ -307,8 +333,10 @@ class _WalletBridgeScreenState extends ConsumerState<WalletBridgeScreen> {
}

_bridge_confirmation() async {
final memoText =
!isWithdraw ? await TFChain.getMemo(toController.text.trim()) : null;
final memoHash = isSolana ? base58.decode(toController.text.trim()) : null;
final memoText = !isWithdraw && !isSolana
? await TFChain.getMemo(toController.text.trim())
: null;
showModalBottomSheet(
isScrollControlled: true,
useSafeArea: true,
Expand All @@ -323,7 +351,9 @@ class _WalletBridgeScreenState extends ConsumerState<WalletBridgeScreen> {
from: fromController.text.trim(),
to: toController.text.trim(),
amount: amountController.text.trim(),
isSolana: isSolana,
memo: memoText,
memoHash: memoHash,
reloadBalance:
isWithdraw ? _loadTFChainBalance : _loadStellarBalance,
));
Expand Down
7 changes: 5 additions & 2 deletions app/lib/services/stellar_service.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:typed_data';

import 'package:stellar_client/models/transaction.dart';
import 'package:stellar_client/models/vesting_account.dart';
import 'package:stellar_client/stellar_client.dart';
Expand Down Expand Up @@ -57,14 +59,15 @@ Future<List<VestingAccount>?> listVestedAccounts(String secret) async {
return accounts;
}

Future<void> transfer(
String secret, String dest, String amount, String memo) async {
Future<void> transfer(String secret, String dest, String amount, String memo,
[Uint8List? memoHash]) async {
final client = Client(NetworkType.PUBLIC, secret);
await client.transferThroughThreefoldService(
destinationAddress: dest,
amount: amount,
currency: 'TFT',
memoText: memo,
memoHash: memoHash,
);
}

Expand Down
26 changes: 22 additions & 4 deletions app/lib/widgets/wallets/bridge_confirmation.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:threebotlogin/helpers/globals.dart';
import 'package:threebotlogin/helpers/transaction_helpers.dart';
Expand All @@ -15,6 +17,8 @@ class BridgeConfirmationWidget extends StatefulWidget {
required this.to,
required this.amount,
required this.memo,
required this.memoHash,
required this.isSolana,
required this.reloadBalance,
});

Expand All @@ -24,6 +28,8 @@ class BridgeConfirmationWidget extends StatefulWidget {
final String to;
final String amount;
final String? memo;
final Uint8List? memoHash;
final bool isSolana;
final void Function() reloadBalance;

@override
Expand All @@ -43,8 +49,11 @@ class _BridgeConfirmationWidgetState extends State<BridgeConfirmationWidget> {
fromController.text = widget.from;
toController.text = widget.to;
amountController.text = widget.amount;
feeController.text =
widget.bridgeOperation == BridgeOperation.Deposit ? '1.1' : '1.01';
feeController.text = widget.bridgeOperation == BridgeOperation.Deposit
? widget.isSolana
? '100'
: '1.1'
: '1.01';
super.initState();
}

Expand Down Expand Up @@ -167,8 +176,17 @@ class _BridgeConfirmationWidgetState extends State<BridgeConfirmationWidget> {
});
try {
if (widget.bridgeOperation == BridgeOperation.Deposit) {
await Stellar.transfer(widget.secret, Globals().bridgeTFTAddress,
widget.amount, widget.memo!);
if (widget.isSolana) {
await Stellar.transfer(
widget.secret,
'GDGIQWZDFVWJPAFG7PJ5AXMOK7NVFVFWELZILI5MLHGSZULBTBGIBYHW',
widget.amount,
'',
widget.memoHash);
} else {
await Stellar.transfer(widget.secret, Globals().bridgeTFTAddress,
widget.amount, widget.memo!);
}
} else {
await TFChain.swapToStellar(
widget.secret, widget.to, BigInt.from(double.parse(widget.amount)));
Expand Down
Loading
Loading