Flutter调用以太坊区块链智能合约 (私链)
该案例适用于Flutter调用以太坊solidity智能合约
前言
一、需要使用哪些开发工具?
- Remix IDE
- android studio
- ubuntu
- 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代码复制
- 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(), ); } }
-
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; } }
-
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(); }, ), ], ); }); } }
-
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 ), ), ), ), ); } }
-
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
-
新建一个 .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文件所在目录