Flutter区块链交互插件flutter_web3的使用

发布于 1周前 作者 vueper 来自 Flutter

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
  • TransactionTransactionRequestTransactionOverride 添加了 maxFeePerGasmaxPriorityFeePerGas 属性。
  • 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

1 回复

更多关于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'),
      ),
    ],
  );
}

注意

  1. 在生产环境中,请确保不要硬编码私钥。
  2. 发送交易前,请确保你的账户有足够的余额和正确的gas价格。
  3. 使用Infura或其他服务时,请确保你的项目ID是有效的。

这个代码案例为你提供了一个基础框架,你可以在此基础上进行扩展,以满足你的具体需求。

回到顶部