mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-01-23 11:15:33 +00:00
Add send ERC-20 tokens initial flow
This commit is contained in:
parent
f08ab62359
commit
94216e6987
11 changed files with 515 additions and 421 deletions
|
@ -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 =
|
||||
|
|
|
@ -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(
|
||||
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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -511,17 +511,16 @@ class ExchangeCardState extends State<ExchangeCard> {
|
|||
|
||||
void _presentPicker(BuildContext context) {
|
||||
showPopUp<void>(
|
||||
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 != null
|
||||
? widget.onCurrencySelected(item as CryptoCurrency)
|
||||
: null),
|
||||
context: context);
|
||||
onItemSelected: (Currency item) => widget.onCurrencySelected(item as CryptoCurrency),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showAmountPopup(BuildContext context, PaymentRequest paymentRequest) {
|
||||
|
|
|
@ -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';
|
||||
|
@ -38,12 +41,8 @@ class SendCard extends StatefulWidget {
|
|||
);
|
||||
}
|
||||
|
||||
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(),
|
||||
|
@ -102,10 +101,7 @@ class SendCardState extends State<SendCard>
|
|||
KeyboardActions(
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.bodyLarge!
|
||||
.backgroundColor!,
|
||||
keyboardBarColor: Theme.of(context).accentTextTheme.bodyLarge!.backgroundColor!,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
|
@ -116,24 +112,26 @@ class SendCardState extends State<SendCard>
|
|||
focusNode: fiatAmountFocus,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
)
|
||||
]),
|
||||
],
|
||||
),
|
||||
child: Container(
|
||||
height: 0,
|
||||
color: Colors.transparent,
|
||||
)),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: ResponsiveLayoutUtil.instance.isMobile(context) ? BoxDecoration(
|
||||
decoration: ResponsiveLayoutUtil.instance.isMobile(context)
|
||||
? BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(24),
|
||||
bottomRight: Radius.circular(24)),
|
||||
bottomRight: Radius.circular(24),
|
||||
),
|
||||
gradient: LinearGradient(colors: [
|
||||
Theme.of(context).primaryTextTheme!.titleMedium!.color!,
|
||||
Theme.of(context)
|
||||
.primaryTextTheme!
|
||||
.titleMedium!
|
||||
.decorationColor!,
|
||||
Theme.of(context).primaryTextTheme.titleMedium!.color!,
|
||||
Theme.of(context).primaryTextTheme.titleMedium!.decorationColor!,
|
||||
], begin: Alignment.topLeft, end: Alignment.bottomRight),
|
||||
) : null,
|
||||
)
|
||||
: 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,22 +185,17 @@ class SendCardState extends State<SendCard>
|
|||
selectedCurrency: sendViewModel.currency,
|
||||
);
|
||||
}),
|
||||
if (output.isParsedAddress) Padding(
|
||||
if (output.isParsedAddress)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: BaseTextFormField(
|
||||
controller: extractedAddressController,
|
||||
readOnly: true,
|
||||
borderColor: Theme.of(context)
|
||||
.primaryTextTheme!
|
||||
.headlineSmall!
|
||||
.color!,
|
||||
borderColor:
|
||||
Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
validator: sendViewModel.addressValidator
|
||||
)
|
||||
),
|
||||
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||
validator: sendViewModel.addressValidator)),
|
||||
Observer(
|
||||
builder: (_) => Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
|
@ -220,46 +205,80 @@ class SendCardState extends State<SendCard>
|
|||
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(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
)),
|
||||
sendViewModel.selectedCryptoCurrency.tag != null ? Padding(
|
||||
fontSize: 16,
|
||||
color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
sendViewModel.selectedCryptoCurrency.title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
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!
|
||||
.primaryTextTheme.headlineMedium!
|
||||
.color!,
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(6))),
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(6),
|
||||
)),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: Text( sendViewModel.selectedCryptoCurrency.tag!,
|
||||
child: Text(
|
||||
sendViewModel.selectedCryptoCurrency.tag!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme!
|
||||
.headlineMedium!
|
||||
.decorationColor!)),
|
||||
.primaryTextTheme.headlineMedium!
|
||||
.decorationColor!),
|
||||
),
|
||||
),
|
||||
),
|
||||
) : Container(),
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 10.0),
|
||||
child: Text(':',
|
||||
child: Text(
|
||||
':',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: Colors.white)),
|
||||
color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -270,8 +289,7 @@ class SendCardState extends State<SendCard>
|
|||
BaseTextFormField(
|
||||
focusNode: cryptoAmountFocus,
|
||||
controller: cryptoAmountController,
|
||||
keyboardType:
|
||||
TextInputType.numberWithOptions(
|
||||
keyboardType: TextInputType.numberWithOptions(
|
||||
signed: false, decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))
|
||||
|
@ -287,89 +305,89 @@ class SendCardState extends State<SendCard>
|
|||
color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme!
|
||||
.headlineSmall!
|
||||
.primaryTextTheme.headlineSmall!
|
||||
.decorationColor!,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14),
|
||||
validator: output.sendAll
|
||||
? sendViewModel.allAmountValidator
|
||||
: sendViewModel
|
||||
.amountValidator),
|
||||
if (!sendViewModel.isBatchSending) Positioned(
|
||||
: sendViewModel.amountValidator,
|
||||
),
|
||||
if (!sendViewModel.isBatchSending)
|
||||
Positioned(
|
||||
top: 2,
|
||||
right: 0,
|
||||
child: Container(
|
||||
width: prefixIconWidth,
|
||||
height: prefixIconHeight,
|
||||
child: InkWell(
|
||||
onTap: () async =>
|
||||
output.setSendAll(),
|
||||
onTap: () async => output.setSendAll(),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme!
|
||||
.headlineMedium!
|
||||
.primaryTextTheme.headlineMedium!
|
||||
.color!,
|
||||
borderRadius:
|
||||
BorderRadius.all(
|
||||
Radius.circular(6))),
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(6),
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
S.of(context).all,
|
||||
textAlign:
|
||||
TextAlign.center,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight:
|
||||
FontWeight.bold,
|
||||
color:
|
||||
Theme.of(context)
|
||||
.primaryTextTheme!
|
||||
.headlineMedium!
|
||||
.decorationColor!))),
|
||||
))))]),
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme.headlineMedium!
|
||||
.decorationColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
)),
|
||||
Divider(height: 1,color: Theme.of(context)
|
||||
.primaryTextTheme!
|
||||
.headlineSmall!
|
||||
.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,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Text(
|
||||
S.of(context).available_balance +
|
||||
':',
|
||||
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!
|
||||
.primaryTextTheme.headlineSmall!
|
||||
.decorationColor!),
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
if (!sendViewModel.isFiatDisabled)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
|
@ -377,81 +395,70 @@ 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 + ':',
|
||||
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),
|
||||
onTap: () => _setTransactionPriority(context),
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
S
|
||||
.of(context)
|
||||
.send_estimated_fee,
|
||||
S.of(context).send_estimated_fee,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight:
|
||||
FontWeight.w500,
|
||||
fontWeight: FontWeight.w500,
|
||||
//color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
|
||||
color: Colors.white)),
|
||||
color: Colors.white),
|
||||
),
|
||||
Container(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
@ -461,45 +468,37 @@ class SendCardState extends State<SendCard>
|
|||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
output
|
||||
.estimatedFee
|
||||
.toString() +
|
||||
output.estimatedFee.toString() +
|
||||
' ' +
|
||||
sendViewModel
|
||||
.selectedCryptoCurrency.toString(),
|
||||
sendViewModel.selectedCryptoCurrency.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight:
|
||||
FontWeight.w600,
|
||||
fontWeight: FontWeight.w600,
|
||||
//color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
|
||||
color:
|
||||
Colors.white)),
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsets.only(top: 5),
|
||||
padding: EdgeInsets.only(top: 5),
|
||||
child: sendViewModel.isFiatDisabled
|
||||
? const SizedBox(height: 14)
|
||||
: Text(output
|
||||
.estimatedFeeFiatAmount
|
||||
+ ' ' +
|
||||
sendViewModel
|
||||
.fiat.title,
|
||||
: Text(
|
||||
output.estimatedFeeFiatAmount +
|
||||
' ' +
|
||||
sendViewModel.fiat.title,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight:
|
||||
FontWeight.w600,
|
||||
color: Theme
|
||||
.of(context)
|
||||
.primaryTextTheme!
|
||||
.headlineSmall!
|
||||
.decorationColor!))
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme.headlineSmall!
|
||||
.decorationColor!,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 2,
|
||||
left: 5),
|
||||
padding: EdgeInsets.only(top: 2, left: 5),
|
||||
child: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 12,
|
||||
|
@ -512,36 +511,38 @@ class SendCardState extends State<SendCard>
|
|||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
if (sendViewModel.isElectrumWallet) Padding(
|
||||
),
|
||||
),
|
||||
if (sendViewModel.isElectrumWallet)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 6),
|
||||
child: GestureDetector(
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.unspentCoinsList),
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.unspentCoinsList),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).coin_control,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white)),
|
||||
color: Colors.white),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 12,
|
||||
color: Colors.white,
|
||||
)
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
))
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -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,6 +663,7 @@ class SendCardState extends State<SendCard>
|
|||
final selectedItem = items.indexOf(sendViewModel.transactionPriority);
|
||||
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) => Picker(
|
||||
items: items,
|
||||
displayItem: sendViewModel.displayFeeRate,
|
||||
|
@ -671,7 +673,20 @@ class SendCardState extends State<SendCard>
|
|||
onItemSelected: (TransactionPriority priority) =>
|
||||
sendViewModel.setTransactionPriority(priority),
|
||||
),
|
||||
context: context);
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -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}');
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
""";
|
||||
|
||||
|
|
Loading…
Reference in a new issue