From 2efa5dfa21da420278178584310a4e9ea516d81c Mon Sep 17 00:00:00 2001 From: Hanh Date: Wed, 7 Jun 2023 13:49:50 +1000 Subject: [PATCH] BTC Testnet --- assets/bitcoin.png | Bin 0 -> 12112 bytes build.env | 2 +- idl/data.fbs | 44 +- lib/account.dart | 48 +- lib/account_manager.dart | 7 +- lib/accounts.dart | 12 +- lib/budget.dart | 122 ++- lib/coin/btc.dart | 23 + lib/coin/coin.dart | 3 +- lib/coin/coins.dart | 7 +- lib/coin/ycash.dart | 3 +- lib/coin/zcash.dart | 3 +- lib/coin/zcashtest.dart | 3 +- lib/home.dart | 82 +- lib/note.dart | 99 +- lib/rescan.dart | 1 + lib/restore.dart | 25 +- lib/send.dart | 119 +-- lib/store.dart | 15 +- lib/transaction.dart | 25 +- lib/txplan.dart | 29 +- native/zcash-sync | 2 +- .../warp_api_ffi/lib/data_fb_generated.dart | 908 +++++++++++++++++- packages/warp_api_ffi/lib/warp_api.dart | 43 +- .../warp_api_ffi/lib/warp_api_generated.dart | 79 +- packages/warp_api_ffi/pubspec.yaml | 2 +- pubspec.yaml | 3 +- 27 files changed, 1368 insertions(+), 341 deletions(-) create mode 100644 assets/bitcoin.png create mode 100644 lib/coin/btc.dart diff --git a/assets/bitcoin.png b/assets/bitcoin.png new file mode 100644 index 0000000000000000000000000000000000000000..1f86e0cd8a6517dc8af3db9e268c9c40006c1812 GIT binary patch literal 12112 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 @override bool get wantKeepAlive => true; //Set to true + @override + void initState() { + if (!_hasAddress(active.addrMode)) active.addrMode = _getNextMode(); + } + @override Widget build(BuildContext context) { super.build(context); @@ -182,7 +187,7 @@ class QRAddressState extends State { final simpleMode = settings.simpleMode; final address = _address(); final shortAddress = centerTrim(address); - final addrMode = active.addrMode; + var addrMode = active.addrMode; final qrSize = getScreenSize(context) / 2.5; final coinDef = active.coinDef; final nextMode = _getNextMode(); @@ -199,7 +204,7 @@ class QRAddressState extends State { child: QrImage( data: address, size: qrSize, - embeddedImage: coinDef.image, + embeddedImage: AssetImage(coinDef.image), backgroundColor: Colors.white))), Padding(padding: EdgeInsets.symmetric(vertical: 8)), RichText( @@ -338,24 +343,6 @@ class QRAddressState extends State { } } - int _getNextMode() { - int next = active.addrMode; - do { - next = (next + 1) % 3; - switch (next) { - case 0: - return next; - case 1: // we have orchard -> show zaddr - if (addrsAvailable & 4 != 0) return next; - break; - case 2: // we have taddr -> show taddr - if (addrsAvailable & 1 != 0) return next; - break; - } - } while (next != active.addrMode); - return 0; // unreachable - } - String? _getTapMessage(int mode) { final s = S.of(context); switch (mode) { @@ -519,3 +506,24 @@ _getBalanceHi(int b) => decimalFormat((b.abs() ~/ 100000) / 1000.0, 3); _getBalanceLo(int b) => (b.abs() % 100000).toString().padLeft(5, '0'); _sign(int b) => b < 0 ? '-' : '+'; + +bool _hasAddress(int mode) { + switch (mode) { + case 0: + return active.availabeAddrs & 2 != 0; + case 1: + return active.availabeAddrs & 4 != 0; + case 2: + return active.availabeAddrs & 1 != 0; + } + return false; +} + +int _getNextMode() { + int next = active.addrMode; + do { + next = (next + 1) % 3; + if (_hasAddress(next)) return next; + } while (next != active.addrMode); + return 0; // unreachable +} diff --git a/lib/account_manager.dart b/lib/account_manager.dart index c87bdcc..caa6cd6 100644 --- a/lib/account_manager.dart +++ b/lib/account_manager.dart @@ -80,15 +80,16 @@ class AccountManagerState extends State { break; } final weight = a.active ? FontWeight.bold : FontWeight.normal; + final def = settings.coins[a.coin].def; final zbal = a.balance / ZECUNIT; - final tbal = a.tbalance / ZECUNIT; + final tbal = def.coin < 2 ? a.tbalance / ZECUNIT : 0.0; final balance = zbal + tbal; return Card( child: Dismissible( key: Key(a.name), child: ListTile( - leading: CircleAvatar( - backgroundImage: settings.coins[a.coin].def.image), + leading: + CircleAvatar(backgroundImage: AssetImage(def.image)), title: RichText( text: TextSpan(children: [ TextSpan( diff --git a/lib/accounts.dart b/lib/accounts.dart index b2fc317..b94c3bb 100644 --- a/lib/accounts.dart +++ b/lib/accounts.dart @@ -162,6 +162,8 @@ abstract class _ActiveAccount with Store { return AccountId(coin, id); } + bool get isPrivate => coin < 2; + @action Future restore() async { final prefs = await SharedPreferences.getInstance(); @@ -234,7 +236,7 @@ abstract class _ActiveAccount with Store { @action void updateTBalance() { try { - tbalance = WarpApi.getTBalance(); + tbalance = WarpApi.getTBalance(active.coin, active.id); } on String {} } @@ -368,13 +370,15 @@ abstract class _ActiveAccount with Store { @action void excludeNote(Note note) { - WarpApi.updateExcluded(coin, note.id, note.excluded); + if (active.isPrivate) WarpApi.updateExcluded(coin, note.id, note.excluded); } @action void invertExcludedNotes() { - WarpApi.invertExcluded(coin, id); - notes = notes.map((n) => n.invertExcluded).toList(); + if (active.isPrivate) { + WarpApi.invertExcluded(coin, id); + notes = notes.map((n) => n.invertExcluded).toList(); + } } @action diff --git a/lib/budget.dart b/lib/budget.dart index 958238c..bc6dcb4 100644 --- a/lib/budget.dart +++ b/lib/budget.dart @@ -31,22 +31,26 @@ class BudgetState extends State final _ = active.dataEpoch; return Column( children: [ - Card( - child: Column(children: [ - Text(S.of(context).largestSpendingsByAddress, - style: Theme.of(context).textTheme.titleLarge), - Padding(padding: EdgeInsets.symmetric(vertical: 4)), - BudgetChart(), - ])), + if (active.isPrivate) + Card( + child: Column(children: [ + Text(S.of(context).largestSpendingsByAddress, + style: Theme.of(context).textTheme.titleLarge), + Padding(padding: EdgeInsets.symmetric(vertical: 4)), + BudgetChart(), + ])), Expanded( child: Card( child: Column(children: [ - Text(S.of(context).accountBalanceHistory, - style: Theme.of(context).textTheme.titleLarge), - Padding(padding: EdgeInsets.symmetric(vertical: 4)), - Expanded(child: Padding(padding: EdgeInsets.only(right: 20), - child: LineChartTimeSeries.fromTimeSeries(active.accountBalances))) - ]))), + Text(S.of(context).accountBalanceHistory, + style: Theme.of(context).textTheme.titleLarge), + Padding(padding: EdgeInsets.symmetric(vertical: 4)), + Expanded( + child: Padding( + padding: EdgeInsets.only(right: 20), + child: LineChartTimeSeries.fromTimeSeries( + active.accountBalances))) + ]))), ], ); })); @@ -67,27 +71,31 @@ class PnLState extends State with AutomaticKeepAliveClientMixin { super.build(context); return IconTheme.merge( data: IconThemeData(opacity: 0.54), - child: - Column(children: [ + child: Column(children: [ Row(children: [ - Expanded(child: - FormBuilderRadioGroup( - orientation: OptionsOrientation.horizontal, - name: S.of(context).pnl, - initialValue: active.pnlSeriesIndex, - onChanged: (int? v) { - setState(() { - active.setPnlSeriesIndex(v!); - }); - }, - options: [ - FormBuilderFieldOption(child: Text(S.of(context).price), value: 0), + Expanded( + child: FormBuilderRadioGroup( + orientation: OptionsOrientation.horizontal, + name: S.of(context).pnl, + initialValue: active.pnlSeriesIndex, + onChanged: (int? v) { + setState(() { + active.setPnlSeriesIndex(v!); + }); + }, + options: [ + FormBuilderFieldOption( + child: Text(S.of(context).price), value: 0), FormBuilderFieldOption( child: Text(S.of(context).realized), value: 1), - FormBuilderFieldOption(child: Text(S.of(context).mm), value: 2), - FormBuilderFieldOption(child: Text(S.of(context).total), value: 3), - FormBuilderFieldOption(child: Text(S.of(context).qty), value: 4), - FormBuilderFieldOption(child: Text(S.of(context).table), value: 5), + FormBuilderFieldOption( + child: Text(S.of(context).mm), value: 2), + FormBuilderFieldOption( + child: Text(S.of(context).total), value: 3), + FormBuilderFieldOption( + child: Text(S.of(context).qty), value: 4), + FormBuilderFieldOption( + child: Text(S.of(context).table), value: 5), ])), IconButton(onPressed: _onExport, icon: Icon(Icons.save)), ]), @@ -97,21 +105,23 @@ class PnLState extends State with AutomaticKeepAliveClientMixin { child: Padding( padding: EdgeInsets.only(right: 20), child: active.pnlSeriesIndex != 5 - ? PnLChart( - active.pnls, active.pnlSeriesIndex) + ? PnLChart(active.pnls, active.pnlSeriesIndex) : PnLTable())); }) ])); } _onExport() async { - final csvData = active.pnlSorted.map((pnl) => [ - pnl.timestamp, - pnl.amount, - pnl.price, - pnl.realized, - pnl.unrealized, - pnl.realized + pnl.unrealized]).toList(); + final csvData = active.pnlSorted + .map((pnl) => [ + pnl.timestamp, + pnl.amount, + pnl.price, + pnl.realized, + pnl.unrealized, + pnl.realized + pnl.unrealized + ]) + .toList(); await shareCsv(csvData, 'pnl_history.csv', S.of(context).pnlHistory); } } @@ -148,8 +158,8 @@ class PnLChart extends StatelessWidget { List data, int index, BuildContext context) { return data .map((pnl) => TimeSeriesPoint( - pnl.timestamp.millisecondsSinceEpoch ~/ DAY_MS, - _seriesData(pnl, index))) + pnl.timestamp.millisecondsSinceEpoch ~/ DAY_MS, + _seriesData(pnl, index))) .toList(); } } @@ -227,10 +237,13 @@ class BudgetChartState extends State { return Observer( builder: (context) => Padding( padding: EdgeInsets.symmetric(horizontal: 8), - child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - HorizontalBarChart(active.spendings.map((s) => s.amount / ZECUNIT).toList()), - BudgetTable(active.spendings) - ]))); + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + HorizontalBarChart( + active.spendings.map((s) => s.amount / ZECUNIT).toList()), + BudgetTable(active.spendings) + ]))); } } @@ -241,17 +254,24 @@ class BudgetTable extends StatelessWidget { @override Widget build(BuildContext context) { - final palette = getPalette(Theme.of(context).primaryColor, spendings.length); + final palette = + getPalette(Theme.of(context).primaryColor, spendings.length); final rows = spendings.asMap().entries.map((e) { - final style = TextStyle(color: palette[e.key], fontFeatures: [FontFeature.tabularFigures()]); + final style = TextStyle( + color: palette[e.key], fontFeatures: [FontFeature.tabularFigures()]); final recipient = e.value.recipient!; return TableRow(children: [ - Text(recipient, style: style, maxLines: 1, overflow: TextOverflow.ellipsis,), + Text( + recipient, + style: style, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), Text(decimalFormat(e.value.amount / ZECUNIT, 8), style: style) ]); }).toList(); return Table( - columnWidths: { 0: FlexColumnWidth(), 1: IntrinsicColumnWidth() }, - children: rows); + columnWidths: {0: FlexColumnWidth(), 1: IntrinsicColumnWidth()}, + children: rows); } } diff --git a/lib/coin/btc.dart b/lib/coin/btc.dart new file mode 100644 index 0000000..ad60f7e --- /dev/null +++ b/lib/coin/btc.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +import "coin.dart"; + +class BTCCoin extends CoinBase { + int coin = 2; + bool transparentOnly = true; + String name = "Bitcoin"; + String symbol = "\u20BF"; + String currency = "bitcoin"; + int coinIndex = 0; + String ticker = "BTC"; + String dbName = "btc.db"; + String image = 'assets/bitcoin.png'; + List lwd = [ + LWInstance("Blockstream", "tcp://blackie.c3-soft.com:57005") + ]; + bool supportsUA = false; + bool supportsMultisig = false; + List weights = [0.001, 0.01, 0.1]; + List blockExplorers = ["https://blockstream.info/testnet/tx"]; + bool supportsLedger = false; +} diff --git a/lib/coin/coin.dart b/lib/coin/coin.dart index d9751b9..145257b 100644 --- a/lib/coin/coin.dart +++ b/lib/coin/coin.dart @@ -16,12 +16,11 @@ class LWInstance { abstract class CoinBase { String get name; int get coin; - String get app; String get symbol; String get currency; String get ticker; int get coinIndex; - AssetImage get image; + String get image; String get dbName; late String dbDir; late String dbFullPath; diff --git a/lib/coin/coins.dart b/lib/coin/coins.dart index 8d7ae8a..f9c8a45 100644 --- a/lib/coin/coins.dart +++ b/lib/coin/coins.dart @@ -1,11 +1,10 @@ import 'coin.dart'; import 'ycash.dart'; import 'zcash.dart'; -import 'zcashtest.dart'; +import 'btc.dart'; CoinBase ycash = YcashCoin(); CoinBase zcash = ZcashCoin(); -CoinBase zcashtest = ZcashTestCoin(); - -final coins = [zcash, ycash]; +CoinBase btc = BTCCoin(); +final coins = [zcash, ycash, btc]; diff --git a/lib/coin/ycash.dart b/lib/coin/ycash.dart index 31d7611..7963a23 100644 --- a/lib/coin/ycash.dart +++ b/lib/coin/ycash.dart @@ -5,13 +5,12 @@ import "coin.dart"; class YcashCoin extends CoinBase { int coin = 1; String name = "Ycash"; - String app = "YWallet"; String symbol = "\u24E8"; String currency = "ycash"; int coinIndex = 347; String ticker = "YEC"; String dbName = "yec.db"; - AssetImage image = AssetImage('assets/ycash.png'); + String image = 'assets/ycash.png'; List lwd = [ LWInstance("Lightwalletd", "https://lite.ycash.xyz:9067"), ]; diff --git a/lib/coin/zcash.dart b/lib/coin/zcash.dart index 7d3e478..4450ee6 100644 --- a/lib/coin/zcash.dart +++ b/lib/coin/zcash.dart @@ -5,13 +5,12 @@ import 'coin.dart'; class ZcashCoin extends CoinBase { int coin = 0; String name = "Zcash"; - String app = "ZWallet"; String symbol = "\u24E9"; String currency = "zcash"; int coinIndex = 133; String ticker = "ZEC"; String dbName = "zec.db"; - AssetImage image = AssetImage('assets/zcash.png'); + String image = 'assets/zcash.png'; List lwd = [ LWInstance("Lightwalletd", "https://mainnet.lightwalletd.com:9067"), LWInstance("Zecwallet", "https://lwdv3.zecwallet.co"), diff --git a/lib/coin/zcashtest.dart b/lib/coin/zcashtest.dart index 1a320ec..0a6b6da 100644 --- a/lib/coin/zcashtest.dart +++ b/lib/coin/zcashtest.dart @@ -5,13 +5,12 @@ import 'coin.dart'; class ZcashTestCoin extends CoinBase { int coin = 0; String name = "Zcash Test"; - String app = "ZWallet"; String symbol = "\u24E9"; String currency = "zcash"; int coinIndex = 133; String ticker = "ZEC"; String dbName = "zec-test.db"; - AssetImage image = AssetImage('assets/zcash.png'); + String image = 'assets/zcash.png'; List lwd = [ LWInstance("Lightwalletd", "https://testnet.lightwalletd.com:9067"), ]; diff --git a/lib/home.dart b/lib/home.dart index f5f95e1..2517a62 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:collection'; import 'dart:convert'; import 'dart:io'; @@ -8,6 +9,7 @@ import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:velocity_x/velocity_x.dart'; import 'package:warp_api/data_fb_generated.dart'; import 'package:warp_api/warp_api.dart'; import 'package:badges/badges.dart' as Badges; @@ -112,13 +114,34 @@ class HomeInnerState extends State TabController? _tabController; int _tabIndex = 0; final contactKey = GlobalKey(); + List tabsShown = []; @override void initState() { super.initState(); if (Platform.isAndroid) _initForegroundTask(); - final tabController = - TabController(length: settings.simpleMode ? 4 : 7, vsync: this); + var tabs = { + // order is guaranteed by dart + "account": true, + "messages": true, + "notes": false, + "history": true, + "budget": false, + "pnl": false, + "contacts": true, + }; + if (!settings.simpleMode) { + tabs["notes"] = true; + tabs["budget"] = true; + tabs["pnl"] = true; + } + if (!active.isPrivate) { + tabs["messages"] = false; + tabs["contacts"] = false; + } + tabsShown = + tabs.entries.where((kv) => kv.value).map((kv) => kv.key).toList(); + final tabController = TabController(length: tabsShown.length, vsync: this); tabController.addListener(() { setState(() { _tabIndex = tabController.index; @@ -133,7 +156,8 @@ class HomeInnerState extends State final theme = Theme.of(context); final simpleMode = settings.simpleMode; - final contactTabIndex = simpleMode ? 3 : 6; + final contactTabIndex = tabsShown.indexOf("contacts"); + final messageTabIndex = tabsShown.indexOf("messages"); Widget button = Container(); if (_tabIndex == 0) button = FloatingActionButton( @@ -147,7 +171,7 @@ class HomeInnerState extends State backgroundColor: theme.colorScheme.secondary, child: Icon(Icons.add), ); - else if (_tabIndex == 1) + else if (_tabIndex == messageTabIndex) button = FloatingActionButton( onPressed: _onSend, backgroundColor: theme.colorScheme.secondary, @@ -184,13 +208,15 @@ class HomeInnerState extends State return SizedBox(); // Show a placeholder } + final privateCoin = active.isPrivate; + final menu = PopupMenuButton( itemBuilder: (context) { return [ PopupMenuItem(child: Text(s.accounts), value: "Accounts"), PopupMenuItem(child: Text(s.backup), value: "Backup"), PopupMenuItem(child: Text(rescanMsg), value: "Rescan"), - if (!simpleMode) + if (!simpleMode && privateCoin) PopupMenuItem(child: Text(s.pools), value: "Pools"), if (!simpleMode) PopupMenuItem( @@ -216,18 +242,18 @@ class HomeInnerState extends State child: Text(s.broadcast), value: "Broadcast"), PopupMenuItem( child: Text(s.multipay), value: "MultiPay"), - PopupMenuItem( - child: Text(s.keyTool), - enabled: active.canPay, - value: "KeyTool"), - PopupMenuItem( - child: Text(s.sweep), - enabled: active.canPay, - value: "Sweep"), + if (privateCoin) + PopupMenuItem( + child: Text(s.keyTool), + enabled: active.canPay, + value: "KeyTool"), + if (privateCoin) + PopupMenuItem( + child: Text(s.sweep), + enabled: active.canPay, + value: "Sweep"), ], onSelected: _onMenu)), - // if (!simpleMode && !isMobile()) - // PopupMenuItem(child: Text(s.ledger), value: "Ledger"), if (settings.isDeveloper) PopupMenuItem(child: Text(s.expert), value: "Expert"), PopupMenuItem(child: Text(s.settings), value: "Settings"), @@ -252,12 +278,12 @@ class HomeInnerState extends State isScrollable: true, tabs: [ Tab(text: s.account), - messageTab, - if (!simpleMode) Tab(text: s.notes), - Tab(text: s.history), - if (!simpleMode) Tab(text: s.budget), - if (!simpleMode) Tab(text: s.tradingPl), - Tab(text: s.contacts), + if (tabsShown.contains("messages")) messageTab, + if (tabsShown.contains("notes")) Tab(text: s.notes), + if (tabsShown.contains("history")) Tab(text: s.history), + if (tabsShown.contains("budget")) Tab(text: s.budget), + if (tabsShown.contains("pnl")) Tab(text: s.tradingPl), + if (tabsShown.contains("contacts")) Tab(text: s.contacts), ], ), actions: [menu], @@ -266,12 +292,12 @@ class HomeInnerState extends State controller: _tabController, children: [ AccountPage(), - MessageWidget(messageKey), - if (!simpleMode) NoteWidget(), - HistoryWidget(), - if (!simpleMode) BudgetWidget(), - if (!simpleMode) PnLWidget(), - ContactsTab(key: contactKey), + if (tabsShown.contains("messages")) MessageWidget(messageKey), + if (tabsShown.contains("notes")) NoteWidget(), + if (tabsShown.contains("history")) HistoryWidget(), + if (tabsShown.contains("budget")) BudgetWidget(), + if (tabsShown.contains("pnl")) PnLWidget(), + if (tabsShown.contains("contacts")) ContactsTab(key: contactKey), ], ), floatingActionButton: button, @@ -492,7 +518,7 @@ class HomeInnerState extends State if (rawTx != null) { try { - final res = WarpApi.broadcast(rawTx); + final res = WarpApi.broadcast(active.coin, rawTx); showSnackBar(res); } on String catch (e) { showSnackBar(e, error: true); diff --git a/lib/note.dart b/lib/note.dart index d17311a..109288a 100644 --- a/lib/note.dart +++ b/lib/note.dart @@ -12,12 +12,17 @@ class NoteWidget extends StatelessWidget { Widget build(BuildContext context) { return Observer(builder: (context) { switch (settings.noteView) { - case ViewStyle.Table: return NoteTable(); - case ViewStyle.List: return NoteList(); - case ViewStyle.Auto: return OrientationBuilder(builder: (context, orientation) { - if (orientation == Orientation.portrait) return NoteList(); - else return NoteTable(); - }); + case ViewStyle.Table: + return NoteTable(); + case ViewStyle.List: + return NoteList(); + case ViewStyle.Auto: + return OrientationBuilder(builder: (context, orientation) { + if (orientation == Orientation.portrait) + return NoteList(); + else + return NoteTable(); + }); } }); } @@ -30,7 +35,8 @@ class NoteTable extends StatefulWidget { State createState() => _NoteTableState(); } -class _NoteTableState extends State with AutomaticKeepAliveClientMixin { +class _NoteTableState extends State + with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; //Set to true @@ -77,7 +83,9 @@ class _NoteTableState extends State with AutomaticKeepAliveClientMixi header: Text(S.of(context).selectNotesToExcludeFromPayments, style: Theme.of(context).textTheme.bodyMedium), actions: [ - IconButton(onPressed: _selectInverse, icon: Icon(MdiIcons.selectInverse)), + IconButton( + onPressed: _selectInverse, + icon: Icon(MdiIcons.selectInverse)), ], columnSpacing: 16, showCheckboxColumn: false, @@ -93,6 +101,7 @@ class _NoteTableState extends State with AutomaticKeepAliveClientMixi } _onRowSelected(Note note) { + if (!active.isPrivate) return; active.excludeNote(note); } @@ -121,8 +130,7 @@ class NotesDataSource extends DataTableSource { if (note.spent) style = style.merge(TextStyle(decoration: TextDecoration.lineThrough)); - if (note.orchard) - style = style.merge(TextStyle(color: theme.primaryColor)); + if (note.orchard) style = style.merge(TextStyle(color: theme.primaryColor)); final amountStyle = weightFromAmount(style, note.value); @@ -135,7 +143,8 @@ class NotesDataSource extends DataTableSource { : theme.colorScheme.background), cells: [ DataCell(Text("$confsOrHeight", style: style)), - DataCell(Text("${noteDateFormat.format(note.timestamp)}", style: style)), + DataCell( + Text("${noteDateFormat.format(note.timestamp)}", style: style)), DataCell(Text(decimalFormat(note.value, 8), style: amountStyle)), ], onSelectChanged: (selected) => _noteSelected(note, selected), @@ -152,6 +161,7 @@ class NotesDataSource extends DataTableSource { int get selectedRowCount => 0; void _noteSelected(Note note, bool? selected) { + if (!active.isPrivate) return; note.excluded = !note.excluded; notifyListeners(); onRowSelected(note); @@ -170,21 +180,24 @@ class NoteListState extends State with AutomaticKeepAliveClientMixin { final s = S.of(context); return Observer(builder: (context) { final notes = active.sortedNotes; - return Padding(padding: EdgeInsets.all(16), child: CustomScrollView( - key: UniqueKey(), - slivers: [ - SliverToBoxAdapter(child: ListTile( - onTap: _onInvert, - title: Text(s.selectNotesToExcludeFromPayments), - trailing: Icon(Icons.select_all), - )), - SliverFixedExtentList( - itemExtent: 50, - delegate: SliverChildBuilderDelegate((context, index) { - return NoteItem(notes[index], index); - }, childCount: notes.length)) - ], - )); + return Padding( + padding: EdgeInsets.all(16), + child: CustomScrollView( + key: UniqueKey(), + slivers: [ + SliverToBoxAdapter( + child: ListTile( + onTap: _onInvert, + title: Text(s.selectNotesToExcludeFromPayments), + trailing: Icon(Icons.select_all), + )), + SliverFixedExtentList( + itemExtent: 50, + delegate: SliverChildBuilderDelegate((context, index) { + return NoteItem(notes[index], index); + }, childCount: notes.length)) + ], + )); }); } @@ -225,24 +238,33 @@ class NoteItemState extends State { style = style.merge(TextStyle(color: style.color!.withOpacity(0.5))); if (note.spent) style = style.merge(TextStyle(decoration: TextDecoration.lineThrough)); - if (note.orchard) - style = style.merge(TextStyle(color: theme.primaryColor)); + if (note.orchard) style = style.merge(TextStyle(color: theme.primaryColor)); final amountStyle = weightFromAmount(style, note.value); - return GestureDetector(onTap: _onSelected, behavior: HitTestBehavior.opaque, child: - ColoredBox(color: excluded ? theme.primaryColor.withOpacity(0.5) : theme.colorScheme.background, child: - Padding(padding: EdgeInsets.all(8), child: - Row(children: [ - Column(children: [Text("${note.height}", style: theme.textTheme.bodySmall), - Text("$confirmations", style: theme.textTheme.bodyMedium), - ]), - Expanded(child: Center(child: Text("${note.value}", style: amountStyle))), - Text("$timestamp"), - ])))); + return GestureDetector( + onTap: _onSelected, + behavior: HitTestBehavior.opaque, + child: ColoredBox( + color: excluded + ? theme.primaryColor.withOpacity(0.5) + : theme.colorScheme.background, + child: Padding( + padding: EdgeInsets.all(8), + child: Row(children: [ + Column(children: [ + Text("${note.height}", style: theme.textTheme.bodySmall), + Text("$confirmations", style: theme.textTheme.bodyMedium), + ]), + Expanded( + child: Center( + child: Text("${note.value}", style: amountStyle))), + Text("$timestamp"), + ])))); } _onSelected() { + if (!active.isPrivate) return; setState(() { excluded = !excluded; widget.note.excluded = excluded; @@ -254,4 +276,3 @@ class NoteItemState extends State { bool confirmed(int height) { return syncStatus.latestHeight - height >= settings.anchorOffset; } - diff --git a/lib/rescan.dart b/lib/rescan.dart index 68cfb69..2216e0e 100644 --- a/lib/rescan.dart +++ b/lib/rescan.dart @@ -9,6 +9,7 @@ final rescanKey = GlobalKey(); Future rescanDialog(BuildContext context) async { try { + if (active.coin >= 2) return 0; DateTime minDate = WarpApi.getActivationDate(); final bool approved = await showDialog( context: context, diff --git a/lib/restore.dart b/lib/restore.dart index bae8839..6375028 100644 --- a/lib/restore.dart +++ b/lib/restore.dart @@ -36,6 +36,14 @@ class _AddAccountPageState extends State { @override Widget build(BuildContext context) { final s = S.of(context); + + final options = coins + .map((c) => FormBuilderFieldOption( + child: ListTile( + title: Text(c.name), + trailing: Image.asset(c.image, height: 32)), + value: c.coin)) + .toList(); return Scaffold( appBar: AppBar( title: Text(s.newAccount), @@ -70,22 +78,7 @@ class _AddAccountPageState extends State { _coin = v!; }); }, - options: [ - FormBuilderFieldOption( - child: ListTile( - title: Text('Ycash'), - trailing: Image.asset( - 'assets/ycash.png', - height: 32)), - value: 1), - FormBuilderFieldOption( - child: ListTile( - title: Text('Zcash'), - trailing: Image.asset( - 'assets/zcash.png', - height: 32)), - value: 0), - ]), + options: options), FormBuilderCheckbox( name: 'restore', title: Text(s.restoreAnAccount), diff --git a/lib/send.dart b/lib/send.dart index 6d1fea6..6dcb3d9 100644 --- a/lib/send.dart +++ b/lib/send.dart @@ -101,8 +101,10 @@ class SendState extends State { _setPaymentURI(uri); }); - final templateIds = active.dbReader.loadTemplates(); - _templates = templateIds; + if (active.isPrivate) { + final templateIds = active.dbReader.loadTemplates(); + _templates = templateIds; + } } @override @@ -214,64 +216,67 @@ class SendState extends State { if (!simpleMode) BalanceTable(_sBalance, _tBalance, _excludedBalance, _underConfirmedBalance, change, _usedBalance, _fee), - Container( - child: InputDecorator( - decoration: InputDecoration(labelText: s.memo), - child: Column(children: [ - FormBuilderCheckbox( - key: UniqueKey(), - name: 'reply-to', - title: Text(s.includeReplyTo), - initialValue: _replyTo, + if (active.isPrivate) + Container( + child: InputDecorator( + decoration: InputDecoration(labelText: s.memo), + child: Column(children: [ + FormBuilderCheckbox( + key: UniqueKey(), + name: 'reply-to', + title: Text(s.includeReplyTo), + initialValue: _replyTo, + onChanged: (v) { + setState(() { + _replyTo = v ?? false; + }); + }, + ), + TextFormField( + decoration: + InputDecoration(labelText: s.subject), + controller: _subjectController, + ), + TextFormField( + decoration: + InputDecoration(labelText: s.body), + minLines: 4, + maxLines: null, + keyboardType: TextInputType.multiline, + controller: _memoController, + ) + ]))), + Padding(padding: EdgeInsets.all(8)), + if (active.isPrivate) + Row(children: [ + Expanded( + child: DropdownButtonFormField( + hint: Text(s.template), + items: templates, + value: _template, onChanged: (v) { setState(() { - _replyTo = v ?? false; + _template = v; }); - }, - ), - TextFormField( - decoration: - InputDecoration(labelText: s.subject), - controller: _subjectController, - ), - TextFormField( - decoration: - InputDecoration(labelText: s.body), - minLines: 4, - maxLines: null, - keyboardType: TextInputType.multiline, - controller: _memoController, - ) - ]))), - Padding(padding: EdgeInsets.all(8)), - Row(children: [ - Expanded( - child: DropdownButtonFormField( - hint: Text(s.template), - items: templates, - value: _template, - onChanged: (v) { - setState(() { - _template = v; - }); - })), - addReset, - IconButton( - onPressed: _template != null ? _openTemplate : null, - icon: Icon(Icons.open_in_new)), - IconButton( - onPressed: _template != null - ? () { - _saveTemplate( - _template!.id, _template!.title!, true); - } - : null, - icon: Icon(Icons.save)), - IconButton( - onPressed: - _template != null ? _deleteTemplate : null, - icon: Icon(Icons.delete)), - ]), + })), + addReset, + IconButton( + onPressed: + _template != null ? _openTemplate : null, + icon: Icon(Icons.open_in_new)), + IconButton( + onPressed: _template != null + ? () { + _saveTemplate(_template!.id, + _template!.title!, true); + } + : null, + icon: Icon(Icons.save)), + IconButton( + onPressed: + _template != null ? _deleteTemplate : null, + icon: Icon(Icons.delete)), + ]), Padding(padding: EdgeInsets.all(8)), ButtonBar(children: [ ElevatedButton.icon( diff --git a/lib/store.dart b/lib/store.dart index 7d4431d..fa04375 100644 --- a/lib/store.dart +++ b/lib/store.dart @@ -8,6 +8,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:flutter/material.dart'; import 'package:mobx/mobx.dart'; +import 'package:velocity_x/velocity_x.dart'; import 'package:queue/queue.dart'; import 'package:shared_preferences_android/shared_preferences_android.dart'; import 'package:shared_preferences_ios/shared_preferences_ios.dart'; @@ -21,6 +22,7 @@ import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:sensors_plus/sensors_plus.dart'; import 'coin/coin.dart'; +import 'coin/coins.dart' as Coins; import 'generated/l10n.dart'; import 'main.dart'; @@ -92,7 +94,10 @@ abstract class _Settings with Store { @observable bool simpleMode = true; - List coins = [CoinData(0, zcash), CoinData(1, ycash)]; + List coins = Coins.coins + .sortedByNum((c) => c.coin) + .map((c) => CoinData(c.coin, c)) + .toList(); @observable String version = "1.0.0"; @@ -636,8 +641,8 @@ abstract class _PriceStore with Store { if (f || _lastChartUpdateTime == null || now > _lastChartUpdateTime + 5 * 60) { - await fetchQueue - .add(() => WarpApi.syncHistoricalPrices(settings.currency)); + await fetchQueue.add( + () => WarpApi.syncHistoricalPrices(active.coin, settings.currency)); active.fetchChartData(); lastChartUpdateTime = now; } @@ -739,7 +744,7 @@ abstract class _SyncStatus with Store { if (url.isNotEmpty) WarpApi.updateLWD(active.coin, url); } try { - latestHeight = await WarpApi.getLatestHeight(); + latestHeight = await WarpApi.getLatestHeight(active.coin); } on String {} final _syncedInfo = getDbSyncedHeight(); // if syncedHeight = 0, we just started sync therefore don't set it back to null @@ -816,7 +821,7 @@ abstract class _SyncStatus with Store { showSnackBar(S.current.rescanRequested(height)); syncedHeight = height; timestamp = null; - WarpApi.rescanFrom(height); + WarpApi.rescanFrom(active.coin, height); await sync(true); final rh = pendingRescanHeight; if (rh != null) { diff --git a/lib/transaction.dart b/lib/transaction.dart index 3ef555f..078e148 100644 --- a/lib/transaction.dart +++ b/lib/transaction.dart @@ -50,17 +50,20 @@ class TransactionState extends State { ListTile( title: Text(S.of(context).amount), subtitle: SelectableText(decimalFormat(tx.value, 8))), - ListTile( - title: Text(S.of(context).address), - subtitle: SelectableText('${tx.address}'), - trailing: IconButton( - icon: Icon(Icons.contacts), onPressed: _addContact)), - ListTile( - title: Text(S.of(context).contactName), - subtitle: SelectableText('${tx.contact ?? "N/A"}')), - ListTile( - title: Text(S.of(context).memo), - subtitle: SelectableText('${tx.memo}')), + if (active.isPrivate) + ListTile( + title: Text(S.of(context).address), + subtitle: SelectableText('${tx.address}'), + trailing: IconButton( + icon: Icon(Icons.contacts), onPressed: _addContact)), + if (active.isPrivate) + ListTile( + title: Text(S.of(context).contactName), + subtitle: SelectableText('${tx.contact ?? "N/A"}')), + if (active.isPrivate) + ListTile( + title: Text(S.of(context).memo), + subtitle: SelectableText('${tx.memo}')), ButtonBar(alignment: MainAxisAlignment.center, children: [ IconButton( onPressed: txIndex > 0 ? _prev : null, diff --git a/lib/txplan.dart b/lib/txplan.dart index f348b54..4260c76 100644 --- a/lib/txplan.dart +++ b/lib/txplan.dart @@ -31,10 +31,12 @@ class TxPlanPage extends StatelessWidget { .map((e) => DataRow(cells: [ DataCell(Text('...${trailing(e.address!, 12)}')), DataCell(Text('${poolToString(e.pool)}')), - DataCell(Text('${amountToString(e.amount, 3)}')), + DataCell(Text('${amountToString(e.amount, MAX_PRECISION)}')), ])) .toList(); final invalidPrivacy = report.privacyLevel < settings.minPrivacyLevel; + // TODO: Abstract sat into coinDef + final feeFx = decimalFormat(report.fee * priceStore.coinPrice / ZECUNIT, 2); return Scaffold( appBar: AppBar(title: Text('Transaction Plan')), @@ -60,20 +62,22 @@ class TxPlanPage extends StatelessWidget { title: Text('Transparent Input'), trailing: Text(amountToString(report.transparent, MAX_PRECISION))), - ListTile( - title: Text('Sapling Input'), - trailing: - Text(amountToString(report.sapling, MAX_PRECISION))), - if (supportsUA) + if (active.isPrivate) + ListTile( + title: Text('Sapling Input'), + trailing: + Text(amountToString(report.sapling, MAX_PRECISION))), + if (active.isPrivate && supportsUA) ListTile( title: Text('Orchard Input'), trailing: Text(amountToString(report.orchard, MAX_PRECISION))), - ListTile( - title: Text('Net Sapling Change'), - trailing: - Text(amountToString(report.netSapling, MAX_PRECISION))), - if (supportsUA) + if (active.isPrivate) + ListTile( + title: Text('Net Sapling Change'), + trailing: + Text(amountToString(report.netSapling, MAX_PRECISION))), + if (active.isPrivate && supportsUA) ListTile( title: Text('Net Orchard Change'), trailing: @@ -81,6 +85,9 @@ class TxPlanPage extends StatelessWidget { ListTile( title: Text('Fee'), trailing: Text(amountToString(report.fee, MAX_PRECISION))), + ListTile( + title: Text('Fee ${settings.currency}'), + trailing: Text(feeFx)), privacyToString(context, report.privacyLevel)!, if (invalidPrivacy) Padding( diff --git a/native/zcash-sync b/native/zcash-sync index 388e3de..ad2515d 160000 --- a/native/zcash-sync +++ b/native/zcash-sync @@ -1 +1 @@ -Subproject commit 388e3de26c0a6a5d899024a406510285aee28736 +Subproject commit ad2515d47ae1ba4a97cb9860170f4fa9228ffcaa diff --git a/packages/warp_api_ffi/lib/data_fb_generated.dart b/packages/warp_api_ffi/lib/data_fb_generated.dart index 40317de..6cf21ff 100644 --- a/packages/warp_api_ffi/lib/data_fb_generated.dart +++ b/packages/warp_api_ffi/lib/data_fb_generated.dart @@ -833,7 +833,7 @@ class ShieldedTx { String? get shortTxId => const fb.StringReader().vTableGetNullable(_bc, _bcOffset, 10); int get timestamp => const fb.Uint32Reader().vTableGet(_bc, _bcOffset, 12, 0); String? get name => const fb.StringReader().vTableGetNullable(_bc, _bcOffset, 14); - int get value => const fb.Uint64Reader().vTableGet(_bc, _bcOffset, 16, 0); + int get value => const fb.Int64Reader().vTableGet(_bc, _bcOffset, 16, 0); String? get address => const fb.StringReader().vTableGetNullable(_bc, _bcOffset, 18); String? get memo => const fb.StringReader().vTableGetNullable(_bc, _bcOffset, 20); @@ -900,7 +900,7 @@ class ShieldedTxT implements fb.Packable { fbBuilder.addOffset(3, shortTxIdOffset); fbBuilder.addUint32(4, timestamp); fbBuilder.addOffset(5, nameOffset); - fbBuilder.addUint64(6, value); + fbBuilder.addInt64(6, value); fbBuilder.addOffset(7, addressOffset); fbBuilder.addOffset(8, memoOffset); return fbBuilder.endTable(); @@ -954,7 +954,7 @@ class ShieldedTxBuilder { return fbBuilder.offset; } int addValue(int? value) { - fbBuilder.addUint64(6, value); + fbBuilder.addInt64(6, value); return fbBuilder.offset; } int addAddressOffset(int? offset) { @@ -1023,7 +1023,7 @@ class ShieldedTxObjectBuilder extends fb.ObjectBuilder { fbBuilder.addOffset(3, shortTxIdOffset); fbBuilder.addUint32(4, _timestamp); fbBuilder.addOffset(5, nameOffset); - fbBuilder.addUint64(6, _value); + fbBuilder.addInt64(6, _value); fbBuilder.addOffset(7, addressOffset); fbBuilder.addOffset(8, memoOffset); return fbBuilder.endTable(); @@ -1139,6 +1139,906 @@ class ShieldedTxVecObjectBuilder extends fb.ObjectBuilder { return fbBuilder.buffer; } } +class PlainTx { + PlainTx._(this._bc, this._bcOffset); + factory PlainTx(List bytes) { + final rootRef = fb.BufferContext.fromBytes(bytes); + return reader.read(rootRef, 0); + } + + static const fb.Reader reader = _PlainTxReader(); + + final fb.BufferContext _bc; + final int _bcOffset; + + int get id => const fb.Uint32Reader().vTableGet(_bc, _bcOffset, 4, 0); + String? get txId => const fb.StringReader().vTableGetNullable(_bc, _bcOffset, 6); + int get height => const fb.Uint32Reader().vTableGet(_bc, _bcOffset, 8, 0); + int get timestamp => const fb.Uint32Reader().vTableGet(_bc, _bcOffset, 10, 0); + int get value => const fb.Int64Reader().vTableGet(_bc, _bcOffset, 12, 0); + + @override + String toString() { + return 'PlainTx{id: ${id}, txId: ${txId}, height: ${height}, timestamp: ${timestamp}, value: ${value}}'; + } + + PlainTxT unpack() => PlainTxT( + id: id, + txId: txId, + height: height, + timestamp: timestamp, + value: value); + + static int pack(fb.Builder fbBuilder, PlainTxT? object) { + if (object == null) return 0; + return object.pack(fbBuilder); + } +} + +class PlainTxT implements fb.Packable { + int id; + String? txId; + int height; + int timestamp; + int value; + + PlainTxT({ + this.id = 0, + this.txId, + this.height = 0, + this.timestamp = 0, + this.value = 0}); + + @override + int pack(fb.Builder fbBuilder) { + final int? txIdOffset = txId == null ? null + : fbBuilder.writeString(txId!); + fbBuilder.startTable(5); + fbBuilder.addUint32(0, id); + fbBuilder.addOffset(1, txIdOffset); + fbBuilder.addUint32(2, height); + fbBuilder.addUint32(3, timestamp); + fbBuilder.addInt64(4, value); + return fbBuilder.endTable(); + } + + @override + String toString() { + return 'PlainTxT{id: ${id}, txId: ${txId}, height: ${height}, timestamp: ${timestamp}, value: ${value}}'; + } +} + +class _PlainTxReader extends fb.TableReader { + const _PlainTxReader(); + + @override + PlainTx createObject(fb.BufferContext bc, int offset) => + PlainTx._(bc, offset); +} + +class PlainTxBuilder { + PlainTxBuilder(this.fbBuilder); + + final fb.Builder fbBuilder; + + void begin() { + fbBuilder.startTable(5); + } + + int addId(int? id) { + fbBuilder.addUint32(0, id); + return fbBuilder.offset; + } + int addTxIdOffset(int? offset) { + fbBuilder.addOffset(1, offset); + return fbBuilder.offset; + } + int addHeight(int? height) { + fbBuilder.addUint32(2, height); + return fbBuilder.offset; + } + int addTimestamp(int? timestamp) { + fbBuilder.addUint32(3, timestamp); + return fbBuilder.offset; + } + int addValue(int? value) { + fbBuilder.addInt64(4, value); + return fbBuilder.offset; + } + + int finish() { + return fbBuilder.endTable(); + } +} + +class PlainTxObjectBuilder extends fb.ObjectBuilder { + final int? _id; + final String? _txId; + final int? _height; + final int? _timestamp; + final int? _value; + + PlainTxObjectBuilder({ + int? id, + String? txId, + int? height, + int? timestamp, + int? value, + }) + : _id = id, + _txId = txId, + _height = height, + _timestamp = timestamp, + _value = value; + + /// Finish building, and store into the [fbBuilder]. + @override + int finish(fb.Builder fbBuilder) { + final int? txIdOffset = _txId == null ? null + : fbBuilder.writeString(_txId!); + fbBuilder.startTable(5); + fbBuilder.addUint32(0, _id); + fbBuilder.addOffset(1, txIdOffset); + fbBuilder.addUint32(2, _height); + fbBuilder.addUint32(3, _timestamp); + fbBuilder.addInt64(4, _value); + return fbBuilder.endTable(); + } + + /// Convenience method to serialize to byte list. + @override + Uint8List toBytes([String? fileIdentifier]) { + final fbBuilder = fb.Builder(deduplicateTables: false); + fbBuilder.finish(finish(fbBuilder), fileIdentifier); + return fbBuilder.buffer; + } +} +class PlainTxVec { + PlainTxVec._(this._bc, this._bcOffset); + factory PlainTxVec(List bytes) { + final rootRef = fb.BufferContext.fromBytes(bytes); + return reader.read(rootRef, 0); + } + + static const fb.Reader reader = _PlainTxVecReader(); + + final fb.BufferContext _bc; + final int _bcOffset; + + List? get txs => const fb.ListReader(PlainTx.reader).vTableGetNullable(_bc, _bcOffset, 4); + + @override + String toString() { + return 'PlainTxVec{txs: ${txs}}'; + } + + PlainTxVecT unpack() => PlainTxVecT( + txs: txs?.map((e) => e.unpack()).toList()); + + static int pack(fb.Builder fbBuilder, PlainTxVecT? object) { + if (object == null) return 0; + return object.pack(fbBuilder); + } +} + +class PlainTxVecT implements fb.Packable { + List? txs; + + PlainTxVecT({ + this.txs}); + + @override + int pack(fb.Builder fbBuilder) { + final int? txsOffset = txs == null ? null + : fbBuilder.writeList(txs!.map((b) => b.pack(fbBuilder)).toList()); + fbBuilder.startTable(1); + fbBuilder.addOffset(0, txsOffset); + return fbBuilder.endTable(); + } + + @override + String toString() { + return 'PlainTxVecT{txs: ${txs}}'; + } +} + +class _PlainTxVecReader extends fb.TableReader { + const _PlainTxVecReader(); + + @override + PlainTxVec createObject(fb.BufferContext bc, int offset) => + PlainTxVec._(bc, offset); +} + +class PlainTxVecBuilder { + PlainTxVecBuilder(this.fbBuilder); + + final fb.Builder fbBuilder; + + void begin() { + fbBuilder.startTable(1); + } + + int addTxsOffset(int? offset) { + fbBuilder.addOffset(0, offset); + return fbBuilder.offset; + } + + int finish() { + return fbBuilder.endTable(); + } +} + +class PlainTxVecObjectBuilder extends fb.ObjectBuilder { + final List? _txs; + + PlainTxVecObjectBuilder({ + List? txs, + }) + : _txs = txs; + + /// Finish building, and store into the [fbBuilder]. + @override + int finish(fb.Builder fbBuilder) { + final int? txsOffset = _txs == null ? null + : fbBuilder.writeList(_txs!.map((b) => b.getOrCreateOffset(fbBuilder)).toList()); + fbBuilder.startTable(1); + fbBuilder.addOffset(0, txsOffset); + return fbBuilder.endTable(); + } + + /// Convenience method to serialize to byte list. + @override + Uint8List toBytes([String? fileIdentifier]) { + final fbBuilder = fb.Builder(deduplicateTables: false); + fbBuilder.finish(finish(fbBuilder), fileIdentifier); + return fbBuilder.buffer; + } +} +class PlainNote { + PlainNote._(this._bc, this._bcOffset); + factory PlainNote(List bytes) { + final rootRef = fb.BufferContext.fromBytes(bytes); + return reader.read(rootRef, 0); + } + + static const fb.Reader reader = _PlainNoteReader(); + + final fb.BufferContext _bc; + final int _bcOffset; + + int get id => const fb.Uint32Reader().vTableGet(_bc, _bcOffset, 4, 0); + String? get txId => const fb.StringReader().vTableGetNullable(_bc, _bcOffset, 6); + int get height => const fb.Uint32Reader().vTableGet(_bc, _bcOffset, 8, 0); + int get timestamp => const fb.Uint32Reader().vTableGet(_bc, _bcOffset, 10, 0); + int get vout => const fb.Uint32Reader().vTableGet(_bc, _bcOffset, 12, 0); + int get value => const fb.Uint64Reader().vTableGet(_bc, _bcOffset, 14, 0); + + @override + String toString() { + return 'PlainNote{id: ${id}, txId: ${txId}, height: ${height}, timestamp: ${timestamp}, vout: ${vout}, value: ${value}}'; + } + + PlainNoteT unpack() => PlainNoteT( + id: id, + txId: txId, + height: height, + timestamp: timestamp, + vout: vout, + value: value); + + static int pack(fb.Builder fbBuilder, PlainNoteT? object) { + if (object == null) return 0; + return object.pack(fbBuilder); + } +} + +class PlainNoteT implements fb.Packable { + int id; + String? txId; + int height; + int timestamp; + int vout; + int value; + + PlainNoteT({ + this.id = 0, + this.txId, + this.height = 0, + this.timestamp = 0, + this.vout = 0, + this.value = 0}); + + @override + int pack(fb.Builder fbBuilder) { + final int? txIdOffset = txId == null ? null + : fbBuilder.writeString(txId!); + fbBuilder.startTable(6); + fbBuilder.addUint32(0, id); + fbBuilder.addOffset(1, txIdOffset); + fbBuilder.addUint32(2, height); + fbBuilder.addUint32(3, timestamp); + fbBuilder.addUint32(4, vout); + fbBuilder.addUint64(5, value); + return fbBuilder.endTable(); + } + + @override + String toString() { + return 'PlainNoteT{id: ${id}, txId: ${txId}, height: ${height}, timestamp: ${timestamp}, vout: ${vout}, value: ${value}}'; + } +} + +class _PlainNoteReader extends fb.TableReader { + const _PlainNoteReader(); + + @override + PlainNote createObject(fb.BufferContext bc, int offset) => + PlainNote._(bc, offset); +} + +class PlainNoteBuilder { + PlainNoteBuilder(this.fbBuilder); + + final fb.Builder fbBuilder; + + void begin() { + fbBuilder.startTable(6); + } + + int addId(int? id) { + fbBuilder.addUint32(0, id); + return fbBuilder.offset; + } + int addTxIdOffset(int? offset) { + fbBuilder.addOffset(1, offset); + return fbBuilder.offset; + } + int addHeight(int? height) { + fbBuilder.addUint32(2, height); + return fbBuilder.offset; + } + int addTimestamp(int? timestamp) { + fbBuilder.addUint32(3, timestamp); + return fbBuilder.offset; + } + int addVout(int? vout) { + fbBuilder.addUint32(4, vout); + return fbBuilder.offset; + } + int addValue(int? value) { + fbBuilder.addUint64(5, value); + return fbBuilder.offset; + } + + int finish() { + return fbBuilder.endTable(); + } +} + +class PlainNoteObjectBuilder extends fb.ObjectBuilder { + final int? _id; + final String? _txId; + final int? _height; + final int? _timestamp; + final int? _vout; + final int? _value; + + PlainNoteObjectBuilder({ + int? id, + String? txId, + int? height, + int? timestamp, + int? vout, + int? value, + }) + : _id = id, + _txId = txId, + _height = height, + _timestamp = timestamp, + _vout = vout, + _value = value; + + /// Finish building, and store into the [fbBuilder]. + @override + int finish(fb.Builder fbBuilder) { + final int? txIdOffset = _txId == null ? null + : fbBuilder.writeString(_txId!); + fbBuilder.startTable(6); + fbBuilder.addUint32(0, _id); + fbBuilder.addOffset(1, txIdOffset); + fbBuilder.addUint32(2, _height); + fbBuilder.addUint32(3, _timestamp); + fbBuilder.addUint32(4, _vout); + fbBuilder.addUint64(5, _value); + return fbBuilder.endTable(); + } + + /// Convenience method to serialize to byte list. + @override + Uint8List toBytes([String? fileIdentifier]) { + final fbBuilder = fb.Builder(deduplicateTables: false); + fbBuilder.finish(finish(fbBuilder), fileIdentifier); + return fbBuilder.buffer; + } +} +class PlainNoteVec { + PlainNoteVec._(this._bc, this._bcOffset); + factory PlainNoteVec(List bytes) { + final rootRef = fb.BufferContext.fromBytes(bytes); + return reader.read(rootRef, 0); + } + + static const fb.Reader reader = _PlainNoteVecReader(); + + final fb.BufferContext _bc; + final int _bcOffset; + + List? get notes => const fb.ListReader(PlainNote.reader).vTableGetNullable(_bc, _bcOffset, 4); + + @override + String toString() { + return 'PlainNoteVec{notes: ${notes}}'; + } + + PlainNoteVecT unpack() => PlainNoteVecT( + notes: notes?.map((e) => e.unpack()).toList()); + + static int pack(fb.Builder fbBuilder, PlainNoteVecT? object) { + if (object == null) return 0; + return object.pack(fbBuilder); + } +} + +class PlainNoteVecT implements fb.Packable { + List? notes; + + PlainNoteVecT({ + this.notes}); + + @override + int pack(fb.Builder fbBuilder) { + final int? notesOffset = notes == null ? null + : fbBuilder.writeList(notes!.map((b) => b.pack(fbBuilder)).toList()); + fbBuilder.startTable(1); + fbBuilder.addOffset(0, notesOffset); + return fbBuilder.endTable(); + } + + @override + String toString() { + return 'PlainNoteVecT{notes: ${notes}}'; + } +} + +class _PlainNoteVecReader extends fb.TableReader { + const _PlainNoteVecReader(); + + @override + PlainNoteVec createObject(fb.BufferContext bc, int offset) => + PlainNoteVec._(bc, offset); +} + +class PlainNoteVecBuilder { + PlainNoteVecBuilder(this.fbBuilder); + + final fb.Builder fbBuilder; + + void begin() { + fbBuilder.startTable(1); + } + + int addNotesOffset(int? offset) { + fbBuilder.addOffset(0, offset); + return fbBuilder.offset; + } + + int finish() { + return fbBuilder.endTable(); + } +} + +class PlainNoteVecObjectBuilder extends fb.ObjectBuilder { + final List? _notes; + + PlainNoteVecObjectBuilder({ + List? notes, + }) + : _notes = notes; + + /// Finish building, and store into the [fbBuilder]. + @override + int finish(fb.Builder fbBuilder) { + final int? notesOffset = _notes == null ? null + : fbBuilder.writeList(_notes!.map((b) => b.getOrCreateOffset(fbBuilder)).toList()); + fbBuilder.startTable(1); + fbBuilder.addOffset(0, notesOffset); + return fbBuilder.endTable(); + } + + /// Convenience method to serialize to byte list. + @override + Uint8List toBytes([String? fileIdentifier]) { + final fbBuilder = fb.Builder(deduplicateTables: false); + fbBuilder.finish(finish(fbBuilder), fileIdentifier); + return fbBuilder.buffer; + } +} +class Btcinput { + Btcinput._(this._bc, this._bcOffset); + factory Btcinput(List bytes) { + final rootRef = fb.BufferContext.fromBytes(bytes); + return reader.read(rootRef, 0); + } + + static const fb.Reader reader = _BtcinputReader(); + + final fb.BufferContext _bc; + final int _bcOffset; + + String? get txId => const fb.StringReader().vTableGetNullable(_bc, _bcOffset, 4); + int get vout => const fb.Uint32Reader().vTableGet(_bc, _bcOffset, 6, 0); + int get value => const fb.Uint64Reader().vTableGet(_bc, _bcOffset, 8, 0); + + @override + String toString() { + return 'Btcinput{txId: ${txId}, vout: ${vout}, value: ${value}}'; + } + + BtcinputT unpack() => BtcinputT( + txId: txId, + vout: vout, + value: value); + + static int pack(fb.Builder fbBuilder, BtcinputT? object) { + if (object == null) return 0; + return object.pack(fbBuilder); + } +} + +class BtcinputT implements fb.Packable { + String? txId; + int vout; + int value; + + BtcinputT({ + this.txId, + this.vout = 0, + this.value = 0}); + + @override + int pack(fb.Builder fbBuilder) { + final int? txIdOffset = txId == null ? null + : fbBuilder.writeString(txId!); + fbBuilder.startTable(3); + fbBuilder.addOffset(0, txIdOffset); + fbBuilder.addUint32(1, vout); + fbBuilder.addUint64(2, value); + return fbBuilder.endTable(); + } + + @override + String toString() { + return 'BtcinputT{txId: ${txId}, vout: ${vout}, value: ${value}}'; + } +} + +class _BtcinputReader extends fb.TableReader { + const _BtcinputReader(); + + @override + Btcinput createObject(fb.BufferContext bc, int offset) => + Btcinput._(bc, offset); +} + +class BtcinputBuilder { + BtcinputBuilder(this.fbBuilder); + + final fb.Builder fbBuilder; + + void begin() { + fbBuilder.startTable(3); + } + + int addTxIdOffset(int? offset) { + fbBuilder.addOffset(0, offset); + return fbBuilder.offset; + } + int addVout(int? vout) { + fbBuilder.addUint32(1, vout); + return fbBuilder.offset; + } + int addValue(int? value) { + fbBuilder.addUint64(2, value); + return fbBuilder.offset; + } + + int finish() { + return fbBuilder.endTable(); + } +} + +class BtcinputObjectBuilder extends fb.ObjectBuilder { + final String? _txId; + final int? _vout; + final int? _value; + + BtcinputObjectBuilder({ + String? txId, + int? vout, + int? value, + }) + : _txId = txId, + _vout = vout, + _value = value; + + /// Finish building, and store into the [fbBuilder]. + @override + int finish(fb.Builder fbBuilder) { + final int? txIdOffset = _txId == null ? null + : fbBuilder.writeString(_txId!); + fbBuilder.startTable(3); + fbBuilder.addOffset(0, txIdOffset); + fbBuilder.addUint32(1, _vout); + fbBuilder.addUint64(2, _value); + return fbBuilder.endTable(); + } + + /// Convenience method to serialize to byte list. + @override + Uint8List toBytes([String? fileIdentifier]) { + final fbBuilder = fb.Builder(deduplicateTables: false); + fbBuilder.finish(finish(fbBuilder), fileIdentifier); + return fbBuilder.buffer; + } +} +class Btcoutput { + Btcoutput._(this._bc, this._bcOffset); + factory Btcoutput(List bytes) { + final rootRef = fb.BufferContext.fromBytes(bytes); + return reader.read(rootRef, 0); + } + + static const fb.Reader reader = _BtcoutputReader(); + + final fb.BufferContext _bc; + final int _bcOffset; + + String? get scriptPubkey => const fb.StringReader().vTableGetNullable(_bc, _bcOffset, 4); + int get value => const fb.Uint64Reader().vTableGet(_bc, _bcOffset, 6, 0); + + @override + String toString() { + return 'Btcoutput{scriptPubkey: ${scriptPubkey}, value: ${value}}'; + } + + BtcoutputT unpack() => BtcoutputT( + scriptPubkey: scriptPubkey, + value: value); + + static int pack(fb.Builder fbBuilder, BtcoutputT? object) { + if (object == null) return 0; + return object.pack(fbBuilder); + } +} + +class BtcoutputT implements fb.Packable { + String? scriptPubkey; + int value; + + BtcoutputT({ + this.scriptPubkey, + this.value = 0}); + + @override + int pack(fb.Builder fbBuilder) { + final int? scriptPubkeyOffset = scriptPubkey == null ? null + : fbBuilder.writeString(scriptPubkey!); + fbBuilder.startTable(2); + fbBuilder.addOffset(0, scriptPubkeyOffset); + fbBuilder.addUint64(1, value); + return fbBuilder.endTable(); + } + + @override + String toString() { + return 'BtcoutputT{scriptPubkey: ${scriptPubkey}, value: ${value}}'; + } +} + +class _BtcoutputReader extends fb.TableReader { + const _BtcoutputReader(); + + @override + Btcoutput createObject(fb.BufferContext bc, int offset) => + Btcoutput._(bc, offset); +} + +class BtcoutputBuilder { + BtcoutputBuilder(this.fbBuilder); + + final fb.Builder fbBuilder; + + void begin() { + fbBuilder.startTable(2); + } + + int addScriptPubkeyOffset(int? offset) { + fbBuilder.addOffset(0, offset); + return fbBuilder.offset; + } + int addValue(int? value) { + fbBuilder.addUint64(1, value); + return fbBuilder.offset; + } + + int finish() { + return fbBuilder.endTable(); + } +} + +class BtcoutputObjectBuilder extends fb.ObjectBuilder { + final String? _scriptPubkey; + final int? _value; + + BtcoutputObjectBuilder({ + String? scriptPubkey, + int? value, + }) + : _scriptPubkey = scriptPubkey, + _value = value; + + /// Finish building, and store into the [fbBuilder]. + @override + int finish(fb.Builder fbBuilder) { + final int? scriptPubkeyOffset = _scriptPubkey == null ? null + : fbBuilder.writeString(_scriptPubkey!); + fbBuilder.startTable(2); + fbBuilder.addOffset(0, scriptPubkeyOffset); + fbBuilder.addUint64(1, _value); + return fbBuilder.endTable(); + } + + /// Convenience method to serialize to byte list. + @override + Uint8List toBytes([String? fileIdentifier]) { + final fbBuilder = fb.Builder(deduplicateTables: false); + fbBuilder.finish(finish(fbBuilder), fileIdentifier); + return fbBuilder.buffer; + } +} +class Btctx { + Btctx._(this._bc, this._bcOffset); + factory Btctx(List bytes) { + final rootRef = fb.BufferContext.fromBytes(bytes); + return reader.read(rootRef, 0); + } + + static const fb.Reader reader = _BtctxReader(); + + final fb.BufferContext _bc; + final int _bcOffset; + + List? get txins => const fb.ListReader(Btcinput.reader).vTableGetNullable(_bc, _bcOffset, 4); + List? get txouts => const fb.ListReader(Btcoutput.reader).vTableGetNullable(_bc, _bcOffset, 6); + int get fee => const fb.Uint64Reader().vTableGet(_bc, _bcOffset, 8, 0); + + @override + String toString() { + return 'Btctx{txins: ${txins}, txouts: ${txouts}, fee: ${fee}}'; + } + + BtctxT unpack() => BtctxT( + txins: txins?.map((e) => e.unpack()).toList(), + txouts: txouts?.map((e) => e.unpack()).toList(), + fee: fee); + + static int pack(fb.Builder fbBuilder, BtctxT? object) { + if (object == null) return 0; + return object.pack(fbBuilder); + } +} + +class BtctxT implements fb.Packable { + List? txins; + List? txouts; + int fee; + + BtctxT({ + this.txins, + this.txouts, + this.fee = 0}); + + @override + int pack(fb.Builder fbBuilder) { + final int? txinsOffset = txins == null ? null + : fbBuilder.writeList(txins!.map((b) => b.pack(fbBuilder)).toList()); + final int? txoutsOffset = txouts == null ? null + : fbBuilder.writeList(txouts!.map((b) => b.pack(fbBuilder)).toList()); + fbBuilder.startTable(3); + fbBuilder.addOffset(0, txinsOffset); + fbBuilder.addOffset(1, txoutsOffset); + fbBuilder.addUint64(2, fee); + return fbBuilder.endTable(); + } + + @override + String toString() { + return 'BtctxT{txins: ${txins}, txouts: ${txouts}, fee: ${fee}}'; + } +} + +class _BtctxReader extends fb.TableReader { + const _BtctxReader(); + + @override + Btctx createObject(fb.BufferContext bc, int offset) => + Btctx._(bc, offset); +} + +class BtctxBuilder { + BtctxBuilder(this.fbBuilder); + + final fb.Builder fbBuilder; + + void begin() { + fbBuilder.startTable(3); + } + + int addTxinsOffset(int? offset) { + fbBuilder.addOffset(0, offset); + return fbBuilder.offset; + } + int addTxoutsOffset(int? offset) { + fbBuilder.addOffset(1, offset); + return fbBuilder.offset; + } + int addFee(int? fee) { + fbBuilder.addUint64(2, fee); + return fbBuilder.offset; + } + + int finish() { + return fbBuilder.endTable(); + } +} + +class BtctxObjectBuilder extends fb.ObjectBuilder { + final List? _txins; + final List? _txouts; + final int? _fee; + + BtctxObjectBuilder({ + List? txins, + List? txouts, + int? fee, + }) + : _txins = txins, + _txouts = txouts, + _fee = fee; + + /// Finish building, and store into the [fbBuilder]. + @override + int finish(fb.Builder fbBuilder) { + final int? txinsOffset = _txins == null ? null + : fbBuilder.writeList(_txins!.map((b) => b.getOrCreateOffset(fbBuilder)).toList()); + final int? txoutsOffset = _txouts == null ? null + : fbBuilder.writeList(_txouts!.map((b) => b.getOrCreateOffset(fbBuilder)).toList()); + fbBuilder.startTable(3); + fbBuilder.addOffset(0, txinsOffset); + fbBuilder.addOffset(1, txoutsOffset); + fbBuilder.addUint64(2, _fee); + return fbBuilder.endTable(); + } + + /// Convenience method to serialize to byte list. + @override + Uint8List toBytes([String? fileIdentifier]) { + final fbBuilder = fb.Builder(deduplicateTables: false); + fbBuilder.finish(finish(fbBuilder), fileIdentifier); + return fbBuilder.buffer; + } +} class Message { Message._(this._bc, this._bcOffset); factory Message(List bytes) { diff --git a/packages/warp_api_ffi/lib/warp_api.dart b/packages/warp_api_ffi/lib/warp_api.dart index 77f9d61..6a4aac0 100644 --- a/packages/warp_api_ffi/lib/warp_api.dart +++ b/packages/warp_api_ffi/lib/warp_api.dart @@ -117,10 +117,6 @@ class WarpApi { name.toNativeUtf8().cast(), index, count); } - static String ledgerGetFVK(int coin) { - return unwrapResultString(warp_api_lib.ledger_get_fvk(coin)); - } - static void convertToWatchOnly(int coin, int id) { warp_api_lib.convert_to_watchonly(coin, id); } @@ -159,8 +155,8 @@ class WarpApi { return unwrapResultU32(warp_api_lib.rewind_to(height)); } - static void rescanFrom(int height) { - warp_api_lib.rescan_from(height); + static void rescanFrom(int coin, int height) { + warp_api_lib.rescan_from(coin, height); } static int warpSync(SyncParams params) { @@ -174,8 +170,8 @@ class WarpApi { warp_api_lib.cancel_warp(); } - static Future getLatestHeight() async { - return await compute(getLatestHeightIsolateFn, null); + static Future getLatestHeight(int coin) async { + return await compute(getLatestHeightIsolateFn, coin); } static int validKey(int coin, String key) { @@ -221,8 +217,8 @@ class WarpApi { // recipientJson, useTransparent, anchorOffset, receivePort.sendPort)); // } - static int getTBalance() { - final balance = warp_api_lib.get_taddr_balance(0xFF, 0); + static int getTBalance(int coin, int account) { + final balance = warp_api_lib.get_taddr_balance(coin, account); return unwrapResultU64(balance); } @@ -303,8 +299,9 @@ class WarpApi { SignOnlyParams(coin, account, tx, receivePort.sendPort)); } - static String broadcast(String txStr) { - final res = warp_api_lib.broadcast_tx(txStr.toNativeUtf8().cast()); + static String broadcast(int coin, String txStr) { + final res = + warp_api_lib.broadcast_tx(coin, txStr.toNativeUtf8().cast()); return unwrapResultString(res); } @@ -341,9 +338,9 @@ class WarpApi { return res; } - static Future syncHistoricalPrices(String currency) async { - return await compute( - syncHistoricalPricesIsolateFn, SyncHistoricalPricesParams(currency)); + static Future syncHistoricalPrices(int coin, String currency) async { + return await compute(syncHistoricalPricesIsolateFn, + SyncHistoricalPricesParams(coin, currency)); } static void setDbPasswd(int coin, String passwd) { @@ -455,14 +452,6 @@ class WarpApi { return kp; } - static void ledgerBuildKeys() { - unwrapResultU8(warp_api_lib.ledger_build_keys()); - } - - static String ledgerGetAddress() { - return unwrapResultString(warp_api_lib.ledger_get_address()); - } - static Future ledgerSend(int coin, String txPlan) async { return await compute((_) { return unwrapResultString( @@ -667,8 +656,8 @@ String signOnlyIsolateFn(SignOnlyParams params) { return convertCString(txIdRes.value); } -int getLatestHeightIsolateFn(Null n) { - return unwrapResultU32(warp_api_lib.get_latest_height()); +int getLatestHeightIsolateFn(int coin) { + return unwrapResultU32(warp_api_lib.get_latest_height(coin)); } String transferPoolsIsolateFn(TransferPoolsParams params) { @@ -689,6 +678,7 @@ int syncHistoricalPricesIsolateFn(SyncHistoricalPricesParams params) { final now = DateTime.now(); final today = DateTime.utc(now.year, now.month, now.day); return unwrapResultU32(warp_api_lib.sync_historical_prices( + params.coin, today.millisecondsSinceEpoch ~/ 1000, 365, params.currency.toNativeUtf8().cast())); @@ -768,9 +758,10 @@ class TransferPoolsParams { } class SyncHistoricalPricesParams { + final int coin; final String currency; - SyncHistoricalPricesParams(this.currency); + SyncHistoricalPricesParams(this.coin, this.currency); } class GetTBalanceParams { diff --git a/packages/warp_api_ffi/lib/warp_api_generated.dart b/packages/warp_api_ffi/lib/warp_api_generated.dart index 96c8806..a3564ed 100644 --- a/packages/warp_api_ffi/lib/warp_api_generated.dart +++ b/packages/warp_api_ffi/lib/warp_api_generated.dart @@ -415,8 +415,12 @@ class NativeLibrary { late final _dart_get_diversified_address _get_diversified_address = _get_diversified_address_ptr.asFunction<_dart_get_diversified_address>(); - CResult_u32 get_latest_height() { - return _get_latest_height(); + CResult_u32 get_latest_height( + int coin, + ) { + return _get_latest_height( + coin, + ); } late final _get_latest_height_ptr = @@ -424,37 +428,6 @@ class NativeLibrary { late final _dart_get_latest_height _get_latest_height = _get_latest_height_ptr.asFunction<_dart_get_latest_height>(); - CResult_u8 ledger_build_keys() { - return _ledger_build_keys(); - } - - late final _ledger_build_keys_ptr = - _lookup>('ledger_build_keys'); - late final _dart_ledger_build_keys _ledger_build_keys = - _ledger_build_keys_ptr.asFunction<_dart_ledger_build_keys>(); - - CResult_____c_char ledger_get_fvk( - int coin, - ) { - return _ledger_get_fvk( - coin, - ); - } - - late final _ledger_get_fvk_ptr = - _lookup>('ledger_get_fvk'); - late final _dart_ledger_get_fvk _ledger_get_fvk = - _ledger_get_fvk_ptr.asFunction<_dart_ledger_get_fvk>(); - - CResult_____c_char ledger_get_address() { - return _ledger_get_address(); - } - - late final _ledger_get_address_ptr = - _lookup>('ledger_get_address'); - late final _dart_ledger_get_address _ledger_get_address = - _ledger_get_address_ptr.asFunction<_dart_ledger_get_address>(); - void skip_to_last_height( int coin, ) { @@ -483,9 +456,11 @@ class NativeLibrary { _rewind_to_ptr.asFunction<_dart_rewind_to>(); void rescan_from( + int coin, int height, ) { return _rescan_from( + coin, height, ); } @@ -649,9 +624,11 @@ class NativeLibrary { _sign_and_broadcast_ptr.asFunction<_dart_sign_and_broadcast>(); CResult_____c_char broadcast_tx( + int coin, ffi.Pointer tx_str, ) { return _broadcast_tx( + coin, tx_str, ); } @@ -717,11 +694,13 @@ class NativeLibrary { _get_block_by_time_ptr.asFunction<_dart_get_block_by_time>(); CResult_u32 sync_historical_prices( + int coin, int now, int days, ffi.Pointer currency, ) { return _sync_historical_prices( + coin, now, days, currency, @@ -1655,6 +1634,16 @@ const int ShieldedTx_VT_MEMO = 20; const int ShieldedTxVec_VT_TXS = 4; +const int PlainNote_VT_VOUT = 12; + +const int BTCOutput_VT_SCRIPT_PUBKEY = 4; + +const int BTCTx_VT_TXINS = 4; + +const int BTCTx_VT_TXOUTS = 6; + +const int BTCTx_VT_FEE = 8; + const int Message_VT_ID_MSG = 4; const int Message_VT_ID_TX = 6; @@ -1743,8 +1732,6 @@ const int TxReport_VT_NET_SAPLING = 12; const int TxReport_VT_NET_ORCHARD = 14; -const int TxReport_VT_FEE = 16; - const int TxReport_VT_PRIVACY_LEVEL = 18; typedef _c_dummy_export = ffi.Void Function(); @@ -2005,26 +1992,14 @@ typedef _dart_get_diversified_address = CResult_____c_char Function( int time, ); -typedef _c_get_latest_height = CResult_u32 Function(); - -typedef _dart_get_latest_height = CResult_u32 Function(); - -typedef _c_ledger_build_keys = CResult_u8 Function(); - -typedef _dart_ledger_build_keys = CResult_u8 Function(); - -typedef _c_ledger_get_fvk = CResult_____c_char Function( +typedef _c_get_latest_height = CResult_u32 Function( ffi.Uint8 coin, ); -typedef _dart_ledger_get_fvk = CResult_____c_char Function( +typedef _dart_get_latest_height = CResult_u32 Function( int coin, ); -typedef _c_ledger_get_address = CResult_____c_char Function(); - -typedef _dart_ledger_get_address = CResult_____c_char Function(); - typedef _c_skip_to_last_height = ffi.Void Function( ffi.Uint8 coin, ); @@ -2042,10 +2017,12 @@ typedef _dart_rewind_to = CResult_u32 Function( ); typedef _c_rescan_from = ffi.Void Function( + ffi.Uint8 coin, ffi.Uint32 height, ); typedef _dart_rescan_from = void Function( + int coin, int height, ); @@ -2162,10 +2139,12 @@ typedef _dart_sign_and_broadcast = CResult_____c_char Function( ); typedef _c_broadcast_tx = CResult_____c_char Function( + ffi.Uint8 coin, ffi.Pointer tx_str, ); typedef _dart_broadcast_tx = CResult_____c_char Function( + int coin, ffi.Pointer tx_str, ); @@ -2204,12 +2183,14 @@ typedef _dart_get_block_by_time = CResult_u32 Function( ); typedef _c_sync_historical_prices = CResult_u32 Function( + ffi.Uint8 coin, ffi.Int64 now, ffi.Uint32 days, ffi.Pointer currency, ); typedef _dart_sync_historical_prices = CResult_u32 Function( + int coin, int now, int days, ffi.Pointer currency, diff --git a/packages/warp_api_ffi/pubspec.yaml b/packages/warp_api_ffi/pubspec.yaml index 839b11b..a2c93ba 100644 --- a/packages/warp_api_ffi/pubspec.yaml +++ b/packages/warp_api_ffi/pubspec.yaml @@ -30,7 +30,7 @@ ffigen: - '../../native/zcash-sync/binding.h' # On MacOS llvm-path: - - '/opt/homebrew/Cellar/llvm/15.0.7_1' + - '/opt/homebrew/Cellar/llvm/16.0.5' # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/pubspec.yaml b/pubspec.yaml index 18a5a9f..68a3cb0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.4.1+447 +version: 1.4.1+454 environment: sdk: ">=2.12.0 <3.0.0" @@ -147,6 +147,7 @@ flutter: - assets/multipay.svg - assets/ycash.png - assets/zcash.png + - assets/bitcoin.png - assets/success.mp3 - assets/fail.mp3 - assets/ding.mp3