Add send ERC-20 tokens initial flow

This commit is contained in:
OmarHatem 2023-06-02 04:02:43 +03:00
parent f08ab62359
commit 94216e6987
11 changed files with 515 additions and 421 deletions

View file

@ -67,6 +67,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
CryptoCurrency.uni,
CryptoCurrency.stx,
CryptoCurrency.btcln,
CryptoCurrency.shib,
];
static const havenCurrencies = [
@ -152,7 +153,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
static const uni = CryptoCurrency(title: 'UNI', tag: 'ETH', fullName: 'Uniswap', raw: 60, name: 'uni', iconPath: 'assets/images/uni_icon.png');
static const stx = CryptoCurrency(title: 'STX', fullName: 'Stacks', raw: 61, name: 'stx', iconPath: 'assets/images/stx_icon.png');
static const btcln = CryptoCurrency(title: 'BTC', tag: 'LN', fullName: 'Bitcoin Lightning Network', raw: 62, name: 'btcln', iconPath: 'assets/images/btc.png');
static const shib = CryptoCurrency(title: 'SHIB', tag: 'ETH', fullName: 'SHIBA INU', raw: 63, name: 'shib', iconPath: 'assets/images/shib.png'); // TODO: add image
static const shib = CryptoCurrency(title: 'SHIB', tag: 'ETH', fullName: 'SHIBA INU', raw: 63, name: 'shib', iconPath: 'assets/images/shib_icon.png');
static final Map<int, CryptoCurrency> _rawCurrencyMap =

View file

@ -14,6 +14,8 @@ class EthereumClient {
CryptoCurrency.shib: "0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE",
};
Map<CryptoCurrency, String> get erc20Currencies => _erc20Currencies;
Web3Client? _client;
bool connect(Node node) {
@ -47,13 +49,14 @@ class EthereumClient {
return result.map((e) => e.toInt()).toList();
}
Future<PendingEthereumTransaction> signTransaction(
EthPrivateKey privateKey,
String toAddress,
String amount,
int gas,
EthereumTransactionPriority priority,
) async {
Future<PendingEthereumTransaction> signTransaction({
required EthPrivateKey privateKey,
required String toAddress,
required String amount,
required int gas,
required EthereumTransactionPriority priority,
required CryptoCurrency currency,
}) async {
final estimatedGas = await _client!.estimateGas(
maxPriorityFeePerGas: EtherAmount.fromUnitAndValue(EtherUnit.gwei, priority.tip),
maxFeePerGas: EtherAmount.fromUnitAndValue(EtherUnit.gwei, 100),
@ -64,13 +67,36 @@ class EthereumClient {
final price = await _client!.getGasPrice();
final transaction = Transaction(
from: privateKey.address,
to: EthereumAddress.fromHex(toAddress),
maxGas: gas,
gasPrice: price,
value: EtherAmount.inWei(BigInt.parse(amount)),
);
final Transaction transaction;
if (erc20Currencies.containsKey(currency)) {
final String abi = await rootBundle.loadString("assets/abi_json/erc20_abi.json");
final contractAbi = ContractAbi.fromJson(abi, "ERC20");
final contract = DeployedContract(
contractAbi,
EthereumAddress.fromHex(_erc20Currencies[currency]!),
);
final transferFunction = contract.function('transfer');
transaction = Transaction.callContract(
contract: contract,
function: transferFunction,
parameters: [EthereumAddress.fromHex(toAddress), BigInt.parse(amount)],
from: privateKey.address,
maxGas: gas,
gasPrice: price,
value: EtherAmount.inWei(BigInt.parse(amount)),
);
} else {
transaction = Transaction(
from: privateKey.address,
to: EthereumAddress.fromHex(toAddress),
maxGas: gas,
gasPrice: price,
value: EtherAmount.inWei(BigInt.parse(amount)),
);
}
final signedTransaction = await _client!.signTransaction(privateKey, transaction);

View file

@ -1,10 +1,17 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/output_info.dart';
import 'package:cw_ethereum/ethereum_transaction_priority.dart';
class EthereumTransactionCredentials {
EthereumTransactionCredentials(this.outputs, {required this.priority, this.feeRate});
EthereumTransactionCredentials(
this.outputs, {
required this.priority,
required this.currency,
this.feeRate,
});
final List<OutputInfo> outputs;
final EthereumTransactionPriority? priority;
final int? feeRate;
final CryptoCurrency currency;
}

View file

@ -123,7 +123,7 @@ abstract class EthereumWalletBase
final _credentials = credentials as EthereumTransactionCredentials;
final outputs = _credentials.outputs;
final hasMultiDestination = outputs.length > 1;
final balance = await _client.getBalance(_privateKey.address);
final _erc20Balance = balance[_credentials.currency]!;
int totalAmount = 0;
if (hasMultiDestination) {
@ -133,27 +133,28 @@ abstract class EthereumWalletBase
totalAmount = outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0));
if (balance.getInWei < EtherAmount.inWei(totalAmount as BigInt).getInWei) {
if (_erc20Balance.balance < EtherAmount.inWei(totalAmount as BigInt).getInWei) {
throw EthereumTransactionCreationException();
}
} else {
final output = outputs.first;
final int allAmount = balance.getInWei.toInt() - feeRate(_credentials.priority!);
final int allAmount = _erc20Balance.balance.toInt() - feeRate(_credentials.priority!);
totalAmount = output.sendAll ? allAmount : output.formattedCryptoAmount ?? 0;
if ((output.sendAll &&
balance.getInWei < EtherAmount.inWei(totalAmount as BigInt).getInWei) ||
(!output.sendAll && balance.getInWei.toInt() <= 0)) {
_erc20Balance.balance < EtherAmount.inWei(totalAmount as BigInt).getInWei) ||
(!output.sendAll && _erc20Balance.balance.toInt() <= 0)) {
throw EthereumTransactionCreationException();
}
}
final pendingEthereumTransaction = await _client.signTransaction(
_privateKey,
_credentials.outputs.first.address,
totalAmount.toString(),
_priorityFees[_credentials.priority!.raw],
_credentials.priority!,
privateKey: _privateKey,
toAddress: _credentials.outputs.first.address,
amount: totalAmount.toString(),
gas: _priorityFees[_credentials.priority!.raw],
priority: _credentials.priority!,
currency: _credentials.currency,
);
return pendingEthereumTransaction;
@ -233,8 +234,7 @@ abstract class EthereumWalletBase
final jsonSource = await read(path: path, password: password);
final data = json.decode(jsonSource) as Map;
final mnemonic = data['mnemonic'] as String;
final balance =
ERC20Balance.fromJSON(data['balance'] as String) ?? ERC20Balance(BigInt.zero);
final balance = ERC20Balance.fromJSON(data['balance'] as String) ?? ERC20Balance(BigInt.zero);
return EthereumWallet(
walletInfo: walletInfo,
@ -268,4 +268,6 @@ abstract class EthereumWalletBase
}
Future<void>? updateBalance() => null;
List<CryptoCurrency> get erc20Currencies => _client.erc20Currencies.keys.toList();
}

View file

@ -36,6 +36,7 @@ class AddressValidator extends TextValidator {
case CryptoCurrency.oxt:
case CryptoCurrency.paxg:
case CryptoCurrency.uni:
case CryptoCurrency.shib:
return '0x[0-9a-zA-Z]';
case CryptoCurrency.xrp:
return '^[0-9a-zA-Z]{34}\$|^X[0-9a-zA-Z]{46}\$';
@ -118,6 +119,7 @@ class AddressValidator extends TextValidator {
case CryptoCurrency.eos:
return [42];
case CryptoCurrency.eth:
case CryptoCurrency.shib:
return [42];
case CryptoCurrency.ltc:
return [34, 43, 63];

View file

@ -41,8 +41,12 @@ class CWEthereum extends Ethereum {
return ethereumWallet.feeRate(priority);
}
Object createEthereumTransactionCredentials(List<Output> outputs,
{required TransactionPriority priority, int? feeRate}) =>
Object createEthereumTransactionCredentials(
List<Output> outputs, {
required TransactionPriority priority,
required CryptoCurrency currency,
int? feeRate,
}) =>
EthereumTransactionCredentials(
outputs
.map((out) => OutputInfo(
@ -56,17 +60,29 @@ class CWEthereum extends Ethereum {
formattedCryptoAmount: out.formattedCryptoAmount))
.toList(),
priority: priority as EthereumTransactionPriority,
currency: currency,
feeRate: feeRate,
);
Object createEthereumTransactionCredentialsRaw(List<OutputInfo> outputs,
{TransactionPriority? priority, required int feeRate}) =>
Object createEthereumTransactionCredentialsRaw(
List<OutputInfo> outputs, {
TransactionPriority? priority,
required CryptoCurrency currency,
required int feeRate,
}) =>
EthereumTransactionCredentials(
outputs,
priority: priority as EthereumTransactionPriority,
priority: priority as EthereumTransactionPriority?,
currency: currency,
feeRate: feeRate,
);
@override
int formatterEthereumParseAmount(String amount) => EthereumFormatter.parseEthereumAmount(amount);
@override
List<CryptoCurrency> getERC20Currencies(Object wallet) {
final ethereumWallet = wallet as EthereumWallet;
return ethereumWallet.erc20Currencies;
}
}

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:cake_wallet/core/fiat_conversion_service.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/update_haven_rate.dart';
import 'package:cake_wallet/ethereum/ethereum.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';
@ -30,6 +31,17 @@ Future<void> startFiatRateUpdate(
fiat: settingsStore.fiatCurrency,
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
}
if (appStore.wallet!.type == WalletType.ethereum) {
final currencies = ethereum!.getERC20Currencies(appStore.wallet!);
for (final currency in currencies) {
fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice(
crypto: currency,
fiat: settingsStore.fiatCurrency,
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
}
}
} catch (e) {
print(e);
}

View file

@ -511,17 +511,16 @@ class ExchangeCardState extends State<ExchangeCard> {
void _presentPicker(BuildContext context) {
showPopUp<void>(
builder: (_) => CurrencyPicker(
selectedAtIndex: widget.currencies.indexOf(_selectedCurrency),
items: widget.currencies,
hintText: S.of(context).search_currency,
isMoneroWallet: _isMoneroWallet,
isConvertFrom: widget.hasRefundAddress,
onItemSelected: (Currency item) =>
widget.onCurrencySelected != null
? widget.onCurrencySelected(item as CryptoCurrency)
: null),
context: context);
context: context,
builder: (_) => CurrencyPicker(
selectedAtIndex: widget.currencies.indexOf(_selectedCurrency),
items: widget.currencies,
hintText: S.of(context).search_currency,
isMoneroWallet: _isMoneroWallet,
isConvertFrom: widget.hasRefundAddress,
onItemSelected: (Currency item) => widget.onCurrencySelected(item as CryptoCurrency),
),
);
}
void _showAmountPopup(BuildContext context, PaymentRequest paymentRequest) {

View file

@ -1,7 +1,10 @@
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
@ -32,18 +35,14 @@ class SendCard extends StatefulWidget {
@override
SendCardState createState() => SendCardState(
output: output,
sendViewModel: sendViewModel,
initialPaymentRequest: initialPaymentRequest,
);
output: output,
sendViewModel: sendViewModel,
initialPaymentRequest: initialPaymentRequest,
);
}
class SendCardState extends State<SendCard>
with AutomaticKeepAliveClientMixin<SendCard> {
SendCardState({
required this.output,
required this.sendViewModel,
this.initialPaymentRequest})
class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<SendCard> {
SendCardState({required this.output, required this.sendViewModel, this.initialPaymentRequest})
: addressController = TextEditingController(),
cryptoAmountController = TextEditingController(),
fiatAmountController = TextEditingController(),
@ -100,40 +99,39 @@ class SendCardState extends State<SendCard>
return Stack(
children: [
KeyboardActions(
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context)
.accentTextTheme!
.bodyLarge!
.backgroundColor!,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: cryptoAmountFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
),
KeyboardActionsItem(
focusNode: fiatAmountFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
)
]),
child: Container(
height: 0,
color: Colors.transparent,
)),
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).accentTextTheme.bodyLarge!.backgroundColor!,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: cryptoAmountFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
),
KeyboardActionsItem(
focusNode: fiatAmountFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
)
],
),
child: Container(
height: 0,
color: Colors.transparent,
),
),
Container(
decoration: ResponsiveLayoutUtil.instance.isMobile(context) ? BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24)),
gradient: LinearGradient(colors: [
Theme.of(context).primaryTextTheme!.titleMedium!.color!,
Theme.of(context)
.primaryTextTheme!
.titleMedium!
.decorationColor!,
], begin: Alignment.topLeft, end: Alignment.bottomRight),
) : null,
decoration: ResponsiveLayoutUtil.instance.isMobile(context)
? BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24),
),
gradient: LinearGradient(colors: [
Theme.of(context).primaryTextTheme.titleMedium!.color!,
Theme.of(context).primaryTextTheme.titleMedium!.decorationColor!,
], begin: Alignment.topLeft, end: Alignment.bottomRight),
)
: null,
child: Padding(
padding: EdgeInsets.fromLTRB(
24,
@ -142,7 +140,8 @@ class SendCardState extends State<SendCard>
ResponsiveLayoutUtil.instance.isMobile(context) ? 32 : 0,
),
child: SingleChildScrollView(
child: Observer(builder: (_) => Column(
child: Observer(
builder: (_) => Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Observer(builder: (_) {
@ -164,24 +163,15 @@ class SendCardState extends State<SendCard>
AddressTextFieldOption.qrCode,
AddressTextFieldOption.addressBook
],
buttonColor: Theme.of(context)
.primaryTextTheme!
.headlineMedium!
.color!,
borderColor: Theme.of(context)
.primaryTextTheme!
.headlineSmall!
.color!,
buttonColor: Theme.of(context).primaryTextTheme.headlineMedium!.color!,
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
hintStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme!
.headlineSmall!
.primaryTextTheme.headlineSmall!
.decorationColor!),
onPushPasteButton: (context) async {
output.resetParsedAddress();
@ -195,181 +185,209 @@ class SendCardState extends State<SendCard>
selectedCurrency: sendViewModel.currency,
);
}),
if (output.isParsedAddress) Padding(
padding: const EdgeInsets.only(top: 20),
child: BaseTextFormField(
controller: extractedAddressController,
readOnly: true,
borderColor: Theme.of(context)
.primaryTextTheme!
.headlineSmall!
.color!,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
validator: sendViewModel.addressValidator
)
),
if (output.isParsedAddress)
Padding(
padding: const EdgeInsets.only(top: 20),
child: BaseTextFormField(
controller: extractedAddressController,
readOnly: true,
borderColor:
Theme.of(context).primaryTextTheme.headlineSmall!.color!,
textStyle: TextStyle(
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
validator: sendViewModel.addressValidator)),
Observer(
builder: (_) => Padding(
padding: const EdgeInsets.only(top: 20),
child: Row(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Row(
children: [
Text(
sendViewModel.selectedCryptoCurrency.title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
)),
sendViewModel.selectedCryptoCurrency.tag != null ? Padding(
padding: const EdgeInsets.fromLTRB(3.0,0,3.0,0),
child: Container(
height: 32,
decoration: BoxDecoration(
color: Theme.of(context)
.primaryTextTheme!
.headlineMedium!
.color!,
borderRadius:
BorderRadius.all(Radius.circular(6))),
child: Center(
child: Padding(
padding: const EdgeInsets.all(6.0),
child: Text( sendViewModel.selectedCryptoCurrency.tag!,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme!
.headlineMedium!
.decorationColor!)),
builder: (_) => Padding(
padding: const EdgeInsets.only(top: 20),
child: Row(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Row(
children: [
sendViewModel.hasMultipleTokens
? Container(
padding: EdgeInsets.only(right: 8),
height: 32,
child: InkWell(
onTap: () => _presentPicker(context),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.only(right: 5),
child: Image.asset(
'assets/images/arrow_bottom_purple_icon.png',
color: Colors.white,
height: 8,
),
),
Text(
sendViewModel.selectedCryptoCurrency.title,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Colors.white),
),
],
),
),
),
),
) : Container(),
Padding(
padding: const EdgeInsets.only(right: 10.0),
child: Text(':',
)
: Text(
sendViewModel.selectedCryptoCurrency.title,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Colors.white)),
),
],
),
),
Expanded(
child: Stack(
children: [
BaseTextFormField(
focusNode: cryptoAmountFocus,
controller: cryptoAmountController,
keyboardType:
TextInputType.numberWithOptions(
signed: false, decimal: true),
inputFormatters: [
FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))
],
suffixIcon: SizedBox(
width: prefixIconWidth,
),
hintText: '0.0000',
borderColor: Colors.transparent,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
placeholderTextStyle: TextStyle(
color: Theme.of(context)
.primaryTextTheme!
.headlineSmall!
.decorationColor!,
fontWeight: FontWeight.w500,
fontSize: 14),
validator: output.sendAll
? sendViewModel.allAmountValidator
: sendViewModel
.amountValidator),
if (!sendViewModel.isBatchSending) Positioned(
top: 2,
right: 0,
),
sendViewModel.selectedCryptoCurrency.tag != null
? Padding(
padding: const EdgeInsets.fromLTRB(3.0, 0, 3.0, 0),
child: Container(
width: prefixIconWidth,
height: prefixIconHeight,
child: InkWell(
onTap: () async =>
output.setSendAll(),
child: Container(
decoration: BoxDecoration(
color: Theme.of(context)
.primaryTextTheme!
.headlineMedium!
.color!,
borderRadius:
BorderRadius.all(
Radius.circular(6))),
child: Center(
child: Text(
S.of(context).all,
textAlign:
TextAlign.center,
style: TextStyle(
fontSize: 12,
fontWeight:
FontWeight.bold,
color:
Theme.of(context)
.primaryTextTheme!
.headlineMedium!
.decorationColor!))),
))))]),
),
],
)
)),
Divider(height: 1,color: Theme.of(context)
.primaryTextTheme!
.headlineSmall!
.decorationColor!),
Observer(
builder: (_) => Padding(
padding: EdgeInsets.only(top: 10),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: Text(
S.of(context).available_balance +
height: 32,
decoration: BoxDecoration(
color: Theme.of(context)
.primaryTextTheme.headlineMedium!
.color!,
borderRadius: BorderRadius.all(
Radius.circular(6),
)),
child: Center(
child: Padding(
padding: const EdgeInsets.all(6.0),
child: Text(
sendViewModel.selectedCryptoCurrency.tag!,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme.headlineMedium!
.decorationColor!),
),
),
),
),
)
: Container(),
Padding(
padding: const EdgeInsets.only(right: 10.0),
child: Text(
':',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.primaryTextTheme!
.headlineSmall!
.decorationColor!),
)),
Text(
sendViewModel.balance,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Colors.white),
),
),
],
),
),
Expanded(
child: Stack(
children: [
BaseTextFormField(
focusNode: cryptoAmountFocus,
controller: cryptoAmountController,
keyboardType: TextInputType.numberWithOptions(
signed: false, decimal: true),
inputFormatters: [
FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))
],
suffixIcon: SizedBox(
width: prefixIconWidth,
),
hintText: '0.0000',
borderColor: Colors.transparent,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
placeholderTextStyle: TextStyle(
color: Theme.of(context)
.primaryTextTheme.headlineSmall!
.decorationColor!,
fontWeight: FontWeight.w500,
fontSize: 14),
validator: output.sendAll
? sendViewModel.allAmountValidator
: sendViewModel.amountValidator,
),
if (!sendViewModel.isBatchSending)
Positioned(
top: 2,
right: 0,
child: Container(
width: prefixIconWidth,
height: prefixIconHeight,
child: InkWell(
onTap: () async => output.setSendAll(),
child: Container(
decoration: BoxDecoration(
color: Theme.of(context)
.primaryTextTheme.headlineMedium!
.color!,
borderRadius: BorderRadius.all(
Radius.circular(6),
),
),
child: Center(
child: Text(
S.of(context).all,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme.headlineMedium!
.decorationColor!,
),
),
),
),
),
),
),
],
),
),
],
)),
),
Divider(
height: 1,
color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!),
Observer(
builder: (_) => Padding(
padding: EdgeInsets.only(top: 10),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: Text(
S.of(context).available_balance + ':',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.primaryTextTheme!
.headlineSmall!
.primaryTextTheme.headlineSmall!
.decorationColor!),
)
],
),
)),
),
),
Text(
sendViewModel.balance,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.primaryTextTheme.headlineSmall!
.decorationColor!),
)
],
),
),
),
if (!sendViewModel.isFiatDisabled)
Padding(
padding: const EdgeInsets.only(top: 20),
@ -377,171 +395,154 @@ class SendCardState extends State<SendCard>
focusNode: fiatAmountFocus,
controller: fiatAmountController,
keyboardType:
TextInputType.numberWithOptions(
signed: false, decimal: true),
TextInputType.numberWithOptions(signed: false, decimal: true),
inputFormatters: [
FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))
FilteringTextInputFormatter.deny(
RegExp('[\\-|\\ ]'),
)
],
prefixIcon: Padding(
padding: EdgeInsets.only(top: 9),
child:
Text(sendViewModel.fiat.title + ':',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
)),
child: Text(
sendViewModel.fiat.title + ':',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
hintText: '0.00',
borderColor: Theme.of(context)
.primaryTextTheme!
.headlineSmall!
.color!,
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
placeholderTextStyle: TextStyle(
color: Theme.of(context)
.primaryTextTheme!.headlineSmall!.decorationColor!,
.primaryTextTheme.headlineSmall!
.decorationColor!,
fontWeight: FontWeight.w500,
fontSize: 14),
)),
),
),
Padding(
padding: EdgeInsets.only(top: 20),
child: BaseTextFormField(
controller: noteController,
keyboardType: TextInputType.multiline,
maxLines: null,
borderColor: Theme.of(context)
.primaryTextTheme!
.headlineSmall!
.color!,
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
hintText: S.of(context).note_optional,
placeholderTextStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme!
.headlineSmall!
.primaryTextTheme.headlineSmall!
.decorationColor!),
),
),
Observer(
builder: (_) => GestureDetector(
onTap: () =>
_setTransactionPriority(context),
child: Container(
padding: EdgeInsets.only(top: 24),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
S
.of(context)
.send_estimated_fee,
style: TextStyle(
fontSize: 12,
fontWeight:
FontWeight.w500,
//color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
color: Colors.white)),
Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
output
.estimatedFee
.toString() +
' ' +
sendViewModel
.selectedCryptoCurrency.toString(),
style: TextStyle(
fontSize: 12,
fontWeight:
FontWeight.w600,
//color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
color:
Colors.white)),
Padding(
padding:
EdgeInsets.only(top: 5),
child: sendViewModel.isFiatDisabled
? const SizedBox(height: 14)
: Text(output
.estimatedFeeFiatAmount
+ ' ' +
sendViewModel
.fiat.title,
style: TextStyle(
fontSize: 12,
fontWeight:
FontWeight.w600,
color: Theme
.of(context)
.primaryTextTheme!
.headlineSmall!
.decorationColor!))
builder: (_) => GestureDetector(
onTap: () => _setTransactionPriority(context),
child: Container(
padding: EdgeInsets.only(top: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
S.of(context).send_estimated_fee,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
//color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
color: Colors.white),
),
Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
output.estimatedFee.toString() +
' ' +
sendViewModel.selectedCryptoCurrency.toString(),
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
//color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
color: Colors.white,
),
],
),
Padding(
padding: EdgeInsets.only(
top: 2,
left: 5),
child: Icon(
Icons.arrow_forward_ios,
size: 12,
color: Colors.white,
),
)
],
),
)
Padding(
padding: EdgeInsets.only(top: 5),
child: sendViewModel.isFiatDisabled
? const SizedBox(height: 14)
: Text(
output.estimatedFeeFiatAmount +
' ' +
sendViewModel.fiat.title,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.primaryTextTheme.headlineSmall!
.decorationColor!,
),
),
),
],
),
Padding(
padding: EdgeInsets.only(top: 2, left: 5),
child: Icon(
Icons.arrow_forward_ios,
size: 12,
color: Colors.white,
),
)
],
),
)
],
),
),
),
),
if (sendViewModel.isElectrumWallet)
Padding(
padding: EdgeInsets.only(top: 6),
child: GestureDetector(
onTap: () => Navigator.of(context).pushNamed(Routes.unspentCoinsList),
child: Container(
color: Colors.transparent,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
S.of(context).coin_control,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.white),
),
Icon(
Icons.arrow_forward_ios,
size: 12,
color: Colors.white,
),
],
),
),
)),
if (sendViewModel.isElectrumWallet) Padding(
padding: EdgeInsets.only(top: 6),
child: GestureDetector(
onTap: () => Navigator.of(context)
.pushNamed(Routes.unspentCoinsList),
child: Container(
color: Colors.transparent,
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
S.of(context).coin_control,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.white)),
Icon(
Icons.arrow_forward_ios,
size: 12,
color: Colors.white,
)
],
)
)
)
)
),
),
],
))
),
),
),
),
)
@ -550,7 +551,7 @@ class SendCardState extends State<SendCard>
}
void _setEffects(BuildContext context) {
if (_effectsInstalled) {
if (_effectsInstalled) {
return;
}
@ -586,7 +587,7 @@ class SendCardState extends State<SendCard>
});
noteController.addListener(() {
final note = noteController.text ?? '';
final note = noteController.text;
if (note != output.note) {
output.note = note;
@ -662,16 +663,30 @@ class SendCardState extends State<SendCard>
final selectedItem = items.indexOf(sendViewModel.transactionPriority);
await showPopUp<void>(
builder: (_) => Picker(
items: items,
displayItem: sendViewModel.displayFeeRate,
selectedAtIndex: selectedItem,
title: S.of(context).please_select,
mainAxisAlignment: MainAxisAlignment.center,
onItemSelected: (TransactionPriority priority) =>
sendViewModel.setTransactionPriority(priority),
),
context: context);
context: context,
builder: (_) => Picker(
items: items,
displayItem: sendViewModel.displayFeeRate,
selectedAtIndex: selectedItem,
title: S.of(context).please_select,
mainAxisAlignment: MainAxisAlignment.center,
onItemSelected: (TransactionPriority priority) =>
sendViewModel.setTransactionPriority(priority),
),
);
}
void _presentPicker(BuildContext context) {
showPopUp<void>(
context: context,
builder: (_) => CurrencyPicker(
selectedAtIndex: sendViewModel.currencies.indexOf(sendViewModel.selectedCryptoCurrency),
items: sendViewModel.currencies,
hintText: S.of(context).search_currency,
onItemSelected: (Currency cur) =>
sendViewModel.selectedCryptoCurrency = (cur as CryptoCurrency),
),
);
}
@override

View file

@ -1,4 +1,3 @@
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
@ -43,6 +42,7 @@ abstract class SendViewModelBase with Store {
: state = InitialExecutionState(),
currencies = _wallet.balance.keys.toList(),
selectedCryptoCurrency = _wallet.currency,
hasMultipleTokens = _wallet.type == WalletType.ethereum,
outputs = ObservableList<Output>(),
fiatFromSettings = _settingsStore.fiatCurrency {
final priority = _settingsStore.priority[_wallet.type];
@ -127,14 +127,14 @@ abstract class SendViewModelBase with Store {
CryptoCurrency get currency => _wallet.currency;
Validator get amountValidator =>
Validator<String> get amountValidator =>
AmountValidator(currency: walletTypeToCryptoCurrency(_wallet.type));
Validator get allAmountValidator => AllAmountValidator();
Validator<String> get allAmountValidator => AllAmountValidator();
Validator get addressValidator => AddressValidator(type: selectedCryptoCurrency);
Validator<String> get addressValidator => AddressValidator(type: selectedCryptoCurrency);
Validator get textValidator => TextValidator();
Validator<String> get textValidator => TextValidator();
final FiatCurrency fiatFromSettings;
@ -142,7 +142,7 @@ abstract class SendViewModelBase with Store {
PendingTransaction? pendingTransaction;
@computed
String get balance => balanceViewModel.availableBalance;
String get balance => _wallet.balance[selectedCryptoCurrency]!.formattedAvailableBalance;
@computed
bool get isFiatDisabled => balanceViewModel.isFiatDisabled;
@ -196,6 +196,7 @@ abstract class SendViewModelBase with Store {
final BalanceViewModel balanceViewModel;
final FiatConversionStore _fiatConversationStore;
final Box<TransactionDescription> transactionDescriptionBox;
final bool hasMultipleTokens;
@action
Future<void> createTransaction() async {
@ -295,7 +296,8 @@ abstract class SendViewModelBase with Store {
throw Exception('Priority is null for wallet type: ${_wallet.type}');
}
return ethereum!.createEthereumTransactionCredentials(outputs, priority: priority);
return ethereum!.createEthereumTransactionCredentials(
outputs, priority: priority, currency: selectedCryptoCurrency);
default:
throw Exception('Unexpected wallet type: ${_wallet.type}');
}

View file

@ -508,10 +508,22 @@ abstract class Ethereum {
TransactionPriority deserializeEthereumTransactionPriority(int raw);
int getEstimatedFee(Object wallet, TransactionPriority priority);
Object createEthereumTransactionCredentials(List<Output> outputs, {required TransactionPriority priority, int? feeRate});
Object createEthereumTransactionCredentialsRaw(List<OutputInfo> outputs, {TransactionPriority? priority, required int feeRate});
Object createEthereumTransactionCredentials(
List<Output> outputs, {
required TransactionPriority priority,
required CryptoCurrency currency,
int? feeRate,
});
Object createEthereumTransactionCredentialsRaw(
List<OutputInfo> outputs, {
TransactionPriority? priority,
required CryptoCurrency currency,
required int feeRate,
});
int formatterEthereumParseAmount(String amount);
List<CryptoCurrency> getERC20Currencies(Object wallet);
}
""";