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
    import 'package:flutter/material.dart';
    import 'package:flutter_basic_dapp/pages/home_page.dart';
    import 'package:flutter_dotenv/flutter_dotenv.dart';
    
    Future main() async {
      await dotenv.load(fileName: ".env");
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          home: HomePage(),
        );
      }
    }
    
    
    
  2.  ethereum_utils.dart

    import 'dart:convert';
    
    import 'package:flutter/services.dart';
    import 'package:flutter_dotenv/flutter_dotenv.dart';
    import 'package:http/http.dart';
    import 'package:web3dart/web3dart.dart';
    
    var apiUrl = "http://192.168.1.161:8545"; //Replace with your API  
    var httpClient = Client();
    var web3client = Web3Client(apiUrl, httpClient);
    
    class EthereumUtils {
      final contractAddress = dotenv.env['CONTRACT_ADDRESS'];
    
      Future getBalance() async {
        final contract = await getDeployedContract();
        final etherFunction = contract.function("getBalance");
        final result = await web3client.call(contract: contract, function: etherFunction, params: []);
        List<dynamic> res = result;
        return res[0];
      }
    
      Future<String> sendBalance(int amount) async {
        var bigAmount = BigInt.from(amount);
        EthPrivateKey privateKeyCred = EthPrivateKey.fromHex(dotenv.env['METAMASK_PRIVATE_KEY']!);
        DeployedContract contract = await getDeployedContract();
        final etherFunction = contract.function("sendBalance");
        final result = await web3client.sendTransaction(
            privateKeyCred,
            Transaction.callContract(
              contract: contract,
              function: etherFunction,
              parameters: [bigAmount],
              maxGas: 100000,
            ),chainId: 20220824,
            fetchChainIdFromNetworkId: false);
        return result;
      }
    
      Future<String> withDrawBalance(int amount) async {
        var bigAmount = BigInt.from(amount);
        EthPrivateKey privateKeyCred = EthPrivateKey.fromHex(dotenv.env['METAMASK_PRIVATE_KEY']!);
        DeployedContract contract = await getDeployedContract();
        final etherFunction = contract.function("withDrawBalance");
        final result = await web3client.sendTransaction(
            privateKeyCred,
            Transaction.callContract(
              contract: contract,
              function: etherFunction,
              parameters: [bigAmount],
              maxGas: 100000,
            ),chainId: 20220824,
            fetchChainIdFromNetworkId: false);
        return result;
    
      }
    
      Future<DeployedContract> getDeployedContract() async {
        String abiStringFile = await rootBundle.loadString("assets/artifacts/BasicDapp.json");
        var jsonAbi = jsonDecode(abiStringFile);
        final contract = DeployedContract(ContractAbi.fromJson(jsonEncode(jsonAbi["abi"]), "BasicDapp"), EthereumAddress.fromHex(contractAddress!));
        return contract;
      }
    }
  3.  home_page.dart

    import 'package:flutter/material.dart';
    import 'package:flutter_basic_dapp/models/ethereum_utils.dart';
    import 'package:flutter_basic_dapp/widgets/button_container_widget.dart';
    import 'package:syncfusion_flutter_sliders/sliders.dart';
    
    class HomePage extends StatefulWidget {
      const HomePage({Key? key}) : super(key: key);
    
      @override
      State<HomePage> createState() => _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
    
      EthereumUtils ethUtils = EthereumUtils();
    
      double? _value = 0.0;
    
      var _data;
    
    
      @override
      void initState() {
        ethUtils.getBalance().then((value) {
          _data = value;
          setState(() {
    
          });
        });
        super.initState();
      }
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: Colors.deepPurple.withOpacity(.9),
          appBar: AppBar(
            title: Text(" "),
          ),
          body: Container(
            margin: EdgeInsets.symmetric(horizontal: 20, vertical: 30),
            // child: SingleChildScrollView(
             child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                Container(
                  width: MediaQuery.of(context).size.width,
                  height: MediaQuery.of(context).size.height * 0.13,
                  decoration: BoxDecoration(
                    color: Colors.deepPurple..withOpacity(.4),
                    borderRadius: BorderRadius.circular(10),
                  ),
                  child: Center(
                    child: Padding(
                      padding: EdgeInsets.symmetric(horizontal: 10, vertical: 10),
                      child: Column(
                        children: [
                          Text("Current Balance", style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold, color: Colors.white),),
                          SizedBox(height: 12,),
                          _data == null ? CircularProgressIndicator() : Text("${_data}", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30, color: Colors.white),)
                        ],
                      ),
                    ),
                  ),
                ),
                SizedBox(height: 40,),
                SfSlider(
                  value: _value,
                  onChanged: (value) {
                    setState(() {
                      _value = value;
                    });
                  } ,
                  interval: 1,
                  activeColor: Colors.white,
                  enableTooltip: true,
                  stepSize: 1.0,
                  showLabels: true,
                  min: 0.0,
                  max: 10.0,
                ),
                SizedBox(height: 40,),
                CustomContainerButtonWidget(title: "Get Balance", color: Colors.green, onTap: () {
                  ethUtils.getBalance().then((value) {
                    _data = value;
                    setState(() {
    
                    });
                  });
                }),
                SizedBox(height: 40,),
                CustomContainerButtonWidget(title: "Send Balance", color: Colors.deepPurpleAccent,
                    onTap: () async {
                      await ethUtils.sendBalance(_value!.toInt());
                      if (_value == 0) {
                        incorrectValueDialogBox(context);
                      } else {
                        sendDialogBox(context);
                      }
                    }),
                SizedBox(height: 40,),
                CustomContainerButtonWidget(title: "WithDraw", color: Colors.deepOrange,
                    onTap: () async {
                      await ethUtils.withDrawBalance(_value!.toInt());
    
                      if (_value == 0) {
                        incorrectValueDialogBox(context);
                      } else {
                        withDrawDialogBox(context);
                      }
                    }),
              ],
            ),
        // ),
          ),
        );
      }
    
      incorrectValueDialogBox(BuildContext context) {
        return showDialog(
            context: context,
            builder: (BuildContext context) {
              return AlertDialog(
                title: Text(
                  'Invalid Value',
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    fontSize: 18.0,
                  ),
                ),
                content: const Text('Please put a value greater then 0.',
                    textAlign: TextAlign.center,
                    style: TextStyle(
                      color: Colors.black87,
                    )),
                actions: [
                  ElevatedButton(
                    child: Text('OK'),
                    onPressed: () {
                      Navigator.of(context).pop();
                    },
                  ),
                ],
              );
            });
      }
    
      sendDialogBox(BuildContext context) {
        return showDialog(
            context: context,
            builder: (BuildContext context) {
              return AlertDialog(
                title: Padding(
                  padding: const EdgeInsets.all(10.0),
                  child: Text(
                    "Thanks for your Transaction",
                    textAlign: TextAlign.center,
                    style: TextStyle(
                      fontSize: 20.0,
                    ),
                  ),
                ),
                actions: [
                  ElevatedButton(
                    child: Text('Cancel'),
                    onPressed: () {
                      Navigator.of(context).pop();
                    },
                  ),
                ],
              );
            });
      }
    
      withDrawDialogBox(BuildContext context) {
        return showDialog(
            context: context,
            builder: (BuildContext context) {
              return AlertDialog(
                title: Padding(
                  padding: const EdgeInsets.all(10.0),
                  child: Text(
                    "Thanks for your Withdrawal",
                    textAlign: TextAlign.center,
                    style: TextStyle(
    
                      fontSize: 20.0,
                    ),
                  ),
                ),
                actions: [
                  ElevatedButton(
                    child: Text('Cancel'),
                    onPressed: () {
                      Navigator.of(context).pop();
                    },
                  ),
                ],
              );
            });
      }
    }
    
  4. button_container_widget.dart

    
    import 'package:flutter/material.dart';
    
    class CustomContainerButtonWidget extends StatelessWidget {
      final String title;
      final Color color;
      final VoidCallback onTap;
      final int? value;
      const CustomContainerButtonWidget({Key? key, required this.title, required this.color, required this.onTap, this.value}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return InkWell(
          onTap: onTap,
          child: Container(
            height: 60,
            width: MediaQuery.of(context).size.width * 0.6,
            decoration: BoxDecoration(
              color: color,
            ),
            child: Center(
              child: Text(
                title, textAlign: TextAlign.left,
                style: TextStyle(
                    fontSize: 20,
                    fontWeight: FontWeight.bold,
                    color: Colors.white
                ),
              ),
            ),
          ),
        );
      }
    }
    
  5. pubspec.yaml

    name: flutter_basic_dapp
    description: A new Flutter project.
    
    # The following line prevents the package from being accidentally published to
    # pub.dev using `flutter pub publish`. This is preferred for private packages.
    publish_to: 'none' # Remove this line if you wish to publish to pub.dev
    
    # The following defines the version and build number for your application.
    # A version number is three numbers separated by dots, like 1.2.43
    # followed by an optional build number separated by a +.
    # Both the version and the builder number may be overridden in flutter
    # build by specifying --build-name and --build-number, respectively.
    # In Android, build-name is used as versionName while build-number used as versionCode.
    # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
    # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
    # Read more about iOS versioning at
    # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
    version: 1.0.0+1
    
    environment:
      sdk: ">=2.16.1 <3.0.0"
    
    # Dependencies specify other packages that your package needs in order to work.
    # To automatically upgrade your package dependencies to the latest versions
    # consider running `flutter pub upgrade --major-versions`. Alternatively,
    # dependencies can be manually updated by changing the version numbers below to
    # the latest version available on pub.dev. To see which dependencies have newer
    # versions available, run `flutter pub outdated`.
    dependencies:
      flutter:
        sdk: flutter
      flutter_dotenv: ^5.0.2
      http: ^0.13.4
      web3dart: ^2.3.5
      syncfusion_flutter_sliders: ^19.4.56
    
    
      # The following adds the Cupertino Icons font to your application.
      # Use with the CupertinoIcons class for iOS style icons.
      cupertino_icons: ^1.0.2
    
    dev_dependencies:
      flutter_test:
        sdk: flutter
    
      # The "flutter_lints" package below contains a set of recommended lints to
      # encourage good coding practices. The lint set provided by the package is
      # activated in the `analysis_options.yaml` file located at the root of your
      # package. See that file for information about deactivating specific lint
      # rules and activating additional ones.
      flutter_lints: ^1.0.0
    
    # For information on the generic Dart part of this file, see the
    # following page: https://dart.dev/tools/pub/pubspec
    
    # The following section is specific to Flutter.
    flutter:
    
      # The following line ensures that the Material Icons font is
      # included with your application, so that you can use the icons in
      # the material Icons class.
      uses-material-design: true
    
      # To add assets to your application, add an assets section, like this:
      assets:
        - .env
        - assets/artifacts/.
    
    
      # An image asset can refer to one or more resolution-specific "variants", see
      # https://flutter.dev/assets-and-images/#resolution-aware.
    
      # For details regarding adding assets from package dependencies, see
      # https://flutter.dev/assets-and-images/#from-packages
    
      # To add custom fonts to your application, add a fonts section here,
      # in this "flutter" section. Each entry in this list should have a
      # "family" key with the font family name, and a "fonts" key with a
      # list giving the asset and other descriptors for the font. For
      # example:
      # fonts:
      #   - family: Schyler
      #     fonts:
      #       - asset: fonts/Schyler-Regular.ttf
      #       - asset: fonts/Schyler-Italic.ttf
      #         style: italic
      #   - family: Trajan Pro
      #     fonts:
      #       - asset: fonts/TrajanPro.ttf
      #       - asset: fonts/TrajanPro_Bold.ttf
      #         weight: 700
      #
      # For details regarding fonts from package dependencies,
      # see https://flutter.dev/custom-fonts/#from-packages
    

  6. 新建一个 .env文件

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

 智能合约

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 < 0.9.0;

  contract BasicDapp {

    uint balance;

    constructor() {
        balance = 0;
    }

    function sendBalance(uint amount) public {
        balance += amount;
    }

    function withDrawBalance(uint amount) public {
        require(balance > amount ,"Not Enough Balance");
        balance -= amount;
    }

    function getBalance() public view returns (uint){
        return balance;
    }

  }

 运行截图:

总结

特别注意的地方:

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

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

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

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

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