mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-31 06:35:53 +00:00
Merge branch 'staging' into togglebalance
This commit is contained in:
commit
9e6d9f8bad
13 changed files with 258 additions and 44 deletions
|
@ -116,6 +116,22 @@ cd ..
|
|||
or manually by creating the files referenced in that script with the specified content.
|
||||
|
||||
### Build plugins
|
||||
#### Build script: `build_app.sh`
|
||||
The `build_app.sh` script is use to build applications Stack Wallet. View the script's help message with `./build_app.sh -h` for more information on its usage.
|
||||
|
||||
Options:
|
||||
|
||||
- `a <app>`: Specify the application ID (required). Valid options are `stack_wallet` or `stack_duo`.
|
||||
- `b <build_number>`: Specify the build number in 123 (required).
|
||||
- `p <platform>`: Specify the platform to build for (required). Valid options are `android`, `ios`, `macos`, `linux`, or `windows`.
|
||||
- `v <version>`: Specify the version of the application in 1.2.3 format (required).
|
||||
- `i`: Optional flag to skip building crypto plugins. Useful for updating `pubspec.yaml` and white-labelling different apps with the same plugins.
|
||||
|
||||
For example,
|
||||
```
|
||||
./build_app.sh -a stack_wallet -p linux -v 2.1.0 -b 210
|
||||
```
|
||||
|
||||
#### Building plugins for Android
|
||||
> Warning: This will take a long time, please be patient
|
||||
```
|
||||
|
|
|
@ -8,11 +8,15 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
import '../../models/buy/response_objects/order.dart';
|
||||
import '../../notifications/show_flush_bar.dart';
|
||||
import '../../themes/stack_colors.dart';
|
||||
import '../../themes/theme_providers.dart';
|
||||
import '../../utilities/assets.dart';
|
||||
|
@ -44,6 +48,16 @@ class _BuyOrderDetailsViewState extends ConsumerState<BuyOrderDetailsView> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final orderDetails = '''
|
||||
Purchase ID: ${widget.order.paymentId}
|
||||
User ID: ${widget.order.userId}
|
||||
Quote ID: ${widget.order.quote.id}
|
||||
Quoted cost: ${widget.order.quote.youPayFiatPrice.toStringAsFixed(2)} ${widget.order.quote.fiat.ticker.toUpperCase()}
|
||||
Quoted amount: ${widget.order.quote.youReceiveCryptoAmount} ${widget.order.quote.crypto.ticker.toUpperCase()}
|
||||
Receiving ${widget.order.quote.crypto.ticker.toUpperCase()} address: ${widget.order.quote.receivingAddress}
|
||||
Provider: Simplex
|
||||
''';
|
||||
|
||||
return ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) {
|
||||
|
@ -272,6 +286,43 @@ class _BuyOrderDetailsViewState extends ConsumerState<BuyOrderDetailsView> {
|
|||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await Clipboard.setData(ClipboardData(text: orderDetails));
|
||||
if (context.mounted) {
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.info,
|
||||
message: "Copied to clipboard",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.copy,
|
||||
width: 20,
|
||||
height: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextSecondary,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Text(
|
||||
"Copy to clipboard",
|
||||
style: STextStyles.desktopButtonSecondaryEnabled(
|
||||
context,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
PrimaryButton(
|
||||
label: "Dismiss",
|
||||
|
|
|
@ -188,12 +188,14 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
|
|||
_timeout = Duration.zero;
|
||||
|
||||
_checkUseBiometrics();
|
||||
_pinTextController.addListener(_onPinChanged);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
dispose() {
|
||||
// _shakeController.dispose();
|
||||
_pinTextController.removeListener(_onPinChanged);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -208,13 +210,27 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
|
|||
);
|
||||
}
|
||||
|
||||
final _pinTextController = TextEditingController();
|
||||
final FocusNode _pinFocusNode = FocusNode();
|
||||
|
||||
late SecureStorageInterface _secureStore;
|
||||
late Biometrics biometrics;
|
||||
int pinCount = 1;
|
||||
|
||||
final _pinTextController = TextEditingController();
|
||||
|
||||
void _onPinChanged() async {
|
||||
String enteredPin = _pinTextController.text;
|
||||
final storedPin = await _secureStore.read(key: 'stack_pin');
|
||||
final autoPin = ref.read(prefsChangeNotifierProvider).autoPin;
|
||||
|
||||
if (enteredPin.length >= 4 && autoPin && enteredPin == storedPin) {
|
||||
await Future<void>.delayed(
|
||||
const Duration(milliseconds: 200),
|
||||
);
|
||||
unawaited(_onUnlock());
|
||||
}
|
||||
}
|
||||
|
||||
Widget get _body => Background(
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
|
|
|
@ -61,9 +61,12 @@ class _ChangePinViewState extends ConsumerState<ChangePinView> {
|
|||
|
||||
int pinCount = 1;
|
||||
|
||||
final TextEditingController _pinTextController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_secureStore = ref.read(secureStoreProvider);
|
||||
_pinTextController.addListener(_onPinChanged);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -74,9 +77,23 @@ class _ChangePinViewState extends ConsumerState<ChangePinView> {
|
|||
_pinPutController2.dispose();
|
||||
_pinPutFocusNode1.dispose();
|
||||
_pinPutFocusNode2.dispose();
|
||||
_pinTextController.removeListener(_onPinChanged);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onPinChanged() async {
|
||||
String enteredPin = _pinTextController.text;
|
||||
final storedPin = await _secureStore.read(key: 'stack_pin');
|
||||
final autoPin = ref.read(prefsChangeNotifierProvider).autoPin;
|
||||
|
||||
if (enteredPin.length >= 4 && autoPin && enteredPin == storedPin) {
|
||||
await _pageController.nextPage(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.linear,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Background(
|
||||
|
|
|
@ -10,8 +10,7 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../../pinpad_views/lock_screen_view.dart';
|
||||
import 'change_pin_view/change_pin_view.dart';
|
||||
|
||||
import '../../../../providers/global/prefs_provider.dart';
|
||||
import '../../../../route_generator.dart';
|
||||
import '../../../../themes/stack_colors.dart';
|
||||
|
@ -21,6 +20,8 @@ import '../../../../widgets/background.dart';
|
|||
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../../../widgets/custom_buttons/draggable_switch_button.dart';
|
||||
import '../../../../widgets/rounded_white_container.dart';
|
||||
import '../../../pinpad_views/lock_screen_view.dart';
|
||||
import 'change_pin_view/change_pin_view.dart';
|
||||
|
||||
class SecurityView extends StatelessWidget {
|
||||
const SecurityView({
|
||||
|
@ -203,6 +204,54 @@ class SecurityView extends StatelessWidget {
|
|||
},
|
||||
),
|
||||
),
|
||||
// The "autoPin" preference (whether to automatically accept a correct PIN).
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
child: Consumer(
|
||||
builder: (_, ref, __) {
|
||||
return RawMaterialButton(
|
||||
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
onPressed: null,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Auto-accept correct PIN",
|
||||
style: STextStyles.titleBold12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
width: 40,
|
||||
child: DraggableSwitchButton(
|
||||
isOn: ref.watch(
|
||||
prefsChangeNotifierProvider
|
||||
.select((value) => value.autoPin),
|
||||
),
|
||||
onValueChanged: (newValue) {
|
||||
ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.autoPin = newValue;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -11,18 +11,19 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../app_config.dart';
|
||||
import '../db/hive/db.dart';
|
||||
import '../services/event_bus/events/global/tor_status_changed_event.dart';
|
||||
import '../services/event_bus/global_event_bus.dart';
|
||||
import '../app_config.dart';
|
||||
import '../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart';
|
||||
import 'amount/amount_unit.dart';
|
||||
import 'constants.dart';
|
||||
import 'enums/backup_frequency_type.dart';
|
||||
import 'enums/languages_enum.dart';
|
||||
import 'enums/sync_type_enum.dart';
|
||||
import '../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class Prefs extends ChangeNotifier {
|
||||
Prefs._();
|
||||
|
@ -1103,4 +1104,30 @@ class Prefs extends ChangeNotifier {
|
|||
|
||||
return actualMap;
|
||||
}
|
||||
|
||||
// Automatic PIN entry.
|
||||
|
||||
bool _autoPin = false;
|
||||
|
||||
bool get autoPin => _autoPin;
|
||||
|
||||
set autoPin(bool autoPin) {
|
||||
if (_autoPin != autoPin) {
|
||||
DB.instance.put<dynamic>(
|
||||
boxName: DB.boxNamePrefs,
|
||||
key: "autoPin",
|
||||
value: autoPin,
|
||||
);
|
||||
_autoPin = autoPin;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _getAutoPin() async {
|
||||
return await DB.instance.get<dynamic>(
|
||||
boxName: DB.boxNamePrefs,
|
||||
key: "autoPin",
|
||||
) as bool? ??
|
||||
false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import 'dart:io';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:solana/solana.dart';
|
||||
|
||||
import '../networking/http.dart';
|
||||
import '../pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
|
||||
|
@ -15,6 +14,7 @@ import '../wallets/crypto_currency/crypto_currency.dart';
|
|||
import '../wallets/crypto_currency/interfaces/electrumx_currency_interface.dart';
|
||||
import '../wallets/crypto_currency/intermediate/cryptonote_currency.dart';
|
||||
import '../wallets/crypto_currency/intermediate/nano_currency.dart';
|
||||
import '../wallets/wallet/impl/solana_wallet.dart';
|
||||
import 'connection_check/electrum_connection_check.dart';
|
||||
import 'logger.dart';
|
||||
import 'test_epic_box_connection.dart';
|
||||
|
@ -210,14 +210,20 @@ Future<bool> testNodeConnection({
|
|||
|
||||
case Solana():
|
||||
try {
|
||||
RpcClient rpcClient;
|
||||
if (formData.host!.startsWith("http") ||
|
||||
formData.host!.startsWith("https")) {
|
||||
rpcClient = RpcClient("${formData.host}:${formData.port}");
|
||||
} else {
|
||||
rpcClient = RpcClient("http://${formData.host}:${formData.port}");
|
||||
}
|
||||
await rpcClient.getEpochInfo().then((value) => testPassed = true);
|
||||
final rpcClient = SolanaWallet.createRpcClient(
|
||||
formData.host!,
|
||||
formData.port!,
|
||||
formData.useSSL ?? false,
|
||||
ref.read(prefsChangeNotifierProvider),
|
||||
ref.read(pTorService),
|
||||
);
|
||||
|
||||
final health = await rpcClient.getHealth();
|
||||
Logging.instance.log(
|
||||
"Solana testNodeConnection \"health=$health\"",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
return true;
|
||||
} catch (_) {
|
||||
testPassed = false;
|
||||
}
|
||||
|
|
|
@ -55,8 +55,6 @@ class Bitcoincash extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
|||
|
||||
@override
|
||||
int get maxUnusedAddressGap => 50;
|
||||
@override
|
||||
int get maxNumberOfIndexesToCheck => 10000000;
|
||||
|
||||
@override
|
||||
// change this to change the number of confirms a tx needs in order to show as confirmed
|
||||
|
|
|
@ -50,8 +50,6 @@ class Ecash extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
|||
|
||||
@override
|
||||
int get maxUnusedAddressGap => 50;
|
||||
@override
|
||||
int get maxNumberOfIndexesToCheck => 10000000;
|
||||
|
||||
@override
|
||||
// change this to change the number of confirms a tx needs in order to show as confirmed
|
||||
|
|
|
@ -46,8 +46,7 @@ class Solana extends Bip39Currency {
|
|||
switch (network) {
|
||||
case CryptoCurrencyNetwork.main:
|
||||
return NodeModel(
|
||||
host:
|
||||
"https://api.mainnet-beta.solana.com/", // TODO: Change this to stack wallet one
|
||||
host: "https://solana.stackwallet.com",
|
||||
port: 443,
|
||||
name: DefaultNodes.defaultName,
|
||||
id: DefaultNodes.buildId(this),
|
||||
|
@ -70,9 +69,13 @@ class Solana extends Bip39Currency {
|
|||
|
||||
@override
|
||||
bool validateAddress(String address) {
|
||||
return isPointOnEd25519Curve(
|
||||
Ed25519HDPublicKey.fromBase58(address).toByteArray(),
|
||||
);
|
||||
try {
|
||||
return isPointOnEd25519Curve(
|
||||
Ed25519HDPublicKey.fromBase58(address).toByteArray(),
|
||||
);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib;
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../../../models/isar/models/blockchain_data/address.dart';
|
||||
import '../../../utilities/amount/amount.dart';
|
||||
import '../../../utilities/enums/derive_path_type_enum.dart';
|
||||
|
@ -16,7 +17,6 @@ abstract class Bip39HDCurrency extends Bip39Currency {
|
|||
List<DerivePathType> get supportedDerivationPathTypes;
|
||||
|
||||
int get maxUnusedAddressGap => 50;
|
||||
int get maxNumberOfIndexesToCheck => 10000;
|
||||
|
||||
String constructDerivePath({
|
||||
required DerivePathType derivePathType,
|
||||
|
|
|
@ -18,6 +18,7 @@ import '../../../services/node_service.dart';
|
|||
import '../../../services/tor_service.dart';
|
||||
import '../../../utilities/amount/amount.dart';
|
||||
import '../../../utilities/logger.dart';
|
||||
import '../../../utilities/prefs.dart';
|
||||
import '../../crypto_currency/crypto_currency.dart';
|
||||
import '../../models/tx_data.dart';
|
||||
import '../intermediate/bip39_wallet.dart';
|
||||
|
@ -245,14 +246,15 @@ class SolanaWallet extends Bip39Wallet<Solana> {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<bool> pingCheck() {
|
||||
Future<bool> pingCheck() async {
|
||||
String? health;
|
||||
try {
|
||||
_checkClient();
|
||||
_rpcClient?.getHealth();
|
||||
return Future.value(true);
|
||||
health = await _rpcClient?.getHealth();
|
||||
return health != null;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$runtimeType Solana pingCheck failed: $e\n$s",
|
||||
"$runtimeType Solana pingCheck failed \"health=$health\": $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
return Future.value(false);
|
||||
|
@ -453,32 +455,67 @@ class SolanaWallet extends Bip39Wallet<Solana> {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<bool> updateUTXOs() {
|
||||
Future<bool> updateUTXOs() async {
|
||||
// No UTXOs in Solana
|
||||
return Future.value(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Make sure the Solana RpcClient uses Tor if it's enabled.
|
||||
///
|
||||
void _checkClient() async {
|
||||
void _checkClient() {
|
||||
final node = getCurrentNode();
|
||||
_rpcClient = createRpcClient(
|
||||
node.host,
|
||||
node.port,
|
||||
node.useSSL,
|
||||
prefs,
|
||||
TorService.sharedInstance,
|
||||
);
|
||||
}
|
||||
|
||||
// static helper function for building a sol rpc client
|
||||
static RpcClient createRpcClient(
|
||||
final String host,
|
||||
final int port,
|
||||
final bool useSSL,
|
||||
final Prefs prefs,
|
||||
final TorService torService,
|
||||
) {
|
||||
HttpClient? httpClient;
|
||||
|
||||
if (prefs.useTor) {
|
||||
// Make proxied HttpClient.
|
||||
final ({InternetAddress host, int port}) proxyInfo =
|
||||
TorService.sharedInstance.getProxyInfo();
|
||||
final proxyInfo = torService.getProxyInfo();
|
||||
|
||||
final proxySettings = ProxySettings(proxyInfo.host, proxyInfo.port);
|
||||
httpClient = HttpClient();
|
||||
SocksTCPClient.assignToHttpClient(httpClient, [proxySettings]);
|
||||
}
|
||||
|
||||
_rpcClient = RpcClient(
|
||||
"${getCurrentNode().host}:${getCurrentNode().port}",
|
||||
final regex = RegExp("^(http|https)://");
|
||||
|
||||
String editedHost;
|
||||
if (host.startsWith(regex)) {
|
||||
editedHost = host.replaceFirst(regex, "");
|
||||
} else {
|
||||
editedHost = host;
|
||||
}
|
||||
|
||||
while (editedHost.endsWith("/")) {
|
||||
editedHost = editedHost.substring(0, editedHost.length - 1);
|
||||
}
|
||||
|
||||
final uri = Uri(
|
||||
scheme: useSSL ? "https" : "http",
|
||||
host: editedHost,
|
||||
port: port,
|
||||
);
|
||||
|
||||
return RpcClient(
|
||||
uri.toString(),
|
||||
timeout: const Duration(seconds: 30),
|
||||
customHeaders: {},
|
||||
httpClient: httpClient,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -937,8 +937,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
int highestIndexWithHistory = 0;
|
||||
|
||||
for (int index = 0;
|
||||
index < cryptoCurrency.maxNumberOfIndexesToCheck &&
|
||||
gapCounter < cryptoCurrency.maxUnusedAddressGap;
|
||||
gapCounter < cryptoCurrency.maxUnusedAddressGap;
|
||||
index += txCountBatchSize) {
|
||||
Logging.instance.log(
|
||||
"index: $index, \t GapCounter $chain ${type.name}: $gapCounter",
|
||||
|
@ -1017,10 +1016,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
|
|||
final List<Address> addressArray = [];
|
||||
int gapCounter = 0;
|
||||
int index = 0;
|
||||
for (;
|
||||
index < cryptoCurrency.maxNumberOfIndexesToCheck &&
|
||||
gapCounter < cryptoCurrency.maxUnusedAddressGap;
|
||||
index++) {
|
||||
for (; gapCounter < cryptoCurrency.maxUnusedAddressGap; index++) {
|
||||
Logging.instance.log(
|
||||
"index: $index, \t GapCounter chain=$chain ${type.name}: $gapCounter",
|
||||
level: LogLevel.Info,
|
||||
|
|
Loading…
Reference in a new issue