Skip to content

Commit

Permalink
feat: add developer debug setting page
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewwahid committed May 29, 2023
1 parent b5e4356 commit dba668a
Show file tree
Hide file tree
Showing 6 changed files with 321 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import 'dart:convert';

import 'package:candide_mobile_app/config/env.dart';
import 'package:candide_mobile_app/config/network.dart';
import 'package:candide_mobile_app/config/theme.dart';
import 'package:candide_mobile_app/config/top_tokens.dart';
import 'package:candide_mobile_app/services/balance_service.dart';
import 'package:candide_mobile_app/utils/utils.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:phosphor_flutter/phosphor_flutter.dart';
import 'package:web3dart/credentials.dart';

class VerifyEndpointsDialog extends StatefulWidget {
final Network network;
const VerifyEndpointsDialog({Key? key, required this.network}) : super(key: key);

@override
State<VerifyEndpointsDialog> createState() => _VerifyEndpointsDeialogState();
}

class _VerifyEndpointsDeialogState extends State<VerifyEndpointsDialog> {
Map<String, List<dynamic>> endpoints = { // endpoint: [status, chain-dependent]
"ethereum-node-endpoints": ["scheduled", true],
"bundler-endpoints": ["scheduled", true],
"paymaster-endpoints": ["scheduled", true],
"coingecko": ["scheduled", false],
"cryptocompare": ["scheduled", false],
};

Future<bool> verifyBundlerEndpoint() async {
var bundlerEndpoint = Env.getBundlerUrlByChainId(widget.network.chainId.toInt());
try{
var response = await Dio().post(
bundlerEndpoint,
data: jsonEncode({"jsonrpc": "2.0", "id": 1, "method": "eth_chainId", "params": []})
);
//
if ((response.data as Map).containsKey("error")){
return false;
}
if (Utils.decodeBigInt(response.data["result"]) == widget.network.chainId){
response = await Dio().post(
bundlerEndpoint,
data: jsonEncode({"jsonrpc": "2.0", "id": 1, "method": "eth_supportedEntryPoints", "params": []})
);
if ((response.data["result"] as List<dynamic>).map((e) => e.toString().toLowerCase()).contains(widget.network.entrypoint.hex)) return true;
}
return false;
} on DioError {
return false;
}
}

Future<bool> verifyEthereumEndpoint() async {
try{
BigInt chainId = await widget.network.client.getChainId();
if (chainId == widget.network.chainId){
await widget.network.client.getBlockNumber();
return true;
}
//
return false;
} on DioError {
return false;
}
}

Future<bool> verifyPaymasterEndpoint() async {
var bundlerEndpoint = Env.getPaymasterUrlByChainId(widget.network.chainId.toInt());
try{
var response = await Dio().post(
bundlerEndpoint,
data: jsonEncode({"jsonrpc": "2.0", "id": 1, "method": "pm_getApprovedTokens", "params": []})
);
//
if ((response.data as Map).containsKey("error")){
return false;
}
return true;
} on DioError {
return false;
}
}

Future<bool> verifyCoingeckoEndpoint() async {
try{
int chainId = widget.network.chainId.toInt();
if (widget.network.testnetData != null){
chainId = widget.network.testnetData!.testnetForChainId;
}
EthereumAddress tokenAddress = TopTokens.getChainTokens(chainId)[1];
var response = await Dio().get("https://api.coingecko.com/api/v3/simple/token_price/${widget.network.coinGeckoAssetPlatform}?contract_addresses=$tokenAddress&vs_currencies=eth");
return response.data.toString().toLowerCase().contains(tokenAddress.hex.toLowerCase());
} on DioError {
return false;
}
}

Future<bool> verifyEndpoint(String name) async {
if (name == "ethereum-node-endpoints"){
return await verifyEthereumEndpoint();
}else if (name == "bundler-endpoints"){
return await verifyBundlerEndpoint();
}else if (name == "paymaster-endpoints"){
return await verifyPaymasterEndpoint();
}else if (name == "coingecko"){
return await verifyCoingeckoEndpoint();
}else if (name == "cryptocompare"){
return (await BalanceService.getETHUSDPrice()) > 0;
}
return false;
}

void startVerifying() async {
for (MapEntry entry in endpoints.entries){
setState(() {
entry.value[0] = "pending";
});
bool status = await verifyEndpoint(entry.key);
if (!mounted) return;
setState(() {
entry.value[0] = status ? "success" : "failed";
});
}
}

@override
void initState() {
startVerifying();
super.initState();
}


@override
Widget build(BuildContext context) {
return AlertDialog(
// contentPadding: EdgeInsets.symmetric(horizontal: 10),
title: Text("Verify Endpoints", style: TextStyle(fontFamily: AppThemes.fonts.gilroyBold)),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
for (MapEntry<String, List<dynamic>> endpoint in endpoints.entries)
_VerifyEndpointItem(
name: endpoint.key.replaceAll("-", " ").capitalizeFirst!,
networkName: endpoint.value[1] ? widget.network.name : null,
status: endpoint.value[0],
),
],
),
);
}
}

