• Flutter调用以太坊区块链智能合约 (私链)


    该案例适用于Flutter调用以太坊solidity智能合约


    前言


    一、需要使用哪些开发工具?

    1. Remix IDE
    2. android studio
    3. ubuntu
    4. truffle

    二、操作步骤(如果出现问题看最后的总结)

    1.搭建Flutter Project

     首先打开Android studio开发工具,按照如下步骤进行新建项目

     

     然后会自动生成一个Flutter项目的demo,点击如下配置文件,该文件右上角会出现 Pub get

     然后可以运行该demo了。


    2.集成Truffle框架

       1、首先确保你的truffle已经安装,在Terminal控制台进行 truffle version

    2、接着truffle init 会生成几个文件夹

     3、在contract编写自己的智能合约并部署,注意修改truffle-config.js文件的配置


     3.Copy代码

       首先在lib文件夹下面新建如下文件夹并进行下面的dart代码复制

    1. main.dart
      1. import 'package:flutter/material.dart';
      2. import 'package:flutter_basic_dapp/pages/home_page.dart';
      3. import 'package:flutter_dotenv/flutter_dotenv.dart';
      4. Future main() async {
      5. await dotenv.load(fileName: ".env");
      6. runApp(MyApp());
      7. }
      8. class MyApp extends StatelessWidget {
      9. const MyApp({Key? key}) : super(key: key);
      10. @override
      11. Widget build(BuildContext context) {
      12. return MaterialApp(
      13. debugShowCheckedModeBanner: false,
      14. home: HomePage(),
      15. );
      16. }
      17. }
    2.  ethereum_utils.dart

      1. import 'dart:convert';
      2. import 'package:flutter/services.dart';
      3. import 'package:flutter_dotenv/flutter_dotenv.dart';
      4. import 'package:http/http.dart';
      5. import 'package:web3dart/web3dart.dart';
      6. var apiUrl = "http://192.168.1.161:8545"; //Replace with your API
      7. var httpClient = Client();
      8. var web3client = Web3Client(apiUrl, httpClient);
      9. class EthereumUtils {
      10. final contractAddress = dotenv.env['CONTRACT_ADDRESS'];
      11. Future getBalance() async {
      12. final contract = await getDeployedContract();
      13. final etherFunction = contract.function("getBalance");
      14. final result = await web3client.call(contract: contract, function: etherFunction, params: []);
      15. List<dynamic> res = result;
      16. return res[0];
      17. }
      18. Future<String> sendBalance(int amount) async {
      19. var bigAmount = BigInt.from(amount);
      20. EthPrivateKey privateKeyCred = EthPrivateKey.fromHex(dotenv.env['METAMASK_PRIVATE_KEY']!);
      21. DeployedContract contract = await getDeployedContract();
      22. final etherFunction = contract.function("sendBalance");
      23. final result = await web3client.sendTransaction(
      24. privateKeyCred,
      25. Transaction.callContract(
      26. contract: contract,
      27. function: etherFunction,
      28. parameters: [bigAmount],
      29. maxGas: 100000,
      30. ),chainId: 20220824,
      31. fetchChainIdFromNetworkId: false);
      32. return result;
      33. }
      34. Future<String> withDrawBalance(int amount) async {
      35. var bigAmount = BigInt.from(amount);
      36. EthPrivateKey privateKeyCred = EthPrivateKey.fromHex(dotenv.env['METAMASK_PRIVATE_KEY']!);
      37. DeployedContract contract = await getDeployedContract();
      38. final etherFunction = contract.function("withDrawBalance");
      39. final result = await web3client.sendTransaction(
      40. privateKeyCred,
      41. Transaction.callContract(
      42. contract: contract,
      43. function: etherFunction,
      44. parameters: [bigAmount],
      45. maxGas: 100000,
      46. ),chainId: 20220824,
      47. fetchChainIdFromNetworkId: false);
      48. return result;
      49. }
      50. Future getDeployedContract() async {
      51. String abiStringFile = await rootBundle.loadString("assets/artifacts/BasicDapp.json");
      52. var jsonAbi = jsonDecode(abiStringFile);
      53. final contract = DeployedContract(ContractAbi.fromJson(jsonEncode(jsonAbi["abi"]), "BasicDapp"), EthereumAddress.fromHex(contractAddress!));
      54. return contract;
      55. }
      56. }
    3.  home_page.dart

      1. import 'package:flutter/material.dart';
      2. import 'package:flutter_basic_dapp/models/ethereum_utils.dart';
      3. import 'package:flutter_basic_dapp/widgets/button_container_widget.dart';
      4. import 'package:syncfusion_flutter_sliders/sliders.dart';
      5. class HomePage extends StatefulWidget {
      6. const HomePage({Key? key}) : super(key: key);
      7. @override
      8. State createState() => _HomePageState();
      9. }
      10. class _HomePageState extends State<HomePage> {
      11. EthereumUtils ethUtils = EthereumUtils();
      12. double? _value = 0.0;
      13. var _data;
      14. @override
      15. void initState() {
      16. ethUtils.getBalance().then((value) {
      17. _data = value;
      18. setState(() {
      19. });
      20. });
      21. super.initState();
      22. }
      23. @override
      24. Widget build(BuildContext context) {
      25. return Scaffold(
      26. backgroundColor: Colors.deepPurple.withOpacity(.9),
      27. appBar: AppBar(
      28. title: Text(" "),
      29. ),
      30. body: Container(
      31. margin: EdgeInsets.symmetric(horizontal: 20, vertical: 30),
      32. // child: SingleChildScrollView(
      33. child: Column(
      34. mainAxisAlignment: MainAxisAlignment.spaceAround,
      35. children: [
      36. Container(
      37. width: MediaQuery.of(context).size.width,
      38. height: MediaQuery.of(context).size.height * 0.13,
      39. decoration: BoxDecoration(
      40. color: Colors.deepPurple..withOpacity(.4),
      41. borderRadius: BorderRadius.circular(10),
      42. ),
      43. child: Center(
      44. child: Padding(
      45. padding: EdgeInsets.symmetric(horizontal: 10, vertical: 10),
      46. child: Column(
      47. children: [
      48. Text("Current Balance", style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold, color: Colors.white),),
      49. SizedBox(height: 12,),
      50. _data == null ? CircularProgressIndicator() : Text("${_data}", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30, color: Colors.white),)
      51. ],
      52. ),
      53. ),
      54. ),
      55. ),
      56. SizedBox(height: 40,),
      57. SfSlider(
      58. value: _value,
      59. onChanged: (value) {
      60. setState(() {
      61. _value = value;
      62. });
      63. } ,
      64. interval: 1,
      65. activeColor: Colors.white,
      66. enableTooltip: true,
      67. stepSize: 1.0,
      68. showLabels: true,
      69. min: 0.0,
      70. max: 10.0,
      71. ),
      72. SizedBox(height: 40,),
      73. CustomContainerButtonWidget(title: "Get Balance", color: Colors.green, onTap: () {
      74. ethUtils.getBalance().then((value) {
      75. _data = value;
      76. setState(() {
      77. });
      78. });
      79. }),
      80. SizedBox(height: 40,),
      81. CustomContainerButtonWidget(title: "Send Balance", color: Colors.deepPurpleAccent,
      82. onTap: () async {
      83. await ethUtils.sendBalance(_value!.toInt());
      84. if (_value == 0) {
      85. incorrectValueDialogBox(context);
      86. } else {
      87. sendDialogBox(context);
      88. }
      89. }),
      90. SizedBox(height: 40,),
      91. CustomContainerButtonWidget(title: "WithDraw", color: Colors.deepOrange,
      92. onTap: () async {
      93. await ethUtils.withDrawBalance(_value!.toInt());
      94. if (_value == 0) {
      95. incorrectValueDialogBox(context);
      96. } else {
      97. withDrawDialogBox(context);
      98. }
      99. }),
      100. ],
      101. ),
      102. // ),
      103. ),
      104. );
      105. }
      106. incorrectValueDialogBox(BuildContext context) {
      107. return showDialog(
      108. context: context,
      109. builder: (BuildContext context) {
      110. return AlertDialog(
      111. title: Text(
      112. 'Invalid Value',
      113. textAlign: TextAlign.center,
      114. style: TextStyle(
      115. fontSize: 18.0,
      116. ),
      117. ),
      118. content: const Text('Please put a value greater then 0.',
      119. textAlign: TextAlign.center,
      120. style: TextStyle(
      121. color: Colors.black87,
      122. )),
      123. actions: [
      124. ElevatedButton(
      125. child: Text('OK'),
      126. onPressed: () {
      127. Navigator.of(context).pop();
      128. },
      129. ),
      130. ],
      131. );
      132. });
      133. }
      134. sendDialogBox(BuildContext context) {
      135. return showDialog(
      136. context: context,
      137. builder: (BuildContext context) {
      138. return AlertDialog(
      139. title: Padding(
      140. padding: const EdgeInsets.all(10.0),
      141. child: Text(
      142. "Thanks for your Transaction",
      143. textAlign: TextAlign.center,
      144. style: TextStyle(
      145. fontSize: 20.0,
      146. ),
      147. ),
      148. ),
      149. actions: [
      150. ElevatedButton(
      151. child: Text('Cancel'),
      152. onPressed: () {
      153. Navigator.of(context).pop();
      154. },
      155. ),
      156. ],
      157. );
      158. });
      159. }
      160. withDrawDialogBox(BuildContext context) {
      161. return showDialog(
      162. context: context,
      163. builder: (BuildContext context) {
      164. return AlertDialog(
      165. title: Padding(
      166. padding: const EdgeInsets.all(10.0),
      167. child: Text(
      168. "Thanks for your Withdrawal",
      169. textAlign: TextAlign.center,
      170. style: TextStyle(
      171. fontSize: 20.0,
      172. ),
      173. ),
      174. ),
      175. actions: [
      176. ElevatedButton(
      177. child: Text('Cancel'),
      178. onPressed: () {
      179. Navigator.of(context).pop();
      180. },
      181. ),
      182. ],
      183. );
      184. });
      185. }
      186. }
    4. button_container_widget.dart

      1. import 'package:flutter/material.dart';
      2. class CustomContainerButtonWidget extends StatelessWidget {
      3. final String title;
      4. final Color color;
      5. final VoidCallback onTap;
      6. final int? value;
      7. const CustomContainerButtonWidget({Key? key, required this.title, required this.color, required this.onTap, this.value}) : super(key: key);
      8. @override
      9. Widget build(BuildContext context) {
      10. return InkWell(
      11. onTap: onTap,
      12. child: Container(
      13. height: 60,
      14. width: MediaQuery.of(context).size.width * 0.6,
      15. decoration: BoxDecoration(
      16. color: color,
      17. ),
      18. child: Center(
      19. child: Text(
      20. title, textAlign: TextAlign.left,
      21. style: TextStyle(
      22. fontSize: 20,
      23. fontWeight: FontWeight.bold,
      24. color: Colors.white
      25. ),
      26. ),
      27. ),
      28. ),
      29. );
      30. }
      31. }
    5. pubspec.yaml

      1. name: flutter_basic_dapp
      2. description: A new Flutter project.
      3. # The following line prevents the package from being accidentally published to
      4. # pub.dev using `flutter pub publish`. This is preferred for private packages.
      5. publish_to: 'none' # Remove this line if you wish to publish to pub.dev
      6. # The following defines the version and build number for your application.
      7. # A version number is three numbers separated by dots, like 1.2.43
      8. # followed by an optional build number separated by a +.
      9. # Both the version and the builder number may be overridden in flutter
      10. # build by specifying --build-name and --build-number, respectively.
      11. # In Android, build-name is used as versionName while build-number used as versionCode.
      12. # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
      13. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
      14. # Read more about iOS versioning at
      15. # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
      16. version: 1.0.0+1
      17. environment:
      18. sdk: ">=2.16.1 <3.0.0"
      19. # Dependencies specify other packages that your package needs in order to work.
      20. # To automatically upgrade your package dependencies to the latest versions
      21. # consider running `flutter pub upgrade --major-versions`. Alternatively,
      22. # dependencies can be manually updated by changing the version numbers below to
      23. # the latest version available on pub.dev. To see which dependencies have newer
      24. # versions available, run `flutter pub outdated`.
      25. dependencies:
      26. flutter:
      27. sdk: flutter
      28. flutter_dotenv: ^5.0.2
      29. http: ^0.13.4
      30. web3dart: ^2.3.5
      31. syncfusion_flutter_sliders: ^19.4.56
      32. # The following adds the Cupertino Icons font to your application.
      33. # Use with the CupertinoIcons class for iOS style icons.
      34. cupertino_icons: ^1.0.2
      35. dev_dependencies:
      36. flutter_test:
      37. sdk: flutter
      38. # The "flutter_lints" package below contains a set of recommended lints to
      39. # encourage good coding practices. The lint set provided by the package is
      40. # activated in the `analysis_options.yaml` file located at the root of your
      41. # package. See that file for information about deactivating specific lint
      42. # rules and activating additional ones.
      43. flutter_lints: ^1.0.0
      44. # For information on the generic Dart part of this file, see the
      45. # following page: https://dart.dev/tools/pub/pubspec
      46. # The following section is specific to Flutter.
      47. flutter:
      48. # The following line ensures that the Material Icons font is
      49. # included with your application, so that you can use the icons in
      50. # the material Icons class.
      51. uses-material-design: true
      52. # To add assets to your application, add an assets section, like this:
      53. assets:
      54. - .env
      55. - assets/artifacts/.
      56. # An image asset can refer to one or more resolution-specific "variants", see
      57. # https://flutter.dev/assets-and-images/#resolution-aware.
      58. # For details regarding adding assets from package dependencies, see
      59. # https://flutter.dev/assets-and-images/#from-packages
      60. # To add custom fonts to your application, add a fonts section here,
      61. # in this "flutter" section. Each entry in this list should have a
      62. # "family" key with the font family name, and a "fonts" key with a
      63. # list giving the asset and other descriptors for the font. For
      64. # example:
      65. # fonts:
      66. # - family: Schyler
      67. # fonts:
      68. # - asset: fonts/Schyler-Regular.ttf
      69. # - asset: fonts/Schyler-Italic.ttf
      70. # style: italic
      71. # - family: Trajan Pro
      72. # fonts:
      73. # - asset: fonts/TrajanPro.ttf
      74. # - asset: fonts/TrajanPro_Bold.ttf
      75. # weight: 700
      76. #
      77. # For details regarding fonts from package dependencies,
      78. # see https://flutter.dev/custom-fonts/#from-packages

    6. 新建一个 .env文件

      1. METAMASK_WALLET_ADDRESS = 0x94B6D5dC903c96299C16bdBe45652A61a2334424
      2. METAMASK_PRIVATE_KEY = ee4923c9c3154ccc71697c048ad079c600e09b8a730f1c80b8931ca0c519dc3f
      3. CONTRACT_ADDRESS = 0x0585c0f265D6073d7D46ed339Eaf2b8035982655
      METAMASK_WALLET_ADDRESS是钱包地址
      METAMASK_PRIVATE_KEY是账户私钥
      CONTRACT_ADDRESS是智能合约地址(在Remix中可以获取,truffle框架进行编译也可以进行获取)

     智能合约

    1. // SPDX-License-Identifier: MIT
    2. pragma solidity >=0.7.0 < 0.9.0;
    3. contract BasicDapp {
    4. uint balance;
    5. constructor() {
    6. balance = 0;
    7. }
    8. function sendBalance(uint amount) public {
    9. balance += amount;
    10. }
    11. function withDrawBalance(uint amount) public {
    12. require(balance > amount ,"Not Enough Balance");
    13. balance -= amount;
    14. }
    15. function getBalance() public view returns (uint){
    16. return balance;
    17. }
    18. }

     运行截图:

    总结

    特别注意的地方:

    1、新建一个 .env文件(有一个点),在flutter项目目录下。里面的配置需要按照自己私链的配置及metamask连上私链配置

    2、在models文件夹下的etherum_utils.dart中,这个需要修改为自己私链的ip加端口。

     3、分别代表私钥和智能合约函数名

     4、这个需要修改为链的chainid

     5、这个是truffle编译成的json文件所在目录

     

  • 相关阅读:
    FIX - 克隆虚拟机NAT模式网络不通、不稳定、vMnet8网络故障、网卡冲突、ssh连接慢
    使用Flink批处理实现WordCount
    安卓高级编程之实现类似三星系统的设置界面,并用lucene建立搜索系统
    Github每日精选(第14期):在线富文本编辑器Editor.js
    自动化测试面试题解析,半小时通透
    一次业务代码的流式重构
    C++项目实战--线程池代码讲解
    [架构之路-19]:目标系统 - 硬件平台 - 案例1 - 用单片机STM8/STM32搭建目标系统(以无线传感器LoRa终端为例)
    天河超算,安装Hypre
    mysql的存储过程
  • 原文地址:https://blog.csdn.net/weixin_46085718/article/details/127427616