mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-16 09:17:37 +00:00
WIP firo exchange addresses
This commit is contained in:
parent
8639309e70
commit
f634ce8701
9 changed files with 229 additions and 29 deletions
87
lib/models/coinlib/exp2pkh_address.dart
Normal file
87
lib/models/coinlib/exp2pkh_address.dart
Normal file
|
@ -0,0 +1,87 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib;
|
||||
|
||||
const OP_EXCHANGEADDR = 0xe0;
|
||||
|
||||
class EXP2PKHAddress implements coinlib.Address {
|
||||
/// The 160bit public key or redeemScript hash for the base58 address
|
||||
final Uint8List _hash;
|
||||
|
||||
/// The network and address type version of the address
|
||||
final Uint8List version;
|
||||
|
||||
String? _encodedCache;
|
||||
|
||||
EXP2PKHAddress._(Uint8List hash, this.version) : _hash = hash {
|
||||
if (version.length != 3) {
|
||||
throw ArgumentError(
|
||||
"version bytes length must be 3",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
factory EXP2PKHAddress.fromString(String encoded, Uint8List versionBytes) {
|
||||
if (versionBytes.length != 3) {
|
||||
throw ArgumentError(
|
||||
"version bytes length must be 3",
|
||||
);
|
||||
}
|
||||
|
||||
final data = coinlib.base58Decode(encoded);
|
||||
if (data.length != 23) throw coinlib.InvalidAddress();
|
||||
|
||||
final version = data.sublist(0, 3);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (version[i] != versionBytes[i]) {
|
||||
throw Exception("EX address version bytes do not match");
|
||||
}
|
||||
}
|
||||
|
||||
final payload = data.sublist(3);
|
||||
|
||||
final addr = EXP2PKHAddress._(payload, version);
|
||||
|
||||
addr._encodedCache = encoded;
|
||||
return addr;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => _encodedCache.toString();
|
||||
|
||||
@override
|
||||
coinlib.Program get program => EXP2PKH.fromHash(_hash);
|
||||
}
|
||||
|
||||
class EXP2PKH implements coinlib.Program {
|
||||
static const template =
|
||||
"OP_EXCHANGEADDR OP_DUP OP_HASH160 <20-bytes> OP_EQUALVERIFY OP_CHECKSIG";
|
||||
|
||||
@override
|
||||
final coinlib.Script script;
|
||||
|
||||
EXP2PKH.fromScript(this.script);
|
||||
|
||||
factory EXP2PKH.fromHash(Uint8List pkHash) {
|
||||
final List<coinlib.ScriptOp> ops = [
|
||||
coinlib.ScriptOpCode(OP_EXCHANGEADDR),
|
||||
];
|
||||
final parts = template.split(" ").sublist(1);
|
||||
for (final name in parts) {
|
||||
if (name.startsWith("OP_")) {
|
||||
ops.add(
|
||||
coinlib.ScriptOpCode(
|
||||
coinlib.scriptOpNameToCode[name.substring(3)]!,
|
||||
),
|
||||
);
|
||||
} else if (name == "<20-bytes>") {
|
||||
ops.add(coinlib.ScriptPushData(pkHash));
|
||||
} else {
|
||||
throw Exception("Something went wrong in this hacked code");
|
||||
}
|
||||
}
|
||||
|
||||
return EXP2PKH.fromScript(coinlib.Script(ops));
|
||||
}
|
||||
}
|
|
@ -56,6 +56,7 @@ import '../../widgets/animated_text.dart';
|
|||
import '../../widgets/background.dart';
|
||||
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../widgets/custom_buttons/blue_text_button.dart';
|
||||
import '../../widgets/dialogs/firo_exchange_address_dialog.dart';
|
||||
import '../../widgets/fee_slider.dart';
|
||||
import '../../widgets/icon_widgets/addressbook_icon.dart';
|
||||
import '../../widgets/icon_widgets/clipboard_icon.dart';
|
||||
|
@ -394,6 +395,14 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
address: address ?? "",
|
||||
isTestNet: wallet.cryptoCurrency.network.isTestNet,
|
||||
);
|
||||
|
||||
ref.read(pIsExchangeAddress.state).state =
|
||||
(coin as Firo).isExchangeAddress(_address ?? "");
|
||||
|
||||
if (ref.read(publicPrivateBalanceStateProvider) == FiroType.spark &&
|
||||
ref.read(pIsExchangeAddress)) {
|
||||
showFiroExchangeAddressWarning(context);
|
||||
}
|
||||
}
|
||||
|
||||
ref.read(pValidSendToAddress.notifier).state =
|
||||
|
@ -875,7 +884,10 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
@override
|
||||
void initState() {
|
||||
coin = widget.coin;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
ref.refresh(feeSheetSessionCacheProvider);
|
||||
ref.refresh(pIsExchangeAddress);
|
||||
});
|
||||
_currentFee = 0.toAmountAsRaw(fractionDigits: coin.fractionDigits);
|
||||
|
||||
_calculateFeesFuture =
|
||||
|
@ -1003,6 +1015,8 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
: true);
|
||||
|
||||
if (isFiro) {
|
||||
final isExchangeAddress = ref.watch(pIsExchangeAddress);
|
||||
|
||||
ref.listen(publicPrivateBalanceStateProvider, (previous, next) {
|
||||
selectedUTXOs = {};
|
||||
|
||||
|
@ -1019,6 +1033,12 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (previous != next && next == FiroType.spark && isExchangeAddress) {
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => showFiroExchangeAddressWarning(context),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -60,6 +60,7 @@ import '../../../../widgets/desktop/desktop_dialog_close_button.dart';
|
|||
import '../../../../widgets/desktop/desktop_fee_dialog.dart';
|
||||
import '../../../../widgets/desktop/primary_button.dart';
|
||||
import '../../../../widgets/desktop/secondary_button.dart';
|
||||
import '../../../../widgets/dialogs/firo_exchange_address_dialog.dart';
|
||||
import '../../../../widgets/fee_slider.dart';
|
||||
import '../../../../widgets/icon_widgets/addressbook_icon.dart';
|
||||
import '../../../../widgets/icon_widgets/clipboard_icon.dart';
|
||||
|
@ -706,6 +707,9 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
address: address ?? "",
|
||||
isTestNet: wallet.cryptoCurrency.network.isTestNet,
|
||||
);
|
||||
|
||||
ref.read(pIsExchangeAddress.state).state =
|
||||
(coin as Firo).isExchangeAddress(_address ?? "");
|
||||
}
|
||||
|
||||
ref.read(pValidSendToAddress.notifier).state =
|
||||
|
@ -842,6 +846,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
void initState() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
ref.refresh(feeSheetSessionCacheProvider);
|
||||
ref.refresh(pIsExchangeAddress);
|
||||
ref.read(pValidSendToAddress.state).state = false;
|
||||
ref.read(pValidSparkSendToAddress.state).state = false;
|
||||
});
|
||||
|
@ -944,15 +949,22 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
});
|
||||
}
|
||||
|
||||
final firoType = ref.watch(publicPrivateBalanceStateProvider);
|
||||
if (coin is Firo && firoType == FiroType.spark) {
|
||||
if (ref.watch(pIsExchangeAddress)) {
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => showFiroExchangeAddressWarning(context),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final showCoinControl = ref.watch(
|
||||
prefsChangeNotifierProvider.select(
|
||||
(value) => value.enableCoinControl,
|
||||
),
|
||||
) &&
|
||||
ref.watch(pWallets).getWallet(walletId) is CoinControlInterface &&
|
||||
(coin is Firo
|
||||
? ref.watch(publicPrivateBalanceStateProvider) == FiroType.public
|
||||
: true);
|
||||
(coin is Firo ? firoType == FiroType.public : true);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
@ -978,7 +990,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton2(
|
||||
isExpanded: true,
|
||||
value: ref.watch(publicPrivateBalanceStateProvider.state).state,
|
||||
value: firoType,
|
||||
items: [
|
||||
DropdownMenuItem(
|
||||
value: FiroType.spark,
|
||||
|
@ -1464,8 +1476,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
if (_address == null || _address!.isEmpty) {
|
||||
error = null;
|
||||
} else if (coin is Firo) {
|
||||
if (ref.watch(publicPrivateBalanceStateProvider) ==
|
||||
FiroType.lelantus) {
|
||||
if (firoType == FiroType.lelantus) {
|
||||
if (_data != null && _data!.contactLabel == _address) {
|
||||
error = SparkInterface.validateSparkAddress(
|
||||
address: _data!.address,
|
||||
|
@ -1526,15 +1537,13 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
),
|
||||
if (isStellar ||
|
||||
(ref.watch(pValidSparkSendToAddress) &&
|
||||
ref.watch(publicPrivateBalanceStateProvider) !=
|
||||
FiroType.lelantus))
|
||||
firoType != FiroType.lelantus))
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
if (isStellar ||
|
||||
(ref.watch(pValidSparkSendToAddress) &&
|
||||
ref.watch(publicPrivateBalanceStateProvider) !=
|
||||
FiroType.lelantus))
|
||||
firoType != FiroType.lelantus))
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
|
|
|
@ -9,27 +9,37 @@
|
|||
*/
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../wallet/public_private_balance_state_provider.dart';
|
||||
|
||||
import '../../utilities/amount/amount.dart';
|
||||
import '../../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../wallet/public_private_balance_state_provider.dart';
|
||||
|
||||
final pSendAmount = StateProvider.autoDispose<Amount?>((_) => null);
|
||||
final pValidSendToAddress = StateProvider.autoDispose<bool>((_) => false);
|
||||
final pValidSparkSendToAddress = StateProvider.autoDispose<bool>((_) => false);
|
||||
|
||||
final pIsExchangeAddress = StateProvider<bool>((_) => false);
|
||||
|
||||
final pPreviewTxButtonEnabled =
|
||||
Provider.autoDispose.family<bool, CryptoCurrency>((ref, coin) {
|
||||
final amount = ref.watch(pSendAmount) ?? Amount.zero;
|
||||
|
||||
if (coin is Firo) {
|
||||
if (ref.watch(publicPrivateBalanceStateProvider) == FiroType.lelantus) {
|
||||
final firoType = ref.watch(publicPrivateBalanceStateProvider);
|
||||
switch (firoType) {
|
||||
case FiroType.lelantus:
|
||||
return ref.watch(pValidSendToAddress) &&
|
||||
!ref.watch(pValidSparkSendToAddress) &&
|
||||
amount > Amount.zero;
|
||||
} else {
|
||||
|
||||
case FiroType.spark:
|
||||
return (ref.watch(pValidSendToAddress) ||
|
||||
ref.watch(pValidSparkSendToAddress)) &&
|
||||
!ref.watch(pIsExchangeAddress) &&
|
||||
amount > Amount.zero;
|
||||
|
||||
case FiroType.public:
|
||||
return ref.watch(pValidSendToAddress) && amount > Amount.zero;
|
||||
}
|
||||
} else {
|
||||
return ref.watch(pValidSendToAddress) && amount > Amount.zero;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib;
|
||||
|
||||
import '../../../models/coinlib/exp2pkh_address.dart';
|
||||
import '../../../models/isar/models/blockchain_data/address.dart';
|
||||
import '../../../models/node_model.dart';
|
||||
import '../../../utilities/amount/amount.dart';
|
||||
|
@ -77,6 +80,21 @@ class Firo extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
|||
fractionDigits: fractionDigits,
|
||||
);
|
||||
|
||||
Uint8List get exAddressVersion {
|
||||
switch (network) {
|
||||
case CryptoCurrencyNetwork.main:
|
||||
// https://github.com/firoorg/firo/blob/master/src/chainparams.cpp#L357
|
||||
return Uint8List.fromList([0x01, 0xb9, 0xbb]);
|
||||
|
||||
case CryptoCurrencyNetwork.test:
|
||||
// https://github.com/firoorg/firo/blob/master/src/chainparams.cpp#L669
|
||||
return Uint8List.fromList([0x01, 0xb9, 0xb1]);
|
||||
|
||||
default:
|
||||
throw Exception("Unsupported network: $network");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
coinlib.Network get networkParams {
|
||||
switch (network) {
|
||||
|
@ -169,7 +187,11 @@ class Firo extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
|||
coinlib.Address.fromString(address, networkParams);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return validateSparkAddress(address);
|
||||
if (validateSparkAddress(address)) {
|
||||
return true;
|
||||
} else {
|
||||
return isExchangeAddress(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,6 +202,18 @@ class Firo extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
|||
);
|
||||
}
|
||||
|
||||
bool isExchangeAddress(String address) {
|
||||
try {
|
||||
EXP2PKHAddress.fromString(
|
||||
address,
|
||||
exAddressVersion,
|
||||
);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
NodeModel get defaultNode {
|
||||
switch (network) {
|
||||
|
|
|
@ -8,6 +8,7 @@ import 'package:isar/isar.dart';
|
|||
import '../../../electrumx_rpc/cached_electrumx_client.dart';
|
||||
import '../../../electrumx_rpc/client_manager.dart';
|
||||
import '../../../electrumx_rpc/electrumx_client.dart';
|
||||
import '../../../models/coinlib/exp2pkh_address.dart';
|
||||
import '../../../models/isar/models/blockchain_data/v2/input_v2.dart';
|
||||
import '../../../models/isar/models/blockchain_data/v2/output_v2.dart';
|
||||
import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||
|
@ -24,6 +25,7 @@ import '../../crypto_currency/coins/firo.dart';
|
|||
import '../../crypto_currency/interfaces/electrumx_currency_interface.dart';
|
||||
import '../../models/tx_data.dart';
|
||||
import '../impl/bitcoin_wallet.dart';
|
||||
import '../impl/firo_wallet.dart';
|
||||
import '../impl/peercoin_wallet.dart';
|
||||
import '../intermediate/bip39_hd_wallet.dart';
|
||||
import 'cpfp_interface.dart';
|
||||
|
@ -725,11 +727,23 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
|
||||
// Add transaction output
|
||||
for (var i = 0; i < txData.recipients!.length; i++) {
|
||||
final address = coinlib.Address.fromString(
|
||||
late final coinlib.Address address;
|
||||
|
||||
try {
|
||||
address = coinlib.Address.fromString(
|
||||
normalizeAddress(txData.recipients![i].address),
|
||||
cryptoCurrency.networkParams,
|
||||
);
|
||||
|
||||
} catch (_) {
|
||||
if (this is FiroWallet) {
|
||||
address = EXP2PKHAddress.fromString(
|
||||
normalizeAddress(txData.recipients![i].address),
|
||||
(cryptoCurrency as Firo).exAddressVersion,
|
||||
);
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
final output = coinlib.Output.fromAddress(
|
||||
txData.recipients![i].amount.raw,
|
||||
address,
|
||||
|
|
|
@ -716,13 +716,13 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
|
|||
return result;
|
||||
} catch (e) {
|
||||
Logging.instance.log(
|
||||
"refreshSparkMempoolData() failed: $e",
|
||||
"_refreshSparkCoinsMempoolCheck() failed: $e",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
return [];
|
||||
} finally {
|
||||
Logging.instance.log(
|
||||
"$walletId ${info.name} refreshSparkCoinsMempoolCheck() run "
|
||||
"$walletId ${info.name} _refreshSparkCoinsMempoolCheck() run "
|
||||
"duration: ${DateTime.now().difference(start)}",
|
||||
level: LogLevel.Debug,
|
||||
);
|
||||
|
|
26
lib/widgets/dialogs/firo_exchange_address_dialog.dart
Normal file
26
lib/widgets/dialogs/firo_exchange_address_dialog.dart
Normal file
|
@ -0,0 +1,26 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../utilities/util.dart';
|
||||
import '../stack_dialog.dart';
|
||||
|
||||
class FiroExchangeAddressDialog extends StatelessWidget {
|
||||
const FiroExchangeAddressDialog({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StackOkDialog(
|
||||
title: "Firo exchange address detected",
|
||||
message: "Sending to an exchange address from a Spark balance is not"
|
||||
" allowed. Please send from your transparent balance.",
|
||||
desktopPopRootNavigator: Util.isDesktop,
|
||||
maxWidth: Util.isDesktop ? 500 : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> showFiroExchangeAddressWarning(BuildContext context) async {
|
||||
return await showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => const FiroExchangeAddressDialog(),
|
||||
);
|
||||
}
|
|
@ -1807,8 +1807,8 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: f1d02f7ad489df3119a540a7f31485db6d837843
|
||||
resolved-ref: f1d02f7ad489df3119a540a7f31485db6d837843
|
||||
ref: "647cadc3c82c276dc07915b02d24538fd610f220"
|
||||
resolved-ref: "647cadc3c82c276dc07915b02d24538fd610f220"
|
||||
url: "https://github.com/cypherstack/tor.git"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
|
|
Loading…
Reference in a new issue