Flutter区块链交互插件flutter_web3的使用
Flutter区块链交互插件flutter_web3的使用
Introduction
flutter_web3 v2 是一个全 Dart 类和函数包装器,用于:
- 从提供者(例如 MetaMask)获取 Ethereum 对象。
- 使用 Ether.js 包。
- 可用于签名交易、与智能合约交互、查询区块链数据以及开发 DApp 的各种辅助函数。
- 使用 Wallet Connect Provider 包。
- 启用 QR 码模态框交互,并支持使用 Wallet Connect 的钱包。
此包特别适用于在 Flutter Web 上开发跨(多)链 DApp。
V2.1 Changes
版本 2.1 引入了 EIP-1559 属性:
- 许多(所有)类中的
gasPrice
属性现在是可选的。- 在主网上将为
null
,但在尚未实现 EIP-1559 的分叉或链上不为null
。
- 在主网上将为
- 为
Transaction
、TransactionRequest
和TransactionOverride
添加了maxFeePerGas
和maxPriorityFeePerGas
属性。 - 为
Provider
添加了getFeeData
方法。 - 为
Block
添加了baseFee
属性。
欢迎提交缺失功能/属性的拉取请求。
Example Usage And Tutorial
Auction
- 一个基于 Flutter 构建的 ERC20 代币拍卖网站。
- 源码
Building Modern Web Dapp with Flutter
Getting Started
Installing
要使用 Flutter Web3 包,请运行:
flutter pub add flutter_web3
Ethers JS and Wallet Connect Provider
要使用 Ethers JS 和 Wallet Connect Provider,需要在 web/index.html
中包含脚本:
<!-- Ethers -->
<script src="https://cdn.ethers.io/lib/ethers-5.4.umd.min.js" type="application/javascript"></script>
<!-- Wallet Connect -->
<script src="https://cdn.jsdelivr.net/npm/@walletconnect/web3-provider@1.6.5/dist/umd/index.min.js" type="application/javascript"></script>
Ethereum Provider
Prompt the connection to MetaMask or other provider
if (ethereum != null) {
try {
final accs = await ethereum!.requestAccount();
accs; // [foo, bar]
} on EthereumUserRejected {
print('User rejected the modal');
}
}
Subscribe to Ethereum events
ethereum!.onChainChanged((chainId) {
chainId; // foo
});
ethereum!.onAccountsChanged((accounts) {
print(accounts); // ['0xbar']
});
ethereum!.on('message', (message) {
dartify(message); // baz
});
Call other JSON RPC API
final result = await ethereum!.request<BigInt>('eth_gasPrice');
result; // 5000000000
result is BigInt; // true
Ethers
Connecting to Ethereum: Metamask
final web3provider = Web3Provider(ethereum!);
// or
final web3provider = Web3Provider.fromEthereum(ethereum!);
// or
provider; // Default Web3Provider instance from default Ethereum provider
Connecting to Ethereum: RPC
final rpcProvider = JsonRpcProvider(); // Rpc Provider from default Rpc url, i.e. https://localhost:8545
final rpcProvider = JsonRpcProvider('https://bsc-dataseed.binance.org/'); // Rpc Provider from specific Rpc url
Querying the Blockchain
await provider!.getBlockNumber(); // 9261427
await provider!.getLastestBlock(); // Block: 9261427 0x9e7900b8 mined at 2021-07-18T16:58:45.000 with diff 2
await provider!.getBalance('0xgarply'); // 315752957360231815
await provider!.getTransactionReceipt('0xwaldo'); // TransactionReceipt: 0x1612d8ba from 0x6886ec02 with 20 confirmations and 12 logs
Signer
Query data about your account
final signer = provider!.getSigner();
await signer.getBalance(); // 315752957360231815
await signer.getTransactionCount(BlockTag.latest); // 1
Send/write to the Blockchain
final tx = await provider!.getSigner().sendTransaction(
TransactionRequest(
to: '0xcorge',
value: BigInt.from(1000000000),
),
);
tx.hash; // 0xplugh
final receipt = await tx.wait();
receipt is TransactionReceipt; // true
Wallet
Create a wallet from mnemonic phrase
final mnemonic = "announce room limb pattern dry unit scale effort smooth jazz weasel alcohol";
final wallet = Wallet.fromMnemonic(mnemonic);
Or directly from private key
final anotherWallet = Wallet(wallet.privateKey);
Then connect the wallet to specific provider
final testnetProvider = JsonRpcProvider('https://data-seed-prebsc-1-s2.binance.org:8545/');
final walletWithProvider = wallet.connect(testnetProvider);
After that, the wallet object can be used as normal signer object
final tx = await walletWithProvider.sendTransaction(
TransactionRequest(
to: '0xbar',
value: BigInt.from(100),
),
); // Send 100 wei to `0xbar`
tx.hash; // 0xbash
Contract
Define ABI object
final humanReadableAbi = [
"function balanceOf(address owner) view returns (uint256 balance)",
"function addPerson(tuple(string name, uint16 age) person)", // Or "function addPerson((string name, uint16 age) person)"
];
final jsonAbi = '''[
{
"type": "function",
"name": "balanceOf",
"constant": true,
"stateMutability": "view",
"payable": false,
"inputs": [
{ "type": "address", "name": "owner" }
],
"outputs": [
{ "type": "uint256" }
]
}
]''';
final humanInterface = Interface(humanReadableAbi);
final jsonInterface = Interface(jsonAbi);
humanInterface.format(FormatTypes.minimal); // [function balanceOf(address) view returns (uint256)]
humanInterface.format(FormatTypes.minimal)[0] == jsonInterface.format(FormatTypes.minimal)[0]; // true
Initialize Contract object
final abi = [
"function name() view returns (string)",
"function symbol() view returns (string)",
"function balanceOf(address) view returns (uint)",
"function transfer(address to, uint amount)",
"event Transfer(address indexed from, address indexed to, uint amount)"
];
final busdAddress = '0xe9e7cea3dedca5984780bafc599bd69add087d56';
final busd = Contract(
busdAddress,
abi,
provider!,
);
final anotherBusd = Contract(
busdAddress,
Interface(abi),
provider!.getSigner(),
);
Read-only method
await busd.call<String>('name'); // BUSD Token
await busd.call<String>('symbol'); // BUSD
await busd.call<BigInt>(
'balanceOf',
['0xthud'],
); // 2886780594123782414119
Write/State-changing method
final tx = await anotherBusd.send('transfer', ['0xfoo', '1000000000000000000']);
tx.hash; // 0xbar
final receipt = tx.wait(); // Wait until transaction complete
receipt.from; // 0xthud
receipt.to; // 0xe9e7cea3dedca5984780bafc599bd69add087d56 (BUSD Address)
Listening to Events
busd.on('Transfer', (from, to, amount, event) {
from; // 0x0648ff5de80Adf54aAc07EcE2490f50a418Dde23
to; // 0x12c64E61440582793EF4964A36d98020d83490a3
amount; // 1015026418461703883891
Event.fromJS(event); // Event: Transfer Transfer(address,address,uint256) with args [0x0648ff5de80Adf54aAc07EcE2490f50a418Dde23, 0x12c64E61440582793EF4964A36d98020d83490a3, 1015026418461703883891]
});
final myAddress = "0x8ba1f109551bD432803012645Ac136ddd64DBA72";
final filter = busd.getFilter('Transfer', [null, myAddress]);
busd.on(filter, (from, to, amount, event) {
// ...
});
Query Historic Events
final filter = busd.getFilter('Transfer');
final events = await busd.queryFilter(filter, -100);
events.first; // Event: Transfer Transfer(address,address,uint256) with args [0x209F2A37Ccb5672794329cB311406A995de9347c, 0x928bE3DEB1f8B9e4A24a5744bD313E726462961D, 150000000000000000000]
Alternatively for ERC20 Contract
final token = ContractERC20('0xfoo', provider!.getSigner());
await token.name; // foo
await token.symbol; // bar
await token.decimals; // baz
final tx = await token.transfer('0xbar', BigInt.parse('10000000000000'));
tx.hash; // 0xbarbaz
token.onApproval((owner, spender, value, event) {
owner; // 0xfoo
spender; // 0xbar
value; // 0xbaz
});
Wallet Connect Provider
Create WalletConnectProvider
object
final wc = WalletConnectProvider.fromRpc(
{56: 'https://bsc-dataseed.binance.org/'},
chainId: 56,
network: 'binance',
);
final infuraWc = WalletConnectProvider.fromInfura('https://foo.infura.io/v3/barbaz');
final binanceWc = WalletConnectProvider.binance();
final polygonWc = WalletConnectProvider.polygon();
Enable the session, toggle QRCode Modal
await wc.connect();
Use in Ethers Web3Provider
final web3provider = Web3Provider.fromWalletConnect(wc);
await web3provider.getGasPrice(); // 5000000000
示例代码
以下是一个完整的示例代码,展示了如何使用 flutter_web3
插件与区块链进行交互。
import 'package:flutter/material.dart';
import 'package:flutter_web3/flutter_web3.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) => GetMaterialApp(
title: 'Flutter Web3 Example',
home: Home(),
);
}
class HomeController extends GetxController {
bool get isInOperatingChain => currentChain == OPERATING_CHAIN;
bool get isConnected => Ethereum.isSupported && currentAddress.isNotEmpty;
String currentAddress = '';
int currentChain = -1;
bool wcConnected = false;
static const OPERATING_CHAIN = 56;
final wc = WalletConnectProvider.binance();
Web3Provider? web3wc;
connectProvider() async {
if (Ethereum.isSupported) {
final accs = await ethereum!.requestAccount();
if (accs.isNotEmpty) {
currentAddress = accs.first;
currentChain = await ethereum!.getChainId();
}
update();
}
}
connectWC() async {
await wc.connect();
if (wc.connected) {
currentAddress = wc.accounts.first;
currentChain = 56;
wcConnected = true;
web3wc = Web3Provider.fromWalletConnect(wc);
}
update();
}
clear() {
currentAddress = '';
currentChain = -1;
wcConnected = false;
web3wc = null;
update();
}
init() {
if (Ethereum.isSupported) {
connectProvider();
ethereum!.onAccountsChanged((accs) {
clear();
});
ethereum!.onChainChanged((chain) {
clear();
});
}
}
getLastestBlock() async {
print(await provider!.getLastestBlock());
print(await provider!.getLastestBlockWithTransaction());
}
testProvider() async {
final rpcProvider = JsonRpcProvider('https://bsc-dataseed.binance.org/');
print(rpcProvider);
print(await rpcProvider.getNetwork());
}
test() async {}
testSwitchChain() async {
await ethereum!.walletSwitchChain(97, () async {
await ethereum!.walletAddChain(
chainId: 97,
chainName: 'Binance Testnet',
nativeCurrency: CurrencyParams(name: 'BNB', symbol: 'BNB', decimals: 18),
rpcUrls: ['https://data-seed-prebsc-1-s1.binance.org:8545/'],
);
});
}
@override
void onInit() {
init();
super.onInit();
}
}
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetBuilder<HomeController>(
init: HomeController(),
builder: (h) => Scaffold(
body: Center(
child: Column(children: [
Container(height: 10),
Builder(builder: (_) {
var shown = '';
if (h.isConnected && h.isInOperatingChain)
shown = 'You\'re connected!';
else if (h.isConnected && !h.isInOperatingChain)
shown = 'Wrong chain! Please connect to BSC. (56)';
else if (Ethereum.isSupported)
return OutlinedButton(
child: Text('Connect'), onPressed: h.connectProvider);
else
shown = 'Your browser is not supported!';
return Text(shown,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20));
}),
Container(height: 30),
if (h.isConnected && h.isInOperatingChain) ...[
TextButton(
onPressed: h.getLastestBlock,
child: Text('get lastest block')),
Container(height: 10),
TextButton(
onPressed: h.testProvider,
child: Text('test binance rpc provider')),
Container(height: 10),
TextButton(onPressed: h.test, child: Text('test')),
Container(height: 10),
TextButton(
onPressed: h.testSwitchChain,
child: Text('test switch chain')),
],
Container(height: 30),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Wallet Connect connected: ${h.wcConnected}'),
Container(width: 10),
OutlinedButton(
child: Text('Connect to WC'), onPressed: h.connectWC)
],
),
Container(height: 30),
if (h.wcConnected && h.wc.connected) ...[
Text(h.wc.walletMeta.toString()),
],
]),
),
),
);
}
}
希望这些信息对你有帮助!如果你有任何问题或需要进一步的帮助,请随时提问。
更多关于Flutter区块链交互插件flutter_web3的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter区块链交互插件flutter_web3的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,作为一个IT专家,我将为你提供一个关于如何在Flutter项目中使用flutter_web3
插件与区块链进行交互的代码案例。flutter_web3
是一个用于与以太坊区块链进行交互的Flutter插件,它允许你连接到以太坊节点、查询区块链信息、发送交易等。
首先,你需要确保你的Flutter项目已经设置好,并且已经添加了flutter_web3
依赖。你可以在你的pubspec.yaml
文件中添加以下依赖:
dependencies:
flutter:
sdk: flutter
flutter_web3: ^x.y.z # 请替换为最新版本号
然后运行flutter pub get
来安装依赖。
以下是一个简单的代码案例,演示了如何使用flutter_web3
连接到以太坊节点、获取账户余额以及发送一个简单的交易。
1. 连接到以太坊节点并获取账户余额
import 'package:flutter/material.dart';
import 'package:flutter_web3/flutter_web3.dart';
import 'dart:convert';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Flutter Web3 Demo'),
),
body: Center(
child: MyHomePage(),
),
),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String accountBalance = '';
@override
void initState() {
super.initState();
_getAccountBalance();
}
Future<void> _getAccountBalance() async {
// 替换为你的以太坊节点URL和私钥
String rpcUrl = 'https://mainnet.infura.io/v3/YOUR_PROJECT_ID';
String privateKey = 'YOUR_PRIVATE_KEY';
final client = Web3Client(rpcUrl, Credentials.fromPrivateKey(privateKey));
try {
final balance = await client.getBalance(
'YOUR_ETHEREUM_ADDRESS',
DefaultBlockParameterName.latest,
);
setState(() {
accountBalance = 'Balance: ${balance / BigInt.fromInt(1e18)} ETH';
});
} catch (e) {
print('Error fetching balance: $e');
}
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(accountBalance),
],
);
}
}
2. 发送交易
发送交易需要构建一个交易对象,并使用私钥进行签名,然后发送到以太坊节点。下面是一个简单的发送ETH到另一个地址的示例:
Future<void> _sendTransaction() async {
// 替换为你的以太坊节点URL和私钥
String rpcUrl = 'https://mainnet.infura.io/v3/YOUR_PROJECT_ID';
String privateKey = 'YOUR_PRIVATE_KEY';
String toAddress = 'RECIPIENT_ETHEREUM_ADDRESS';
BigInt amount = BigInt.fromInt(1) * BigInt.fromInt(1e18); // 发送1 ETH
final client = Web3Client(rpcUrl, Credentials.fromPrivateKey(privateKey));
try {
final transaction = TransactionBuilder(chainId: BigInt.fromInt(1))
.from(Credentials.fromPrivateKey(privateKey).address)
.to(toAddress)
.value(amount)
.gasPrice(BigInt.fromInt(20 * 1e9)) // 设置gas价格
.gasLimit(BigInt.fromInt(21000)) // 设置gas上限
.build()
.sign(Credentials.fromPrivateKey(privateKey));
final receipt = await client.sendSignedTransaction(transaction.rawTransaction);
print('Transaction receipt: ${jsonEncode(receipt)}');
} catch (e) {
print('Error sending transaction: $e');
}
}
你可以在按钮点击事件中调用_sendTransaction()
方法:
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(accountBalance),
ElevatedButton(
onPressed: _sendTransaction,
child: Text('Send Transaction'),
),
],
);
}
注意:
- 在生产环境中,请确保不要硬编码私钥。
- 发送交易前,请确保你的账户有足够的余额和正确的gas价格。
- 使用Infura或其他服务时,请确保你的项目ID是有效的。
这个代码案例为你提供了一个基础框架,你可以在此基础上进行扩展,以满足你的具体需求。