Yat, electrum addresses fixes

This commit is contained in:
M 2021-12-08 11:09:38 +00:00
parent cf20123ede
commit 42104fd825
14 changed files with 333 additions and 136 deletions

4
.gitignore vendored
View file

@ -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/**

View file

@ -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);
} }

View file

@ -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;

View file

@ -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);
} }

View file

@ -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({

View file

@ -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);
} }
} }
} }

View file

@ -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>());

View file

@ -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;
} }

View file

@ -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,

View file

@ -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>

View file

@ -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());
});
} }
} }
} }

View file

@ -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);

View file

@ -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);
} }
} }

View file

@ -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;