class _VerifyEndpointItem extends StatelessWidget {
final String name;
final String? networkName;
final String status;
const _VerifyEndpointItem({Key? key, required this.name, required this.status, this.networkName}) : super(key: key);

@override
Widget build(BuildContext context) {
Widget leading;
if (status != "pending"){
IconData leadingIconData = PhosphorIcons.timer;
Color leadingColor = Colors.white;
if (status == "success"){
leadingIconData = PhosphorIcons.checkBold;
leadingColor = Colors.green;
}else if (status == "failed"){
leadingIconData = PhosphorIcons.xBold;
leadingColor = Colors.red;
}
leading = Icon(
leadingIconData,
size: 20,
color: leadingColor,
);
if (status == "scheduled"){
leading = const SizedBox(width: 20,);
}
}else{
leading = Transform.scale(
scale: 0.75,
child: const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2,)
),
);
}
return Container(
margin: const EdgeInsets.only(bottom: 10),
child: Row(
children: [
leading,
const SizedBox(width: 10,),
RichText(
text: TextSpan(
text: name,
children: [
if (networkName != null)
TextSpan(
text: "\n$networkName",
style: const TextStyle(color: Colors.grey, fontSize: 11)
)
]
),
),
],
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import 'package:candide_mobile_app/config/network.dart';
import 'package:candide_mobile_app/config/theme.dart';
import 'package:candide_mobile_app/screens/home/settings/components/setting_menu_item.dart';
import 'package:candide_mobile_app/screens/home/settings/developer_settings/components/debug_verify_endpoints_dialog.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:phosphor_flutter/phosphor_flutter.dart';

class DeveloperDebugPage extends StatefulWidget {
const DeveloperDebugPage({Key? key}) : super(key: key);

@override
State<DeveloperDebugPage> createState() => _DeveloperDebugPageState();
}

class _DeveloperDebugPageState extends State<DeveloperDebugPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Developer Debug", style: TextStyle(fontFamily: AppThemes.fonts.gilroyBold),),
centerTitle: true,
backgroundColor: Colors.transparent,
elevation: 0,
leadingWidth: 40,
leading: Container(
margin: const EdgeInsets.only(left: 10),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey[800],
),
child: IconButton(
onPressed: () => Navigator.maybePop(context),
padding: const EdgeInsets.all(0),
splashRadius: 15,
style: const ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: VisualDensity.compact,
),
icon: const Icon(PhosphorIcons.caretLeftBold, size: 17,),
),
),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SettingMenuItem(
onPress: (){
Get.dialog(VerifyEndpointsDialog(network: Networks.selected()));
},
label: Text("Verify endpoints", style: TextStyle(fontFamily: AppThemes.fonts.gilroyBold, fontSize: 17)),
trailing: const Icon(PhosphorIcons.arrowRightBold),
),
],
),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:candide_mobile_app/config/network.dart';
import 'package:candide_mobile_app/config/theme.dart';
import 'package:candide_mobile_app/screens/home/settings/components/setting_menu_item.dart';
import 'package:candide_mobile_app/screens/home/settings/components/test_token_account_selection.dart';
import 'package:candide_mobile_app/screens/home/settings/developer_settings/developer_debug_page.dart';
import 'package:candide_mobile_app/screens/home/settings/developer_settings/manage_networks_page.dart';
import 'package:candide_mobile_app/utils/utils.dart';
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -61,14 +62,14 @@ class _DeveloperSettingsPageState extends State<DeveloperSettingsPage> {
Get.to(const ManageNetworksPage(), transition: Transition.rightToLeft);
},
leading: Container(
decoration: const BoxDecoration(
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.deepOrange,
Colors.deepOrangeAccent,
Colors.deepOrange[300]!,
],
),
),
Expand All @@ -80,6 +81,30 @@ class _DeveloperSettingsPageState extends State<DeveloperSettingsPage> {
label: Text("Manage Networks", style: TextStyle(fontFamily: AppThemes.fonts.gilroyBold, fontSize: 17)),
trailing: const Icon(PhosphorIcons.arrowRightBold),
),
SettingMenuItem(
onPress: (){
Get.to(const DeveloperDebugPage(), transition: Transition.rightToLeft);
},
leading: Container(
decoration: const BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.indigo,
Colors.indigoAccent,
],
),
),
child: const CircleAvatar(
backgroundColor: Colors.transparent,
child: Icon(PhosphorIcons.bugBeetleLight, color: Colors.white,),
),
),
label: Text("Developer Debug", style: TextStyle(fontFamily: AppThemes.fonts.gilroyBold, fontSize: 17)),
trailing: const Icon(PhosphorIcons.arrowRightBold),
),
SettingMenuItem(
leading: Container(
decoration: const BoxDecoration(
Expand All @@ -88,8 +113,8 @@ class _DeveloperSettingsPageState extends State<DeveloperSettingsPage> {
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.yellow,
Colors.green,
Colors.greenAccent,
],
),
),
Expand Down
2 changes: 1 addition & 1 deletion lib/services/balance_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class BalanceService {
var response = await Dio().get("https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD");
return response.data["USD"].toDouble();
} catch (e) {
// todo handle network errors
// todo handle network errors (note: return of 0 is used in debug_verify_endpoints_dialog.dart)
return 0;
}
}
Expand Down
23 changes: 4 additions & 19 deletions lib/services/bundler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import 'package:candide_mobile_app/controller/persistent_data.dart';
import 'package:candide_mobile_app/models/gas.dart';
import 'package:candide_mobile_app/models/relay_response.dart';
import 'package:candide_mobile_app/utils/extensions/bigint_extensions.dart';
import 'package:candide_mobile_app/utils/utils.dart';
import 'package:dio/dio.dart';
import 'package:get/get.dart';
import 'package:wallet_dart/wallet/user_operation.dart';
import 'package:web3dart/web3dart.dart';

