mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-11-16 17:27:37 +00:00
Yat, electrum addresses fixes
This commit is contained in:
parent
cf20123ede
commit
42104fd825
14 changed files with 333 additions and 136 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -109,3 +109,7 @@ ios/Flutter/.last_build_id
|
||||||
ios/build
|
ios/build
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
*.sublime-project
|
*.sublime-project
|
||||||
|
|
||||||
|
shared_external/**
|
||||||
|
cw_shared_external/**
|
||||||
|
cw_haven/**
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
|
||||||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
@ -35,7 +36,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
walletInfo,
|
walletInfo,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
accountIndex: accountIndex,
|
accountIndex: accountIndex,
|
||||||
hd: hd,
|
mainHd: hd,
|
||||||
|
sideHd: bitcoin.HDWallet.fromSeed(
|
||||||
|
mnemonicToSeedBytes(mnemonic), network: networkType)
|
||||||
|
.derivePath("m/0'/1"),
|
||||||
networkType: networkType);
|
networkType: networkType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,13 +17,15 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses
|
||||||
WalletInfo walletInfo,
|
WalletInfo walletInfo,
|
||||||
{@required List<BitcoinAddressRecord> initialAddresses,
|
{@required List<BitcoinAddressRecord> initialAddresses,
|
||||||
int accountIndex = 0,
|
int accountIndex = 0,
|
||||||
@required bitcoin.HDWallet hd,
|
@required bitcoin.HDWallet mainHd,
|
||||||
|
@required bitcoin.HDWallet sideHd,
|
||||||
@required this.networkType})
|
@required this.networkType})
|
||||||
: super(
|
: super(
|
||||||
walletInfo,
|
walletInfo,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
accountIndex: accountIndex,
|
accountIndex: accountIndex,
|
||||||
hd: hd);
|
mainHd: mainHd,
|
||||||
|
sideHd: sideHd);
|
||||||
|
|
||||||
bitcoin.NetworkType networkType;
|
bitcoin.NetworkType networkType;
|
||||||
|
|
||||||
|
|
|
@ -14,19 +14,23 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
ElectrumWalletAddressesBase(WalletInfo walletInfo,
|
ElectrumWalletAddressesBase(WalletInfo walletInfo,
|
||||||
{@required List<BitcoinAddressRecord> initialAddresses,
|
{@required List<BitcoinAddressRecord> initialAddresses,
|
||||||
int accountIndex = 0,
|
int accountIndex = 0,
|
||||||
@required bitcoin.HDWallet hd})
|
this.mainHd,
|
||||||
|
this.sideHd})
|
||||||
: super(walletInfo) {
|
: super(walletInfo) {
|
||||||
this.hd = hd;
|
|
||||||
this.accountIndex = accountIndex;
|
this.accountIndex = accountIndex;
|
||||||
addresses = ObservableList<BitcoinAddressRecord>.of(
|
addresses = ObservableList<BitcoinAddressRecord>.of(
|
||||||
(initialAddresses ?? []).toSet());
|
(initialAddresses ?? []).toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const regularAddressesCount = 22;
|
||||||
|
static const hiddenAddressesCount = 17;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@observable
|
@observable
|
||||||
String address;
|
String address;
|
||||||
|
|
||||||
bitcoin.HDWallet hd;
|
bitcoin.HDWallet mainHd;
|
||||||
|
bitcoin.HDWallet sideHd;
|
||||||
|
|
||||||
ObservableList<BitcoinAddressRecord> addresses;
|
ObservableList<BitcoinAddressRecord> addresses;
|
||||||
|
|
||||||
|
@ -53,19 +57,36 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> generateAddresses() async {
|
Future<void> generateAddresses() async {
|
||||||
if (addresses.length < 33) {
|
final regularAddresses = <BitcoinAddressRecord>[];
|
||||||
final addressesCount = 33 - addresses.length;
|
final hiddenAddresses = <BitcoinAddressRecord>[];
|
||||||
|
|
||||||
|
addresses.forEach((addr) {
|
||||||
|
if (addr.isHidden) {
|
||||||
|
hiddenAddresses.add(addr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
regularAddresses.add(addr);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (regularAddresses.length < regularAddressesCount) {
|
||||||
|
final addressesCount = regularAddressesCount - regularAddresses.length;
|
||||||
await generateNewAddresses(addressesCount,
|
await generateNewAddresses(addressesCount,
|
||||||
startIndex: addresses.length, hd: hd);
|
startIndex: regularAddresses.length, hd: mainHd, isHidden: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hiddenAddresses.length < hiddenAddressesCount) {
|
||||||
|
final addressesCount = hiddenAddressesCount - hiddenAddresses.length;
|
||||||
|
await generateNewAddresses(addressesCount,
|
||||||
|
startIndex: hiddenAddresses.length, hd: sideHd, isHidden: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<BitcoinAddressRecord> generateNewAddress(
|
Future<BitcoinAddressRecord> generateNewAddress(
|
||||||
{bool isHidden = false, bitcoin.HDWallet hd}) async {
|
{bool isHidden = false, bitcoin.HDWallet hd}) async {
|
||||||
accountIndex += 1;
|
accountIndex += 1;
|
||||||
final _hd = hd ?? this.hd;
|
|
||||||
final address = BitcoinAddressRecord(
|
final address = BitcoinAddressRecord(
|
||||||
getAddress(index: accountIndex, hd: _hd),
|
getAddress(index: accountIndex, hd: hd),
|
||||||
index: accountIndex,
|
index: accountIndex,
|
||||||
isHidden: isHidden);
|
isHidden: isHidden);
|
||||||
addresses.add(address);
|
addresses.add(address);
|
||||||
|
@ -79,6 +100,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||||
for (var i = startIndex; i < count + startIndex; i++) {
|
for (var i = startIndex; i < count + startIndex; i++) {
|
||||||
final address = BitcoinAddressRecord(getAddress(index: i, hd: hd),
|
final address = BitcoinAddressRecord(getAddress(index: i, hd: hd),
|
||||||
index: i, isHidden: isHidden);
|
index: i, isHidden: isHidden);
|
||||||
|
print(address.address);
|
||||||
list.add(address);
|
list.add(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
|
||||||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
||||||
import 'package:cake_wallet/bitcoin/litecoin_wallet_addresses.dart';
|
import 'package:cake_wallet/bitcoin/litecoin_wallet_addresses.dart';
|
||||||
|
@ -11,6 +12,7 @@ import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cake_wallet/bitcoin/electrum_balance.dart';
|
import 'package:cake_wallet/bitcoin/electrum_balance.dart';
|
||||||
import 'package:cake_wallet/bitcoin/litecoin_network.dart';
|
import 'package:cake_wallet/bitcoin/litecoin_network.dart';
|
||||||
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
|
|
||||||
part 'litecoin_wallet.g.dart';
|
part 'litecoin_wallet.g.dart';
|
||||||
|
|
||||||
|
@ -37,9 +39,11 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
||||||
walletInfo,
|
walletInfo,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
accountIndex: accountIndex,
|
accountIndex: accountIndex,
|
||||||
hd: hd,
|
mainHd: hd,
|
||||||
networkType: networkType,
|
sideHd: bitcoin.HDWallet
|
||||||
mnemonic: mnemonic);
|
.fromSeed(mnemonicToSeedBytes(mnemonic), network: networkType)
|
||||||
|
.derivePath("m/0'/1"),
|
||||||
|
networkType: networkType,);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<LitecoinWallet> open({
|
static Future<LitecoinWallet> open({
|
||||||
|
|
|
@ -18,18 +18,18 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
|
||||||
WalletInfo walletInfo,
|
WalletInfo walletInfo,
|
||||||
{@required List<BitcoinAddressRecord> initialAddresses,
|
{@required List<BitcoinAddressRecord> initialAddresses,
|
||||||
int accountIndex = 0,
|
int accountIndex = 0,
|
||||||
@required bitcoin.HDWallet hd,
|
@required bitcoin.HDWallet mainHd,
|
||||||
@required this.networkType,
|
@required bitcoin.HDWallet sideHd,
|
||||||
@required this.mnemonic})
|
@required this.networkType})
|
||||||
: super(
|
: super(
|
||||||
walletInfo,
|
walletInfo,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
accountIndex: accountIndex,
|
accountIndex: accountIndex,
|
||||||
hd: hd);
|
mainHd: mainHd,
|
||||||
|
sideHd: sideHd);
|
||||||
|
|
||||||
bitcoin.NetworkType networkType;
|
bitcoin.NetworkType networkType;
|
||||||
|
|
||||||
final String mnemonic;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAddress({@required int index, @required bitcoin.HDWallet hd}) =>
|
String getAddress({@required int index, @required bitcoin.HDWallet hd}) =>
|
||||||
|
@ -40,15 +40,9 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
|
||||||
if (addresses.length < 33) {
|
if (addresses.length < 33) {
|
||||||
final addressesCount = 22 - addresses.length;
|
final addressesCount = 22 - addresses.length;
|
||||||
await generateNewAddresses(addressesCount,
|
await generateNewAddresses(addressesCount,
|
||||||
hd: hd, startIndex: addresses.length);
|
hd: mainHd, startIndex: addresses.length);
|
||||||
|
|
||||||
final changeRoot = bitcoin.HDWallet.fromSeed(
|
|
||||||
mnemonicToSeedBytes(mnemonic),
|
|
||||||
network: networkType)
|
|
||||||
.derivePath("m/0'/1");
|
|
||||||
|
|
||||||
await generateNewAddresses(11,
|
await generateNewAddresses(11,
|
||||||
startIndex: 0, hd: changeRoot, isHidden: true);
|
startIndex: 0, hd: sideHd, isHidden: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -194,8 +194,9 @@ Future setup(
|
||||||
getIt.registerSingleton<ExchangeTemplateStore>(
|
getIt.registerSingleton<ExchangeTemplateStore>(
|
||||||
ExchangeTemplateStore(templateSource: _exchangeTemplates));
|
ExchangeTemplateStore(templateSource: _exchangeTemplates));
|
||||||
getIt.registerSingleton<YatStore>(YatStore(
|
getIt.registerSingleton<YatStore>(YatStore(
|
||||||
appStore: getIt.get<AppStore>()
|
appStore: getIt.get<AppStore>(),
|
||||||
));
|
secureStorage: getIt.get<FlutterSecureStorage>())
|
||||||
|
..init());
|
||||||
|
|
||||||
final secretStore =
|
final secretStore =
|
||||||
await SecretStoreBase.load(getIt.get<FlutterSecureStorage>());
|
await SecretStoreBase.load(getIt.get<FlutterSecureStorage>());
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
part 'wallet_info.g.dart';
|
part 'wallet_info.g.dart';
|
||||||
|
|
||||||
|
@ -8,7 +9,8 @@ part 'wallet_info.g.dart';
|
||||||
class WalletInfo extends HiveObject {
|
class WalletInfo extends HiveObject {
|
||||||
WalletInfo(this.id, this.name, this.type, this.isRecovery, this.restoreHeight,
|
WalletInfo(this.id, this.name, this.type, this.isRecovery, this.restoreHeight,
|
||||||
this.timestamp, this.dirPath, this.path, this.address, this.yatEid,
|
this.timestamp, this.dirPath, this.path, this.address, this.yatEid,
|
||||||
this.yatRefreshToken);
|
this.yatLastUsedAddressRaw)
|
||||||
|
: _yatLastUsedAddressController = StreamController<String>.broadcast();
|
||||||
|
|
||||||
factory WalletInfo.external(
|
factory WalletInfo.external(
|
||||||
{@required String id,
|
{@required String id,
|
||||||
|
@ -21,10 +23,10 @@ class WalletInfo extends HiveObject {
|
||||||
@required String path,
|
@required String path,
|
||||||
@required String address,
|
@required String address,
|
||||||
String yatEid ='',
|
String yatEid ='',
|
||||||
String yatRefreshToken = ''}) {
|
String yatLastUsedAddressRaw = ''}) {
|
||||||
return WalletInfo(id, name, type, isRecovery, restoreHeight,
|
return WalletInfo(id, name, type, isRecovery, restoreHeight,
|
||||||
date.millisecondsSinceEpoch ?? 0, dirPath, path, address,
|
date.millisecondsSinceEpoch ?? 0, dirPath, path, address,
|
||||||
yatEid, yatRefreshToken);
|
yatEid, yatLastUsedAddressRaw);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const typeId = 4;
|
static const typeId = 4;
|
||||||
|
@ -64,11 +66,20 @@ class WalletInfo extends HiveObject {
|
||||||
String yatEid;
|
String yatEid;
|
||||||
|
|
||||||
@HiveField(12)
|
@HiveField(12)
|
||||||
String yatRefreshToken;
|
String yatLastUsedAddressRaw;
|
||||||
|
|
||||||
|
String get yatLastUsedAddress => yatLastUsedAddressRaw;
|
||||||
|
|
||||||
|
set yatLastUsedAddress(String address) {
|
||||||
|
yatLastUsedAddressRaw = address;
|
||||||
|
_yatLastUsedAddressController.add(address);
|
||||||
|
}
|
||||||
|
|
||||||
String get yatEmojiId => yatEid ?? '';
|
String get yatEmojiId => yatEid ?? '';
|
||||||
|
|
||||||
String get yatToken => yatRefreshToken ?? '';
|
|
||||||
|
|
||||||
DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp);
|
DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp);
|
||||||
|
|
||||||
|
Stream<String> get yatLastUsedAddressStream => _yatLastUsedAddressController.stream;
|
||||||
|
|
||||||
|
StreamController<String> _yatLastUsedAddressController;
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,8 +110,6 @@ Future<void> main() async {
|
||||||
final unspentCoinsInfoSource =
|
final unspentCoinsInfoSource =
|
||||||
await Hive.openBox<UnspentCoinsInfo>(UnspentCoinsInfo.boxName);
|
await Hive.openBox<UnspentCoinsInfo>(UnspentCoinsInfo.boxName);
|
||||||
|
|
||||||
await visualisationForEmojiId('%E2%98%A0%EF%B8%8F%F0%9F%90%99%E2%98%A0%EF%B8%8F');
|
|
||||||
|
|
||||||
await initialSetup(
|
await initialSetup(
|
||||||
sharedPreferences: await SharedPreferences.getInstance(),
|
sharedPreferences: await SharedPreferences.getInstance(),
|
||||||
nodes: nodes,
|
nodes: nodes,
|
||||||
|
|
|
@ -13,14 +13,38 @@ import 'package:cake_wallet/store/settings_store.dart';
|
||||||
import 'package:cake_wallet/core/fiat_conversion_service.dart';
|
import 'package:cake_wallet/core/fiat_conversion_service.dart';
|
||||||
import 'package:cake_wallet/core/wallet_base.dart';
|
import 'package:cake_wallet/core/wallet_base.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||||
|
import 'package:cake_wallet/store/yat/yat_store.dart';
|
||||||
|
|
||||||
ReactionDisposer _onCurrentWalletChangeReaction;
|
ReactionDisposer _onCurrentWalletChangeReaction;
|
||||||
ReactionDisposer _onCurrentWalletChangeFiatRateUpdateReaction;
|
ReactionDisposer _onCurrentWalletChangeFiatRateUpdateReaction;
|
||||||
|
ReactionDisposer _onCurrentWalletAddressChangeReaction;
|
||||||
|
|
||||||
void startCurrentWalletChangeReaction(AppStore appStore,
|
void startCurrentWalletChangeReaction(AppStore appStore,
|
||||||
SettingsStore settingsStore, FiatConversionStore fiatConversionStore) {
|
SettingsStore settingsStore, FiatConversionStore fiatConversionStore) {
|
||||||
_onCurrentWalletChangeReaction?.reaction?.dispose();
|
_onCurrentWalletChangeReaction?.reaction?.dispose();
|
||||||
_onCurrentWalletChangeFiatRateUpdateReaction?.reaction?.dispose();
|
_onCurrentWalletChangeFiatRateUpdateReaction?.reaction?.dispose();
|
||||||
|
_onCurrentWalletAddressChangeReaction?.reaction?.dispose();
|
||||||
|
|
||||||
|
_onCurrentWalletAddressChangeReaction = reaction((_) => appStore.wallet.walletAddresses.address,
|
||||||
|
(String address) async {
|
||||||
|
if (address == appStore.wallet.walletInfo.yatLastUsedAddress) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final yatStore = getIt.get<YatStore>();
|
||||||
|
await updateEmojiIdAddress(
|
||||||
|
appStore.wallet.walletInfo.yatEmojiId,
|
||||||
|
appStore.wallet.walletAddresses.address,
|
||||||
|
yatStore.apiKey,
|
||||||
|
appStore.wallet.type
|
||||||
|
);
|
||||||
|
appStore.wallet.walletInfo.yatLastUsedAddress = address;
|
||||||
|
await appStore.wallet.walletInfo.save();
|
||||||
|
} catch (e) {
|
||||||
|
print(e.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
_onCurrentWalletChangeReaction = reaction((_) => appStore.wallet, (WalletBase<
|
_onCurrentWalletChangeReaction = reaction((_) => appStore.wallet, (WalletBase<
|
||||||
Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
|
Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
|
||||||
|
|
|
@ -98,6 +98,10 @@ class DashboardPage extends BasePage {
|
||||||
height: 22.24,
|
height: 22.24,
|
||||||
width: 24,
|
width: 24,
|
||||||
color: Theme.of(context).accentTextTheme.display3.backgroundColor);
|
color: Theme.of(context).accentTextTheme.display3.backgroundColor);
|
||||||
|
final sellImage = Image.asset('assets/images/restore_wallet_image.png',
|
||||||
|
height: 22.24,
|
||||||
|
width: 24,
|
||||||
|
color: Theme.of(context).accentTextTheme.display3.backgroundColor);
|
||||||
_setEffects(context);
|
_setEffects(context);
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
|
@ -144,6 +148,12 @@ class DashboardPage extends BasePage {
|
||||||
onClick: () async =>
|
onClick: () async =>
|
||||||
await _onClickBuyButton(context),
|
await _onClickBuyButton(context),
|
||||||
),
|
),
|
||||||
|
ActionButton(
|
||||||
|
image: sellImage,
|
||||||
|
title: 'Sell',
|
||||||
|
onClick: () async =>
|
||||||
|
await _onClickSellButton(context),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -227,7 +237,13 @@ class DashboardPage extends BasePage {
|
||||||
final walletType = walletViewModel.type;
|
final walletType = walletViewModel.type;
|
||||||
|
|
||||||
switch (walletType) {
|
switch (walletType) {
|
||||||
case WalletType.monero:
|
case WalletType.bitcoin:
|
||||||
|
Navigator.of(context).pushNamed(Routes.preOrder);
|
||||||
|
break;
|
||||||
|
case WalletType.litecoin:
|
||||||
|
Navigator.of(context).pushNamed(Routes.preOrder);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
await showPopUp<void>(
|
await showPopUp<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
|
@ -237,10 +253,29 @@ class DashboardPage extends BasePage {
|
||||||
buttonText: S.of(context).ok,
|
buttonText: S.of(context).ok,
|
||||||
buttonAction: () => Navigator.of(context).pop());
|
buttonAction: () => Navigator.of(context).pop());
|
||||||
});
|
});
|
||||||
break;
|
}
|
||||||
default:
|
}
|
||||||
|
|
||||||
|
Future<void> _onClickSellButton(BuildContext context) async {
|
||||||
|
final walletType = walletViewModel.type;
|
||||||
|
|
||||||
|
switch (walletType) {
|
||||||
|
case WalletType.bitcoin:
|
||||||
Navigator.of(context).pushNamed(Routes.preOrder);
|
Navigator.of(context).pushNamed(Routes.preOrder);
|
||||||
break;
|
break;
|
||||||
|
case WalletType.litecoin:
|
||||||
|
Navigator.of(context).pushNamed(Routes.preOrder);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
await showPopUp<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertWithOneAction(
|
||||||
|
alertTitle: S.of(context).buy,
|
||||||
|
alertContent: S.of(context).buy_alert_content,
|
||||||
|
buttonText: S.of(context).ok,
|
||||||
|
buttonAction: () => Navigator.of(context).pop());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_isInactiveController = StreamController<bool>();
|
_isInactiveController = StreamController<bool>.broadcast();
|
||||||
_isInactive = false;
|
_isInactive = false;
|
||||||
_postFrameCallback = false;
|
_postFrameCallback = false;
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
|
|
@ -13,6 +13,7 @@ import 'dart:convert';
|
||||||
import 'package:cake_wallet/store/yat/yat_exception.dart';
|
import 'package:cake_wallet/store/yat/yat_exception.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
|
||||||
part 'yat_store.g.dart';
|
part 'yat_store.g.dart';
|
||||||
|
|
||||||
|
@ -23,61 +24,135 @@ class YatLink {
|
||||||
static const signInSuffix = '/partner/$partnerId/link-email';
|
static const signInSuffix = '/partner/$partnerId/link-email';
|
||||||
static const createSuffix = '/create';
|
static const createSuffix = '/create';
|
||||||
static const managePath = '/partner/$partnerId/manage';
|
static const managePath = '/partner/$partnerId/manage';
|
||||||
static const queryParameter = '?addresses=';
|
static const queryParameter = '?address_json=';
|
||||||
|
static const apiDevUrl = 'https://a.yat.fyi';
|
||||||
|
static const apiReleaseUrl = 'https://a.y.at';
|
||||||
static const requestDevUrl = 'https://a.yat.fyi/emoji_id/';
|
static const requestDevUrl = 'https://a.yat.fyi/emoji_id/';
|
||||||
static const requestReleaseUrl = 'https://a.y.at/emoji_id/';
|
static const requestReleaseUrl = 'https://a.y.at/emoji_id/';
|
||||||
static const startFlowUrl = 'https://www.y03btrk.com/4RQSJ/55M6S/';
|
static const startFlowUrl = 'https://www.y03btrk.com/4RQSJ/6JHXF/';
|
||||||
static const isDevMode = false;
|
static const isDevMode = true;
|
||||||
static const tags = <String, List<String>>{"XMR" : ['0x1001', '0x1002'],
|
static const tags = <String, List<String>>{"XMR" : ['0x1001', '0x1002'],
|
||||||
"BTC" : ['0x1003'], "LTC" : ['0x3fff']};
|
"BTC" : ['0x1003'], "LTC" : ['0x3fff']};
|
||||||
|
|
||||||
|
static String get apiUrl => YatLink.isDevMode
|
||||||
|
? YatLink.apiDevUrl
|
||||||
|
: YatLink.apiReleaseUrl;
|
||||||
|
|
||||||
|
static String get emojiIdUrl => apiUrl + '/emoji_id/';
|
||||||
|
|
||||||
static String get baseUrl => YatLink.isDevMode
|
static String get baseUrl => YatLink.isDevMode
|
||||||
? YatLink.baseDevUrl
|
? YatLink.baseDevUrl
|
||||||
: YatLink.baseReleaseUrl;
|
: YatLink.baseReleaseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<String>> fetchYatAddress(String emojiId, String ticker) async {
|
Future<List<String>> fetchYatAddress(String emojiId, String ticker) async {
|
||||||
final requestURL = YatLink.isDevMode
|
final url = YatLink.emojiIdUrl + emojiId + '/payment';
|
||||||
? YatLink.requestDevUrl
|
|
||||||
: YatLink.requestReleaseUrl;
|
|
||||||
final url = requestURL + emojiId;
|
|
||||||
final response = await get(url);
|
final response = await get(url);
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
if (response.statusCode != 200) {
|
||||||
throw YatException(text: response.body.toString());
|
throw YatException(text: response.body.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
final addresses = <String>[];
|
||||||
final result = responseJSON['result'] as List<dynamic>;
|
|
||||||
|
|
||||||
if (result?.isEmpty ?? true) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<String> addresses = [];
|
|
||||||
final currency = ticker.toUpperCase();
|
final currency = ticker.toUpperCase();
|
||||||
|
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||||
for (var elem in result) {
|
final result = responseJSON['result'] as Map<dynamic, dynamic>;
|
||||||
final tag = elem['tag'] as String;
|
result.forEach((dynamic key, dynamic value) {
|
||||||
if (tag?.isEmpty ?? true) {
|
final tag = key as String ?? '';
|
||||||
continue;
|
final record = value as Map<String, dynamic>;
|
||||||
}
|
|
||||||
if (YatLink.tags[currency]?.contains(tag) ?? false) {
|
if (YatLink.tags[currency]?.contains(tag) ?? false) {
|
||||||
final yatAddress = elem['data'] as String;
|
final address = record['address'] as String;
|
||||||
if (yatAddress?.isNotEmpty ?? false) {
|
if (address?.isNotEmpty ?? false) {
|
||||||
addresses.add(yatAddress);
|
addresses.add(address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
return addresses;
|
return addresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String> fetchYatAccessToken(String refreshToken) async {
|
||||||
|
try {
|
||||||
|
final url = YatLink.apiUrl + '/auth/token/refresh';
|
||||||
|
final bodyJson = json.encode({'refresh_token': refreshToken});
|
||||||
|
final response = await post(
|
||||||
|
url,
|
||||||
|
headers: <String, String>{
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': '*/*'
|
||||||
|
},
|
||||||
|
body: bodyJson);
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw YatException(text: response.body.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||||
|
return responseJSON['access_token'] as String;
|
||||||
|
}catch(_) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> fetchYatApiKey(String accessKey) async {
|
||||||
|
try {
|
||||||
|
final url = YatLink.apiUrl + '/api_keys';
|
||||||
|
final bodyJson = json.encode({'name': 'CW'});
|
||||||
|
final response = await post(
|
||||||
|
url,
|
||||||
|
headers: <String, String>{
|
||||||
|
'Authorization': 'Bearer $accessKey',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': '*/*'
|
||||||
|
},
|
||||||
|
body: bodyJson);
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw YatException(text: response.body.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||||
|
return responseJSON['api_key'] as String;
|
||||||
|
}catch(_) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateEmojiIdAddress(String emojiId, String address, String apiKey, WalletType type) async {
|
||||||
|
final url = YatLink.emojiIdUrl + emojiId;
|
||||||
|
final cur = walletTypeToCryptoCurrency(type);
|
||||||
|
final curFormatted = cur.toString().toUpperCase();
|
||||||
|
var tag = '';
|
||||||
|
|
||||||
|
if (type == WalletType.monero && !address.startsWith('4')) {
|
||||||
|
tag = YatLink.tags[curFormatted].last;
|
||||||
|
} else {
|
||||||
|
tag = YatLink.tags[curFormatted].first;
|
||||||
|
}
|
||||||
|
|
||||||
|
final bodyJson = json.encode({
|
||||||
|
'insert': [{
|
||||||
|
'data': address,
|
||||||
|
'tag': tag
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
final response = await patch(
|
||||||
|
url,
|
||||||
|
headers: <String, String>{
|
||||||
|
'x-api-key': apiKey,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': '*/*'
|
||||||
|
},
|
||||||
|
body: bodyJson);
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw YatException(text: response.body.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<String> visualisationForEmojiId(String emojiId) async {
|
Future<String> visualisationForEmojiId(String emojiId) async {
|
||||||
final requestURL = YatLink.isDevMode
|
final url = YatLink.emojiIdUrl + emojiId + '/json/VisualizerFileLocations';
|
||||||
? YatLink.requestDevUrl
|
|
||||||
: YatLink.requestReleaseUrl;
|
|
||||||
final url = requestURL + emojiId + '/json/VisualizerFileLocations';
|
|
||||||
final response = await get(url);
|
final response = await get(url);
|
||||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||||
final data = responseJSON['data'] as Map<String, dynamic>;
|
final data = responseJSON['data'] as Map<String, dynamic>;
|
||||||
|
@ -88,23 +163,39 @@ Future<String> visualisationForEmojiId(String emojiId) async {
|
||||||
class YatStore = YatStoreBase with _$YatStore;
|
class YatStore = YatStoreBase with _$YatStore;
|
||||||
|
|
||||||
abstract class YatStoreBase with Store {
|
abstract class YatStoreBase with Store {
|
||||||
YatStoreBase({@required this.appStore}) {
|
YatStoreBase({@required this.appStore, @required this.secureStorage}) {
|
||||||
_wallet ??= appStore.wallet;
|
_wallet ??= appStore.wallet;
|
||||||
emoji = _wallet?.walletInfo?.yatEmojiId ?? '';
|
emoji = _wallet?.walletInfo?.yatEmojiId ?? '';
|
||||||
refreshToken = _wallet?.walletInfo?.yatToken ?? '';
|
|
||||||
reaction((_) => appStore.wallet, _onWalletChange);
|
reaction((_) => appStore.wallet, _onWalletChange);
|
||||||
reaction((_) => emoji, (String emoji) => _onEmojiChange());
|
reaction((_) => emoji, (String _) => _onEmojiChange());
|
||||||
emojiIncommingSC = StreamController<String>();
|
reaction((_) => refreshToken, (String _) => _onRefreshTokenChange());
|
||||||
|
emojiIncommingSC = StreamController<String>.broadcast();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const yatRefreshTokenKeyBase = 'yat_refresh_token';
|
||||||
|
static const yatAccessTokenKeyBase = 'yat_access_token';
|
||||||
|
static const yatApiKeyBase = 'yat_api_key';
|
||||||
|
|
||||||
|
static String yatRefreshTokenKey(String name) => '${yatRefreshTokenKeyBase}_$name';
|
||||||
|
static String yatAccessTokenKey(String name) => '${yatAccessTokenKeyBase}_$name';
|
||||||
|
static String yatApiKey(String name) => '${yatApiKeyBase}_$name';
|
||||||
|
|
||||||
AppStore appStore;
|
AppStore appStore;
|
||||||
|
|
||||||
|
FlutterSecureStorage secureStorage;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
String emoji;
|
String emoji;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
String refreshToken;
|
String refreshToken;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
String accessToken;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
String apiKey;
|
||||||
|
|
||||||
StreamController<String> emojiIncommingSC;
|
StreamController<String> emojiIncommingSC;
|
||||||
|
|
||||||
Stream<String> get emojiIncommingStream => emojiIncommingSC.stream;
|
Stream<String> get emojiIncommingStream => emojiIncommingSC.stream;
|
||||||
|
@ -113,6 +204,16 @@ abstract class YatStoreBase with Store {
|
||||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
|
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
|
||||||
_wallet;
|
_wallet;
|
||||||
|
|
||||||
|
Future<void> init() async {
|
||||||
|
if (_wallet == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshToken = await secureStorage.read(key: yatRefreshTokenKey(_wallet.walletInfo.name));
|
||||||
|
accessToken = await secureStorage.read(key: yatAccessTokenKey(_wallet.walletInfo.name));
|
||||||
|
apiKey = await secureStorage.read(key: yatApiKey(_wallet.walletInfo.name));
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void _onWalletChange(
|
void _onWalletChange(
|
||||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
|
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
|
||||||
|
@ -120,7 +221,7 @@ abstract class YatStoreBase with Store {
|
||||||
wallet) {
|
wallet) {
|
||||||
this._wallet = wallet;
|
this._wallet = wallet;
|
||||||
emoji = wallet?.walletInfo?.yatEmojiId ?? '';
|
emoji = wallet?.walletInfo?.yatEmojiId ?? '';
|
||||||
refreshToken = wallet?.walletInfo?.yatToken ?? '';
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -133,7 +234,6 @@ abstract class YatStoreBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
walletInfo.yatEid = emoji;
|
walletInfo.yatEid = emoji;
|
||||||
walletInfo.yatRefreshToken = refreshToken;
|
|
||||||
|
|
||||||
if (walletInfo.isInBox) {
|
if (walletInfo.isInBox) {
|
||||||
walletInfo.save();
|
walletInfo.save();
|
||||||
|
@ -143,62 +243,32 @@ abstract class YatStoreBase with Store {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String defineQueryParameters() {
|
@action
|
||||||
String parameters = '';
|
Future<void> _onRefreshTokenChange() async {
|
||||||
switch (_wallet.type) {
|
try {
|
||||||
case WalletType.monero:
|
await secureStorage.write(key: yatRefreshTokenKey(_wallet.walletInfo.name), value: refreshToken);
|
||||||
final wallet = _wallet as MoneroWallet;
|
accessToken = await fetchYatAccessToken(refreshToken);
|
||||||
final subaddressList = MoneroSubaddressList();
|
await secureStorage.write(key: yatAccessTokenKey(_wallet.walletInfo.name), value: accessToken);
|
||||||
var isFirstAddress = true;
|
apiKey = await fetchYatApiKey(accessToken);
|
||||||
|
await secureStorage.write(key: yatApiKey(_wallet.walletInfo.name), value: accessToken);
|
||||||
wallet.walletAddresses.accountList.accounts.forEach((account) {
|
} catch (e) {
|
||||||
subaddressList.update(accountIndex: account.id);
|
print(e.toString());
|
||||||
subaddressList.subaddresses.forEach((subaddress) {
|
|
||||||
if (!isFirstAddress) {
|
|
||||||
parameters += '%7C';
|
|
||||||
} else {
|
|
||||||
isFirstAddress = !isFirstAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters += subaddress.address.startsWith('4')
|
|
||||||
? YatLink.tags["XMR"].first + '%3D'
|
|
||||||
: YatLink.tags["XMR"].last + '%3D';
|
|
||||||
|
|
||||||
parameters += subaddress.address;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case WalletType.bitcoin:
|
|
||||||
final wallet = _wallet as ElectrumWallet;
|
|
||||||
var isFirstAddress = true;
|
|
||||||
|
|
||||||
wallet.walletAddresses.addresses.forEach((record) {
|
|
||||||
if (!isFirstAddress) {
|
|
||||||
parameters += '%7C';
|
|
||||||
} else {
|
|
||||||
isFirstAddress = !isFirstAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters += YatLink.tags["BTC"].first + '%3D' + record.address;
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case WalletType.litecoin:
|
|
||||||
final wallet = _wallet as ElectrumWallet;
|
|
||||||
var isFirstAddress = true;
|
|
||||||
|
|
||||||
wallet.walletAddresses.addresses.forEach((record) {
|
|
||||||
if (!isFirstAddress) {
|
|
||||||
parameters += '%7C';
|
|
||||||
} else {
|
|
||||||
isFirstAddress = !isFirstAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters += YatLink.tags["LTC"].first + '%3D' + record.address;
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
parameters = '';
|
|
||||||
}
|
}
|
||||||
return parameters;
|
}
|
||||||
|
|
||||||
|
String defineQueryParameters() {
|
||||||
|
final result = <String, String>{};
|
||||||
|
final tags = YatLink.tags[_wallet.currency.toString().toUpperCase()];
|
||||||
|
String tag = tags.first;
|
||||||
|
|
||||||
|
if (_wallet.type == WalletType.monero
|
||||||
|
&& _wallet.walletAddresses.address.startsWith('4')) {
|
||||||
|
tag = tags.last;
|
||||||
|
}
|
||||||
|
result[tag] = '${_wallet.walletAddresses.address}|${_wallet.name}';
|
||||||
|
final addressJson = json.encode([result]);
|
||||||
|
final addressJsonBytes = utf8.encode(addressJson);
|
||||||
|
|
||||||
|
return base64.encode(addressJsonBytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -14,6 +14,7 @@ import 'package:cake_wallet/core/transaction_history.dart';
|
||||||
import 'package:cake_wallet/entities/balance.dart';
|
import 'package:cake_wallet/entities/balance.dart';
|
||||||
import 'package:cake_wallet/entities/transaction_info.dart';
|
import 'package:cake_wallet/entities/transaction_info.dart';
|
||||||
import 'package:cake_wallet/store/app_store.dart';
|
import 'package:cake_wallet/store/app_store.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
part 'wallet_address_list_view_model.g.dart';
|
part 'wallet_address_list_view_model.g.dart';
|
||||||
|
|
||||||
|
@ -66,7 +67,31 @@ abstract class WalletAddressListViewModelBase with Store {
|
||||||
}) {
|
}) {
|
||||||
_appStore = appStore;
|
_appStore = appStore;
|
||||||
_wallet = _appStore.wallet;
|
_wallet = _appStore.wallet;
|
||||||
|
emoji = '';
|
||||||
hasAccounts = _wallet?.type == WalletType.monero;
|
hasAccounts = _wallet?.type == WalletType.monero;
|
||||||
|
reaction((_) => _wallet.walletAddresses.address, (String address) {
|
||||||
|
if (address == _wallet.walletInfo.yatLastUsedAddress) {
|
||||||
|
emoji = yatStore.emoji;
|
||||||
|
} else {
|
||||||
|
emoji = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
reaction((_) => yatStore.emoji, (String emojiId) => this.emoji = emojiId);
|
||||||
|
|
||||||
|
_onLastUsedYatAddressSubscription =
|
||||||
|
_wallet.walletInfo.yatLastUsedAddressStream.listen((String yatAddress) {
|
||||||
|
if (yatAddress == _wallet.walletAddresses.address) {
|
||||||
|
emoji = yatStore.emoji;
|
||||||
|
} else {
|
||||||
|
emoji = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (_wallet.walletAddresses.address == _wallet.walletInfo.yatLastUsedAddress) {
|
||||||
|
emoji = yatStore.emoji;
|
||||||
|
}
|
||||||
|
|
||||||
_onWalletChangeReaction = reaction((_) => _appStore.wallet, (WalletBase<
|
_onWalletChangeReaction = reaction((_) => _appStore.wallet, (WalletBase<
|
||||||
Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
|
Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
|
||||||
wallet) {
|
wallet) {
|
||||||
|
@ -154,8 +179,8 @@ abstract class WalletAddressListViewModelBase with Store {
|
||||||
@computed
|
@computed
|
||||||
bool get hasAddressList => _wallet.type == WalletType.monero;
|
bool get hasAddressList => _wallet.type == WalletType.monero;
|
||||||
|
|
||||||
@computed
|
@observable
|
||||||
String get emoji => yatStore.emoji;
|
String emoji;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
|
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
|
||||||
|
@ -169,6 +194,9 @@ abstract class WalletAddressListViewModelBase with Store {
|
||||||
|
|
||||||
ReactionDisposer _onWalletChangeReaction;
|
ReactionDisposer _onWalletChangeReaction;
|
||||||
|
|
||||||
|
StreamSubscription<String> _onLastUsedYatAddressSubscription;
|
||||||
|
StreamSubscription<String> _onEmojiIdChangeSubscription;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void setAddress(WalletAddressListItem address) =>
|
void setAddress(WalletAddressListItem address) =>
|
||||||
_wallet.walletAddresses.address = address.address;
|
_wallet.walletAddresses.address = address.address;
|
||||||
|
|
Loading…
Reference in a new issue