diff --git a/cw_ethereum/lib/deuro/deuro_savings.dart b/cw_ethereum/lib/deuro/deuro_savings.dart new file mode 100644 index 000000000..4c37f944c --- /dev/null +++ b/cw_ethereum/lib/deuro/deuro_savings.dart @@ -0,0 +1,121 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:crypto/crypto.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_ethereum/deuro/deuro_savings_contract.dart'; +import 'package:cw_ethereum/ethereum_wallet.dart'; +import 'package:cw_evm/contract/erc20.dart'; +import 'package:cw_evm/evm_chain_transaction_priority.dart'; +import 'package:cw_evm/pending_evm_chain_transaction.dart'; +import 'package:web3dart/web3dart.dart'; + +const String savingsGatewayAddress = + "0x073493d73258C4BEb6542e8dd3e1b2891C972303"; + +const String dEuroAddress = "0xbA3f535bbCcCcA2A154b573Ca6c5A49BAAE0a3ea"; + +class DEuro { + final SavingsGateway _savingsGateway; + final ERC20 _dEuro; + final EthereumWallet _wallet; + + DEuro(EthereumWallet wallet) + : _wallet = wallet, + _savingsGateway = _getSavingsGateway(wallet.getWeb3Client()!), + _dEuro = _getDEuroToken(wallet.getWeb3Client()!); + + static SavingsGateway _getSavingsGateway(Web3Client client) => SavingsGateway( + address: EthereumAddress.fromHex(savingsGatewayAddress), + client: client, + ); + + static ERC20 _getDEuroToken(Web3Client client) => ERC20( + address: EthereumAddress.fromHex(dEuroAddress), + client: client, + ); + + final frontendCode = + Uint8List.fromList(sha256.convert(utf8.encode("wallet")).bytes); + + EthereumAddress get _address => + EthereumAddress.fromHex(_wallet.walletAddresses.primaryAddress); + + Future get savingsBalance async => + (await _savingsGateway.savings(accountOwner: _address)).saved; + + Future get accruedInterest => + _savingsGateway.accruedInterest(accountOwner: _address); + + Future get interestRate => _savingsGateway.currentRatePPM(); + + Future get approvedBalance => + _dEuro.allowance(_address, _savingsGateway.self.address); + + Future depositSavings( + BigInt amount, EVMChainTransactionPriority priority) async { + final signedTransaction = await _savingsGateway.save( + (amount: amount, frontendCode: frontendCode), + credentials: _wallet.evmChainPrivateKey, + ); + + final fee = await _wallet.calculateActualEstimatedFeeForCreateTransaction( + amount: amount, + contractAddress: _savingsGateway.self.address.hexEip55, + receivingAddressHex: _savingsGateway.self.address.hexEip55, + priority: priority, + data: _savingsGateway.self.abi.functions[17] + .encodeCall([amount, frontendCode]), + ); + + final sendTransaction = + () => _wallet.getWeb3Client()!.sendRawTransaction(signedTransaction); + + return PendingEVMChainTransaction( + sendTransaction: sendTransaction, + signedTransaction: signedTransaction, + fee: BigInt.from(fee.estimatedGasFee), + amount: amount.toString(), + exponent: 18); + } + + Future withdrawSavings( + BigInt amount, EVMChainTransactionPriority priority) async { + final signedTransaction = await _savingsGateway.withdraw( + (target: _address, amount: amount, frontendCode: frontendCode), + credentials: _wallet.evmChainPrivateKey, + ); + + final fee = await _wallet.calculateActualEstimatedFeeForCreateTransaction( + amount: amount, + contractAddress: _savingsGateway.self.address.hexEip55, + receivingAddressHex: _savingsGateway.self.address.hexEip55, + priority: priority, + data: _savingsGateway.self.abi.functions[17] + .encodeCall([amount, frontendCode]), + ); + + final sendTransaction = + () => _wallet.getWeb3Client()!.sendRawTransaction(signedTransaction); + + return PendingEVMChainTransaction( + sendTransaction: sendTransaction, + signedTransaction: signedTransaction, + fee: BigInt.from(fee.estimatedGasFee), + amount: amount.toString(), + exponent: 18); + } + + // Set an infinite approval to save gas in the future + Future enableSavings( + EVMChainTransactionPriority priority) async => + (await _wallet.createApprovalTransaction( + BigInt.parse( + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + radix: 16, + ), + _savingsGateway.self.address.hexEip55, + CryptoCurrency.deuro, + priority, + )) as PendingEVMChainTransaction; +} diff --git a/cw_ethereum/lib/deuro/deuro_savings_contract.dart b/cw_ethereum/lib/deuro/deuro_savings_contract.dart new file mode 100644 index 000000000..ca2eb5dcc --- /dev/null +++ b/cw_ethereum/lib/deuro/deuro_savings_contract.dart @@ -0,0 +1,543 @@ +// ignore_for_file: type=lint +// ignore_for_file: unused_local_variable, unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:web3dart/web3dart.dart' as _i1; +import 'dart:typed_data' as _i2; + +final _contractAbi = _i1.ContractAbi.fromJson( + '[{"inputs":[{"internalType":"contract IDecentralizedEURO","name":"deuro_","type":"address"},{"internalType":"uint24","name":"initialRatePPM","type":"uint24"},{"internalType":"address","name":"gateway_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ChangeNotReady","type":"error"},{"inputs":[],"name":"ModuleDisabled","type":"error"},{"inputs":[],"name":"NoPendingChange","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"interest","type":"uint256"}],"name":"InterestCollected","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint24","name":"newRate","type":"uint24"}],"name":"RateChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"who","type":"address"},{"indexed":false,"internalType":"uint24","name":"nextRate","type":"uint24"},{"indexed":false,"internalType":"uint40","name":"nextChange","type":"uint40"}],"name":"RateProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint192","name":"amount","type":"uint192"}],"name":"Saved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint192","name":"amount","type":"uint192"}],"name":"Withdrawn","type":"event"},{"inputs":[],"name":"GATEWAY","outputs":[{"internalType":"contract IFrontendGateway","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"accountOwner","type":"address"}],"name":"accruedInterest","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"accountOwner","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"accruedInterest","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint192","name":"targetAmount","type":"uint192"},{"internalType":"bytes32","name":"frontendCode","type":"bytes32"}],"name":"adjust","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint192","name":"targetAmount","type":"uint192"}],"name":"adjust","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"applyChange","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint192","name":"saved","type":"uint192"},{"internalType":"uint64","name":"ticks","type":"uint64"}],"internalType":"struct Savings.Account","name":"account","type":"tuple"},{"internalType":"uint64","name":"ticks","type":"uint64"}],"name":"calculateInterest","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentRatePPM","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentTicks","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deuro","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"equity","outputs":[{"internalType":"contract IReserve","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextChange","outputs":[{"internalType":"uint40","name":"","type":"uint40"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextRatePPM","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint24","name":"newRatePPM_","type":"uint24"},{"internalType":"address[]","name":"helpers","type":"address[]"}],"name":"proposeChange","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"refreshBalance","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"refreshMyBalance","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint192","name":"amount","type":"uint192"},{"internalType":"bytes32","name":"frontendCode","type":"bytes32"}],"name":"save","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint192","name":"amount","type":"uint192"}],"name":"save","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint192","name":"amount","type":"uint192"},{"internalType":"bytes32","name":"frontendCode","type":"bytes32"}],"name":"save","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint192","name":"amount","type":"uint192"}],"name":"save","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"savings","outputs":[{"internalType":"uint192","name":"saved","type":"uint192"},{"internalType":"uint64","name":"ticks","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"ticks","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint192","name":"amount","type":"uint192"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint192","name":"amount","type":"uint192"},{"internalType":"bytes32","name":"frontendCode","type":"bytes32"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]', + 'SavingsGateway', +); + +class SavingsGateway extends _i1.GeneratedContract { + SavingsGateway({ + required _i1.EthereumAddress address, + required _i1.Web3Client client, + int? chainId, + }) : super( + _i1.DeployedContract( + _contractAbi, + address, + ), + client, + chainId, + ); + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future<_i1.EthereumAddress> GATEWAY({_i1.BlockNum? atBlock}) async { + final function = self.abi.functions[1]; + assert(checkSignature(function, '338c5371')); + final params = []; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as _i1.EthereumAddress); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future accruedInterest({ + required _i1.EthereumAddress accountOwner, + _i1.BlockNum? atBlock, + }) async { + final function = self.abi.functions[2]; + assert(checkSignature(function, '77267ec3')); + final params = [accountOwner]; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as BigInt); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future accruedInterest$2( + ({_i1.EthereumAddress accountOwner, BigInt timestamp}) args, { + _i1.BlockNum? atBlock, + }) async { + final function = self.abi.functions[3]; + assert(checkSignature(function, 'a696399d')); + final params = [ + args.accountOwner, + args.timestamp, + ]; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as BigInt); + } + + /// The optional [transaction] parameter can be used to override parameters + /// like the gas price, nonce and max gas. The `data` and `to` fields will be + /// set by the contract. + Future adjust( + ({BigInt targetAmount, _i2.Uint8List frontendCode}) args, { + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[4]; + assert(checkSignature(function, '753ef93c')); + final params = [ + args.targetAmount, + args.frontendCode, + ]; + return write( + credentials, + transaction, + function, + params, + ); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future calculateInterest( + ({dynamic account, BigInt ticks}) args, { + _i1.BlockNum? atBlock, + }) async { + final function = self.abi.functions[7]; + assert(checkSignature(function, '7915ce20')); + final params = [ + args.account, + args.ticks, + ]; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as BigInt); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future currentRatePPM({_i1.BlockNum? atBlock}) async { + final function = self.abi.functions[8]; + assert(checkSignature(function, '06a7b376')); + final params = []; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as BigInt); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future currentTicks({_i1.BlockNum? atBlock}) async { + final function = self.abi.functions[9]; + assert(checkSignature(function, 'b079f163')); + final params = []; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as BigInt); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future<_i1.EthereumAddress> deuro({_i1.BlockNum? atBlock}) async { + final function = self.abi.functions[10]; + assert(checkSignature(function, '82b8eaf5')); + final params = []; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as _i1.EthereumAddress); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future<_i1.EthereumAddress> equity({_i1.BlockNum? atBlock}) async { + final function = self.abi.functions[11]; + assert(checkSignature(function, '91a0ac6a')); + final params = []; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as _i1.EthereumAddress); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future nextChange({_i1.BlockNum? atBlock}) async { + final function = self.abi.functions[12]; + assert(checkSignature(function, 'b6f83c17')); + final params = []; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as BigInt); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future nextRatePPM({_i1.BlockNum? atBlock}) async { + final function = self.abi.functions[13]; + assert(checkSignature(function, '2e4b20ab')); + final params = []; + final response = await read( + function, + params, + atBlock, + ); + return (response[0] as BigInt); + } + + /// The optional [transaction] parameter can be used to override parameters + /// like the gas price, nonce and max gas. The `data` and `to` fields will be + /// set by the contract. + Future refreshBalance( + ({_i1.EthereumAddress owner}) args, { + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[15]; + assert(checkSignature(function, 'b77cd1c7')); + final params = [args.owner]; + return write( + credentials, + transaction, + function, + params, + ); + } + + /// The optional [transaction] parameter can be used to override parameters + /// like the gas price, nonce and max gas. The `data` and `to` fields will be + /// set by the contract. + Future refreshMyBalance({ + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[16]; + assert(checkSignature(function, '85bd12d1')); + final params = []; + return write( + credentials, + transaction, + function, + params, + ); + } + + /// The optional [transaction] parameter can be used to override parameters + /// like the gas price, nonce and max gas. The `data` and `to` fields will be + /// set by the contract. + Future<_i2.Uint8List> save( + ({BigInt amount, _i2.Uint8List frontendCode}) args, { + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[17]; + assert(checkSignature(function, '9e2363dc')); + final params = [ + args.amount, + args.frontendCode, + ]; + return writeRaw( + credentials, + transaction, + function, + params, + ); + } + + /// The optional [transaction] parameter can be used to override parameters + /// like the gas price, nonce and max gas. The `data` and `to` fields will be + /// set by the contract. + Future<_i2.Uint8List> saveTo( + ({ + _i1.EthereumAddress owner, + BigInt amount, + _i2.Uint8List frontendCode + }) args, { + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[19]; + assert(checkSignature(function, 'cbcf9676')); + final params = [ + args.owner, + args.amount, + args.frontendCode, + ]; + return writeRaw( + credentials, + transaction, + function, + params, + ); + } + + /// The optional [atBlock] parameter can be used to view historical data. When + /// set, the function will be evaluated in the specified block. By default, the + /// latest on-chain block will be used. + Future savings({ + required _i1.EthereumAddress accountOwner, + _i1.BlockNum? atBlock, + }) async { + final function = self.abi.functions[21]; + assert(checkSignature(function, '1f7cdd5f')); + final params = [accountOwner]; + final response = await read( + function, + params, + atBlock, + ); + return Savings(response); + } + + /// The optional [transaction] parameter can be used to override parameters + /// like the gas price, nonce and max gas. The `data` and `to` fields will be + /// set by the contract. + Future<_i2.Uint8List> withdraw( + ({ + _i1.EthereumAddress target, + BigInt amount, + _i2.Uint8List frontendCode + }) args, { + required _i1.Credentials credentials, + _i1.Transaction? transaction, + }) async { + final function = self.abi.functions[24]; + assert(checkSignature(function, '829a0476')); + final params = [ + args.target, + args.amount, + args.frontendCode, + ]; + return writeRaw( + credentials, + transaction, + function, + params, + ); + } + + /// Returns a live stream of all InterestCollected events emitted by this contract. + Stream interestCollectedEvents({ + _i1.BlockNum? fromBlock, + _i1.BlockNum? toBlock, + }) { + final event = self.event('InterestCollected'); + final filter = _i1.FilterOptions.events( + contract: self, + event: event, + fromBlock: fromBlock, + toBlock: toBlock, + ); + return client.events(filter).map((_i1.FilterEvent result) { + final decoded = event.decodeResults( + result.topics!, + result.data!, + ); + return InterestCollected( + decoded, + result, + ); + }); + } + + /// Returns a live stream of all RateChanged events emitted by this contract. + Stream rateChangedEvents({ + _i1.BlockNum? fromBlock, + _i1.BlockNum? toBlock, + }) { + final event = self.event('RateChanged'); + final filter = _i1.FilterOptions.events( + contract: self, + event: event, + fromBlock: fromBlock, + toBlock: toBlock, + ); + return client.events(filter).map((_i1.FilterEvent result) { + final decoded = event.decodeResults( + result.topics!, + result.data!, + ); + return RateChanged( + decoded, + result, + ); + }); + } + + /// Returns a live stream of all RateProposed events emitted by this contract. + Stream rateProposedEvents({ + _i1.BlockNum? fromBlock, + _i1.BlockNum? toBlock, + }) { + final event = self.event('RateProposed'); + final filter = _i1.FilterOptions.events( + contract: self, + event: event, + fromBlock: fromBlock, + toBlock: toBlock, + ); + return client.events(filter).map((_i1.FilterEvent result) { + final decoded = event.decodeResults( + result.topics!, + result.data!, + ); + return RateProposed( + decoded, + result, + ); + }); + } + + /// Returns a live stream of all Saved events emitted by this contract. + Stream savedEvents({ + _i1.BlockNum? fromBlock, + _i1.BlockNum? toBlock, + }) { + final event = self.event('Saved'); + final filter = _i1.FilterOptions.events( + contract: self, + event: event, + fromBlock: fromBlock, + toBlock: toBlock, + ); + return client.events(filter).map((_i1.FilterEvent result) { + final decoded = event.decodeResults( + result.topics!, + result.data!, + ); + return Saved( + decoded, + result, + ); + }); + } + + /// Returns a live stream of all Withdrawn events emitted by this contract. + Stream withdrawnEvents({ + _i1.BlockNum? fromBlock, + _i1.BlockNum? toBlock, + }) { + final event = self.event('Withdrawn'); + final filter = _i1.FilterOptions.events( + contract: self, + event: event, + fromBlock: fromBlock, + toBlock: toBlock, + ); + return client.events(filter).map((_i1.FilterEvent result) { + final decoded = event.decodeResults( + result.topics!, + result.data!, + ); + return Withdrawn( + decoded, + result, + ); + }); + } +} + +class Savings { + Savings(List response) + : saved = (response[0] as BigInt), + ticks = (response[1] as BigInt); + + final BigInt saved; + + final BigInt ticks; +} + +class InterestCollected { + InterestCollected( + List response, + this.event, + ) : account = (response[0] as _i1.EthereumAddress), + interest = (response[1] as BigInt); + + final _i1.EthereumAddress account; + + final BigInt interest; + + final _i1.FilterEvent event; +} + +class RateChanged { + RateChanged( + List response, + this.event, + ) : newRate = (response[0] as BigInt); + + final BigInt newRate; + + final _i1.FilterEvent event; +} + +class RateProposed { + RateProposed( + List response, + this.event, + ) : who = (response[0] as _i1.EthereumAddress), + nextRate = (response[1] as BigInt), + nextChange = (response[2] as BigInt); + + final _i1.EthereumAddress who; + + final BigInt nextRate; + + final BigInt nextChange; + + final _i1.FilterEvent event; +} + +class Saved { + Saved( + List response, + this.event, + ) : account = (response[0] as _i1.EthereumAddress), + amount = (response[1] as BigInt); + + final _i1.EthereumAddress account; + + final BigInt amount; + + final _i1.FilterEvent event; +} + +class Withdrawn { + Withdrawn( + List response, + this.event, + ) : account = (response[0] as _i1.EthereumAddress), + amount = (response[1] as BigInt); + + final _i1.EthereumAddress account; + + final BigInt amount; + + final _i1.FilterEvent event; +} diff --git a/cw_ethereum/pubspec.yaml b/cw_ethereum/pubspec.yaml index c68f78fc9..1da1e3b3b 100644 --- a/cw_ethereum/pubspec.yaml +++ b/cw_ethereum/pubspec.yaml @@ -6,7 +6,7 @@ author: Cake Wallet homepage: https://cakewallet.com environment: - sdk: '>=2.18.2 <3.0.0' + sdk: ^3.5.0 flutter: ">=1.17.0" dependencies: diff --git a/cw_evm/lib/evm_chain_client.dart b/cw_evm/lib/evm_chain_client.dart index 7e6caf374..8b45d5544 100644 --- a/cw_evm/lib/evm_chain_client.dart +++ b/cw_evm/lib/evm_chain_client.dart @@ -76,7 +76,7 @@ abstract class EVMChainClient { Future getGasUnitPrice() async { try { final gasPrice = await _client!.getGasPrice(); - + return gasPrice.getInWei.toInt(); } catch (_) { return 0; @@ -101,6 +101,7 @@ abstract class EVMChainClient { String? contractAddress, EtherAmount? gasPrice, EtherAmount? maxFeePerGas, + Uint8List? data, }) async { try { if (contractAddress == null) { @@ -124,7 +125,7 @@ abstract class EVMChainClient { final gasEstimate = await _client!.estimateGas( sender: senderAddress, to: EthereumAddress.fromHex(contractAddress), - data: transfer.encodeCall([ + data: data ?? transfer.encodeCall([ toAddress, value.getInWei, ]), @@ -137,6 +138,21 @@ abstract class EVMChainClient { } } + Uint8List getEncodedDataForApprovalTransaction({ + required EthereumAddress toAddress, + required EtherAmount value, + required EthereumAddress contractAddress, + }) { + final contract = DeployedContract(ethereumContractAbi, contractAddress); + + final approve = contract.function('approve'); + + return approve.encodeCall([ + toAddress, + value.getInWei, + ]); + } + Future signTransaction({ required Credentials privateKey, required String toAddress, @@ -198,6 +214,50 @@ abstract class EVMChainClient { ); } + Future signApprovalTransaction({ + required Credentials privateKey, + required String spender, + required BigInt amount, + required BigInt gasFee, + required int estimatedGasUnits, + required int maxFeePerGas, + required EVMChainTransactionPriority priority, + required int exponent, + required String contractAddress, + }) async { + + final Transaction transaction = createTransaction( + from: privateKey.address, + to: EthereumAddress.fromHex(contractAddress), + maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip), + amount: EtherAmount.zero(), + maxGas: estimatedGasUnits, + maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas), + ); + + final erc20 = ERC20( + client: _client!, + address: EthereumAddress.fromHex(contractAddress), + chainId: chainId, + ); + + final signedTransaction = await erc20.approve( + EthereumAddress.fromHex(spender), + amount, + credentials: privateKey, + transaction: transaction, + ); + + return PendingEVMChainTransaction( + signedTransaction: prepareSignedTransactionForSending(signedTransaction), + amount: amount.toString(), + fee: gasFee, + sendTransaction: () => sendTransaction(signedTransaction), + exponent: exponent, + isInfiniteApproval: amount.toRadixString(16) == 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + ); + } + Transaction createTransaction({ required EthereumAddress from, required EthereumAddress to, diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index a1b253dd8..18f6e6ee5 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:math'; +import 'dart:typed_data'; import 'package:bip32/bip32.dart' as bip32; import 'package:bip39/bip39.dart' as bip39; @@ -255,6 +256,7 @@ abstract class EVMChainWalletBase required String? contractAddress, required String receivingAddressHex, required TransactionPriority priority, + Uint8List? data, }) async { try { if (priority is EVMChainTransactionPriority) { @@ -276,6 +278,7 @@ abstract class EVMChainWalletBase gasPrice: EtherAmount.fromInt(EtherUnit.wei, gasPrice), toAddress: EthereumAddress.fromHex(receivingAddressHex), maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas), + data: data, ); final totalGasFee = estimatedGas * maxFeePerGas; @@ -478,6 +481,43 @@ abstract class EVMChainWalletBase return pendingEVMChainTransaction; } + Future createApprovalTransaction( + BigInt amount, + String spender, + CryptoCurrency token, + EVMChainTransactionPriority priority) async { + final CryptoCurrency transactionCurrency = + balance.keys.firstWhere((element) => element.title == token.title); + assert(transactionCurrency is Erc20Token); + + final data = _client.getEncodedDataForApprovalTransaction( + contractAddress: EthereumAddress.fromHex( + (transactionCurrency as Erc20Token).contractAddress), + value: EtherAmount.fromBigInt(EtherUnit.wei, amount), + toAddress: EthereumAddress.fromHex(spender), + ); + + final gasFeesModel = await calculateActualEstimatedFeeForCreateTransaction( + amount: amount, + receivingAddressHex: spender, + priority: priority, + contractAddress: transactionCurrency.contractAddress, + data: data, + ); + + return _client.signApprovalTransaction( + privateKey: _evmChainPrivateKey, + spender: spender, + amount: amount, + priority: priority, + gasFee: BigInt.from(gasFeesModel.estimatedGasFee), + maxFeePerGas: gasFeesModel.maxFeePerGas, + estimatedGasUnits: gasFeesModel.estimatedGasUnits, + exponent: transactionCurrency.decimal, + contractAddress: transactionCurrency.contractAddress, + ); + } + Future _updateTransactions() async { try { if (_isTransactionUpdating) { diff --git a/cw_evm/lib/pending_evm_chain_transaction.dart b/cw_evm/lib/pending_evm_chain_transaction.dart index 6861b41f8..61b406470 100644 --- a/cw_evm/lib/pending_evm_chain_transaction.dart +++ b/cw_evm/lib/pending_evm_chain_transaction.dart @@ -11,6 +11,7 @@ class PendingEVMChainTransaction with PendingTransaction { final BigInt fee; final String amount; final int exponent; + final bool isInfiniteApproval; PendingEVMChainTransaction({ required this.sendTransaction, @@ -18,10 +19,12 @@ class PendingEVMChainTransaction with PendingTransaction { required this.fee, required this.amount, required this.exponent, + this.isInfiniteApproval = false, }); @override String get amountFormatted { + if (isInfiniteApproval) return "∞"; final _amount = (BigInt.parse(amount) / BigInt.from(pow(10, exponent))).toString(); return _amount.substring(0, min(10, _amount.length)); } diff --git a/lib/di.dart b/lib/di.dart index 1d925150f..46c453d61 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -35,6 +35,7 @@ import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart'; import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart'; import 'package:cake_wallet/src/screens/dev/secure_preferences_page.dart'; import 'package:cake_wallet/src/screens/dev/shared_preferences_page.dart'; +import 'package:cake_wallet/src/screens/integrations/deuro/savings_page.dart'; import 'package:cake_wallet/src/screens/settings/background_sync_page.dart'; import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart'; import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart'; @@ -43,6 +44,7 @@ import 'package:cake_wallet/themes/core/theme_store.dart'; import 'package:cake_wallet/view_model/dev/monero_background_sync.dart'; import 'package:cake_wallet/view_model/dev/secure_preferences.dart'; import 'package:cake_wallet/view_model/dev/shared_preferences.dart'; +import 'package:cake_wallet/view_model/integrations/deuro_view_model.dart'; import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart'; @@ -1510,6 +1512,10 @@ Future setup({ getIt.registerFactory(() => BackgroundSyncLogsViewModel()); getIt.registerFactory(() => DevBackgroundSyncLogsPage(getIt.get())); - + + getIt.registerFactory(() => DEuroViewModel(getIt())); + + getIt.registerFactory(() => DEuroSavingsPage(getIt())); + _isSetupFinished = true; } diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart index 40c7a0f77..3e3675d1f 100644 --- a/lib/ethereum/cw_ethereum.dart +++ b/lib/ethereum/cw_ethereum.dart @@ -67,8 +67,7 @@ class CWEthereum extends Ethereum { @override String getPublicKey(WalletBase wallet) { final privateKeyInUnitInt = (wallet as EthereumWallet).evmChainPrivateKey; - final publicKey = privateKeyInUnitInt.address.hex; - return publicKey; + return privateKeyInUnitInt.address.hex; } @override @@ -138,29 +137,24 @@ class CWEthereum extends Ethereum { } @override - List getERC20Currencies(WalletBase wallet) { - final ethereumWallet = wallet as EthereumWallet; - return ethereumWallet.erc20Currencies; - } + List getERC20Currencies(WalletBase wallet) => + (wallet as EthereumWallet).erc20Currencies; @override - Future addErc20Token(WalletBase wallet, CryptoCurrency token) async { - await (wallet as EthereumWallet).addErc20Token(token as Erc20Token); - } + Future addErc20Token(WalletBase wallet, CryptoCurrency token) => + (wallet as EthereumWallet).addErc20Token(token as Erc20Token); @override - Future deleteErc20Token(WalletBase wallet, CryptoCurrency token) async => - await (wallet as EthereumWallet).deleteErc20Token(token as Erc20Token); + Future deleteErc20Token(WalletBase wallet, CryptoCurrency token) => + (wallet as EthereumWallet).deleteErc20Token(token as Erc20Token); @override - Future removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) async => - await (wallet as EthereumWallet).removeTokenTransactionsInHistory(token as Erc20Token); + Future removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) => + (wallet as EthereumWallet).removeTokenTransactionsInHistory(token as Erc20Token); @override - Future getErc20Token(WalletBase wallet, String contractAddress) async { - final ethereumWallet = wallet as EthereumWallet; - return await ethereumWallet.getErc20Token(contractAddress, 'eth'); - } + Future getErc20Token(WalletBase wallet, String contractAddress) => + (wallet as EthereumWallet).getErc20Token(contractAddress, 'eth'); @override CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) { @@ -177,23 +171,19 @@ class CWEthereum extends Ethereum { } @override - void updateEtherscanUsageState(WalletBase wallet, bool isEnabled) { - (wallet as EthereumWallet).updateScanProviderUsageState(isEnabled); - } + void updateEtherscanUsageState(WalletBase wallet, bool isEnabled) => + (wallet as EthereumWallet).updateScanProviderUsageState(isEnabled); @override - Web3Client? getWeb3Client(WalletBase wallet) { - return (wallet as EthereumWallet).getWeb3Client(); - } + Web3Client? getWeb3Client(WalletBase wallet) => (wallet as EthereumWallet).getWeb3Client(); + @override String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress; @override - void setLedgerConnection( - WalletBase wallet, ledger.LedgerConnection connection) { + void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) { ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials) - .setLedgerConnection( - connection, wallet.walletInfo.derivationInfo?.derivationPath); + .setLedgerConnection(connection, wallet.walletInfo.derivationInfo?.derivationPath); } @override @@ -209,7 +199,44 @@ class CWEthereum extends Ethereum { } @override - List getDefaultTokenContractAddresses() { - return DefaultEthereumErc20Tokens().initialErc20Tokens.map((e) => e.contractAddress).toList(); - } + List getDefaultTokenContractAddresses() => + DefaultEthereumErc20Tokens().initialErc20Tokens.map((e) => e.contractAddress).toList(); + + Future createTokenApproval(WalletBase wallet, BigInt amount, String spender, + CryptoCurrency token, TransactionPriority priority) => + (wallet as EVMChainWallet).createApprovalTransaction( + amount, spender, token, priority as EVMChainTransactionPriority); + + // Integrations + @override + Future getDEuroSavingsBalance(WalletBase wallet) => + DEuro(wallet as EthereumWallet).savingsBalance; + + @override + Future getDEuroAccruedInterest(WalletBase wallet) => + DEuro(wallet as EthereumWallet).accruedInterest; + + @override + Future getDEuroInterestRate(WalletBase wallet) => + DEuro(wallet as EthereumWallet).interestRate; + + @override + Future getDEuroSavingsApproved(WalletBase wallet) => + DEuro(wallet as EthereumWallet).approvedBalance; + + @override + Future addDEuroSaving( + WalletBase wallet, BigInt amount, TransactionPriority priority) => + DEuro(wallet as EthereumWallet) + .depositSavings(amount, priority as EVMChainTransactionPriority); + + @override + Future removeDEuroSaving( + WalletBase wallet, BigInt amount, TransactionPriority priority) => + DEuro(wallet as EthereumWallet) + .withdrawSavings(amount, priority as EVMChainTransactionPriority); + + @override + Future enableDEuroSaving(WalletBase wallet, TransactionPriority priority) => + DEuro(wallet as EthereumWallet).enableSavings(priority as EVMChainTransactionPriority); } diff --git a/lib/polygon/cw_polygon.dart b/lib/polygon/cw_polygon.dart index ec98137c5..557cedc39 100644 --- a/lib/polygon/cw_polygon.dart +++ b/lib/polygon/cw_polygon.dart @@ -67,8 +67,7 @@ class CWPolygon extends Polygon { @override String getPublicKey(WalletBase wallet) { final privateKeyInUnitInt = (wallet as PolygonWallet).evmChainPrivateKey; - final publicKey = privateKeyInUnitInt.address.hex; - return publicKey; + return privateKeyInUnitInt.address.hex; } @override @@ -137,28 +136,27 @@ class CWPolygon extends Polygon { } @override - List getERC20Currencies(WalletBase wallet) { - final polygonWallet = wallet as PolygonWallet; - return polygonWallet.erc20Currencies; - } + List getERC20Currencies(WalletBase wallet) => + (wallet as PolygonWallet).erc20Currencies; @override - Future addErc20Token(WalletBase wallet, CryptoCurrency token) async => - await (wallet as PolygonWallet).addErc20Token(token as Erc20Token); + Future addErc20Token(WalletBase wallet, CryptoCurrency token) => + (wallet as PolygonWallet).addErc20Token(token as Erc20Token); @override - Future deleteErc20Token(WalletBase wallet, CryptoCurrency token) async => - await (wallet as PolygonWallet).deleteErc20Token(token as Erc20Token); + Future deleteErc20Token(WalletBase wallet, CryptoCurrency token) => + (wallet as PolygonWallet).deleteErc20Token(token as Erc20Token); @override - Future removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) async => - await (wallet as PolygonWallet).removeTokenTransactionsInHistory(token as Erc20Token); + Future removeTokenTransactionsInHistory( + WalletBase wallet, CryptoCurrency token) => + (wallet as PolygonWallet) + .removeTokenTransactionsInHistory(token as Erc20Token); @override - Future getErc20Token(WalletBase wallet, String contractAddress) async { - final polygonWallet = wallet as PolygonWallet; - return await polygonWallet.getErc20Token(contractAddress, 'polygon'); - } + Future getErc20Token( + WalletBase wallet, String contractAddress) => + (wallet as PolygonWallet).getErc20Token(contractAddress, 'polygon'); @override CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) { @@ -176,23 +174,29 @@ class CWPolygon extends Polygon { } @override - void updatePolygonScanUsageState(WalletBase wallet, bool isEnabled) { - (wallet as PolygonWallet).updateScanProviderUsageState(isEnabled); - } + void updatePolygonScanUsageState(WalletBase wallet, bool isEnabled) => + (wallet as PolygonWallet).updateScanProviderUsageState(isEnabled); @override - Web3Client? getWeb3Client(WalletBase wallet) { - return (wallet as PolygonWallet).getWeb3Client(); - } + Web3Client? getWeb3Client(WalletBase wallet) => + (wallet as PolygonWallet).getWeb3Client(); - String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress; + @override + String getTokenAddress(CryptoCurrency asset) => + (asset as Erc20Token).contractAddress; + + @override + Future createTokenApproval(WalletBase wallet, + BigInt amount, String spender, CryptoCurrency token, TransactionPriority priority) => + (wallet as EVMChainWallet) + .createApprovalTransaction(amount, spender, token, priority as EVMChainTransactionPriority); @override void setLedgerConnection( WalletBase wallet, ledger.LedgerConnection connection) { ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials) .setLedgerConnection( - connection, wallet.walletInfo.derivationInfo?.derivationPath); + connection, wallet.walletInfo.derivationInfo?.derivationPath); } @override @@ -206,9 +210,10 @@ class CWPolygon extends Polygon { throw err; } } - + @override - List getDefaultTokenContractAddresses() { - return DefaultPolygonErc20Tokens().initialPolygonErc20Tokens.map((e) => e.contractAddress).toList(); - } + List getDefaultTokenContractAddresses() => DefaultPolygonErc20Tokens() + .initialPolygonErc20Tokens + .map((e) => e.contractAddress) + .toList(); } diff --git a/lib/router.dart b/lib/router.dart index 4150c239f..afb1b46f8 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -48,6 +48,7 @@ import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dar import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_external_send_page.dart'; import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart'; import 'package:cake_wallet/src/screens/faq/faq_page.dart'; +import 'package:cake_wallet/src/screens/integrations/deuro/savings_page.dart'; import 'package:cake_wallet/src/screens/monero_accounts/monero_account_edit_or_create_page.dart'; import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart'; import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart'; @@ -920,6 +921,11 @@ Route createRoute(RouteSettings settings) { builder: (_) => getIt.get(), ); + case Routes.dEuroSavings: + return MaterialPageRoute( + builder: (_) => getIt.get(), + ); + default: return MaterialPageRoute( builder: (_) => Scaffold( diff --git a/lib/routes.dart b/lib/routes.dart index bbaca6618..addaec1d9 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -127,4 +127,6 @@ class Routes { static const walletGroupExistingSeedDescriptionPage = '/wallet_group_existing_seed_description_page'; static const walletSeedVerificationPage = '/wallet_seed_verification_page'; static const exchangeTradeExternalSendPage = '/exchange_trade_external_send_page'; + + static const dEuroSavings = '/integration/dEuro/savings'; } diff --git a/lib/src/screens/dashboard/pages/cake_features_page.dart b/lib/src/screens/dashboard/pages/cake_features_page.dart index 50257ad07..ba19f5329 100644 --- a/lib/src/screens/dashboard/pages/cake_features_page.dart +++ b/lib/src/screens/dashboard/pages/cake_features_page.dart @@ -5,15 +5,16 @@ import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart'; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; class CakeFeaturesPage extends StatelessWidget { - CakeFeaturesPage({required this.dashboardViewModel, required this.cakeFeaturesViewModel}); + CakeFeaturesPage( + {required this.dashboardViewModel, required this.cakeFeaturesViewModel}); final DashboardViewModel dashboardViewModel; final CakeFeaturesViewModel cakeFeaturesViewModel; @@ -58,6 +59,23 @@ class CakeFeaturesPage extends StatelessWidget { fit: BoxFit.cover, ), ), + if (dashboardViewModel.type == WalletType.ethereum) ...[ + DashBoardRoundedCardWidget( + isDarkTheme: dashboardViewModel.isDarkTheme, + shadowBlur: dashboardViewModel.getShadowBlur(), + shadowSpread: dashboardViewModel.getShadowSpread(), + onTap: () => + Navigator.of(context).pushNamed(Routes.dEuroSavings), + title: S.of(context).deuro_savings, + subTitle: S.of(context).deuro_savings_subtitle, + image: Image.asset( + 'assets/images/deuro_icon.png', + height: 80, + width: 80, + fit: BoxFit.cover, + ), + ), + ], DashBoardRoundedCardWidget( isDarkTheme: dashboardViewModel.isDarkTheme, shadowBlur: dashboardViewModel.getShadowBlur(), diff --git a/lib/src/screens/integrations/deuro/savings_page.dart b/lib/src/screens/integrations/deuro/savings_page.dart new file mode 100644 index 000000000..a58578893 --- /dev/null +++ b/lib/src/screens/integrations/deuro/savings_page.dart @@ -0,0 +1,197 @@ +import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/integrations/deuro/widgets/interest_card_widget.dart'; +import 'package:cake_wallet/src/screens/integrations/deuro/widgets/savings_card_widget.dart'; +import 'package:cake_wallet/src/screens/integrations/deuro/widgets/savings_edit_sheet.dart'; +import 'package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart'; +import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart'; +import 'package:cake_wallet/src/widgets/gradient_background.dart'; +import 'package:cake_wallet/view_model/integrations/deuro_view_model.dart'; +import 'package:cake_wallet/view_model/send/send_view_model_state.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:mobx/mobx.dart'; + +class DEuroSavingsPage extends BasePage { + final DEuroViewModel _dEuroViewModel; + + DEuroSavingsPage(this._dEuroViewModel); + + @override + bool get gradientBackground => true; + + @override + Widget Function(BuildContext, Widget) get rootWrapper => + (context, scaffold) => GradientBackground(scaffold: scaffold); + + @override + String get title => S.current.deuro_savings; + + Widget trailing(BuildContext context) => MergeSemantics( + child: SizedBox( + height: 37, + width: 37, + child: ButtonTheme( + minWidth: double.minPositive, + child: Semantics( + label: "Refresh", + child: TextButton( + style: TextButton.styleFrom( + foregroundColor: Theme.of(context).colorScheme.onSurface, + overlayColor: WidgetStateColor.resolveWith( + (states) => Colors.transparent), + ), + onPressed: _dEuroViewModel.reloadSavingsUserData, + child: Icon( + Icons.refresh, + color: pageIconColor(context), + size: 20, + ), + ), + ), + ), + ), + ); + + @override + Widget body(BuildContext context) { + WidgetsBinding.instance + .addPostFrameCallback((_) => _setReactions(context, _dEuroViewModel)); + + return Container( + width: double.infinity, + child: Column( + children: [ + Observer( + builder: (_) => SavingsCard( + isDarkTheme: currentTheme.isDark, + interestRate: "${_dEuroViewModel.interestRate}%", + savingsBalance: _dEuroViewModel.savingsBalance, + currency: CryptoCurrency.deuro, + onAddSavingsPressed: () => _onSavingsAdd(context), + onRemoveSavingsPressed: () => _onSavingsRemove(context), + onApproveSavingsPressed: _dEuroViewModel.prepareApproval, + isEnabled: _dEuroViewModel.isEnabled, + ), + ), + Observer( + builder: (_) => InterestCardWidget( + isDarkTheme: currentTheme.isDark, + title: S.of(context).deuro_savings_collect_interest, + collectedInterest: _dEuroViewModel.accruedInterest, + onCollectInterest: _dEuroViewModel.prepareCollectInterest, + ), + ), + ], + ), + ); + } + + Future _onSavingsAdd(BuildContext context) async { + final amount = await Navigator.of(context).push(MaterialPageRoute( + builder: (BuildContext context) => SavingEditPage(isAdding: true))); + if (amount != null) _dEuroViewModel.prepareSavingsEdit(amount, true); + } + + Future _onSavingsRemove(BuildContext context) async { + final amount = await Navigator.of(context).push(MaterialPageRoute( + builder: (BuildContext context) => SavingEditPage(isAdding: false))); + if (amount != null) _dEuroViewModel.prepareSavingsEdit(amount, false); + } + + bool _isReactionsSet = false; + + void _setReactions(BuildContext context, DEuroViewModel dEuroViewModel) { + if (_isReactionsSet) return; + + reaction((_) => dEuroViewModel.transaction, (PendingTransaction? tx) async { + if (tx == null) return; + final result = await showModalBottomSheet( + context: context, + isDismissible: false, + isScrollControlled: true, + builder: (BuildContext bottomSheetContext) => ConfirmSendingBottomSheet( + key: ValueKey('savings_page_confirm_sending_dialog_key'), + titleText: S.of(bottomSheetContext).confirm_transaction, + currentTheme: currentTheme, + walletType: WalletType.ethereum, + titleIconPath: CryptoCurrency.deuro.iconPath, + currency: CryptoCurrency.deuro, + amount: S.of(bottomSheetContext).send_amount, + amountValue: tx.amountFormatted, + fiatAmountValue: "", + fee: S.of(bottomSheetContext).send_estimated_fee, + feeValue: tx.feeFormatted, + feeFiatAmount: "", + outputs: [], + onSlideComplete: () async { + Navigator.of(bottomSheetContext).pop(true); + dEuroViewModel.commitTransaction(); + }, + change: tx.change, + ), + ); + + if (result == null) dEuroViewModel.dismissTransaction(); + }); + + reaction((_) => dEuroViewModel.approvalTransaction, (PendingTransaction? tx) async { + if (tx == null) return; + final result = await showModalBottomSheet( + context: context, + isDismissible: false, + isScrollControlled: true, + builder: (BuildContext bottomSheetContext) => ConfirmSendingBottomSheet( + key: ValueKey('savings_page_confirm_approval_dialog_key'), + titleText: S.of(bottomSheetContext).approve_tokens, + currentTheme: currentTheme, + walletType: WalletType.ethereum, + titleIconPath: CryptoCurrency.deuro.iconPath, + currency: CryptoCurrency.deuro, + amount: S.of(bottomSheetContext).send_amount, + amountValue: tx.amountFormatted, + fiatAmountValue: "", + fee: S.of(bottomSheetContext).send_estimated_fee, + feeValue: tx.feeFormatted, + feeFiatAmount: "", + outputs: [], + onSlideComplete: () { + Navigator.of(bottomSheetContext).pop(true); + dEuroViewModel.commitApprovalTransaction(); + }, + change: tx.change, + ), + ); + + if (result == null) dEuroViewModel.dismissTransaction(); + }); + + reaction((_) => dEuroViewModel.state, (ExecutionState state) async { + if (state is TransactionCommitted) { + WidgetsBinding.instance.addPostFrameCallback((_) async { + if (!context.mounted) return; + + await showModalBottomSheet( + context: context, + isDismissible: false, + builder: (BuildContext bottomSheetContext) => InfoBottomSheet( + currentTheme: currentTheme, + titleText: S.of(bottomSheetContext).transaction_sent, + contentImage: 'assets/images/birthday_cake.png', + content: S.of(bottomSheetContext).deuro_tx_commited_content, + actionButtonText: S.of(bottomSheetContext).close, + actionButtonKey: ValueKey('send_page_sent_dialog_ok_button_key'), + actionButton: () => Navigator.of(bottomSheetContext).pop(), + ), + ); + }); + } + }); + + _isReactionsSet = true; + } +} diff --git a/lib/src/screens/integrations/deuro/widgets/edit_savings_bottom_sheet.dart b/lib/src/screens/integrations/deuro/widgets/edit_savings_bottom_sheet.dart new file mode 100644 index 000000000..f76b13e7e --- /dev/null +++ b/lib/src/screens/integrations/deuro/widgets/edit_savings_bottom_sheet.dart @@ -0,0 +1,45 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart'; +import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/view_model/integrations/deuro_view_model.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:flutter/material.dart'; + +class EditSavingsBottomSheet extends BaseBottomSheet { + EditSavingsBottomSheet(this.dEuroViewModel, {required super.titleText}); + + final _amountController = TextEditingController(); + final DEuroViewModel dEuroViewModel; + + @override + Widget contentWidget(BuildContext context) => Column( + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 10), + child: CurrencyAmountTextField( + hasUnderlineBorder: true, + borderWidth: 1.0, + selectedCurrency: CryptoCurrency.deuro.name.toUpperCase(), + amountFocusNode: null, + amountController: _amountController, + tag: CryptoCurrency.deuro.tag, + isAmountEditable: true, + ), + ), + ], + ); + + @override + Widget footerWidget(BuildContext context) => Padding( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 34), + child: LoadingPrimaryButton( + onPressed: () => dEuroViewModel.prepareSavingsEdit(_amountController.text, true), + text: S.of(context).confirm, + color: Theme.of(context).colorScheme.primary, + textColor: Theme.of(context).colorScheme.onPrimary, + isLoading: false, + isDisabled: false, + ), + ); +} diff --git a/lib/src/screens/integrations/deuro/widgets/interest_card_widget.dart b/lib/src/screens/integrations/deuro/widgets/interest_card_widget.dart new file mode 100644 index 000000000..03dd174f6 --- /dev/null +++ b/lib/src/screens/integrations/deuro/widgets/interest_card_widget.dart @@ -0,0 +1,67 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/integrations/deuro/widgets/savings_card_widget.dart'; +import 'package:cake_wallet/themes/utils/custom_theme_colors.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:flutter/material.dart'; + +class InterestCardWidget extends StatelessWidget { + InterestCardWidget({ + required this.title, + required this.collectedInterest, + super.key, + required this.isDarkTheme, + required this.onCollectInterest, + }); + + final String title; + final String collectedInterest; + final bool isDarkTheme; + final VoidCallback onCollectInterest; + + @override + Widget build(BuildContext context) { + return Stack(children: [ + Container( + margin: EdgeInsets.symmetric(horizontal: 16), + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + gradient: LinearGradient( + colors: [ + isDarkTheme + ? CustomThemeColors.cardGradientColorPrimaryDark + : CustomThemeColors.cardGradientColorPrimaryLight, + isDarkTheme + ? CustomThemeColors.cardGradientColorSecondaryDark + : CustomThemeColors.cardGradientColorSecondaryLight, + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: Padding( + padding: EdgeInsets.all(20), + child: Column( + children: [ + SavingsCard.getAssetBalanceRow( + context, + title: title, + subtitle: collectedInterest, + currency: CryptoCurrency.deuro, + hideSymbol: true, + ), + SizedBox(height: 10), + SavingsCard.getButton( + context, + label: S.of(context).deuro_collect_interest, + onPressed: onCollectInterest, + backgroundColor: Theme.of(context).colorScheme.primary, + color: Theme.of(context).colorScheme.onPrimary, + ), + ], + ), + ), + ), + ]); + } +} diff --git a/lib/src/screens/integrations/deuro/widgets/numpad.dart b/lib/src/screens/integrations/deuro/widgets/numpad.dart new file mode 100644 index 000000000..17914376c --- /dev/null +++ b/lib/src/screens/integrations/deuro/widgets/numpad.dart @@ -0,0 +1,107 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class NumberPad extends StatelessWidget { + final VoidCallback? onDecimalPressed; + final VoidCallback onDeletePressed; + final void Function(int index) onNumberPressed; + final FocusNode focusNode; + + const NumberPad({ + super.key, + required this.onNumberPressed, + required this.onDeletePressed, + required this.focusNode, + this.onDecimalPressed, + }); + + @override + Widget build(BuildContext context) => KeyboardListener( + focusNode: focusNode, + onKeyEvent: (keyEvent) { + if (keyEvent is KeyDownEvent) { + if (keyEvent.logicalKey.keyLabel == "Backspace") { + return onDeletePressed(); + } + + if ([".", ","].contains(keyEvent.logicalKey.keyLabel) && + onDecimalPressed != null) { + return onDecimalPressed!(); + } + + int? number = int.tryParse(keyEvent.character ?? ''); + if (number != null) return onNumberPressed(number); + } + }, + child: SizedBox( + height: 300, + child: GridView.count( + childAspectRatio: 2, + shrinkWrap: true, + crossAxisCount: 3, + physics: const NeverScrollableScrollPhysics(), + children: List.generate(12, (index) { + if (index == 9) { + if (onDecimalPressed == null) return Container(); + return InkWell( + onTap: onDecimalPressed, + child: Center( + child: Text( + '.', + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + fontWeight: FontWeight.w600, + fontSize: 30, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + textAlign: TextAlign.center, + ), + ), + ); + } else if (index == 10) { + index = 0; + } else if (index == 11) { + return MergeSemantics( + child: Container( + child: Semantics( + label: S.of(context).delete, + button: true, + onTap: onDeletePressed, + child: TextButton( + onPressed: onDeletePressed, + style: TextButton.styleFrom( + backgroundColor: + Colors.transparent, + shape: CircleBorder(), + ), + child: Image.asset( + 'assets/images/delete_icon.png', + color: Theme.of(context).colorScheme.primary, + ), + ), + ), + ), + ); + } else { + index++; + } + + return InkWell( + onTap: () => onNumberPressed(index), + child: Center( + child: Text( + '$index', + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + fontWeight: FontWeight.w600, + fontSize: 30, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + textAlign: TextAlign.center, + ), + ), + ); + }), + ), + ), + ); +} diff --git a/lib/src/screens/integrations/deuro/widgets/savings_card_widget.dart b/lib/src/screens/integrations/deuro/widgets/savings_card_widget.dart new file mode 100644 index 000000000..9196929c4 --- /dev/null +++ b/lib/src/screens/integrations/deuro/widgets/savings_card_widget.dart @@ -0,0 +1,263 @@ +import 'dart:math'; + +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; +import 'package:cake_wallet/themes/utils/custom_theme_colors.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:flutter/material.dart'; + +class SavingsCard extends StatelessWidget { + final bool isDarkTheme; + final bool isEnabled; + final String interestRate; + final String savingsBalance; + final CryptoCurrency currency; + final VoidCallback onAddSavingsPressed; + final VoidCallback onRemoveSavingsPressed; + final VoidCallback onApproveSavingsPressed; + + const SavingsCard({ + super.key, + required this.isDarkTheme, + required this.interestRate, + required this.savingsBalance, + required this.currency, + required this.onAddSavingsPressed, + required this.onRemoveSavingsPressed, + required this.onApproveSavingsPressed, + this.isEnabled = true, + }); + + @override + Widget build(BuildContext context) => Container( + margin: const EdgeInsets.all(15), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + gradient: LinearGradient( + colors: [ + isDarkTheme + ? CustomThemeColors.cardGradientColorPrimaryDark + : CustomThemeColors.cardGradientColorPrimaryLight, + isDarkTheme + ? CustomThemeColors.cardGradientColorSecondaryDark + : CustomThemeColors.cardGradientColorSecondaryLight, + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: Container( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + getAssetBalanceRow(context, + title: S.of(context).deuro_savings_balance, + subtitle: savingsBalance, + currency: currency), + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Row( + children: [ + Expanded( + child: Text( + 'Current APR', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: + Theme.of(context).colorScheme.onSurfaceVariant, + fontWeight: FontWeight.w500, + ), + softWrap: true, + ), + ), + Text( + interestRate, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + fontWeight: FontWeight.w500, + ), + softWrap: true, + ), + ], + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: isEnabled + ? [ + Expanded( + child: getButton( + context, + label: S.of(context).deuro_savings_add, + imagePath: 'assets/images/received.png', + onPressed: onAddSavingsPressed, + backgroundColor: + Theme.of(context).colorScheme.primary, + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + SizedBox(width: 12), + Expanded( + child: getButton( + context, + label: S.of(context).deuro_savings_remove, + imagePath: 'assets/images/upload.png', + onPressed: onRemoveSavingsPressed, + backgroundColor: + Theme.of(context).colorScheme.surface, + color: Theme.of(context) + .colorScheme + .onSecondaryContainer, + ), + ), + ] + : [ + Expanded( + child: getButton( + context, + label: S.of(context).deuro_savings_set_approval, + onPressed: onApproveSavingsPressed, + backgroundColor: + Theme.of(context).colorScheme.primary, + color: Theme.of(context).colorScheme.onPrimary, + ), + ) + ], + ), + ], + ), + )); + + static Widget getButton( + BuildContext context, { + required String label, + String? imagePath, + required VoidCallback onPressed, + required Color backgroundColor, + required Color color, + }) => + Semantics( + label: label, + child: OutlinedButton( + onPressed: onPressed, + style: OutlinedButton.styleFrom( + backgroundColor: backgroundColor, + side: BorderSide( + color: Theme.of(context).colorScheme.outlineVariant.withAlpha(0), + width: 0, + ), + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + ), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (imagePath != null) ...[ + Image.asset( + imagePath, + height: 30, + width: 30, + color: color, + ), + const SizedBox(width: 8), + ], + Text( + label, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: color, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ), + ), + ); + + static Widget getAssetBalanceRow( + BuildContext context, { + required String title, + required String subtitle, + required CryptoCurrency currency, + bool hideSymbol = true, + }) => + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + height: 1, + ), + ), + SizedBox(height: 6), + AutoSizeText( + subtitle, + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: Theme.of(context).colorScheme.onSurface, + fontWeight: FontWeight.w900, + fontSize: 24, + height: 1, + ), + maxLines: 1, + textAlign: TextAlign.start, + ), + ], + ), + SizedBox( + //width: min(MediaQuery.of(context).size.width * 0.2, 100), + child: Center( + child: Column( + children: [ + CakeImageWidget( + imageUrl: currency.iconPath, + height: 40, + width: 40, + errorWidget: Container( + height: 30.0, + width: 30.0, + child: Center( + child: Text( + currency.title + .substring(0, min(currency.title.length, 2)), + style: + Theme.of(context).textTheme.bodySmall?.copyWith( + fontSize: 11, + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ), + ), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Theme.of(context).colorScheme.surfaceContainer, + ), + ), + ), + if (!hideSymbol) ...[ + const SizedBox(height: 10), + Text( + currency.title, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontSize: 16, + fontWeight: FontWeight.w700, + color: Theme.of(context).colorScheme.onSurface, + height: 1, + ), + ), + ] + ], + ), + ), + ), + ], + ); +} diff --git a/lib/src/screens/integrations/deuro/widgets/savings_edit_sheet.dart b/lib/src/screens/integrations/deuro/widgets/savings_edit_sheet.dart new file mode 100644 index 000000000..bb1733fb5 --- /dev/null +++ b/lib/src/screens/integrations/deuro/widgets/savings_edit_sheet.dart @@ -0,0 +1,90 @@ +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/screens/integrations/deuro/widgets/numpad.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:flutter/material.dart'; + +class SavingEditPage extends BasePage { + final bool isAdding; + + SavingEditPage({required this.isAdding}); + + String get title => + isAdding ? S.current.deuro_savings_add : S.current.deuro_savings_remove; + + @override + Widget body(BuildContext context) => _SavingsEditBody(); +} + +class _SavingsEditBody extends StatefulWidget { + const _SavingsEditBody(); + + @override + State createState() => _SavingsEditBodyState(); +} + +class _SavingsEditBodyState extends State<_SavingsEditBody> { + @override + void initState() { + WidgetsBinding.instance + .addPostFrameCallback((_) => _numpadFocusNode.requestFocus()); + super.initState(); + } + + @override + void dispose() { + _numpadFocusNode.dispose(); + super.dispose(); + } + + String amount = '0'; + final FocusNode _numpadFocusNode = FocusNode(); + + @override + Widget build(BuildContext context) => SafeArea( + child: Column(children: [ + Expanded( + child: Center( + child: Padding( + padding: const EdgeInsets.only(left: 26, right: 26, top: 10), + child: AutoSizeText( + "${amount.toString()} dEuro", + maxLines: 1, + maxFontSize: 60, + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + fontWeight: FontWeight.w600, + fontSize: 60, + color: Theme.of(context).colorScheme.onSurface, + ), + textAlign: TextAlign.center, + ), + ), + )), + NumberPad( + focusNode: _numpadFocusNode, + onNumberPressed: (i) => setState( + () => amount = amount == '0' ? i.toString() : '${amount}${i}', + ), + onDeletePressed: () => setState( + () => amount = amount.length > 1 + ? amount.substring(0, amount.length - 1) + : '0', + ), + onDecimalPressed: () => + setState(() => amount = '${amount.replaceAll('.', '')}.'), + ), + Padding( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 34), + child: LoadingPrimaryButton( + onPressed: () => Navigator.pop(context, amount), + text: S.of(context).confirm, + color: Theme.of(context).colorScheme.primary, + textColor: Theme.of(context).colorScheme.onPrimary, + isLoading: false, + isDisabled: false, + ), + ) + ]), + ); +} diff --git a/lib/src/widgets/dashboard_card_widget.dart b/lib/src/widgets/dashboard_card_widget.dart index f1313c525..d5b4f8853 100644 --- a/lib/src/widgets/dashboard_card_widget.dart +++ b/lib/src/widgets/dashboard_card_widget.dart @@ -109,6 +109,7 @@ class DashBoardRoundedCardWidget extends StatelessWidget { ], ), ), + Padding(padding: EdgeInsets.only(left: 10)), if (image != null) image! else if (svgPicture != null) svgPicture!, if (icon != null) icon! ], diff --git a/lib/view_model/integrations/deuro_view_model.dart b/lib/view_model/integrations/deuro_view_model.dart new file mode 100644 index 000000000..09f9a363c --- /dev/null +++ b/lib/view_model/integrations/deuro_view_model.dart @@ -0,0 +1,119 @@ +import 'dart:math'; + +import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/store/app_store.dart'; +import 'package:cake_wallet/view_model/send/send_view_model_state.dart'; +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:mobx/mobx.dart'; + +part 'deuro_view_model.g.dart'; + +class DEuroViewModel = DEuroViewModelBase with _$DEuroViewModel; + +abstract class DEuroViewModelBase with Store { + final AppStore _appStore; + + DEuroViewModelBase(this._appStore) { + reloadInterestRate(); + reloadSavingsUserData(); + } + + @observable + String savingsBalance = '0.00'; + + @observable + ExecutionState state = InitialExecutionState(); + + @observable + String interestRate = '0'; + + @observable + String accruedInterest = '0.00'; + + @observable + BigInt approvedTokens = BigInt.zero; + + @computed + bool get isEnabled => approvedTokens > BigInt.zero; + + @observable + PendingTransaction? transaction = null; + + @observable + PendingTransaction? approvalTransaction = null; + + @action + Future reloadSavingsUserData() async { + final savingsBalanceRaw = + ethereum!.getDEuroSavingsBalance(_appStore.wallet!); + final accruedInterestRaw = + ethereum!.getDEuroAccruedInterest(_appStore.wallet!); + + approvedTokens = await ethereum!.getDEuroSavingsApproved(_appStore.wallet!); + + savingsBalance = ethereum! + .formatterEthereumAmountToDouble(amount: await savingsBalanceRaw) + .toStringAsFixed(6); + accruedInterest = ethereum! + .formatterEthereumAmountToDouble(amount: await accruedInterestRaw) + .toStringAsFixed(6); + } + + @action + Future reloadInterestRate() async { + final interestRateRaw = + await ethereum!.getDEuroInterestRate(_appStore.wallet!); + + interestRate = (interestRateRaw / BigInt.from(10000)).toString(); + } + + @action + Future prepareApproval() async { + final priority = _appStore.settingsStore.priority[WalletType.ethereum]!; + approvalTransaction = + await ethereum!.enableDEuroSaving(_appStore.wallet!, priority); + } + + @action + Future prepareSavingsEdit(String amountRaw, bool isAdding) async { + final amount = BigInt.from(num.parse(amountRaw) * pow(10, 18)); + final priority = _appStore.settingsStore.priority[WalletType.ethereum]!; + transaction = await (isAdding + ? ethereum!.addDEuroSaving(_appStore.wallet!, amount, priority) + : ethereum!.removeDEuroSaving(_appStore.wallet!, amount, priority)); + } + + Future prepareCollectInterest() => + prepareSavingsEdit(accruedInterest, false); + + @action + Future commitTransaction() async { + if (transaction != null) { + state = TransactionCommitting(); + await transaction!.commit(); + transaction = null; + reloadSavingsUserData(); + state = TransactionCommitted(); + } + } + + @action + Future commitApprovalTransaction() async { + if (approvalTransaction != null) { + state = TransactionCommitting(); + await approvalTransaction!.commit(); + approvalTransaction = null; + reloadSavingsUserData(); + state = TransactionCommitted(); + } + } + + @action + void dismissTransaction() { + transaction == null; + approvalTransaction = null; + state = InitialExecutionState(); + } +} diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 0c63a33b4..a30865b76 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -56,6 +56,7 @@ "apk_update": "تحديث APK", "approve": "ﺪﻤﺘﻌﻳ", "approve_request": "الموافقة على الطلب", + "approve_tokens": "الموافقة على الرموز", "arrive_in_this_address": "سيصل ${currency} ${tag}إلى هذا العنوان", "ascending": "تصاعدي", "ask_each_time": "اسأل في كل مرة", @@ -244,6 +245,15 @@ "descending": "النزول", "description": "ﻒﺻﻭ", "destination_tag": "علامة الوجهة:", + "deuro_collect_interest": "يجمع", + "deuro_savings": "الادخار ديورو", + "deuro_savings_add": "إيداع", + "deuro_savings_balance": "توازن الادخار", + "deuro_savings_collect_interest": "جمع الاهتمام", + "deuro_savings_remove": "ينسحب", + "deuro_savings_set_approval": "تعيين الموافقة", + "deuro_savings_subtitle": "كسب ما يصل إلى 10 ٪ فائدة على مقتنيات Deuro Stablecoin", + "deuro_tx_commited_content": "قد يستغرق الأمر بضع ثوانٍ حتى يتم تأكيد المعاملة وينعكس على الشاشة", "device_is_signing": "الجهاز يوقع", "dfx_option_description": "شراء التشفير مع EUR & CHF. لعملاء البيع بالتجزئة والشركات في أوروبا", "didnt_get_code": "لم تحصل على رمز؟", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 8163da646..f0b992160 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -56,6 +56,7 @@ "apk_update": "APK ъпдейт", "approve": "Одобряване", "approve_request": "Одобрете искане", + "approve_tokens": "Одобрете жетоните", "arrive_in_this_address": "${currency} ${tag}ще отидат на този адрес", "ascending": "Възходящ", "ask_each_time": "Питайте всеки път", @@ -244,6 +245,15 @@ "descending": "Низходящ", "description": "Описание", "destination_tag": "Destination tag:", + "deuro_collect_interest": "Събиране", + "deuro_savings": "Спестявания на Деуро", + "deuro_savings_add": "Депозит", + "deuro_savings_balance": "Спестотен баланс", + "deuro_savings_collect_interest": "Събиране на интерес", + "deuro_savings_remove": "Оттегляне", + "deuro_savings_set_approval": "Задайте одобрение", + "deuro_savings_subtitle": "Печелете до 10% лихва за вашите Deuro Stablecoin Holdings", + "deuro_tx_commited_content": "Може да отнеме няколко секунди, за да може транзакцията да се потвърди и да бъде отразена на екрана", "device_is_signing": "Устройството подписва", "dfx_option_description": "Купете криптовалута с Eur & CHF. За търговски и корпоративни клиенти в Европа", "didnt_get_code": "Не получихте код?", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 80b386859..e4b59562e 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -56,6 +56,7 @@ "apk_update": "aktualizace APK", "approve": "Schvalovat", "approve_request": "Schválit žádost", + "approve_tokens": "Schválit tokeny", "arrive_in_this_address": "${currency} ${tag}přijde na tuto adresu", "ascending": "Vzestupné", "ask_each_time": "Zeptejte se pokaždé", @@ -244,6 +245,15 @@ "descending": "Klesající", "description": "Popis", "destination_tag": "Destination Tag:", + "deuro_collect_interest": "Sbírat", + "deuro_savings": "dEuro úspory", + "deuro_savings_add": "Vklad", + "deuro_savings_balance": "Úspora zůstatek", + "deuro_savings_collect_interest": "Sbírat zájem", + "deuro_savings_remove": "Odstoupit", + "deuro_savings_set_approval": "Stanovit schválení", + "deuro_savings_subtitle": "Získejte až 10% úrok z vašeho Deuro Stablecoin Holdings", + "deuro_tx_commited_content": "Transakce může trvat několik sekund, aby se potvrdila a odrážela se na obrazovce", "device_is_signing": "Zařízení se podpisu", "dfx_option_description": "Koupit krypto s EUR & CHF. Pro maloobchodní a firemní zákazníky v Evropě", "didnt_get_code": "Nepřišel Vám kód?", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 479954b23..7e3f72e2a 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -56,6 +56,7 @@ "apk_update": "APK-Update", "approve": "Genehmigen", "approve_request": "Anfrage genehmigen", + "approve_tokens": "Token genehmigen", "arrive_in_this_address": "${currency} ${tag} wird an dieser Adresse ankommen", "ascending": "Aufsteigend", "ask_each_time": "Jedes Mal fragen", @@ -206,7 +207,7 @@ "copy": "Kopieren", "copy_address": "Adresse kopieren", "copy_id": "ID kopieren", - "copy_payjoin_address": "Kopieren Sie Payjoin -Adresse", + "copy_payjoin_address": "Kopieren Sie Payjoin-Adresse", "copy_payjoin_url": "Payjoin URL kopieren", "copyWalletConnectLink": "Kopieren Sie den WalletConnect-Link von dApp und fügen Sie ihn hier ein", "corrupted_seed_notice": "Die Dateien für diese Wallet sind beschädigt und können nicht geöffnet werden. Bitte sehen Sie sich die Seeds an, speichern Sie sie und stellen Sie die Wallet wieder her.\n\nWenn der Wert leer ist, konnte der Seed nicht korrekt wiederhergestellt werden.", @@ -244,6 +245,15 @@ "descending": "Absteigend", "description": "Beschreibung", "destination_tag": "Ziel-Tag:", + "deuro_collect_interest": "Auszahlen", + "deuro_savings": "dEuro-Savings", + "deuro_savings_add": "Einzahlen", + "deuro_savings_balance": "Sparguthaben", + "deuro_savings_collect_interest": "Interesse sammeln", + "deuro_savings_remove": "Auszahlen", + "deuro_savings_set_approval": "Genehmigung festlegen", + "deuro_savings_subtitle": "Verdienen Sie bis zu 10% Zinsen für Ihre dEuro Stablecoin Holdings", + "deuro_tx_commited_content": "Es kann ein paar Sekunden dauern, bis die Transaktion bestätigt und auf dem Bildschirm angezeigt", "device_is_signing": "Das Gerät unterschreibt", "dfx_option_description": "Kaufen Sie Krypto mit EUR & CHF. Für Einzelhandel und Unternehmenskunden in Europa", "didnt_get_code": "Kein Code?", @@ -1098,4 +1108,4 @@ "you_will_send": "Konvertieren von", "youCanGoBackToYourDapp": "Sie können jetzt zu Ihrem Dapp zurückkehren", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 971a68455..a841c3041 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -56,6 +56,7 @@ "apk_update": "APK update", "approve": "Approve", "approve_request": "Approve Request", + "approve_tokens": "Approve tokens", "arrive_in_this_address": "${currency} ${tag}will arrive in this address", "ascending": "Ascending", "ask_each_time": "Ask each time", @@ -244,6 +245,15 @@ "descending": "Descending", "description": "Description", "destination_tag": "Destination tag:", + "deuro_collect_interest": "Collect", + "deuro_savings": "dEuro Savings", + "deuro_savings_add": "Deposit", + "deuro_savings_balance": "Savings Balance", + "deuro_savings_collect_interest": "Collect interest", + "deuro_savings_remove": "Withdraw", + "deuro_savings_set_approval": "Set approval", + "deuro_savings_subtitle": "Earn up to 10% interest on your dEuro Stablecoin holdings", + "deuro_tx_commited_content": "It might take a couple of seconds for the transaction to confirm and be reflected on screen", "device_is_signing": "Device is signing", "dfx_option_description": "Buy crypto with EUR & CHF. For retail and corporate customers in Europe", "didnt_get_code": "Didn't get code?", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index fe802237a..17687e7c0 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -56,6 +56,7 @@ "apk_update": "Actualización de APK", "approve": "Aprobar", "approve_request": "Aprobar la solicitud", + "approve_tokens": "Aprobar tokens", "arrive_in_this_address": "${currency} ${tag}llegará a esta dirección", "ascending": "Ascendente", "ask_each_time": "Pregunta cada vez", @@ -244,6 +245,15 @@ "descending": "Descendente", "description": "Descripción", "destination_tag": "Etiqueta de destino:", + "deuro_collect_interest": "Recolectar", + "deuro_savings": "ahorros de deuro", + "deuro_savings_add": "Depósito", + "deuro_savings_balance": "Saldo de ahorro", + "deuro_savings_collect_interest": "Cobrar interés", + "deuro_savings_remove": "Retirar", + "deuro_savings_set_approval": "Establecer aprobación", + "deuro_savings_subtitle": "Gane hasta un 10% de interés en sus Holdings de Deuro Stablecoin", + "deuro_tx_commited_content": "La transacción puede tardar un par de segundos en confirmar y reflejarse en la pantalla", "device_is_signing": "El dispositivo está firmando", "dfx_option_description": "Compre cripto con EUR y CHF. Para clientes minoristas y corporativos en Europa", "didnt_get_code": "¿No recibiste el código?", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index b78ef8bc5..e6b942f5c 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -56,6 +56,7 @@ "apk_update": "Mise à jour d'APK", "approve": "Approuver", "approve_request": "Approuver la demande", + "approve_tokens": "Approuver les jetons", "arrive_in_this_address": "${currency} ${tag}arrivera à cette adresse", "ascending": "Ascendant", "ask_each_time": "Demander à chaque fois", @@ -244,6 +245,15 @@ "descending": "Descendant", "description": "Description", "destination_tag": "Tag de destination :", + "deuro_collect_interest": "Collecter", + "deuro_savings": "Économies de deuro", + "deuro_savings_add": "Dépôt", + "deuro_savings_balance": "Solde d'épargne", + "deuro_savings_collect_interest": "Percevoir l'intérêt", + "deuro_savings_remove": "Retirer", + "deuro_savings_set_approval": "Établir l'approbation", + "deuro_savings_subtitle": "Gagnez jusqu'à 10% d'intérêt sur vos avoirs de Deuro Stablecoin", + "deuro_tx_commited_content": "Il pourrait prendre quelques secondes pour que la transaction confirme et soit reflétée à l'écran", "device_is_signing": "L'appareil signale", "dfx_option_description": "Achetez de la crypto avec EUR & CHF. Pour les clients de la vente au détail et des entreprises en Europe", "didnt_get_code": "Vous n'avez pas reçu le code ?", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 710960b7d..e38d03602 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -56,6 +56,7 @@ "apk_update": "apk sabunta", "approve": "Amincewa", "approve_request": "Amince da bukata", + "approve_tokens": "Amince da Alamu", "arrive_in_this_address": "${currency} ${tag} zai je wurin wannan adireshi", "ascending": "Hau", "ask_each_time": "Tambaya kowane lokaci", @@ -244,6 +245,15 @@ "descending": "Saukowa", "description": "Bayani", "destination_tag": "Tambarin makoma:", + "deuro_collect_interest": "Tara", + "deuro_savings": "deuro tanadi", + "deuro_savings_add": "Yi ajiya", + "deuro_savings_balance": "Ma'auni", + "deuro_savings_collect_interest": "Tattara amfani da sha'awa", + "deuro_savings_remove": "Janye", + "deuro_savings_set_approval": "Saita yarda", + "deuro_savings_subtitle": "Sami har zuwa 10% sha'awa a kan Deuro Stovecoin Rike", + "deuro_tx_commited_content": "Yana iya ɗaukar wasu secondsan seconds don ma'amala don tabbatarwa kuma a nuna shi a allon", "device_is_signing": "Na'urar tana shiga", "dfx_option_description": "Buy crypto tare da Eur & Chf. Don Retail da abokan ciniki na kamfanoni a Turai", "didnt_get_code": "Ba a samun code?", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index f0a7efaaf..e9c3bca17 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -56,6 +56,7 @@ "apk_update": "APK अद्यतन", "approve": "मंज़ूरी देना", "approve_request": "अनुरोध को स्वीकृत करें", + "approve_tokens": "टोकन को मंजूरी देना", "arrive_in_this_address": "${currency} ${tag}इस पते पर पहुंचेंगे", "ascending": "आरोही", "ask_each_time": "हर बार पूछें", @@ -244,6 +245,15 @@ "descending": "अवरोही", "description": "विवरण", "destination_tag": "गंतव्य टैग:", + "deuro_collect_interest": "इकट्ठा करना", + "deuro_savings": "देउरो बचत", + "deuro_savings_add": "जमा", + "deuro_savings_balance": "बचत शेष", + "deuro_savings_collect_interest": "ब्याज इकट्ठा करना", + "deuro_savings_remove": "निकालना", + "deuro_savings_set_approval": "अनुमोदन निर्धारित करना", + "deuro_savings_subtitle": "अपने Deuro Stablecoin होल्डिंग्स पर 10% ब्याज कमाएँ", + "deuro_tx_commited_content": "लेन -देन की पुष्टि करने और स्क्रीन पर प्रतिबिंबित होने के लिए कुछ सेकंड लग सकते हैं", "device_is_signing": "उपकरण हस्ताक्षर कर रहा है", "dfx_option_description": "EUR और CHF के साथ क्रिप्टो खरीदें। यूरोप में खुदरा और कॉर्पोरेट ग्राहकों के लिए", "didnt_get_code": "कोड नहीं मिला?", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 5ecfb5b21..3f52f5b66 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -56,6 +56,7 @@ "apk_update": "APK ažuriranje", "approve": "Odobriti", "approve_request": "Odobriti zahtjev", + "approve_tokens": "Odobriti tokene", "arrive_in_this_address": "${currency} ${tag}će stići na ovu adresu", "ascending": "Uzlazni", "ask_each_time": "Pitajte svaki put", @@ -244,6 +245,15 @@ "descending": "Silazni", "description": "Opis", "destination_tag": "Odredišna oznaka:", + "deuro_collect_interest": "Prikupiti", + "deuro_savings": "deuro ušteda", + "deuro_savings_add": "Depozit", + "deuro_savings_balance": "Ravnoteža uštede", + "deuro_savings_collect_interest": "Prikupiti interes", + "deuro_savings_remove": "Povući", + "deuro_savings_set_approval": "Odrediti odobrenje", + "deuro_savings_subtitle": "Zaradite do 10% kamate na svoje Deuro Stablecoin Holdings", + "deuro_tx_commited_content": "Možda će trebati nekoliko sekundi da se transakcija potvrdi i odrazi na zaslonu", "device_is_signing": "Uređaj se potpisuje", "dfx_option_description": "Kupite kriptovalute s Eur & CHF. Za maloprodajne i korporativne kupce u Europi", "didnt_get_code": "Ne dobivate kod?", diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index 5d274c632..d1d661e7c 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -56,6 +56,7 @@ "apk_update": "APK թարմացում", "approve": "Հաստատել", "approve_request": "Հաստատում է հայցը", + "approve_tokens": "Հաստատում է նշանները", "arrive_in_this_address": "${currency} ${tag}կժամանի այս հասցեում", "ascending": "Աճող", "ask_each_time": "Հարցնել ամեն անգամ", @@ -244,6 +245,15 @@ "descending": "Նվազող", "description": "Նկարագրություն", "destination_tag": "Նպատակակետի պիտակ:", + "deuro_collect_interest": "Հավաքել", + "deuro_savings": "dEuro խնայողություններ", + "deuro_savings_add": "Ավանդ", + "deuro_savings_balance": "Խնայողական հավասարակշռություն", + "deuro_savings_collect_interest": "Հավաքեք հետաքրքրություն", + "deuro_savings_remove": "Հեռացնել", + "deuro_savings_set_approval": "Սահմանել հաստատում", + "deuro_savings_subtitle": "Վաստակեք մինչեւ 10% տոկոսադրույքներ ձեր Deuro Stablecoin Holdings- ի համար", + "deuro_tx_commited_content": "Գործարքի հաստատման եւ արտացոլվելու համար գործարքի համար կարող է տեւել մի քանի վայրկյան", "device_is_signing": "Սարքը ստորագրում է", "dfx_option_description": "Գնեք կրիպտոարժույթ EUR և CHF: Կորպորատիվ և մանրածախ հաճախորդների համար Եվրոպայում", "didnt_get_code": "Չեք ստացել կոդը?", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index b7847badf..eaccd3d99 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -56,6 +56,7 @@ "apk_update": "Pembaruan APK", "approve": "Menyetujui", "approve_request": "Menyetujui permintaan", + "approve_tokens": "Menyetujui token", "arrive_in_this_address": "${currency} ${tag} akan tiba di alamat ini", "ascending": "Naik", "ask_each_time": "Tanyakan setiap kali", @@ -244,6 +245,15 @@ "descending": "Menurun", "description": "Keterangan", "destination_tag": "Tag tujuan:", + "deuro_collect_interest": "Mengumpulkan", + "deuro_savings": "Tabungan dEuro", + "deuro_savings_add": "Deposito", + "deuro_savings_balance": "Keseimbangan tabungan", + "deuro_savings_collect_interest": "Mengumpulkan minat", + "deuro_savings_remove": "Menarik", + "deuro_savings_set_approval": "Tetapkan persetujuan", + "deuro_savings_subtitle": "Hasilkan hingga 10% bunga di Deuro Stablecoin Holdings Anda", + "deuro_tx_commited_content": "Mungkin butuh beberapa detik untuk transaksi untuk mengkonfirmasi dan direfleksikan di layar", "device_is_signing": "Perangkat sedang menandatangani", "dfx_option_description": "Beli crypto dengan EUR & CHF. Untuk pelanggan ritel dan perusahaan di Eropa", "didnt_get_code": "Tidak mendapatkan kode?", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index a0ff46c4f..65c4085d1 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -56,6 +56,7 @@ "apk_update": "Aggiornamento APK", "approve": "Approvare", "approve_request": "Approvare la richiesta", + "approve_tokens": "Approvare i token", "arrive_in_this_address": "${currency} ${tag}arriverà a questo indirizzo", "ascending": "Ascendente", "ask_each_time": "Chiedi ogni volta", @@ -244,6 +245,15 @@ "descending": "Discendente", "description": "Descrizione", "destination_tag": "Tag destinazione:", + "deuro_collect_interest": "Raccogliere", + "deuro_savings": "Risparmio di dEuro", + "deuro_savings_add": "Depositare", + "deuro_savings_balance": "Saldo di risparmio", + "deuro_savings_collect_interest": "Raccogliere interesse", + "deuro_savings_remove": "Ritirare", + "deuro_savings_set_approval": "Impostare l'approvazione", + "deuro_savings_subtitle": "Guadagna fino al 10% di interesse su Deuro StableCoin Holdings", + "deuro_tx_commited_content": "Potrebbero essere necessari un paio di secondi per confermare la transazione ed essere riflessa sullo schermo", "device_is_signing": "Il dispositivo sta firmando", "dfx_option_description": "Acquista Crypto con EUR & CHF. Per i clienti al dettaglio e aziendali in Europa", "didnt_get_code": "Non hai ricevuto il codice?", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index ac8dafbac..f638c1850 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -56,6 +56,7 @@ "apk_update": "APKアップデート", "approve": "承認する", "approve_request": "リクエストを承認します", + "approve_tokens": "トークンを承認します", "arrive_in_this_address": "${currency} ${tag}はこの住所に到着します", "ascending": "上昇", "ask_each_time": "毎回尋ねてください", @@ -244,6 +245,15 @@ "descending": "下降", "description": "説明", "destination_tag": "宛先タグ:", + "deuro_collect_interest": "集める", + "deuro_savings": "dEuro Savings", + "deuro_savings_add": "デポジット", + "deuro_savings_balance": "貯蓄バランス", + "deuro_savings_collect_interest": "興味を集めます", + "deuro_savings_remove": "撤回する", + "deuro_savings_set_approval": "承認を設定します", + "deuro_savings_subtitle": "Deuro Stablecoin Holdingsで最大10%の利息を稼ぐ", + "deuro_tx_commited_content": "トランザクションが確認され、画面に反映されるまでに数秒かかる場合があります", "device_is_signing": "デバイスが署名しています", "dfx_option_description": "EUR&CHFで暗号を購入します。ヨーロッパの小売および企業の顧客向け", "didnt_get_code": "コードを取得しませんか?", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index e2b21aa04..1c0a4cee7 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -56,6 +56,7 @@ "apk_update": "APK 업데이트", "approve": "승인", "approve_request": "요청 승인", + "approve_tokens": "토큰을 승인합니다", "arrive_in_this_address": "${currency} ${tag}이(가) 이 주소로 도착합니다", "ascending": "오름차순", "ask_each_time": "매번 묻기", @@ -244,6 +245,15 @@ "descending": "내림차순", "description": "설명", "destination_tag": "목적지 태그:", + "deuro_collect_interest": "모으다", + "deuro_savings": "도로 저축", + "deuro_savings_add": "보증금", + "deuro_savings_balance": "저축 잔고", + "deuro_savings_collect_interest": "관심을 모으십시오", + "deuro_savings_remove": "철회하다", + "deuro_savings_set_approval": "승인을 설정하십시오", + "deuro_savings_subtitle": "Deuro Stablecoin Holdings에 최대 10%의이자를 받으십시오.", + "deuro_tx_commited_content": "트랜잭션이 확인하고 화면에 반영되는 데 몇 초가 걸릴 수 있습니다.", "device_is_signing": "장치가 서명 중입니다", "dfx_option_description": "EUR 및 CHF로 암호화폐 구매. 유럽의 개인 및 기업 고객 대상", "didnt_get_code": "코드를 받지 못했나요?", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index edafed5d2..6727c7fcf 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -56,6 +56,7 @@ "apk_update": "APK အပ်ဒိတ်", "approve": "လက်မခံပါ။", "approve_request": "တောင်းဆိုမှုကိုအတည်ပြု", + "approve_tokens": "တိုကင်အတည်ပြု", "arrive_in_this_address": "${currency} ${tag}ဤလိပ်စာသို့ ရောက်ရှိပါမည်။", "ascending": "တက်", "ask_each_time": "တစ်ခုချင်းစီကိုအချိန်မေးပါ", @@ -244,6 +245,15 @@ "descending": "ဆင်း", "description": "ဖော်ပြချက်", "destination_tag": "ခရီးဆုံးအမှတ်-", + "deuro_collect_interest": "စုဝေး", + "deuro_savings": "dEuro ငွေစု", + "deuro_savings_add": "အပ်ငေှ", + "deuro_savings_balance": "ငွေစုချိန်ခွင်လျှာ", + "deuro_savings_collect_interest": "အကျိုးစီးပွားစုဆောင်းပါ", + "deuro_savings_remove": "ဆုတ်ခွာ", + "deuro_savings_set_approval": "အတည်ပြုချက်ကိုသတ်မှတ်ပါ", + "deuro_savings_subtitle": "သင်၏ Deuro Stabloin Holdings တွင် 10% အထိစိတ်ဝင်စားပါ", + "deuro_tx_commited_content": "၎င်းသည်ငွေပေးငွေယူကိုအတည်ပြုရန်နှင့်မျက်နှာပြင်ပေါ်တွင်ထင်ဟပ်ရန်စက္ကန့်အနည်းငယ်ကြာနိုင်သည်", "device_is_signing": "ကိရိယာလက်မှတ်ထိုးနေသည်", "dfx_option_description": "Crypto ကို EUR & CHF ဖြင့် 0 ယ်ပါ။ လက်လီရောင်းဝယ်မှုနှင့်ဥရောပရှိကော်ပိုရိတ်ဖောက်သည်များအတွက်", "didnt_get_code": "ကုဒ်ကို မရဘူးလား?", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index c47160a5f..4f925bc74 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -56,6 +56,7 @@ "apk_update": "APK-update", "approve": "Goedkeuren", "approve_request": "Het verzoek goedkeuren", + "approve_tokens": "Tokens goedkeuren", "arrive_in_this_address": "${currency} ${tag}komt aan op dit adres", "ascending": "Stijgend", "ask_each_time": "Vraag het elke keer", @@ -244,6 +245,15 @@ "descending": "Aflopend", "description": "Beschrijving", "destination_tag": "Bestemmingstag:", + "deuro_collect_interest": "Verzamelen", + "deuro_savings": "dEuro -besparingen", + "deuro_savings_add": "Borg", + "deuro_savings_balance": "Spaarbalans", + "deuro_savings_collect_interest": "Verzamel interesse", + "deuro_savings_remove": "Terugtrekken", + "deuro_savings_set_approval": "Goedkeuring instellen", + "deuro_savings_subtitle": "Verdien tot 10% rente op uw Deuro Stablecoin Holdings", + "deuro_tx_commited_content": "Het kan een paar seconden duren voordat de transactie wordt bevestigd en weerspiegeld op het scherm", "device_is_signing": "Apparaat ondertekent", "dfx_option_description": "Koop crypto met EUR & CHF. Voor retail- en zakelijke klanten in Europa", "didnt_get_code": "Geen code?", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index b3b99e28c..baf0f2502 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -56,6 +56,7 @@ "apk_update": "Aktualizacja APK", "approve": "Zatwierdzić", "approve_request": "Zatwierdzić żądanie", + "approve_tokens": "Zatwierdzić tokeny", "arrive_in_this_address": "${currency} ${tag}dotrze na ten adres", "ascending": "Wznoszący się", "ask_each_time": "Zapytaj za każdym razem", @@ -244,6 +245,15 @@ "descending": "Malejąco", "description": "Opis", "destination_tag": "Tag docelowy:", + "deuro_collect_interest": "Zbierać", + "deuro_savings": "dEuro oszczędności", + "deuro_savings_add": "Depozyt", + "deuro_savings_balance": "Równowaga oszczędności", + "deuro_savings_collect_interest": "Zbieraj zainteresowanie", + "deuro_savings_remove": "Wycofać", + "deuro_savings_set_approval": "Ustaw zatwierdzenie", + "deuro_savings_subtitle": "Zarabiaj do 10% odsetek od Deuro Stablecoin Holdings", + "deuro_tx_commited_content": "Potwierdzenie i odbicie na ekranie może potrwać kilka sekund", "device_is_signing": "Urządzenie podpisuje", "dfx_option_description": "Kup krypto za EUR & CHF. Dla klientów prywatnych i korporacyjnych w Europie", "didnt_get_code": "Nie dostałeś kodu?", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 9b55fc7b6..375996c99 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -56,6 +56,7 @@ "apk_update": "Atualização de APK", "approve": "Aprovar", "approve_request": "Aprovar solicitação", + "approve_tokens": "Aprovar tokens", "arrive_in_this_address": "${currency} ${tag}chegará neste endereço", "ascending": "Ascendente", "ask_each_time": "Pergunte cada vez", @@ -244,6 +245,15 @@ "descending": "descendente", "description": "Descrição", "destination_tag": "Tag de destino:", + "deuro_collect_interest": "Coletar", + "deuro_savings": "dEuro Savings", + "deuro_savings_add": "Depósito", + "deuro_savings_balance": "Balanço de poupança", + "deuro_savings_collect_interest": "Coletar juros", + "deuro_savings_remove": "Retirar", + "deuro_savings_set_approval": "Defina aprovação", + "deuro_savings_subtitle": "Ganhe até 10% de juros em sua Deuro Stablecoin Holdings", + "deuro_tx_commited_content": "Pode levar alguns segundos para a transação confirmar e se refletir na tela", "device_is_signing": "O dispositivo está assinando", "dfx_option_description": "Compre criptografia com EUR & CHF. Para clientes de varejo e corporativo na Europa", "didnt_get_code": "Não recebeu o código?", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 3e61f4277..800201b27 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -56,6 +56,7 @@ "apk_update": "Обновление APK", "approve": "Утвердить", "approve_request": "Утвердить запрос", + "approve_tokens": "Одобрить токены", "arrive_in_this_address": "${currency} ${tag}придет на этот адрес", "ascending": "Восходящий", "ask_each_time": "Спросите каждый раз", @@ -244,6 +245,15 @@ "descending": "Нисходящий", "description": "Описание", "destination_tag": "Целевой тег:", + "deuro_collect_interest": "Собирать", + "deuro_savings": "dEuro Savings", + "deuro_savings_add": "Депозитный", + "deuro_savings_balance": "Сберегательный баланс", + "deuro_savings_collect_interest": "Собирать интерес", + "deuro_savings_remove": "Отзывать", + "deuro_savings_set_approval": "Установить утверждение", + "deuro_savings_subtitle": "Заработайте до 10% процентов на ваших Deuro Stablecoin Holdings", + "deuro_tx_commited_content": "Чтобы подтвердить, может потребоваться пару секунд, чтобы подтвердить и быть отраженным на экране", "device_is_signing": "Устройство подписывает", "dfx_option_description": "Купить крипто с Eur & CHF. Для розничных и корпоративных клиентов в Европе", "didnt_get_code": "Не получить код?", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index a60f1b332..bfaa92b08 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -56,6 +56,7 @@ "apk_update": "ปรับปรุง APK", "approve": "อนุมัติ", "approve_request": "อนุมัติคำขอ", + "approve_tokens": "อนุมัติโทเค็น", "arrive_in_this_address": "${currency} ${tag}จะมาถึงที่อยู่นี้", "ascending": "จากน้อยไปมาก", "ask_each_time": "ถามทุกครั้ง", @@ -244,6 +245,15 @@ "descending": "ลงมา", "description": "คำอธิบาย", "destination_tag": "แท็กปลายทาง:", + "deuro_collect_interest": "เก็บรวบรวม", + "deuro_savings": "การออมของ dEuro", + "deuro_savings_add": "เงินฝาก", + "deuro_savings_balance": "ยอดเงินออม", + "deuro_savings_collect_interest": "เก็บดอกเบี้ย", + "deuro_savings_remove": "ถอน", + "deuro_savings_set_approval": "ตั้งค่าการอนุมัติ", + "deuro_savings_subtitle": "รับดอกเบี้ยมากถึง 10% สำหรับ Deuro Stablecoin Holdings ของคุณ", + "deuro_tx_commited_content": "อาจใช้เวลาสองสามวินาทีในการทำธุรกรรมเพื่อยืนยันและสะท้อนบนหน้าจอ", "device_is_signing": "อุปกรณ์กำลังลงนาม", "dfx_option_description": "ซื้อ crypto ด้วย Eur & CHF สำหรับลูกค้ารายย่อยและลูกค้าในยุโรป", "didnt_get_code": "ไม่ได้รับรหัส?", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 9ed4e13a8..8e571c3cb 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -56,6 +56,7 @@ "apk_update": "APK update", "approve": "Aprubahan", "approve_request": "Aprubahan ang kahilingan", + "approve_tokens": "Aprubahan ang mga token", "arrive_in_this_address": "Ang ${currency} ${tag} ay darating sa address na ito", "ascending": "Umakyat", "ask_each_time": "Magtanong sa tuwing", @@ -244,6 +245,15 @@ "descending": "Pababang", "description": "Paglalarawan", "destination_tag": "Tag ng patutunguhan:", + "deuro_collect_interest": "Mangolekta", + "deuro_savings": "Pagtipid ni dEuro", + "deuro_savings_add": "Deposito", + "deuro_savings_balance": "Balanse sa pagtitipid", + "deuro_savings_collect_interest": "Mangolekta ng interes", + "deuro_savings_remove": "Umatras", + "deuro_savings_set_approval": "Itakda ang pag -apruba", + "deuro_savings_subtitle": "Kumita ng hanggang sa 10% na interes sa iyong mga hawak na Deuro StableCoin", + "deuro_tx_commited_content": "Maaaring tumagal ng ilang segundo para sa transaksyon upang kumpirmahin at maipakita sa screen", "device_is_signing": "Nag -sign ang aparato", "dfx_option_description": "Bumili ng crypto kasama ang EUR & CHF. Para sa mga retail customer at corporate customer sa Europe", "didnt_get_code": "Hindi nakuha ang code?", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 4d790d1c6..9a9690142 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -56,6 +56,7 @@ "apk_update": "APK güncellemesi", "approve": "Onaylamak", "approve_request": "Talebi Onaylama", + "approve_tokens": "Jetonları onaylayın", "arrive_in_this_address": "${currency} ${tag}bu adrese ulaşacak", "ascending": "Yükselen", "ask_each_time": "Her seferinde sor", @@ -244,6 +245,15 @@ "descending": "Azalan", "description": "Tanım", "destination_tag": "Hedef Etiketi:", + "deuro_collect_interest": "TOPLAMAK", + "deuro_savings": "dEuro Tasarruf", + "deuro_savings_add": "Yatırmak", + "deuro_savings_balance": "Tasarruf Bakiyesi", + "deuro_savings_collect_interest": "İlgi toplamak", + "deuro_savings_remove": "Geri çekilmek", + "deuro_savings_set_approval": "Onay ayarlamak", + "deuro_savings_subtitle": "Deuro StableCoin Holdings'e% 10'a kadar faiz kazanın", + "deuro_tx_commited_content": "İşlemin onaylaması ve ekrana yansıtılması birkaç saniye sürebilir", "device_is_signing": "Cihaz imzalıyor", "dfx_option_description": "Eur & chf ile kripto satın alın. Avrupa'daki perakende ve kurumsal müşteriler için", "didnt_get_code": "Kod gelmedi mi?", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 06b6d09e7..e36599a0c 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -56,6 +56,7 @@ "apk_update": "Оновлення APK", "approve": "Затвердити", "approve_request": "Запитайте запит", + "approve_tokens": "Затвердити токени", "arrive_in_this_address": "${currency} ${tag}надійде на цю адресу", "ascending": "Висхід", "ask_each_time": "Запитайте кожен раз", @@ -244,6 +245,15 @@ "descending": "Низхідний", "description": "опис", "destination_tag": "Тег призначення:", + "deuro_collect_interest": "Збирати", + "deuro_savings": "заощадження dEuro", + "deuro_savings_add": "Депозит", + "deuro_savings_balance": "Баланс заощаджень", + "deuro_savings_collect_interest": "Збирати інтерес", + "deuro_savings_remove": "Відступати", + "deuro_savings_set_approval": "Встановити схвалення", + "deuro_savings_subtitle": "Заробляйте до 10% відсотків на ваших Holdings Deuro StableCoin", + "deuro_tx_commited_content": "Це може знадобитися кілька секунд, щоб транзакція підтвердила та відображалася на екрані", "device_is_signing": "Пристрій підписується", "dfx_option_description": "Купуйте криптовалюту з EUR & CHF. Для роздрібних та корпоративних клієнтів у Європі", "didnt_get_code": "Не отримали код?", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index c83de672b..0ce1b4758 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -56,6 +56,7 @@ "apk_update": "APK اپ ڈیٹ", "approve": "ﻭﺮﮐ ﺭﻮﻈﻨﻣ", "approve_request": "درخواست کو منظور کریں", + "approve_tokens": "ٹوکن کو منظور کریں", "arrive_in_this_address": "${currency} ${tag}اس پتے پر پہنچے گا۔", "ascending": "چڑھنے", "ask_each_time": "ہر بار پوچھیں", @@ -244,6 +245,15 @@ "descending": "اترتے ہوئے", "description": "ﻞﯿﺼﻔﺗ", "destination_tag": "منزل کا ٹیگ:", + "deuro_collect_interest": "جمع کریں", + "deuro_savings": "ڈیورو کی بچت", + "deuro_savings_add": "جمع کروائیں", + "deuro_savings_balance": "بچت کا توازن", + "deuro_savings_collect_interest": "دلچسپی جمع کریں", + "deuro_savings_remove": "واپس لے لو", + "deuro_savings_set_approval": "منظوری طے کریں", + "deuro_savings_subtitle": "اپنے ڈیورو اسٹبل کوئن ہولڈنگز پر 10 ٪ سود حاصل کریں", + "deuro_tx_commited_content": "لین دین کی تصدیق اور اسکرین پر عکاسی کرنے میں اس میں کچھ سیکنڈ لگ سکتے ہیں", "device_is_signing": "ڈیوائس پر دستخط کر رہے ہیں", "dfx_option_description": "یورو اور سی ایچ ایف کے ساتھ کرپٹو خریدیں۔ یورپ میں خوردہ اور کارپوریٹ صارفین کے لئے", "didnt_get_code": "کوڈ نہیں ملتا؟", diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index 14eeee189..61cadb10b 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -56,6 +56,7 @@ "apk_update": "Cập nhật APK", "approve": "Phê duyệt", "approve_request": "Phê duyệt yêu cầu", + "approve_tokens": "Phê duyệt mã thông báo", "arrive_in_this_address": "${currency} ${tag} sẽ đến địa chỉ này", "ascending": "Tăng dần", "ask_each_time": "Hỏi mỗi lần", @@ -243,6 +244,15 @@ "descending": "Giảm dần", "description": "Mô tả", "destination_tag": "Thẻ đích:", + "deuro_collect_interest": "Sưu tầm", + "deuro_savings": "Tiết kiệm dEuro", + "deuro_savings_add": "Tiền gửi", + "deuro_savings_balance": "Số dư tiết kiệm", + "deuro_savings_collect_interest": "Thu tiền lãi", + "deuro_savings_remove": "Rút", + "deuro_savings_set_approval": "Đặt phê duyệt", + "deuro_savings_subtitle": "Kiếm tới 10% tiền lãi cho Deuro Storcoin Holdings của bạn", + "deuro_tx_commited_content": "Có thể mất vài giây để giao dịch xác nhận và được phản ánh trên màn hình", "device_is_signing": "Thiết bị đang ký", "dfx_option_description": "Mua tiền điện tử bằng EUR & CHF. Dành cho khách hàng bán lẻ và doanh nghiệp tại Châu Âu", "didnt_get_code": "Không nhận được mã?", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index fee486da4..761667b17 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -56,6 +56,7 @@ "apk_update": "Àtúnse áàpù títun wà", "approve": "Fi ọwọ si", "approve_request": "IKILỌ RẸ", + "approve_tokens": "Ṣe fọwọsi awọn àmi", "arrive_in_this_address": "${currency} ${tag} máa dé sí àdírẹ́sì yìí", "ascending": "Goke", "ask_each_time": "Beere lọwọ kọọkan", @@ -244,6 +245,15 @@ "descending": "Sọkalẹ", "description": "Apejuwe", "destination_tag": "Orúkọ tí ìbí tó a ránṣẹ́ sí:", + "deuro_collect_interest": "Kojọ", + "deuro_savings": "dEuro Awọn ifowopamọ", + "deuro_savings_add": "Owo ifipamọ", + "deuro_savings_balance": "Iwontunws.funfun ifowopamọ", + "deuro_savings_collect_interest": "Gba iwulo", + "deuro_savings_remove": "Yọkuro", + "deuro_savings_set_approval": "Ṣeto ifọwọsi", + "deuro_savings_subtitle": "Jo'gun to 10% iwulo lori awọn idaduro Duroblockoin rẹ", + "deuro_tx_commited_content": "O le gba tọkọtaya kan ti awọn aaya fun idunadura lati jẹrisi ati ṣe afihan loju iboju", "device_is_signing": "Ẹrọ n forukọsilẹ", "dfx_option_description": "Ra Crypto pẹlu EUR & CHF. Fun soobu ati awọn alabara ile-iṣẹ ni Yuroopu", "didnt_get_code": "Ko gba koodu?", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index ffa8509c4..5831a51c0 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -56,6 +56,7 @@ "apk_update": "APK更新", "approve": "批准", "approve_request": "批准请求", + "approve_tokens": "批准令牌", "arrive_in_this_address": "${currency} ${tag}将到达此地址", "ascending": "上升", "ask_each_time": "每次问", @@ -244,6 +245,15 @@ "descending": "下降", "description": "描述", "destination_tag": "目标Tag:", + "deuro_collect_interest": "收集", + "deuro_savings": "dEuro储蓄", + "deuro_savings_add": "订金", + "deuro_savings_balance": "储蓄平衡", + "deuro_savings_collect_interest": "收集兴趣", + "deuro_savings_remove": "提取", + "deuro_savings_set_approval": "设定批准", + "deuro_savings_subtitle": "您的Deuro Stablecoin Holdings最多可赚取10%的利息", + "deuro_tx_commited_content": "交易可能需要几秒钟才能确认并在屏幕上反射", "device_is_signing": "设备正在签名", "dfx_option_description": "用Eur&Chf购买加密货币。对于欧洲的零售和企业客户", "didnt_get_code": "没有获取代码?", diff --git a/tool/configure.dart b/tool/configure.dart index 612958f4a..95c8a4d5a 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -670,6 +670,7 @@ import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/hardware/hardware_account_data.dart'; import 'package:cw_core/output_info.dart'; +import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_base.dart'; @@ -697,6 +698,7 @@ import 'package:cw_ethereum/ethereum_client.dart'; import 'package:cw_ethereum/ethereum_wallet.dart'; import 'package:cw_ethereum/ethereum_wallet_service.dart'; import 'package:cw_ethereum/default_ethereum_erc20_tokens.dart'; +import 'package:cw_ethereum/deuro/deuro_savings.dart'; import 'package:eth_sig_util/util/utils.dart'; @@ -744,6 +746,16 @@ abstract class Ethereum { void updateEtherscanUsageState(WalletBase wallet, bool isEnabled); Web3Client? getWeb3Client(WalletBase wallet); String getTokenAddress(CryptoCurrency asset); + + Future createTokenApproval(WalletBase wallet, BigInt amount, String spender, CryptoCurrency token, TransactionPriority priority); + + Future getDEuroSavingsBalance(WalletBase wallet); + Future getDEuroAccruedInterest(WalletBase wallet); + Future getDEuroInterestRate(WalletBase wallet); + Future getDEuroSavingsApproved(WalletBase wallet); + Future addDEuroSaving(WalletBase wallet, BigInt amount, TransactionPriority priority); + Future removeDEuroSaving(WalletBase wallet, BigInt amount, TransactionPriority priority); + Future enableDEuroSaving(WalletBase wallet, TransactionPriority priority); void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection); Future> getHardwareWalletAccounts(LedgerViewModel ledgerVM, {int index = 0, int limit = 5}); @@ -777,6 +789,7 @@ import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/hardware/hardware_account_data.dart'; import 'package:cw_core/output_info.dart'; +import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_base.dart'; @@ -846,6 +859,8 @@ abstract class Polygon { Future deleteErc20Token(WalletBase wallet, CryptoCurrency token); Future removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token); Future getErc20Token(WalletBase wallet, String contractAddress); + + Future createTokenApproval(WalletBase wallet, BigInt amount, String spender, CryptoCurrency token, TransactionPriority priority); CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction); void updatePolygonScanUsageState(WalletBase wallet, bool isEnabled);