Skip to content

Commit

Permalink
feat: Add seed phrase to settings
Browse files Browse the repository at this point in the history
  • Loading branch information
holzeis committed Jan 23, 2024
1 parent 0922e52 commit b659943
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 15 deletions.
2 changes: 1 addition & 1 deletion mobile/lib/common/settings/seed_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class _SeedScreenState extends State<SeedScreen> {
style: TextStyle(fontWeight: FontWeight.bold)),
TextSpan(
text:
"Save it somewhere safe (not on this phone). If you lose your seed and your phone, you've lost your funds."),
"Save it somewhere safe. If you lose your seed you lose your funds."),
])),
),
),
Expand Down
90 changes: 90 additions & 0 deletions webapp/frontend/lib/settings/seed_screen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:get_10101/settings/seed_words.dart';
import 'package:get_10101/settings/settings_service.dart';
import 'package:provider/provider.dart';

class SeedScreen extends StatefulWidget {
const SeedScreen({super.key});

@override
State<SeedScreen> createState() => _SeedScreenState();
}

class _SeedScreenState extends State<SeedScreen> {
bool checked = false;
bool visibility = false;

List<String>? phrase;

@override
void initState() {
context.read<SettingsService>().getSeedPhrase().then((value) => setState(() => phrase = value));
super.initState();
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.only(top: 20, left: 10, right: 10),
child: Column(
children: [
Container(
margin: const EdgeInsets.all(10),
child: Center(
child: RichText(
text: const TextSpan(
style: TextStyle(color: Colors.black, fontSize: 18),
children: [
TextSpan(
text:
"The recovery phrase (sometimes called a seed), is a list of 12 English words. It allows you to recover full access to your funds if needed\n\n"),
TextSpan(
text: "Do not share this seed with anyone. ",
style: TextStyle(fontWeight: FontWeight.bold)),
TextSpan(
text:
"Beware of phising. The developers of 10101 will never ask for your seed.\n\n"),
TextSpan(
text: "Do not lose this seed. ",
style: TextStyle(fontWeight: FontWeight.bold)),
TextSpan(
text:
"Save it somewhere safe (not on this phone). If you lose your seed and your phone, you've lost your funds."),
])),
),
),
const SizedBox(height: 25),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
buildSeedWordsWidget(phrase!, visibility),
const SizedBox(height: 20),
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconButton(
icon: visibility
? const Icon(Icons.visibility)
: const Icon(Icons.visibility_off),
onPressed: () {
setState(() {
visibility = !visibility;
});
},
tooltip: visibility ? 'Hide Seed' : 'Show Seed'),
Text(visibility ? 'Hide Seed' : 'Show Seed')
],
),
),
],
),
],
),
),
);
}
}
64 changes: 64 additions & 0 deletions webapp/frontend/lib/settings/seed_words.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import 'package:flutter/material.dart';

Row buildSeedWordsWidget(List<String> phrase, bool visible) {
final firstColumn = phrase
.getRange(0, 6)
.toList()
.asMap()
.entries
.map((entry) => SeedWord(entry.value, entry.key + 1, visible))
.toList();
final secondColumn = phrase
.getRange(6, 12)
.toList()
.asMap()
.entries
.map((entry) => SeedWord(entry.value, entry.key + 7, visible))
.toList();

return Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(crossAxisAlignment: CrossAxisAlignment.start, children: firstColumn),
const SizedBox(width: 30),
Column(crossAxisAlignment: CrossAxisAlignment.start, children: secondColumn)
],
);
}

class SeedWord extends StatelessWidget {
final String? word;
final int? index;
final bool visibility;

const SeedWord(this.word, this.index, this.visibility, {super.key});

@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.fromLTRB(0, 5, 0, 5),
child: Row(
crossAxisAlignment: visibility ? CrossAxisAlignment.baseline : CrossAxisAlignment.end,
textBaseline: TextBaseline.alphabetic,
children: [
SizedBox(
width: 25.0,
child: Text(
'#$index',
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
),
),
const SizedBox(width: 5),
visibility
? SizedBox(
width: 100,
child: Text(
word!,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
)
: Container(width: 100, height: 24, color: Colors.grey[300]),
]));
}
}
3 changes: 2 additions & 1 deletion webapp/frontend/lib/settings/settings_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get_10101/common/color.dart';
import 'package:get_10101/settings/app_info_screen.dart';
import 'package:get_10101/settings/seed_screen.dart';

class SettingsScreen extends StatefulWidget {
static const route = "/settings";
Expand Down Expand Up @@ -63,7 +64,7 @@ class _SettingsScreenState extends State<SettingsScreen> with SingleTickerProvid
Expanded(
child: TabBarView(
controller: _tabController,
children: const [AppInfoScreen(), Text("Channel"), Text("Backup")],
children: const [AppInfoScreen(), Text("Channel"), SeedScreen()],
),
),
],
Expand Down
31 changes: 18 additions & 13 deletions webapp/frontend/lib/settings/settings_service.dart
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
import 'package:http/http.dart' as http;
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:get_10101/common/http_client.dart';

class SettingsService {
const SettingsService();

Future<String> getNodeId() async {
// TODO(holzeis): this should come from the config
const port = "3001";
const host = "localhost";
final response = await HttpClientManager.instance.get(Uri(path: '/api/node'));

if (response.statusCode == 200) {
return response.body;
} else {
throw FlutterError("Failed to fetch node id");
}
}

try {
final response = await http.get(Uri.http('$host:$port', '/api/node'));
Future<List<String>> getSeedPhrase() async {
final response = await HttpClientManager.instance.get(Uri(path: '/api/seed'));

if (response.statusCode == 200) {
return response.body;
} else {
return "unknown";
}
} catch (e) {
return "unknown";
if (response.statusCode == 200) {
return jsonDecode(response.body);
} else {
throw FlutterError("Failed to fetch seed phrase");
}
}
}
4 changes: 4 additions & 0 deletions webapp/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ pub async fn get_node_id() -> impl IntoResponse {
ln_dlc::get_node_pubkey().to_string()
}

pub async fn get_seed_phrase() -> Json<Vec<String>> {
Json(ln_dlc::get_seed_phrase())
}

#[derive(Serialize)]
pub struct OrderId {
id: Uuid,
Expand Down
2 changes: 2 additions & 0 deletions webapp/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::api::get_balance;
use crate::api::get_node_id;
use crate::api::get_onchain_payment_history;
use crate::api::get_positions;
use crate::api::get_seed_phrase;
use crate::api::get_unused_address;
use crate::api::post_new_order;
use crate::api::send_payment;
Expand Down Expand Up @@ -101,6 +102,7 @@ fn using_serve_dir(subscribers: Arc<AppSubscribers>, network: Network) -> Router
.route("/api/orders", post(post_new_order))
.route("/api/positions", get(get_positions))
.route("/api/node", get(get_node_id))
.route("/api/seed", get(get_seed_phrase))
.route("/main.dart.js", get(main_dart_handler))
.route("/flutter.js", get(flutter_js))
.route("/index.html", get(index_handler))
Expand Down

0 comments on commit b659943

Please sign in to comment.