zano.dart test app

This commit is contained in:
leo 2023-12-02 09:42:00 +00:00
parent dda6d4c750
commit 2d886e1213
19 changed files with 1206 additions and 145 deletions

View file

@ -3,6 +3,8 @@ import 'dart:convert';
import 'package:cw_zano/api/convert_utf8_to_string.dart';
import 'package:cw_zano/api/model.dart';
import 'package:cw_zano/api/model/get_recent_txs_and_info_params.dart';
import 'package:cw_zano/api/model/transfer_params.dart';
import 'package:cw_zano/api/zano_api.dart';
import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart';
@ -65,6 +67,22 @@ final _closeWalletNative = zanoApi
typedef _close_wallet = Void Function(Int64);
typedef _closeWalletStatus = void Function(int hWallet);
// get_current_tx_fee
final _getCurrentTxFeeNative = zanoApi
.lookup<NativeFunction<_get_current_tx_fee>>('get_current_tx_fee')
.asFunction<_getCurrentTxFee>();
typedef _get_current_tx_fee = Int64 Function(Int64);
typedef _getCurrentTxFee = int Function(int priority);
final _restoreWalletFromSeedNative = zanoApi
.lookup<NativeFunction<_restore_wallet_from_seed>>(
'restore_wallet_from_seed')
.asFunction<_RestoreWalletFromSeed>();
typedef _restore_wallet_from_seed = Pointer<Utf8> Function(
Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, Int32, Int64, Pointer<Utf8>);
typedef _RestoreWalletFromSeed = Pointer<Utf8> Function(
Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, int, int, Pointer<Utf8>);
String doAsyncCall(
{required String methodName,
required int hWallet,
@ -73,7 +91,7 @@ String doAsyncCall(
final paramsPointer = params.toNativeUtf8();
debugPrint(
"async_call method_name $methodName hWallet $hWallet params $params");
'async_call method_name $methodName hWallet $hWallet params $params');
final result = convertUTF8ToString(
pointer: _asyncCallNative(methodNamePointer, hWallet, paramsPointer));
@ -95,33 +113,34 @@ Future<String> invokeMethod(
}));
debugPrint('invoke result $invokeResult');
final map = json.decode(invokeResult);
if (map["job_id"] != null) {
bool done = false;
do {
await Future.delayed(Duration(seconds: 3));
final result = tryPullResult(map["job_id"] as int);
final map2 = json.decode(result);
done = map2["result"] == null || map2["result"]["error"] == null;
} while (!done);
if (map['job_id'] != null) {
await Future.delayed(Duration(seconds: 3));
final result = tryPullResult(map['job_id'] as int);
return result;
}
return "";
return invokeResult;
}
Future<String> store(int hWallet) async {
// debugPrint("store hWallet $hWallet");
// final result = doAsyncCall(
// methodName: 'invoke',
// hWallet: hWallet,
// params: "{method: 'store', params: {}}");
// debugPrint('store result $result');
// final map = json.decode(result);
// if (map["job_id"] != null) {
// await Future.delayed(Duration(seconds: 1));
// tryPullResult(map["job_id"] as int);
// }
return await invokeMethod(hWallet, 'store', '{}');
}
Future<String> transfer(int hWallet, TransferParams params) async {
final invokeResult = await doAsyncCall(
methodName: 'invoke',
hWallet: hWallet,
params: '{"method": "transfer","params": ${jsonEncode(params)}}',
);
debugPrint('invoke result $invokeResult');
var map = json.decode(invokeResult);
if (map['job_id'] != null) {
await Future.delayed(Duration(seconds: 3));
final result = tryPullResult(map['job_id'] as int);
return result;
}
return invokeResult;
}
Future<String> getRecentTxsAndInfo(
{required int hWallet,
required int offset,
@ -140,17 +159,24 @@ Future<String> getRecentTxsAndInfo(
}
String getWalletStatus(int hWallet) {
debugPrint("get_wallet_status hWallet $hWallet");
debugPrint('get_wallet_status hWallet $hWallet');
final result = convertUTF8ToString(pointer: _getWalletStatusNative(hWallet));
debugPrint('get_wallet_status result $result');
return result;
}
void closeWallet(int hWallet) {
debugPrint("close_wallet hWallet $hWallet");
debugPrint('close_wallet hWallet $hWallet');
_closeWalletNative(hWallet);
}
int getCurrentTxFee(int priority) {
debugPrint('get_current_tx_fee priority $priority');
final result = _getCurrentTxFeeNative(priority);
debugPrint('get_current_tx_fee result $result');
return result;
}
String getWalletInfo(int hWallet) {
debugPrint('get_wallet_info hWallet $hWallet');
final result = convertUTF8ToString(pointer: _getWalletInfoNative(hWallet));
@ -170,14 +196,25 @@ String getVersion() {
return result;
}
String restoreWalletFromSeed(String path, String password, String seed) {
debugPrint('restore_wallet_from_seed path $path password $password seed $seed');
final pathPointer = path.toNativeUtf8();
final passwordPointer = password.toNativeUtf8();
final seedPointer = seed.toNativeUtf8();
final errorMessagePointer = ''.toNativeUtf8();
final result = convertUTF8ToString(pointer: _restoreWalletFromSeedNative(pathPointer,
passwordPointer, seedPointer, 0, 0, errorMessagePointer));
return result;
}
String loadWallet(String path, String password, int nettype) {
debugPrint("load_wallet path $path password $password nettype $nettype");
debugPrint('load_wallet path $path password $password nettype $nettype');
final pathPointer = path.toNativeUtf8();
final passwordPointer = password.toNativeUtf8();
final result = convertUTF8ToString(
pointer: _loadWalletNative(pathPointer, passwordPointer, nettype),
);
debugPrint("load_wallet result $result");
debugPrint('load_wallet result $result');
return result;
}

View file

@ -1,49 +1,49 @@
class Destination {
final String amount;
final String address;
final String assetId;
// class Destination {
// final String amount;
// final String address;
// final String assetId;
Destination({required this.amount, required this.address, required this.assetId});
// Destination({required this.amount, required this.address, required this.assetId});
Map<String, dynamic> toJson() => {
"amount": amount,
"address": address,
"asset_id": assetId,
};
}
// Map<String, dynamic> toJson() => {
// "amount": amount,
// "address": address,
// "asset_id": assetId,
// };
// }
class TransferParams {
final List<Destination> destinations;
final int fee;
final int mixin;
final String paymentId;
final String comment;
final bool pushPayer;
final bool hideReceiver;
// class TransferParams {
// final List<Destination> destinations;
// final int fee;
// final int mixin;
// final String paymentId;
// final String comment;
// final bool pushPayer;
// final bool hideReceiver;
TransferParams({required this.destinations, required this.fee, required this.mixin, required this.paymentId, required this.comment, required this.pushPayer, required this.hideReceiver});
// TransferParams({required this.destinations, required this.fee, required this.mixin, required this.paymentId, required this.comment, required this.pushPayer, required this.hideReceiver});
Map<String, dynamic> toJson() => {
"destinations": destinations,
"fee": fee,
"mixin": mixin,
"payment_id": paymentId,
"comment": comment,
"push_payer": pushPayer,
"hide_receiver": hideReceiver,
};
}
// Map<String, dynamic> toJson() => {
// "destinations": destinations,
// "fee": fee,
// "mixin": mixin,
// "payment_id": paymentId,
// "comment": comment,
// "push_payer": pushPayer,
// "hide_receiver": hideReceiver,
// };
// }
class GetRecentTxsAndInfoParams {
final int offset;
final int count;
final bool updateProvisionInfo;
// class GetRecentTxsAndInfoParams {
// final int offset;
// final int count;
// final bool updateProvisionInfo;
GetRecentTxsAndInfoParams({required this.offset, required this.count, required this.updateProvisionInfo});
// GetRecentTxsAndInfoParams({required this.offset, required this.count, required this.updateProvisionInfo});
Map<String, dynamic> toJson() => {
"offset": offset,
"count": count,
"update_provision_info": updateProvisionInfo,
};
}
// Map<String, dynamic> toJson() => {
// "offset": offset,
// "count": count,
// "update_provision_info": updateProvisionInfo,
// };
// }

View file

@ -0,0 +1,34 @@
class AssetInfo {
final String assetId;
final int currentSupply;
final int decimalPoint;
final String fullName;
final bool hiddenSupply;
final String metaInfo;
final String owner;
final String ticker;
final int totalMaxSupply;
AssetInfo(
{required this.assetId,
required this.currentSupply,
required this.decimalPoint,
required this.fullName,
required this.hiddenSupply,
required this.metaInfo,
required this.owner,
required this.ticker,
required this.totalMaxSupply});
factory AssetInfo.fromJson(Map<String, dynamic> json) => AssetInfo(
assetId: json['asset_id'] as String,
currentSupply: json['current_supply'] as int,
decimalPoint: json['decimal_point'] as int,
fullName: json['full_name'] as String,
hiddenSupply: json['hidden_supply'] as bool,
metaInfo: json['meta_info'] as String,
owner: json['owner'] as String,
ticker: json['ticker'] as String,
totalMaxSupply: json['total_max_supply'] as int,
);
}

View file

@ -0,0 +1,27 @@
import 'dart:convert';
import 'package:cw_zano/api/model/asset_info.dart';
class Balance {
final AssetInfo assetInfo;
final int awaitingIn;
final int awaitingOut;
final int total;
final int unlocked;
Balance(
{required this.assetInfo,
required this.awaitingIn,
required this.awaitingOut,
required this.total,
required this.unlocked});
factory Balance.fromJson(Map<String, dynamic> json) => Balance(
assetInfo:
AssetInfo.fromJson(json['asset_info'] as Map<String, dynamic>),
awaitingIn: json['awaiting_in'] as int,
awaitingOut: json['awaiting_out'] as int,
total: json['total'] as int,
unlocked: json['unlocked'] as int,
);
}

View file

@ -0,0 +1,20 @@
class Destination {
final String amount;
final String address;
final String assetId;
Destination(
{required this.amount, required this.address, required this.assetId});
factory Destination.fromJson(Map<String, dynamic> json) => Destination(
amount: json['amount'] as String,
address: json['address'] as String,
assetId: json['asset_id'] as String,
);
Map<String, dynamic> toJson() => {
"amount": amount,
"address": address,
"asset_id": assetId,
};
}

View file

@ -0,0 +1,20 @@
import 'dart:convert';
import 'package:cw_zano/api/model/receive.dart';
class EmployedEntries {
final List<Receive> receive;
final List<Receive> send;
EmployedEntries({required this.receive, required this.send});
factory EmployedEntries.fromJson(Map<String, dynamic> json) =>
EmployedEntries(
receive: json['receive'] == null ? [] : (json['receive'] as List<dynamic>)
.map((e) => Receive.fromJson(e as Map<String, dynamic>))
.toList(),
send: json['spent'] == null ? [] : (json['spent'] as List<dynamic>)
.map((e) => Receive.fromJson(e as Map<String, dynamic>))
.toList(),
);
}

View file

@ -0,0 +1,13 @@
class GetRecentTxsAndInfoParams {
final int offset;
final int count;
final bool updateProvisionInfo;
GetRecentTxsAndInfoParams({required this.offset, required this.count, required this.updateProvisionInfo});
Map<String, dynamic> toJson() => {
"offset": offset,
"count": count,
"update_provision_info": updateProvisionInfo,
};
}

View file

@ -0,0 +1,13 @@
import 'package:cw_zano/api/model/wi.dart';
import 'package:cw_zano/api/model/wi_extended.dart';
class GetWalletInfoResult {
final Wi wi;
final WiExtended wiExtended;
GetWalletInfoResult({required this.wi, required this.wiExtended});
factory GetWalletInfoResult.fromJson(Map<String, dynamic> json) => GetWalletInfoResult(
wi: Wi.fromJson(json['wi'] as Map<String, dynamic>),
wiExtended: WiExtended.fromJson(json['wi_extended'] as Map<String, dynamic>));
}

View file

@ -0,0 +1,35 @@
class GetWalletStatusResult {
final int currentDaemonHeight;
final int currentWalletHeight;
final bool isDaemonConnected;
final bool isInLongRefresh;
final int progress;
final int walletState;
GetWalletStatusResult(
{required this.currentDaemonHeight,
required this.currentWalletHeight,
required this.isDaemonConnected,
required this.isInLongRefresh,
required this.progress,
required this.walletState});
factory GetWalletStatusResult.fromJson(Map<String, dynamic> json) =>
GetWalletStatusResult(
currentDaemonHeight: json['current_daemon_height'] as int,
currentWalletHeight: json['current_wallet_height'] as int,
isDaemonConnected: json['is_daemon_connected'] as bool,
isInLongRefresh: json['is_in_long_refresh'] as bool,
progress: json['progress'] as int,
walletState: json['wallet_state'] as int,
);
/*
"current_daemon_height": 238049,
"current_wallet_height": 238038,
"is_daemon_connected": true,
"is_in_long_refresh": true,
"progress": 0,
"wallet_state": 1
*/
}

View file

@ -0,0 +1,71 @@
import 'dart:convert';
import 'package:cw_zano/api/model/employed_entries.dart';
import 'package:cw_zano/api/model/subtransfer.dart';
class History {
final String comment;
final EmployedEntries employedEntries;
final int fee;
final int height;
final bool isMining;
final bool isMixing;
final bool isService;
final String paymentId;
final List<String> remoteAddresses;
final List<String> remoteAliases;
final bool showSender;
final List<Subtransfer> subtransfers;
final int timestamp;
final int transferInternalIndex;
final int txBlobSize;
final String txHash;
final int txType;
final int unlockTime;
History({
required this.comment,
required this.employedEntries,
required this.fee,
required this.height,
required this.isMining,
required this.isMixing,
required this.isService,
required this.paymentId,
required this.remoteAddresses,
required this.remoteAliases,
required this.showSender,
required this.subtransfers,
required this.timestamp,
required this.transferInternalIndex,
required this.txBlobSize,
required this.txHash,
required this.txType,
required this.unlockTime,
});
factory History.fromJson(Map<String, dynamic> json) => History(
comment: json['comment'] as String,
employedEntries: EmployedEntries.fromJson(
json['employed_entries'] as Map<String, dynamic>),
fee: json['fee'] as int,
height: json['height'] as int,
isMining: json['is_mining'] as bool,
isMixing: json['is_mixing'] as bool,
isService: json['is_service'] as bool,
paymentId: json['payment_id'] as String,
remoteAddresses: json['remote_addresses'] == null ? [] :
(json['remote_addresses'] as List<dynamic>).cast<String>(),
remoteAliases: json['remote_aliases'] == null ? [] : (json['remote_aliases'] as List<dynamic>).cast<String>(),
showSender: json['show_sender'] as bool,
subtransfers: (json['subtransfers'] as List<dynamic>)
.map((e) => Subtransfer.fromJson(e as Map<String, dynamic>))
.toList(),
timestamp: json['timestamp'] as int,
transferInternalIndex: json['transfer_internal_index'] as int,
txBlobSize: json['tx_blob_size'] as int,
txHash: json['tx_hash'] as String,
txType: json['tx_type'] as int,
unlockTime: json['unlock_time'] as int,
);
}

View file

@ -0,0 +1,41 @@
import 'dart:convert';
import 'package:cw_zano/api/model/recent_history.dart';
import 'package:cw_zano/api/model/wi.dart';
class CreateLoadRestoreWalletResult {
final String name;
final String pass;
final RecentHistory recentHistory;
final bool recovered;
final String seed;
final int walletFileSize;
final int walletId;
final int walletLocalBcSize;
final Wi wi;
CreateLoadRestoreWalletResult(
{required this.name,
required this.pass,
required this.recentHistory,
required this.recovered,
required this.seed,
required this.walletFileSize,
required this.walletId,
required this.walletLocalBcSize,
required this.wi});
factory CreateLoadRestoreWalletResult.fromJson(Map<String, dynamic> json) =>
CreateLoadRestoreWalletResult(
name: json['name'] as String,
pass: json['pass'] as String,
recentHistory: RecentHistory.fromJson(
json['recent_history'] as Map<String, dynamic>),
recovered: json['recovered'] as bool,
seed: json['seed'] as String,
walletFileSize: json['wallet_file_size'] as int,
walletId: json['wallet_id'] as int,
walletLocalBcSize: json['wallet_local_bc_size'] as int,
wi: Wi.fromJson(json['wi'] as Map<String, dynamic>),
);
}

View file

@ -0,0 +1,13 @@
class Receive {
final int amount;
final String assetId;
final int index;
Receive({required this.amount, required this.assetId, required this.index});
factory Receive.fromJson(Map<String, dynamic> json) => Receive(
amount: json['amount'] as int,
assetId: json['asset_id'] as String,
index: json['index'] as int,
);
}

View file

@ -0,0 +1,22 @@
import 'dart:convert';
import 'package:cw_zano/api/model/history.dart';
class RecentHistory {
final List<History>? history;
final int lastItemIndex;
final int totalHistoryItems;
RecentHistory(
{required this.history,
required this.lastItemIndex,
required this.totalHistoryItems});
factory RecentHistory.fromJson(Map<String, dynamic> json) => RecentHistory(
history: json['history'] == null ? null : (json['history'] as List<dynamic>)
.map((e) => History.fromJson(e as Map<String, dynamic>))
.toList(),
lastItemIndex: json['last_item_index'] as int,
totalHistoryItems: json['total_history_items'] as int,
);
}

View file

@ -0,0 +1,14 @@
class Subtransfer {
final int amount;
final String assetId;
final bool isIncome;
Subtransfer(
{required this.amount, required this.assetId, required this.isIncome});
factory Subtransfer.fromJson(Map<String, dynamic> json) => Subtransfer(
amount: json['amount'] as int,
assetId: json['asset_id'] as String,
isIncome: json['is_income'] as bool,
);
}

View file

@ -0,0 +1,41 @@
import 'package:cw_zano/api/model/destination.dart';
class TransferParams {
final List<Destination> destinations;
final int fee;
final int mixin;
final String paymentId;
final String comment;
final bool pushPayer;
final bool hideReceiver;
TransferParams({
required this.destinations,
required this.fee,
required this.mixin,
required this.paymentId,
required this.comment,
required this.pushPayer,
required this.hideReceiver,
});
Map<String, dynamic> toJson() => {
"destinations": destinations,
"fee": fee,
"mixin": mixin,
"payment_id": paymentId,
"comment": comment,
"push_payer": pushPayer,
"hide_receiver": hideReceiver,
};
factory TransferParams.fromJson(Map<String, dynamic> json) => TransferParams(
destinations: (json['destinations'] as List<dynamic>).map((e) => Destination.fromJson(e as Map<String, dynamic>)).toList(),
fee: json['fee'] as int,
mixin: json['mixin'] as int,
paymentId: json['payment_id'] as String,
comment: json['comment'] as String,
pushPayer: json["push_payer"] as bool,
hideReceiver: json["hide_receiver"] as bool,
);
}

View file

@ -0,0 +1,34 @@
import 'dart:convert';
import 'package:cw_zano/api/model/balance.dart';
class Wi {
final String address;
final List<Balance> balances;
final bool isAuditable;
final bool isWatchOnly;
final int minedTotal;
final String path;
final String viewSecKey;
Wi(
{required this.address,
required this.balances,
required this.isAuditable,
required this.isWatchOnly,
required this.minedTotal,
required this.path,
required this.viewSecKey});
factory Wi.fromJson(Map<String, dynamic> json) => Wi(
address: json['address'] as String,
balances: (json['balances'] as List<dynamic>)
.map((e) => Balance.fromJson(e as Map<String, dynamic>))
.toList(),
isAuditable: json['is_auditable'] as bool,
isWatchOnly: json['is_watch_only'] as bool,
minedTotal: json['mined_total'] as int,
path: json['path'] as String,
viewSecKey: json['view_sec_key'] as String,
);
}

View file

@ -0,0 +1,17 @@
class WiExtended {
final String seed;
final String spendPrivateKey;
final String spendPublicKey;
final String viewPrivateKey;
final String viewPublicKey;
WiExtended({required this.seed, required this.spendPrivateKey, required this.spendPublicKey, required this.viewPrivateKey, required this.viewPublicKey});
factory WiExtended.fromJson(Map<String, dynamic> json) => WiExtended(
seed: json["seed"] as String,
spendPrivateKey: json["spend_private_key"] as String,
spendPublicKey: json["spend_public_key"] as String,
viewPrivateKey: json["view_private_key"] as String,
viewPublicKey: json["view_public_key"] as String,
);
}

View file

@ -4,16 +4,20 @@ import 'dart:convert';
import 'package:cake_wallet/core/generate_wallet_password.dart';
import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/zano_connected_widget.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_zano/api/calls.dart' as calls;
import 'package:cw_zano/api/model/balance.dart';
import 'package:cw_zano/api/model/load_wallet_result.dart';
import 'package:cw_zano/api/wallet.dart' as zano_wallet;
import 'package:cw_zano/api/wallet_manager.dart' as zano_wallet_manager;
import 'package:cw_zano/api/calls.dart' as calls;
import 'package:cw_zano/zano_wallet_service.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:get_it/get_it.dart';
import 'package:shared_preferences/shared_preferences.dart';
Future<void> main() async {
await runZonedGuarded(() async {
@ -51,96 +55,232 @@ class App extends StatefulWidget {
State<App> createState() => _AppState();
}
class HomeWidget extends StatefulWidget {
const HomeWidget({super.key});
// class HomeWidget extends StatefulWidget {
// const HomeWidget({super.key});
@override
State<HomeWidget> createState() => _HomeWidgetState();
}
// @override
// State<HomeWidget> createState() => _HomeWidgetState();
// }
class _AppState extends State<App> {
@override
Widget build(BuildContext context) {
return MaterialApp(home: HomeWidget());
return MaterialApp(
home: DisconnectedWidget(), //HomeWidget(),
routes: {
ConnectedWidget.route: (context) {
final address = ModalRoute.of(context)!.settings.arguments! as String;
return ConnectedWidget(address: address);
},
DisconnectedWidget.route: (context) => DisconnectedWidget(),
},
);
}
}
class _HomeWidgetState extends State<HomeWidget> {
int hWallet = 0;
CreateLoadRestoreWalletResult? lwr;
List<Balance> balances = [];
String seed = '', version = '';
final assetIds = <String, String>{};
const walletWrongId = 'WALLET_WRONG_ID';
const walletName = 'walletName';
Future<void> init() async {
version = calls.getVersion();
final setupNode = await zano_wallet.setupNode(
address: '195.201.107.230:33336',
login: '',
password: '',
useSSL: false,
isLightWallet: false);
if (!setupNode) {
debugPrint('error setting up node!');
}
}
Future<String?> create(String name) async {
debugPrint('create $name');
await init();
final path = await pathForWallet(name: name, type: WalletType.zano);
final credentials = ZanoNewWalletCredentials(name: name);
final keyService = KeyService(FlutterSecureStorage());
final password = generateWalletPassword();
credentials.password = password;
await keyService.saveWalletPassword(
password: password, walletName: credentials.name);
debugPrint('path $path password $password');
final result = zano_wallet_manager.createWalletSync(
path: path, password: password, language: '');
debugPrint('create result $result');
return _parseResult(result);
}
Future<String?> connect(String name) async {
debugPrint('connect');
await init();
final path = await pathForWallet(name: name, type: WalletType.zano);
final credentials = ZanoNewWalletCredentials(name: name);
final keyService = KeyService(FlutterSecureStorage());
final password =
await keyService.getWalletPassword(walletName: credentials.name);
debugPrint('path $path password $password');
final result = await calls.loadWallet(path, password, 0);
return _parseResult(result);
}
Future<String?> restore(String name, String seed) async {
debugPrint("restore");
await init();
final path = await pathForWallet(name: name, type: WalletType.zano);
final credentials = ZanoNewWalletCredentials(name: name);
final keyService = KeyService(FlutterSecureStorage());
final password = generateWalletPassword();
credentials.password = password;
await keyService.saveWalletPassword(
password: password, walletName: credentials.name);
debugPrint('path $path password $password');
var result = calls.restoreWalletFromSeed(path, password, seed);
debugPrint('restore result $result');
//result = await calls.loadWallet(path, password, 0);
return _parseResult(result);
}
String? _parseResult(String result) {
final map = json.decode(result) as Map<String, dynamic>;
if (map['result'] != null) {
lwr = CreateLoadRestoreWalletResult.fromJson(map['result'] as Map<String, dynamic>);
balances = lwr!.wi.balances;
hWallet = lwr!.walletId;
assetIds.clear();
for (final balance in lwr!.wi.balances) {
assetIds[balance.assetInfo.assetId] = balance.assetInfo.ticker;
}
return lwr!.wi.address;
}
return null;
}
void close() {
calls.closeWallet(hWallet);
}
class DisconnectedWidget extends StatefulWidget {
const DisconnectedWidget({super.key});
static const route = 'disconnected';
@override
State<DisconnectedWidget> createState() => _DisconnectedWidgetState();
}
class _DisconnectedWidgetState extends State<DisconnectedWidget> {
late final TextEditingController _name = TextEditingController(text: "wallet");
late final TextEditingController _seed = TextEditingController(
text:
"palm annoy brush task almost through here sent doll guilty smart horse mere canvas flirt advice fruit known shower happiness steel autumn beautiful approach anymore canvas");
bool _loading = false;
@override
void initState() {
super.initState();
() async {
final preferences = await SharedPreferences.getInstance();
final value = preferences.getString(walletName);
if (value != null && value.isNotEmpty) _name.text = value;
}();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: connect(),
builder: (context, snapshot) {
if (!snapshot.hasData) return CircularProgressIndicator();
return Center(child: Text("connected"));
},
appBar: AppBar(title: Text('Disconnected')),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(32.0),
child: Stack(
children: [
Opacity(
opacity: _loading ? 0.5 : 1,
child: Column(
children: [
TextField(
controller: _name,
decoration: InputDecoration(labelText: 'Wallet name')),
TextButton(
child: Text('Connect and Open Wallet'),
onPressed: () async {
//setState(() => _loading = true);
final preferences =
await SharedPreferences.getInstance();
await preferences.setString(walletName, _name.text);
final result = await connect(_name.text);
//setState(() => _loading = false);
if (result != null) {
debugPrint("navigated to connected");
Navigator.of(context).pushReplacementNamed(
ConnectedWidget.route,
arguments: result,
);
} else {
debugPrint('connect no result');
}
}),
SizedBox(
height: 16,
),
TextButton(
child: Text('Create and Open Wallet'),
onPressed: () async {
//setState(() => _loading = true);
final preferences =
await SharedPreferences.getInstance();
await preferences.setString(walletName, _name.text);
final result = await create(_name.text);
//setState(() => _loading = false);
if (result != null) {
debugPrint("navigating to connected");
Navigator.of(context).pushReplacementNamed(
ConnectedWidget.route,
arguments: result,
);
} else {
debugPrint('create no result');
}
}),
SizedBox(
height: 16,
),
TextField(
controller: _seed,
decoration: InputDecoration(labelText: 'Wallet seed')),
TextButton(
child: Text('Restore from seed'),
onPressed: () async {
final preferences =
await SharedPreferences.getInstance();
await preferences.setString(walletName, _name.text);
final result = await restore(_name.text, _seed.text);
if (result != null) {
Navigator.of(context).pushReplacementNamed(
ConnectedWidget.route,
arguments: result,
);
} else {
debugPrint('restore no result');
}
}),
SizedBox(
height: 16,
),
TextButton(child: Text('Close Wallet'), onPressed: close),
],
),
),
if (_loading) Center(child: CircularProgressIndicator()),
],
),
),
),
);
}
static const name = "leo1";
Future<bool> connect() async {
calls.getVersion();
final setupNode = await zano_wallet.setupNode(
address: "195.201.107.230:33336",
login: "",
password: "",
useSSL: false,
isLightWallet: false);
final path = await pathForWallet(name: name, type: WalletType.zano);
final credentials = ZanoNewWalletCredentials(name: name);
final keyService = KeyService(FlutterSecureStorage());
final password = await keyService.getWalletPassword(walletName: credentials.name);
debugPrint("path $path password $password");
final result = await calls.loadWallet(path, password, 0);
final map = json.decode(result) as Map<String, dynamic>;
int hWallet = 0;
if (map["result"] != null) {
hWallet = (map["result"] as Map<String, dynamic>)["wallet_id"] as int;
debugPrint("hWallet $hWallet");
}
Future.delayed(Duration(seconds: 10));
await calls.getWalletStatus(hWallet);
Future.delayed(Duration(seconds: 10));
await calls.getRecentTxsAndInfo(hWallet: hWallet, offset: 0, count: 30);
Future.delayed(Duration(seconds: 2));
calls.closeWallet(hWallet);
return true;
}
Future<bool> _connect() async {
calls.getVersion();
final result = await zano_wallet.setupNode(
address: "195.201.107.230:33336",
login: "",
password: "",
useSSL: false,
isLightWallet: false);
//debugPrint("setup node result ${result}");
//final name = "leo1";
final path = await pathForWallet(name: name, type: WalletType.zano);
final credentials = ZanoNewWalletCredentials(name: name);
final keyService = KeyService(FlutterSecureStorage());
final password = generateWalletPassword();
credentials.password = password;
await keyService.saveWalletPassword(
password: password, walletName: credentials.name);
final createResult = await zano_wallet_manager.createWallet(
language: "", path: path, password: credentials.password!);
debugPrint("createWallet result $createResult");
final map = json.decode(createResult) as Map<String, dynamic>;
int hWallet = -1;
if (map["result"] != null) {
hWallet = (map["result"] as Map<String, dynamic>)["wallet_id"] as int;
debugPrint("hWallet $hWallet");
}
//await calls.loadWallet(path, password, 0);
calls.getConnectivityStatus();
await calls.store(hWallet);
calls.getWalletInfo(hWallet);
calls.getWalletStatus(hWallet);
return true;
}
}

View file

@ -0,0 +1,469 @@
import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'package:cake_wallet/zano.dart';
import 'package:cw_zano/api/model/destination.dart';
import 'package:cw_zano/api/model/get_wallet_info_result.dart';
import 'package:cw_zano/api/model/get_wallet_status_result.dart';
import 'package:cw_zano/api/model/history.dart';
import 'package:cw_zano/api/model/transfer_params.dart';
import 'package:flutter/material.dart';
import 'package:cw_zano/api/calls.dart' as calls;
import 'package:flutter/services.dart';
class ConnectedWidget extends StatefulWidget {
final String address;
const ConnectedWidget({super.key, required this.address});
static const route = 'connected';
@override
State<ConnectedWidget> createState() => _ConnectedWidgetState();
}
class _ConnectedWidgetState extends State<ConnectedWidget> {
Timer? _longRefreshTimer;
GetWalletStatusResult? _gwsr;
int? _txFee;
final int _mixin = 10;
late final TextEditingController _destinationAddress =
TextEditingController(text: widget.address);
static const defaultAmount = 1.0;
late final TextEditingController _amount = TextEditingController(text: defaultAmount.toString());
late String _amountFormatted = _mulBy10_12(defaultAmount);
late final TextEditingController _paymentId = TextEditingController();
late final TextEditingController _comment = TextEditingController(text: "test");
bool _pushPayer = false;
bool _hideReceiver = true;
String _transferResult = '';
List<History>? _transactions;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
// _getWalletStatus returning true if it's in long refresh
// in a long refresh we keep requesting _getWalletStatus until we get false
if (_getWalletStatus()) {
_longRefreshTimer = Timer.periodic(Duration(milliseconds: 1000), (timer) {
if (!_getWalletStatus()) {
_longRefreshTimer!.cancel();
debugPrint('cancelling get wallet status timer');
_getWalletInfo();
}
});
}
//_getWalletInfo();
});
}
@override
void dispose() {
//_timer.cancel();
// _myAddress.dispose();
// _seed.dispose();
_destinationAddress.dispose();
_amount.dispose();
_paymentId.dispose();
_comment.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 4,
child: Scaffold(
appBar: AppBar(
title: Text('Version $version'),
actions: [
IconButton(
icon: Icon(Icons.close),
onPressed: () {
close();
Navigator.of(context).pushReplacementNamed(DisconnectedWidget.route);
},
)
],
bottom: TabBar(
tabs: [
Tab(text: 'Main'),
Tab(text: 'Transfer'),
Builder(builder: (context) {
if (lwr != null && lwr!.recentHistory.history != null) {
return Tab(text: 'History (${lwr!.recentHistory.history!.length})');
}
return Tab(text: 'History');
}),
Tab(text: 'Transactions')
],
)),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: TabBarView(
children: [
_mainTab(context),
_transferTab(context),
_historyTab(),
_transactionsTab(),
],
),
),
),
),
);
}
Widget _transactionsTab() {
return Column(children: [
TextButton(onPressed: _getTransactions, child: Text('Update list of Transactions')),
Expanded(child: _transactionsListView(_transactions)),
]);
}
Widget _historyTab() {
if (lwr == null) return Text("Empty");
return _transactionsListView(lwr!.recentHistory.history);
}
ListView _transactionsListView(List<History>? list) {
return ListView.builder(
itemCount: list != null ? list.length : 0,
itemBuilder: (context, index) {
final item = list![index];
late String addr;
if (item.remoteAddresses.isNotEmpty) {
addr = _shorten(item.remoteAddresses.first);
} else {
addr = "???";
}
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text("${index + 1}. ${_dateTime(item.timestamp)} Remote addr: $addr"),
if (item.remoteAddresses.isNotEmpty)
IconButton(
onPressed: () =>
Clipboard.setData(ClipboardData(text: item.remoteAddresses.first)),
icon: Icon(Icons.copy),
),
if (item.remoteAliases.isNotEmpty) Text(" (${item.remoteAliases.first})"),
],
),
Text(" txHash: ${item.txHash} comment: ${item.comment}"),
Text(
" paymentId: ${item.paymentId} height: ${item.height} fee: ${_divBy10_12(item.fee)}"),
if (item.employedEntries.receive.isNotEmpty)
Text(" Receive", style: TextStyle(fontWeight: FontWeight.bold)),
for (int i = 0; i < item.employedEntries.receive.length; i++)
Text(
' ${item.employedEntries.receive[i].index}. ${_assetName(item.employedEntries.receive[i].assetId)} ${_divBy10_12(item.employedEntries.receive[i].amount)}'),
if (item.employedEntries.send.isNotEmpty)
Text(" Spent", style: TextStyle(fontWeight: FontWeight.bold)),
for (int i = 0; i < item.employedEntries.send.length; i++)
Text(
' ${item.employedEntries.send[i].index}. ${_assetName(item.employedEntries.send[i].assetId)} ${_divBy10_12(item.employedEntries.send[i].amount)}'),
if (item.subtransfers.isNotEmpty)
Text(" Subtransfers", style: TextStyle(fontWeight: FontWeight.bold)),
for (int i = 0; i < item.subtransfers.length; i++)
Text(
' ${item.subtransfers[i].isIncome ? 'In' : 'Out'}. ${_assetName(item.subtransfers[i].assetId)} ${_divBy10_12(item.subtransfers[i].amount)}'),
Divider(),
],
);
},
);
}
Widget _transferTab(BuildContext context) {
return SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text('Remote Address ', style: TextStyle(fontWeight: FontWeight.bold)),
Expanded(
child: TextField(
controller: _destinationAddress,
),
),
IconButton(
onPressed: () => Clipboard.setData(ClipboardData(text: _destinationAddress.text)),
icon: Icon(Icons.copy)),
IconButton(
onPressed: () async {
final clipboard = await Clipboard.getData("text/plain");
if (clipboard == null || clipboard.text == null) return;
setState(() {
_destinationAddress.text = clipboard.text!;
});
},
icon: Icon(Icons.paste)),
],
),
Row(
children: [
// ${lwr!.wi.address}
Text('Amount ', style: TextStyle(fontWeight: FontWeight.bold)),
Expanded(
child: TextField(
controller: _amount,
onChanged: (value) => setState(() {
_amountFormatted = _mulBy10_12(double.parse(value));
}),
),
),
Text("= ${_amountFormatted}"),
IconButton(
onPressed: () => Clipboard.setData(ClipboardData(text: _amount.text)),
icon: Icon(Icons.copy)),
],
),
if (_txFee != null)
Text('Fee: ${_divBy10_12(_txFee!)} (${_txFee!})')
else
Text("Pls get Tx Fee before transfer!"),
Text('Mixin: $_mixin'),
Row(children: [
Text('Payment Id ', style: TextStyle(fontWeight: FontWeight.bold)),
Expanded(child: TextField(controller: _paymentId)),
]),
Row(children: [
Text('Comment ', style: TextStyle(fontWeight: FontWeight.bold)),
Expanded(child: TextField(controller: _comment)),
]),
Row(
children: [
Text('Push Payer ', style: TextStyle(fontWeight: FontWeight.bold)),
Checkbox(
value: _pushPayer,
onChanged: (value) => setState(() => _pushPayer = value ?? false)),
],
),
Row(
children: [
Text('Hide Receiver ', style: TextStyle(fontWeight: FontWeight.bold)),
Checkbox(
value: _hideReceiver,
onChanged: (value) => setState(() => _hideReceiver = value ?? false)),
],
),
TextButton(onPressed: _transfer, child: Text('Transfer')),
const SizedBox(height: 16),
Text('Transfer result $_transferResult'),
],
),
);
}
Widget _mainTab(BuildContext context) {
return SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text('Wallet Info', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(width: 16),
TextButton(onPressed: _getWalletInfo, child: Text('Update WI & TxFee')),
],
),
Row(
children: [
Text('My Address ', style: TextStyle(fontWeight: FontWeight.bold)),
Expanded(
child: Text(
widget.address,
maxLines: 1,
overflow: TextOverflow.ellipsis,
)),
IconButton(
onPressed: () => Clipboard.setData(ClipboardData(text: widget.address)),
icon: Icon(Icons.copy)),
],
),
for (final balance in balances)
Text(
'Balance (${balance.assetInfo.ticker}) total: ${_divBy10_12(balance.total)}, unlocked: ${_divBy10_12(balance.unlocked)}'),
Row(
children: [
Text('Seed ', style: TextStyle(fontWeight: FontWeight.bold)),
Expanded(child: Text(seed, maxLines: 1, overflow: TextOverflow.ellipsis)),
IconButton(
onPressed: () => Clipboard.setData(ClipboardData(text: seed)),
icon: Icon(Icons.copy)),
],
),
const SizedBox(height: 16),
Row(
children: [
Text('Wallet Status', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(width: 16),
TextButton(onPressed: _getWalletStatus, child: Text('Update')),
],
),
if (_gwsr != null) ...[
Row(
children: [
Expanded(child: Text('Daemon Height ${_gwsr!.currentDaemonHeight}')),
Expanded(child: Text('Wallet Height ${_gwsr!.currentWalletHeight}')),
],
),
Row(
children: [
Expanded(child: Text('Daemon Connected ${_gwsr!.isDaemonConnected}')),
Expanded(child: Text('In Long Refresh ${_gwsr!.isInLongRefresh}')),
],
),
Row(
children: [
Expanded(child: Text('Progress ${_gwsr!.progress}')),
Expanded(child: Text('WalletState ${_gwsr!.walletState}')),
],
),
],
const SizedBox(height: 16),
if (_txFee != null) Text('Tx Fee: ${_divBy10_12(_txFee!)} (${_txFee!})'),
TextButton(
onPressed: () {
close();
Navigator.of(context).pushReplacementNamed(DisconnectedWidget.route);
},
child: Text('Disconnect')),
],
),
);
}
Future<void> _transfer() async {
final result = await calls.transfer(
hWallet,
TransferParams(
destinations: [
Destination(
amount: _mulBy10_12(double.parse(_amount.text)),
address: _destinationAddress.text,
assetId: assetIds.keys.first,
)
],
fee: _txFee!,
mixin: _mixin,
paymentId: _paymentId.text,
comment: _comment.text,
pushPayer: _pushPayer,
hideReceiver: _hideReceiver,
));
debugPrint('transfer result $result');
final map = jsonDecode(result);
if (map['result'] == null) {
setState(() => _transferResult = 'empty result');
} else {
if (map['result']['error'] != null) {
setState(() => _transferResult =
"error code ${map['result']['error']['code']} message ${map['result']['error']['message']} ");
} else if (map['result']['result'] != null) {
setState(() => _transferResult =
"transfer tx hash ${map['result']['result']['tx_hash']} size ${map['result']['result']['tx_size']} ");
}
}
}
bool _getWalletStatus() {
final json = calls.getWalletStatus(hWallet);
if (json == walletWrongId) {
debugPrint('error $walletWrongId');
setState(() => _gwsr = null);
return false;
}
try {
setState(() {
_gwsr = GetWalletStatusResult.fromJson(jsonDecode(json) as Map<String, dynamic>);
});
return _gwsr!.isInLongRefresh;
} catch (e) {
debugPrint('exception $e');
setState(() => _gwsr = null);
return false;
}
}
void _getWalletInfo() {
final result = GetWalletInfoResult.fromJson(
jsonDecode(calls.getWalletInfo(hWallet)) as Map<String, dynamic>);
final fee = calls.getCurrentTxFee(0);
setState(() {
balances = result.wi.balances;
seed = result.wiExtended.seed;
_txFee = fee;
});
// setState(() {
// _gwsr = GetWalletStatusResult.fromJson(
// jsonDecode(calls.getWalletStatus(hWallet)) as Map<String, dynamic>);
// });
}
Future<void> _getTransactions() async {
final result = await calls.getRecentTxsAndInfo(hWallet: hWallet, offset: 0, count: 30);
final map = jsonDecode(result);
if (map == null || map["result"] == null || map["result"]["result"] == null) {
setState(() => _transactions = null);
return;
}
setState(() => _transactions = map["result"]["result"]["transfers"] == null
? null
: (map["result"]["result"]["transfers"] as List<dynamic>)
.map((e) => History.fromJson(e as Map<String, dynamic>))
.toList());
}
String _divBy10_12(int value) {
return (value / pow(10, 12)).toString();
}
String _mulBy10_12(double value) {
var str = (value * pow(10, 12)).toString();
if (str.contains('.')) str = str.split('.')[0];
return str;
}
String _shorten(String someId) {
if (someId.length < 9) return someId;
return '${someId.substring(0, 4).toUpperCase()}...${someId.substring(someId.length - 2)}';
}
String _assetName(String assetId) {
if (assetIds[assetId] != null) {
return assetIds[assetId]!;
} else {
return _shorten(assetId);
}
}
String _dateTime(int timestamp) {
DateTime date = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
return '${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')} ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}';
}
Widget _row(
String first, String second, String third, String forth, String fifth, String sixth) =>
Row(
children: [
Expanded(child: Text(first)),
Expanded(flex: 2, child: Text(second)),
Expanded(flex: 2, child: Text(third)),
Expanded(flex: 3, child: Text(forth)),
Expanded(flex: 3, child: Text(fifth)),
Expanded(child: Text(sixth)),
],
);
}