Expand Down Expand Up @@ -68,9 +68,9 @@ class Bundler {
return null;
}
return GasEstimate(
callGasLimit: _decodeBigInt(response.data["result"]["callGasLimit"]).scale(1.2),
verificationGasLimit: _decodeBigInt(response.data["result"]["verificationGas"]),
basePreVerificationGas: _decodeBigInt(response.data["result"]["preVerificationGas"]),
callGasLimit: Utils.decodeBigInt(response.data["result"]["callGasLimit"]).scale(1.2),
verificationGasLimit: Utils.decodeBigInt(response.data["result"]["verificationGas"]),
basePreVerificationGas: Utils.decodeBigInt(response.data["result"]["preVerificationGas"]),
preVerificationGas: BigInt.zero,
maxFeePerGas: BigInt.zero,
maxPriorityFeePerGas: BigInt.zero,
Expand Down Expand Up @@ -106,19 +106,4 @@ class Bundler {
}
}

static BigInt _decodeBigInt(dynamic value){
if (value == null) return BigInt.zero;
if (value is String){
if (value.startsWith("0x") || !value.isNumericOnly){
if (value == "0x") return BigInt.zero;
return BigInt.parse(value.replaceAll("0x", ""), radix: 16);
}else{
return BigInt.parse(value);
}
}else if (value is num){
return BigInt.from(value);
}
return BigInt.from(value);
}

}
Loading

0 comments on commit dba668a

Please sign in to comment.