Flutter区块链交互插件dapp_injected_eip155的使用
Flutter区块链交互插件dapp_injected_eip155的使用
dapp_injected_eip155
支持在以太坊网络上注入的DApp - EIP155
特性
支持以太坊网络
方法 | 支持 |
---|---|
signTransaction | ✔️ |
signMessage | ✔️ |
signTypedMessage | ✔️ |
signPersonalMessage | ✔️ |
sendTransaction | ✔️ |
ecRecover | |
requestAccounts | ✔️ |
watchAsset | |
addEthereumChain | ✔️ |
switchEthereumChain | ✔️ |
支持
有两项功能我还没有测试过:
watchAsset
ecRecover
请提供我关于如何测试watchAsset
和ecRecover
的信息和步骤。
我的地址: [0xDBEC6913a697B61F218B4a5D33B7561800Fe04E9]
安装
首先,在你的pubspec.yaml
文件中添加dapp_injected_eip155
作为依赖项。
dependencies:
dapp_injected_eip155: ^1.0.4
要求
首次运行此函数以确保库正常工作。
await ScriptUtils.initProviderScript();
方法
wallet_addEthereumChain
创建一个确认请求,让用户将指定链添加到钱包应用。调用者必须指定链ID和一些链元数据。钱包应用可能会拒绝或接受请求。如果链被添加,则返回null,否则返回错误。由EIP-3085引入。
wallet_switchEthereumChain
请求钱包切换其活动的以太坊链。由EIP-3326引入。
Future<NetworkSupport> _switchChain(int chainId) async {
/// 1. 检查情况:未知的链ID: \(chainId)
///
int index = networkLi.indexWhere((e) => e.chainId == chainId);
if (index == -1) {
throw const NotFoundChainException();
}
/// 2. 显示确认切换链的对话框
/// 用户拒绝
if (userReject) {
throw const UserRejectException();
// 或者使用RpcException带有errorCode和message
// throw const RpcException(4001, 'User rejected the request.');
}
return networkLi[index].rpcUrl;
}
personal_sign
向用户展示纯文本签名挑战,并返回签名响应。在某些其他钱包上等同于eth_sign
,并为签名的消息添加安全前缀,以防止挑战欺骗用户签署金融交易。此方法要求用户已授权与他们的帐户进行交互,因此请确保先调用eth_requestAccounts
(推荐)或wallet_requestPermissions
。
eth_signTypedData_v4
向用户展示结构化且可读的数据消息以供签署,并返回签名响应。由EIP-712引入。此方法要求用户已授权与他们的帐户进行交互,因此请确保先调用eth_requestAccounts
(推荐)或wallet_requestPermissions
。
eth_sendTransaction
创建一个新的钱包确认,从用户的帐户发起以太坊交易。此方法要求用户已授权与他们的帐户进行交互,因此请确保先调用eth_requestAccounts
(推荐)或wallet_requestPermissions
。
Future<String> _onSignTransaction(int? chainId, String? rpcUrl, Map<String, dynamic> data) async {
if (chainId == null || rpcUrl == null) {
throw const NotFoundChainException();
}
/// 1. 未经授权
/// 请求的帐户和/或方法未得到用户的授权。
/// throw const UnauthorizedException();
///
/// 2. 用户拒绝
/// throw const RpcException(4001, 'User rejected the request.');
///
JsTransaction transaction = JsTransaction.fromJson(data);
try {
final tx = Transaction(
from: EthereumAddress.fromHex(transaction.from),
data: hexToBytes(transaction.data),
value: EtherAmount.fromBase10String(EtherUnit.wei, transaction.value ?? '0'),
to: EthereumAddress.fromHex(transaction.to),
// maxGas: 61931, /// mock or transaction.gas
);
final client = Web3Client(rpcUrl, http.Client());
final hash = await client.sendTransaction(
EthPrivateKey.fromHex(_privateKey),
tx,
chainId: chainId,
);
return hash;
} on RPCError catch(e) {
throw RpcException(e.errorCode, e.message);
} catch(e) {
rethrow;
}
}
错误
要发送错误消息,需要抛出异常。我已经提供了以下特定异常:
throw UserRejectException();
throw NotFoundChainException();
throw InvalidInputException();
throw UnSupportMethodException();
throw UnauthorizedException();
此外,您可以编辑异常以包含特定的errorCode
和message
:
throw RpcException(4001, 'User rejected the request.');
/// errorCode: 4001
/// message: User rejected the request.
提供商错误
代码 | 消息 |
---|---|
4001 | 用户拒绝了请求。 |
4100 | 请求的帐户和/或方法未得到用户的授权。 |
4200 | 请求的方法不被此以太坊提供商支持。 |
4900 | 提供商已断开与所有链的连接。 |
4901 | 提供商已断开与指定链的连接。 |
RPC 错误
代码 | 消息 |
---|---|
-32000 | 输入无效。 |
-32001 | 资源未找到。 |
-32002 | 资源不可用。 |
-32003 | 交易被拒绝。 |
-32004 | 方法不受支持。 |
-32005 | 请求限制超出。 |
-32700 | 服务器收到无效的JSON。解析JSON时服务器端发生错误。 |
-32600 | 发送的JSON不是有效的请求对象。 |
-32601 | 方法不存在/不可用。 |
-32602 | 无效的方法参数。 |
-32603 | JSON-RPC内部错误。 |
4902 | 无法识别的链ID。尝试先使用wallet_addEthereumChain添加链。 |
示例代码
import 'dart:async';
import 'package:eth_sig_util/eth_sig_util.dart';
import 'package:flutter/material.dart';
import 'package:web3dart/crypto.dart';
import 'package:dapp_injected_eip155/dapp_injected_eip155.dart';
import 'package:web3dart/json_rpc.dart';
import 'js_transaction.dart';
import 'package:web3dart/web3dart.dart';
import 'package:http/http.dart' as http;
import 'network_support.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'widgets/progress_painter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await ScriptUtils.initProviderScript();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
appBarTheme: const AppBarTheme(backgroundColor: Colors.white),
scaffoldBackgroundColor: Colors.white,
brightness: Brightness.light,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
[@override](/user/override)
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final TextEditingController _urlController =
TextEditingController(text: 'https://pancakeswap.finance/');
[@override](/user/override)
void dispose() {
_urlController.dispose();
super.dispose();
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: _urlController,
),
const SizedBox(height: 30),
ElevatedButton(
onPressed: () async {
Navigator.of(context).push(
MaterialPageRoute(
builder: (c) => ExampleDapp(
initialUrl: _urlController.text.trim(),
)),
);
},
child: const Text('DApp Injected'),
),
],
),
),
);
}
}
class ExampleDapp extends StatefulWidget {
final String initialUrl;
const ExampleDapp({super.key, required this.initialUrl});
[@override](/user/override)
State<ExampleDapp> createState() => _ExampleDappState();
}
class _ExampleDappState extends State<ExampleDapp> {
final List<NetworkSupport> networkLi = [
NetworkSupport(
chainId: 1,
chainName: 'Ethereum',
nativeCurrency: NativeCurrency(
name: 'ETH',
symbol: 'eth',
decimals: 18,
),
rpcUrls: [
"https://rpc.ankr.com/eth",
],
blockExplorerUrls: [
"https://etherscan.io/tx/",
],
),
];
final List<String> addressLi = [
'0xDBEC6913a697B61F218B4a5D33B7561800Fe04E9',
'0x1bc9BDF4f77AD6662adD75628c6A65B4062Fd3f3',
];
final String _privateKey = ''; // mock
late NetworkSupport initNetwork = networkLi.first;
late String currentAddress = addressLi.first;
final StreamController<double> _progressController =
StreamController<double>();
InAppWebViewController? _webViewController;
[@override](/user/override)
void dispose() {
_progressController.sink.close();
_progressController.close();
super.dispose();
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: _buildAppBar(),
body: SafeArea(
top: false,
child: _buildBody(context),
),
);
}
AppBar _buildAppBar() {
return AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded),
onPressed: () async {
bool canBack = (await _webViewController?.canGoBack()) ?? false;
if (canBack) {
await _webViewController?.goBack();
return;
}
if (mounted) {
Navigator.of(context).pop();
}
},
),
title: const Text('DApp Browser'),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(2),
child: StreamBuilder<double>(
initialData: 0,
stream: _progressController.stream,
builder: (c, asyncSnapshot) {
if (asyncSnapshot.data == 1) {
return const SizedBox();
}
return ProgressPainter(
progress: asyncSnapshot.data!,
baseColor: Theme.of(context).appBarTheme.backgroundColor,
primaryColor: Colors.blueAccent,
);
},
),
),
actions: [
_buildAccountSwitcher(context),
],
);
}
Widget _buildBody(BuildContext context) {
return DAppInjectedView(
loadingChild: const Center(
child: SizedBox(
height: 32,
width: 32,
child: CircularProgressIndicator(),
),
),
initialScript: '''
window.ethereum.isMetaMask = true;
''',
/// 设置isMetaMask标志
initialUrl: widget.initialUrl,
currentProvider: WalletWeb3Provider(
address: currentAddress,
chainId: initNetwork.chainId,
rpcUrl: initNetwork.rpcUrl,
),
initialSettings: InAppWebViewSettings(
isInspectable: true, // kDebugMode,
mediaPlaybackRequiresUserGesture: true,
allowsInlineMediaPlayback: true,
iframeAllowFullscreen: true,
javaScriptEnabled: true,
supportMultipleWindows: false,
/// 警告:如果设置为'true'
),
methodCallbacks: MethodCallbacks(
onSignTransaction: _onSignTransaction,
onSwitchNetwork: (chainId) async {
/// 需要检查链ID是否不支持
int index = networkLi.indexWhere((e) => e.chainId == chainId);
if (index == -1) {
throw const NotFoundChainException();
}
// currentNetwork = networkLi[index];
return networkLi[index].rpcUrl;
},
onAddNetwork: (data) async {
/// 用户拒绝
// throw const UserRejectException();
NetworkSupport network = NetworkSupport.fromJson(data);
networkLi.add(network);
return (chainId: network.chainId!, rpcUrl: network.rpcUrl);
},
onSignMessage: _onSignMessage,
onSignPersonalMessage: _onSignPersonalMessage,
onSignTypeMessage: _onSignTypeMessage,
// onEcRecover: _onEcRecover,
),
onWebViewCreated: (controller) {
_webViewController = controller;
},
onWebViewClosed: () {
_webViewController = null;
},
shouldOverrideUrlLoading: (c, navAction) async {
// final url = navAction.request.url.toString();
return NavigationActionPolicy.ALLOW;
},
onProgressChanged: (controller, progress) {
_progressController.sink.add(progress / 100);
},
onReceivedError: (controller, request, error) {
_progressController.sink.add(1);
},
);
}
Widget _buildAccountSwitcher(BuildContext context) {
return IconButton(
onPressed: () async {
await showModalBottomSheet(
context: context,
builder: (context) {
return ListView.builder(
padding: const EdgeInsets.only(top: 25, bottom: 35),
itemBuilder: (c, index) {
bool isActive = currentAddress == addressLi[index];
return GestureDetector(
onTap: () {
Navigator.of(context).pop();
if (isActive) return;
currentAddress = addressLi[index];
setState(() {});
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 16),
child: Text(
addressLi[index],
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: isActive
? Theme.of(context).primaryColor
: null,
),
),
),
);
},
itemCount: addressLi.length,
);
});
},
icon: const Icon(Icons.menu),
);
}
Future<bool> _confirmSignMessage(String title, String message) async {
bool? status = await showModalBottomSheet(
context: context,
builder: (c) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 16),
child: Column(
children: [
Text(
title,
style: Theme.of(context).textTheme.headlineLarge,
),
const SizedBox(height: 25),
Expanded(
child: SingleChildScrollView(
child: Text(
message,
style: Theme.of(context).textTheme.bodyLarge,
),
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () async {
Navigator.of(context).pop(false);
},
child: const Text('取消'),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: () async {
Navigator.of(context).pop(true);
},
child: const Text('确认'),
),
),
],
)
],
),
);
},
);
return status ?? false;
}
Future<String> _onSignTransaction(
int? chainId, String? rpcUrl, Map<String, dynamic> data) async {
if (chainId == null || rpcUrl == null) {
throw const NotFoundChainException();
}
/// 如果用户拒绝
// throw const UserRejectException();
JsTransaction transaction = JsTransaction.fromJson(data);
try {
final tx = Transaction(
from: EthereumAddress.fromHex(transaction.from),
data: hexToBytes(transaction.data),
value: EtherAmount.fromBase10String(
EtherUnit.wei, transaction.value ?? '0'),
to: EthereumAddress.fromHex(transaction.to),
maxGas: 61931,
/// mock or transaction.gas
);
final client = Web3Client(rpcUrl, http.Client());
final hash = await client.sendTransaction(
EthPrivateKey.fromHex(_privateKey),
tx,
chainId: chainId,
);
return hash;
} on RPCError catch (e) {
throw RpcException(e.errorCode, e.message);
} catch (e) {
rethrow;
}
}
Future<String> _onSignMessage(String type, String message) async {
bool isAccept = await _confirmSignMessage(type, message);
if (!isAccept) {
// 用户拒绝
throw const UserRejectException();
}
String signature = EthSigUtil.signMessage(
privateKey: _privateKey,
message: hexToBytes(message),
);
return signature;
}
Future<String> _onSignPersonalMessage(String type, String message) async {
bool isAccept = await _confirmSignMessage(type, message);
if (!isAccept) {
// 用户拒绝
throw const UserRejectException();
}
String signature = EthSigUtil.signPersonalMessage(
privateKey: _privateKey,
message: hexToBytes(message),
);
return signature;
}
Future<String> _onSignTypeMessage(String type, JsSignTypeData data) async {
bool isAccept = await _confirmSignMessage(type, data.raw);
if (!isAccept) {
// 用户拒绝
throw const UserRejectException();
}
TypedDataVersion? version;
switch (data.version) {
case 'V1':
version = TypedDataVersion.V1;
break;
case 'V3':
version = TypedDataVersion.V3;
break;
case 'V4':
version = TypedDataVersion.V4;
break;
default:
break;
}
if (version == null) {
throw const InvalidInputException();
}
final String signature = EthSigUtil.signTypedData(
privateKey: _privateKey,
jsonData: data.raw,
version: version,
);
return signature;
}
}
更多关于Flutter区块链交互插件dapp_injected_eip155的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter区块链交互插件dapp_injected_eip155的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何使用Flutter区块链交互插件dapp_injected_eip155
的示例代码。这个插件允许你与遵循EIP-155标准的Web3钱包(如MetaMask)进行交互。假设你已经设置好了Flutter开发环境,并且已经添加了dapp_injected_eip155
依赖到你的pubspec.yaml
文件中。
1. 添加依赖
首先,确保在pubspec.yaml
中添加dapp_injected_eip155
依赖:
dependencies:
flutter:
sdk: flutter
dapp_injected_eip155: ^latest_version # 替换为最新版本号
2. 导入包并初始化
在你的Dart文件中导入dapp_injected_eip155
包,并初始化:
import 'package:flutter/material.dart';
import 'package:dapp_injected_eip155/dapp_injected_eip155.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Ethereum? ethereumClient;
@override
void initState() {
super.initState();
initEthereumClient();
}
Future<void> initEthereumClient() async {
try {
ethereumClient = await Ethereum.connectEIP155();
print('Connected to Ethereum client: ${ethereumClient!.clientVersion}');
} catch (e) {
print('Failed to connect to Ethereum client: $e');
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Flutter EIP-155 Interaction'),
),
body: Center(
child: ethereumClient != null
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Account: ${ethereumClient!.selectedAddress!}'),
ElevatedButton(
onPressed: () async {
await fetchBalance();
},
child: Text('Fetch Balance'),
),
],
)
: CircularProgressIndicator(),
),
),
);
}
Future<void> fetchBalance() async {
if (ethereumClient == null) return;
try {
BigInt balance = await ethereumClient!.getBalance(ethereumClient!.selectedAddress!);
print('Balance: $balance wei');
// Convert wei to ether if needed
double etherBalance = balance.toDouble() / (10 ** 18);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Balance: $etherBalance ether')),
);
} catch (e) {
print('Failed to fetch balance: $e');
}
}
}
3. 解释代码
- 依赖导入:在
pubspec.yaml
中添加dapp_injected_eip155
依赖。 - 初始化Ethereum客户端:在
initState
方法中,调用Ethereum.connectEIP155()
来连接到用户的Ethereum客户端(如MetaMask)。 - UI显示:如果成功连接到Ethereum客户端,显示用户的账户地址,并提供一个按钮来获取余额。
- 获取余额:点击按钮时,调用
ethereumClient!.getBalance()
方法来获取用户的余额,并将其从wei单位转换为ether单位显示。
注意事项
- 确保你的应用运行在支持Web3注入的钱包环境中,如MetaMask浏览器扩展。
- 处理错误和异常,尤其是在处理区块链交互时,网络延迟和节点问题可能会导致请求失败。
- 考虑到安全性,确保你的应用不会泄露用户的私钥或敏感信息。
这个示例展示了如何使用dapp_injected_eip155
插件与Ethereum客户端进行基本的交互。根据需求,你可以扩展这个插件的功能,如发送交易、读取智能合约数据等。