Flutter钱包连接插件wallet_connect的使用
Flutter钱包连接插件wallet_connect的使用
Wallet Connect
使用
import 'package:wallet_connect/wallet_connect.dart';
- 创建一个Wallet Connect客户端实例并定义回调函数。
final wcClient = WCClient(
onConnect: () {
// 响应连接回调
},
onDisconnect: (code, reason) {
// 响应断开连接回调
},
onFailure: (error) {
// 响应连接失败回调
},
onSessionRequest: (id, peerMeta) {
// 响应会话请求回调
},
onEthSign: (id, message) {
// 响应个人签名或eth_sign或eth_signTypedData请求回调
},
onEthSendTransaction: (id, tx) {
// 响应eth_sendTransaction请求回调
},
onEthSignTransaction: (id, tx) {
// 响应eth_signTransaction请求回调
},
);
- 从wc: URI创建WCSession对象。
final session = WCSession.from(wcUri);
- 创建WCPeerMeta对象,包含应用程序的元数据。
final peerMeta = WCPeerMeta(
name: 'Example Wallet',
url: 'https://example.wallet',
description: 'Example Wallet',
icons: [],
);
- 连接到新的会话。
wcClient.connectNewSession(session: session, peerMeta: peerMeta);
- 或者连接到已保存的会话(步骤8)。
wcClient.connectFromSessionStore(sessionStore);
- 批准会话连接请求。
wcClient.approveSession(
accounts: [], // 账户地址
chainId: 1, // 链ID
);
- 或者拒绝会话连接请求。
wcClient.rejectSession();
- 从sessionStore获取活动会话以供以后使用。
final sessionStore = wcClient.sessionStore;
- 通过签署交易并发送已签名的十六进制数据来批准签名交易请求。
wcClient.approveRequest<String>(
id: id,
result: signedDataAsHex,
);
- 通过发送生成的交易哈希来批准发送交易请求。
wcClient.approveRequest<String>(
id: id,
result: transactionHash,
);
- 通过发送生成的签名数据十六进制来批准签名请求。
wcClient.approveRequest<String>(
id: id,
result: signedDataAsHex,
);
- 或者通过指定请求ID来拒绝上述任何请求。
wcClient.rejectRequest(id: id);
- 在本地断开会话连接。
wcClient.disconnect();
- 永久关闭已连接的会话。
wcClient.killSession();
完整示例Demo
以下是完整的示例代码:
import 'dart:convert';
import 'package:eth_sig_util/eth_sig_util.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:wallet_connect/models/jsonrpc/json_rpc_request.dart';
import 'package:wallet_connect/wallet_connect.dart';
import 'package:wallet_connect_example/utils/constants.dart';
import 'package:wallet_connect_example/utils/eth_conversions.dart';
import 'package:wallet_connect_example/widgets/input_field.dart';
import 'package:wallet_connect_example/widgets/qr_scan_view.dart';
import 'package:wallet_connect_example/widgets/session_request_view.dart';
import 'package:wallet_connect_example/widgets/update_session_view.dart';
import 'package:web3dart/crypto.dart';
import 'package:web3dart/web3dart.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
title: 'Wallet Connect',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Wallet Connect'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
[@override](/user/override)
_MyHomePageState createState() => _MyHomePageState();
}
const rpcUri =
'https://rpc-mainnet.maticvigil.com/v1/140d92ff81094f0f3d7babde06603390d7e581be';
enum MenuItems {
PREVIOUS_SESSION,
UPDATE_SESSION,
KILL_SESSION,
SCAN_QR,
PASTE_CODE,
CLEAR_CACHE,
GOTO_URL,
}
class _MyHomePageState extends State<MyHomePage> {
late WCClient _wcClient;
late SharedPreferences _prefs;
late InAppWebViewController _webViewController;
late String walletAddress, privateKey;
bool connected = false;
WCSessionStore? _sessionStore;
final _web3client = Web3Client(rpcUri, http.Client());
[@override](/user/override)
void initState() {
_initialize();
final rpcReq = JsonRpcRequest.fromJson({
"id": 1666695490479571,
"jsonrpc": "2.0",
"method": "hello_World",
"params": [
{"chainId": "0xa4b1"}
],
});
debugPrint('rpcReq $rpcReq');
super.initState();
}
_initialize() async {
_wcClient = WCClient(
onSessionRequest: _onSessionRequest,
onFailure: _onSessionError,
onDisconnect: _onSessionClosed,
onEthSign: _onSign,
onEthSignTransaction: _onSignTransaction,
onEthSendTransaction: _onSendTransaction,
onCustomRequest: (_, __) {},
onConnect: _onConnect,
onWalletSwitchNetwork: _onSwitchNetwork,
);
walletAddress = WALLET_ADDRESS;
privateKey = PRIVATE_KEY;
_prefs = await SharedPreferences.getInstance();
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: [
PopupMenuButton<MenuItems>(
onSelected: (item) {
switch (item) {
case MenuItems.PREVIOUS_SESSION:
_connectToPreviousSession();
break;
case MenuItems.UPDATE_SESSION:
if (_wcClient.isConnected) {
showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: 'Update Session',
pageBuilder: (context, _, __) => UpdateSessionView(
client: _wcClient,
address: walletAddress,
),
).then((value) {
if (value != null && (value as List).isNotEmpty) {
_wcClient.updateSession(
chainId: value[0] as int,
accounts: [value[1] as String],
);
}
});
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Not connected.'),
));
}
break;
case MenuItems.KILL_SESSION:
_wcClient.killSession();
break;
case MenuItems.SCAN_QR:
Navigator.push(
context,
MaterialPageRoute(builder: (_) => QRScanView()),
).then((value) {
if (value != null) {
_qrScanHandler(value);
}
});
break;
case MenuItems.PASTE_CODE:
showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: 'Paste Code',
pageBuilder: (context, _, __) => InputDialog(
title: 'Paste code to connect',
label: 'Enter Code',
),
).then((value) {
if (value != null && (value as String).isNotEmpty) {
_qrScanHandler(value);
}
});
break;
case MenuItems.CLEAR_CACHE:
_webViewController.clearCache();
break;
case MenuItems.GOTO_URL:
showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: 'Goto URL',
pageBuilder: (context, _, __) => InputDialog(
title: 'Enter URL to open',
label: 'Enter URL',
),
).then((value) {
if (value != null && (value as String).isNotEmpty) {
_webViewController.loadUrl(
urlRequest: URLRequest(url: Uri.parse(value)),
);
}
});
break;
}
},
itemBuilder: (_) {
return [
PopupMenuItem(
value: MenuItems.PREVIOUS_SESSION,
child: Text('Connect Previous Session'),
),
PopupMenuItem(
value: MenuItems.UPDATE_SESSION,
child: Text('Update Session'),
),
PopupMenuItem(
value: MenuItems.KILL_SESSION,
child: Text('Kill Session'),
),
PopupMenuItem(
value: MenuItems.SCAN_QR,
child: Text('Connect via QR'),
),
PopupMenuItem(
value: MenuItems.PASTE_CODE,
child: Text('Connect via Code'),
),
PopupMenuItem(
value: MenuItems.CLEAR_CACHE,
child: Text('Clear Cache'),
),
PopupMenuItem(
value: MenuItems.GOTO_URL,
child: Text('Goto URL'),
),
];
},
),
],
),
body: InAppWebView(
initialUrlRequest:
URLRequest(url: Uri.parse('https://example.walletconnect.org')),
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
useShouldOverrideUrlLoading: true,
),
),
onWebViewCreated: (controller) {
_webViewController = controller;
},
shouldOverrideUrlLoading: (controller, navAction) async {
final url = navAction.request.url.toString();
debugPrint('URL $url');
if (url.contains('wc?uri=')) {
final wcUri = Uri.parse(
Uri.decodeFull(Uri.parse(url).queryParameters['uri']!));
_qrScanHandler(wcUri.toString());
return NavigationActionPolicy.CANCEL;
} else if (url.startsWith('wc:')) {
_qrScanHandler(url);
return NavigationActionPolicy.CANCEL;
} else {
return NavigationActionPolicy.ALLOW;
}
},
),
);
}
_qrScanHandler(String value) {
if (value.contains('bridge') && value.contains('key')) {
final session = WCSession.from(value);
debugPrint('session $session');
final peerMeta = WCPeerMeta(
name: "Example Wallet",
url: "https://example.wallet",
description: "Example Wallet",
icons: [
"https://gblobscdn.gitbook.com/spaces%2F-LJJeCjcLrr53DcT1Ml7%2Favatar.png"
],
);
_wcClient.connectNewSession(session: session, peerMeta: peerMeta);
}
}
_connectToPreviousSession() {
final _sessionSaved = _prefs.getString('session');
debugPrint('_sessionSaved $_sessionSaved');
_sessionStore = _sessionSaved != null
? WCSessionStore.fromJson(jsonDecode(_sessionSaved))
: null;
if (_sessionStore != null) {
debugPrint('_sessionStore $_sessionStore');
_wcClient.connectFromSessionStore(_sessionStore!);
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('No previous session found.'),
));
}
}
_onConnect() {
setState(() {
connected = true;
});
}
_onSwitchNetwork(int id, int chainId) async {
await _wcClient.updateSession(chainId: chainId);
_wcClient.approveRequest<Null>(id: id, result: null);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Changed network to $chainId.'),
));
}
_onSessionRequest(int id, WCPeerMeta peerMeta) {
showDialog(
context: context,
builder: (_) => SessionRequestView(
peerMeta: peerMeta,
onApprove: (chainId) async {
_wcClient.approveSession(
accounts: [walletAddress],
chainId: chainId,
);
_sessionStore = _wcClient.sessionStore;
await _prefs.setString(
'session', jsonEncode(_wcClient.sessionStore.toJson()));
Navigator.pop(context);
},
onReject: () {
_wcClient.rejectSession();
Navigator.pop(context);
},
),
);
}
_onSessionError(dynamic message) {
setState(() {
connected = false;
});
showDialog(
context: context,
builder: (_) {
return SimpleDialog(
title: Text("Error"),
contentPadding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 16.0),
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text('Some Error Occured. $message'),
),
Row(
children: [
TextButton(
style: TextButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Theme.of(context).colorScheme.secondary,
),
onPressed: () {
Navigator.pop(context);
},
child: Text('CLOSE'),
),
],
),
],
);
},
);
}
_onSessionClosed(int? code, String? reason) {
_prefs.remove('session');
setState(() {
connected = false;
});
showDialog(
context: context,
builder: (_) {
return SimpleDialog(
title: Text("Session Ended"),
contentPadding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 16.0),
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text('Some Error Occured. ERROR CODE: $code'),
),
if (reason != null)
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text('Failure Reason: $reason'),
),
Row(
children: [
TextButton(
style: TextButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Theme.of(context).colorScheme.secondary,
),
onPressed: () {
Navigator.pop(context);
},
child: Text('CLOSE'),
),
],
),
],
);
},
);
}
_onSignTransaction(
int id,
WCEthereumTransaction ethereumTransaction,
) {
_onTransaction(
id: id,
ethereumTransaction: ethereumTransaction,
title: 'Sign Transaction',
onConfirm: () async {
final creds = EthPrivateKey.fromHex(privateKey);
final tx = await _web3client.signTransaction(
creds,
_wcEthTxToWeb3Tx(ethereumTransaction),
chainId: _wcClient.chainId!,
);
_wcClient.approveRequest<String>(
id: id,
result: bytesToHex(tx),
);
Navigator.pop(context);
},
onReject: () {
_wcClient.rejectRequest(id: id);
Navigator.pop(context);
},
);
}
_onSendTransaction(
int id,
WCEthereumTransaction ethereumTransaction,
) {
_onTransaction(
id: id,
ethereumTransaction: ethereumTransaction,
title: 'Send Transaction',
onConfirm: () async {
final creds = EthPrivateKey.fromHex(privateKey);
final txhash = await _web3client.sendTransaction(
creds,
_wcEthTxToWeb3Tx(ethereumTransaction),
chainId: _wcClient.chainId!,
);
debugPrint('txhash $txhash');
_wcClient.approveRequest<String>(
id: id,
result: txhash,
);
Navigator.pop(context);
},
onReject: () {
_wcClient.rejectRequest(id: id);
Navigator.pop(context);
},
);
}
_onTransaction({
required int id,
required WCEthereumTransaction ethereumTransaction,
required String title,
required VoidCallback onConfirm,
required VoidCallback onReject,
}) async {
BigInt gasPrice = BigInt.parse(ethereumTransaction.gasPrice ?? '0');
if (gasPrice == BigInt.zero) {
gasPrice = await _web3client.estimateGas();
}
showDialog(
context: context,
builder: (_) {
return SimpleDialog(
title: Column(
children: [
if (_wcClient.remotePeerMeta!.icons.isNotEmpty)
Container(
height: 100.0,
width: 100.0,
padding: const EdgeInsets.only(bottom: 8.0),
child: Image.network(_wcClient.remotePeerMeta!.icons.first),
),
Text(
_wcClient.remotePeerMeta!.name,
style: TextStyle(
fontWeight: FontWeight.normal,
fontSize: 20.0,
),
),
],
),
contentPadding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 16.0),
children: [
Container(
alignment: Alignment.center,
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
title,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18.0,
),
),
),
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Receipient',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
const SizedBox(height: 8.0),
Text(
'${ethereumTransaction.to}',
style: TextStyle(fontSize: 16.0),
),
],
),
),
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Row(
children: [
Expanded(
flex: 2,
child: Text(
'Transaction Fee',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
),
Expanded(
child: Text(
'${EthConversions.weiToEthUnTrimmed(gasPrice * BigInt.parse(ethereumTransaction.gas ?? '0'), 18)} MATIC',
style: TextStyle(fontSize: 16.0),
),
),
],
),
),
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Row(
children: [
Expanded(
flex: 2,
child: Text(
'Transaction Amount',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
),
Expanded(
child: Text(
'${EthConversions.weiToEthUnTrimmed(BigInt.parse(ethereumTransaction.value ?? '0'), 18)} MATIC',
style: TextStyle(fontSize: 16.0),
),
),
],
),
),
Theme(
data:
Theme.of(context).copyWith(dividerColor: Colors.transparent),
child: Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: ExpansionTile(
tilePadding: EdgeInsets.zero,
title: Text(
'Data',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
children: [
Text(
'${ethereumTransaction.data}',
style: TextStyle(fontSize: 16.0),
),
],
),
),
),
Row(
children: [
Expanded(
child: TextButton(
style: TextButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Theme.of(context).colorScheme.secondary,
),
onPressed: onConfirm,
child: Text('CONFIRM'),
),
),
const SizedBox(width: 16.0),
Expanded(
child: TextButton(
style: TextButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Theme.of(context).colorScheme.secondary,
),
onPressed: onReject,
child: Text('REJECT'),
),
),
],
),
],
);
},
);
}
_onSign(
int id,
WCEthereumSignMessage ethereumSignMessage,
) {
showDialog(
context: context,
builder: (_) {
return SimpleDialog(
title: Column(
children: [
if (_wcClient.remotePeerMeta!.icons.isNotEmpty)
Container(
height: 100.0,
width: 100.0,
padding: const EdgeInsets.only(bottom: 8.0),
child: Image.network(_wcClient.remotePeerMeta!.icons.first),
),
Text(
_wcClient.remotePeerMeta!.name,
style: TextStyle(
fontWeight: FontWeight.normal,
fontSize: 20.0,
),
),
],
),
contentPadding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 16.0),
children: [
Container(
alignment: Alignment.center,
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
'Sign Message',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18.0,
),
),
),
Theme(
data:
Theme.of(context).copyWith(dividerColor: Colors.transparent),
child: Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: ExpansionTile(
tilePadding: EdgeInsets.zero,
title: Text(
'Message',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
children: [
Text(
ethereumSignMessage.data!,
style: TextStyle(fontSize: 16.0),
),
],
),
),
),
Row(
children: [
Expanded(
child: TextButton(
style: TextButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Theme.of(context).colorScheme.secondary,
),
onPressed: () async {
String signedDataHex;
if (ethereumSignMessage.type ==
WCSignType.TYPED_MESSAGE) {
signedDataHex = EthSigUtil.signTypedData(
privateKey: privateKey,
jsonData: ethereumSignMessage.data!,
version: TypedDataVersion.V4,
);
} else {
final creds = EthPrivateKey.fromHex(privateKey);
final encodedMessage =
hexToBytes(ethereumSignMessage.data!);
final signedData =
await creds.signPersonalMessage(encodedMessage);
signedDataHex = bytesToHex(signedData, include0x: true);
}
debugPrint('SIGNED $signedDataHex');
_wcClient.approveRequest<String>(
id: id,
result: signedDataHex,
);
Navigator.pop(context);
},
child: Text('SIGN'),
),
),
const SizedBox(width: 16.0),
Expanded(
child: TextButton(
style: TextButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Theme.of(context).colorScheme.secondary,
),
onPressed: () {
_wcClient.rejectRequest(id: id);
Navigator.pop(context);
},
child: Text('REJECT'),
),
),
],
),
],
);
},
);
}
Transaction _wcEthTxToWeb3Tx(WCEthereumTransaction ethereumTransaction) {
return Transaction(
from: EthereumAddress.fromHex(ethereumTransaction.from),
to: EthereumAddress.fromHex(ethereumTransaction.to!),
maxGas: ethereumTransaction.gasLimit != null
? int.tryParse(ethereumTransaction.gasLimit!)
: null,
gasPrice: ethereumTransaction.gasPrice != null
? EtherAmount.inWei(BigInt.parse(ethereumTransaction.gasPrice!))
: null,
value: EtherAmount.inWei(BigInt.parse(ethereumTransaction.value ?? '0')),
data: hexToBytes(ethereumTransaction.data!),
nonce: ethereumTransaction.nonce != null
? int.tryParse(ethereumTransaction.nonce!)
: null,
);
}
}
更多关于Flutter钱包连接插件wallet_connect的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter钱包连接插件wallet_connect的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
在Flutter项目中集成WalletConnect插件,可以让你的应用轻松与各种钱包进行交互。以下是一个基本的代码示例,展示如何在Flutter项目中使用wallet_connect
插件来连接和管理钱包。
1. 添加依赖
首先,你需要在pubspec.yaml
文件中添加wallet_connect
插件的依赖:
dependencies:
flutter:
sdk: flutter
wallet_connect: ^最新版本号 # 请替换为实际可用的最新版本号
然后运行flutter pub get
来安装依赖。
2. 配置Android和iOS
由于WalletConnect需要处理URL Scheme和深度链接,你需要在Android和iOS项目中进行一些配置。
Android配置
在android/app/src/main/AndroidManifest.xml
中添加以下代码:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="你的app的scheme" />
</intent-filter>
替换你的app的scheme
为你的应用特定的URL Scheme。
iOS配置
在ios/Runner/Info.plist
中添加以下代码:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>你的app的scheme</string>
</array>
</dict>
</array>
同样替换你的app的scheme
。
3. 使用WalletConnect插件
在你的Flutter项目中,你可以按照以下方式使用wallet_connect
插件:
import 'package:flutter/material.dart';
import 'package:wallet_connect/wallet_connect.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late WalletConnect? wc;
@override
void initState() {
super.initState();
// 初始化WalletConnect
wc = WalletConnect(
bridgeUrl: 'https://bridge.walletconnect.org', // 替换为你的Bridge URL
clientMeta: ClientMeta(
name: 'My Flutter App',
description: 'A Flutter app using WalletConnect',
url: 'https://yourapp.com', // 替换为你的应用URL
icons: [
'https://yourapp.com/icon.png', // 替换为你的应用图标URL
],
),
);
// 监听连接状态变化
wc?.on('connect', (session) {
print('Connected to session: $session');
});
wc?.on('disconnect', (session) {
print('Disconnected from session: $session');
});
// 处理深度链接回调
wc?.handleDeepLink().then((session) {
if (session != null) {
print('Handled deep link for session: $session');
}
}).catchError((error) {
print('Error handling deep link: $error');
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('WalletConnect Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () async {
try {
// 创建会话并显示QRCode
var session = await wc?.createSession({
'chains': ['eip155:1:'], // 替换为你支持的链
'methods': ['eth_sendTransaction'], // 替换为你支持的方法
});
// 显示QRCode的UI代码(这里省略,你可以使用任何QRCode显示库)
} catch (error) {
print('Error creating session: $error');
}
},
child: Text('Create Session'),
),
],
),
),
),
);
}
}
注意事项
- 安全性:确保你的Bridge URL是安全的,并且你的应用正确处理了敏感信息。
- 错误处理:在实际应用中,你需要添加更多的错误处理逻辑来确保应用的稳定性。
- UI优化:上面的代码示例省略了QRCode的显示部分,你可以使用任何QRCode显示库来展示QRCode。
这个示例展示了如何在Flutter项目中集成和使用wallet_connect
插件来管理钱包连接。根据你的具体需求,你可能需要调整配置和代码。