import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import 'package:url_launcher/url_launcher.dart'; class TransactionsPage extends StatefulWidget { TransactionsPage({Key? key, required this.uuid, required this.wallet}) : super(key: key); final String uuid; String wallet; @override _TransactionsPageState createState() => _TransactionsPageState(); } class _TransactionsPageState extends State { @override void initState() { super.initState(); fetchTransactions(); } List transactions = []; Future fetchTransactions() async { // Fetch domains from api final response = await http.get(Uri.parse( 'https://api.firewallet.au/wallet/transactions?uuid=${widget.uuid}&name=${widget.wallet}')); if (response.statusCode == 200) { final data = jsonDecode(response.body); if (data is Map && data.containsKey('error')) { print('Error: ${data['error']}'); return; } // Return if empty if (data.isEmpty) { setState(() { transactions = [ Transaction( confirmations: 0, date: 'No transactions found', hash: 'No transactions found', inputs: [], outputs: []) ]; }); return; } final txData = data as List; setState(() { transactions = txData .map( (tx) => Transaction( confirmations: tx['confirmations'], date: tx['date'], hash: tx['hash'], inputs: (tx['inputs'] as List).map((input) { return TXInput( address: input['address'], value: input['value'], path: input['path'] == null ? Path() : Path( account: input['path']['account'], change: input['path']['change'], derivation: input['path']['derivation'], name: input['path']['name'], )); }).toList(), outputs: (tx['outputs'] as List).map((output) { return TXOutput( address: output['address'], value: output['value'], covenant: Covenant( action: output['covenant']['action'], type: output['covenant']['type'], items: List.from(output['covenant']['items'])), path: output['path'] == null ? Path() : Path( account: output['path']['account'], change: output['path']['change'], derivation: output['path']['derivation'], name: output['path']['name'], ), ); }).toList(), ), ) .toList(); }); print(transactions[0].hash); print(transactions[0].value().toStringAsFixed(2)); } else { // Handle error print('Failed to load wallet names'); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('${widget.wallet} - Transactions'), // Add refresh button to the app bar actions: [ IconButton( icon: Icon(Icons.refresh), onPressed: fetchTransactions, ), ], ), // Get wallet list from api and display here body: Center( child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return ListView( children: [ ...transactions.map((tx) { return Card( child: ListTile( title: Text(tx.toString()), subtitle: Text(tx.hash), trailing: Text('${tx.value().toStringAsFixed(2)} HNS'), onTap: // Open tx in browser () => openTX(tx.hash)), ); }), ], ); }, ), ), ); } Future openTX(String hash) async { // Open the transaction in the browser final Uri url = Uri.parse('https://hns.cymon.de/tx/$hash'); if (!await launchUrl(url)) { throw Exception('Could not launch $url'); } } } class Transaction { final int confirmations; final String date; final String hash; final List inputs; final List outputs; double totalValue = 0; bool valueCalculated = false; Transaction( {required this.confirmations, required this.date, required this.hash, required this.inputs, required this.outputs}); // Calculate the total value of the transaction double value() { if (valueCalculated) { return totalValue / 1000000; } totalValue = inputs.fold(0, (prev, input) => prev + input.getValue()); totalValue += outputs.fold(0, (prev, output) => prev + output.getValue()); valueCalculated = true; return totalValue / 1000000; } @override String toString() { if (inputs.isEmpty) { return 'No Transactions Found'; } int opens = 0; int bids = 0; int reveals = 0; int redeems = 0; int registers = 0; int updates = 0; int transfers = 0; int finalizes = 0; int renews = 0; int revokes = 0; int other = 0; outputs.forEach((output) { if (output.covenant.action == 'OPEN') { opens++; } else if (output.covenant.action == 'BID') { bids++; } else if (output.covenant.action == 'REVEAL') { reveals++; } else if (output.covenant.action == 'REDEEM') { redeems++; } else if (output.covenant.action == 'REGISTER') { registers++; } else if (output.covenant.action == 'UPDATE') { updates++; } else if (output.covenant.action == 'TRANSFER') { transfers++; } else if (output.covenant.action == 'FINALIZE') { finalizes++; } else if (output.covenant.action == 'RENEW') { renews++; } else if (output.covenant.action == 'REVOKE') { revokes++; } else if (output.covenant.action == 'NONE') { other++; } else { other++; print('Unknown action: ${output.covenant.action}'); } }); String outputString = ''; if (opens > 0) { outputString += 'Opens: $opens, '; } if (bids > 0) { outputString += 'Bids: $bids, '; } if (reveals > 0) { outputString += 'Reveals: $reveals, '; } if (redeems > 0) { outputString += 'Redeems: $redeems, '; } if (registers > 0) { outputString += 'Registers: $registers, '; } if (updates > 0) { outputString += 'Updates: $updates, '; } if (transfers > 0) { outputString += 'Transfers: $transfers, '; } if (finalizes > 0) { outputString += 'Finalizes: $finalizes, '; } if (renews > 0) { outputString += 'Renews: $renews, '; } if (revokes > 0) { outputString += 'Revokes: $revokes, '; } // Remove trailing comma if (outputString.isNotEmpty) { outputString = outputString.substring(0, outputString.length - 2); } if (outputString.isEmpty) { // Check if sent or received if (value() > 0) { outputString = 'Received HNS'; } else { outputString = 'Sent HNS'; } } return outputString; } } class TXInput { final String address; final int value; final Path path; TXInput({required this.address, required this.value, required this.path}); int getValue() { if (path.userPath()) { return value * -1; } return value; } } class TXOutput { final String address; final int value; final Covenant covenant; final Path path; TXOutput( {required this.address, required this.value, required this.covenant, required this.path}); int getValue() { if (path.userPath()) { if (covenant.action == 'BID') { return 0; } return value; } return 0; } } class Covenant { final String action; final int type; final List items; Covenant({required this.action, required this.type, required this.items}); } class Path { int? account; bool? change; String? derivation; String? name; Path({this.account, this.change, this.derivation, this.name}); @override String toString() { if (name == null) { return 'No Path Found'; } if (derivation == null) { return 'Path(name: $name)'; } return 'Path(account: $account, change: $change, derivation: $derivation, name: $name)'; } bool userPath() { return derivation != null; } }