Android Flutter Terhubung Dengan Mallet Cardano Pada IELE Testnet

Requirement :

  • Ubuntu server 16.04 atau 18.04
  • Node.js versi v10.12.0
  • Android Studio
  • Flutter plugin
  • Flutter SDK

Mallet

Mallet, minimum wallet adalah sebuah command line untuk men-deploy smart contract dan berinteraksi dengan CARDANO IELE dan KEVM Testnet (https://testnet.iohkdev.io/) yang didevelop oleh IOHK.

Install Mallet

Pertama-tama buka command line pada server linux anda, dan buatlah sebuah directory/folder baru bernama mallet dengan menggunakan command line berikut :

$ mkdir mallet

setelah itu masuk kedalam directory mallet dan clone source code mallet dari github IOHK dengan menggunakan command line sebagai berikut

$ git clone https://github.com/input-output-hk/mallet.git

maka secara otomatis akan meng-copy semua code mallet yang ada pada github IOHK

selanjutnya install mallet cli dengan menggunakan npm

$ npm install
Image for post

Menjalankan command line interface

Baiklah sekarang tahap selanjutnya adalah menjalankan beberapa command line interface yang terdapat dalam mallet.

Untuk berinteraksi dengan Cardano, kita harus menjalankan cli mallet untuk terhubung dengan IELE testnet.

Ketikkan command line berikut

$ ./mallet iele
Image for post

Untuk mengecek command lainnya pada mallet, ketikkan command berikut

mallet> listCommands()
Image for post

untuk mengecek semua address yang ada pada node ketik command berikut

mallet> listAccounts()
Image for post

Pada gambar di atas sudah ada beberapa akun address yang saya buat, untuk membuat akun baru ketikkan command berikut

mallet> newAccount()

Pada saat anda membuat address baru, anda akan diminta memasukkan password untuk akun anda.

Image for post

Lalu cek kembali dengan command ListAccounts()

Image for post

Selanjutnya kita akan mencoba beberapa command lainnya sekaligus yaitu select account untuk memilih akun yang akan anda gunakan, getBalance untuk melihat saldo pada akun yang anda pilih tersebut, dan requestFunds untuk me-request koin pada akun anda.

Image for post

Setelah itu cek kembali balance anda, dan lihatlah saldo pada akun anda sudah bertambah.

Baiklah, sekarang saya akan mencoba menerapkan beberapa command yang sudah kita coba tadi melalui aplikasi mobile android. Saya akan membuat aplikasi android menggunakan flutter, pertama-tama kita harus membuat API JSON yang terhubung dengan mallet untuk mengkoneksikan aplikasi android dengan IELE testnet.

Membuat API

Langkah selanjutnya kita akan memodifikasi file cli.js yang terdapat pada directory mallet yang kita clone tadi. Buka file cli.js tersebut, dan copy semua code yang ada dalam file tersebut kemudian buatlah file baru bernama post.js dalam directory mallet.

Kemudian paste semua code dari cli.js tadi kedalam file post.js yang anda buat. Dan saya akan memodifikasi code tersebut untuk dijadikan sebagai API.

Image for post
const Mallet = require('./lib/mallet.js');const path = require('path');const os = require('os');const Repl = require('repl');const ReplHistory = require('repl.history');const prog = require('caporal');const opn = require('opn');const rlp = require('./lib/rlp.js');const exportedProperties = ['web3','listAccounts','newAccount','importPrivateKey','getBalance','getNonce','selectAccount','currentAccount','sendTransaction','lastTransaction','getReceipt','requestFunds','iele']function listCommands() {const ieleCommands = ['iele.simpleTransfer', 'iele.callContract', 'iele.deployContract', 'iele.constantCall', 'iele.compile'];const utils = ['help', 'listCommands', 'rlp'];return exportedProperties.filter(x => x !== 'iele').concat(ieleCommands).concat(utils).sort();}function help() {opn('https://github.com/input-output-hk/mallet/blob/master/README.md');}function start(args, opts) {console.log(`Mallet ${require('./package.json').version} - IELE/KEVM testnet utility\n` +`Type 'help()' to view the online documentation or 'listCommands()' to view available commands\n`);const mallet = new Mallet(args.testnet, opts.datadir);const repl = Repl.start('mallet> ');repl.context.mallet = mallet;repl.context.listCommands = listCommands;repl.context.help = help;repl.context.rlp = rlp;exportedProperties.forEach(prop => repl.context[prop] = mallet[prop]);ReplHistory(repl, path.join(opts.datadir, '.history'));}prog.bin('mallet').description('command line utility for KEVM/IELE testnets').version(require('./package.json').version).argument('<testnet>', "JSON RPC endpoint to connect. Possible values are: 'kevm', 'iele', or a custom HTTP(S) URL").option('-d, --datadir', 'Specify data directory', prog.STRING, path.join(os.homedir(), '.mallet2')).action(function(args, opts, logger) {start(args, opts);});prog.parse(process.argv);

Sekarang kita modifikasi pada file post.js. Hilangkan beberapa code dan sisakan code berikut ini.

const Mallet = require('./lib/mallet.js');const path = require('path');const os = require('os');const Repl = require('repl');const ReplHistory = require('repl.history');const prog = require('caporal');const opn = require('opn');const rlp = require('./lib/rlp.js');const exportedProperties = ['web3','listAccounts','newAccount','importPrivateKey','getBalance','getNonce','selectAccount','currentAccount','sendTransaction','lastTransaction','getReceipt','requestFunds','iele']function listCommands() {const ieleCommands = ['iele.simpleTransfer', 'iele.callContract', 'iele.deployContract', 'iele.constantCall', 'iele.compile'];const utils = ['help', 'listCommands', 'rlp'];return exportedProperties.filter(x => x !== 'iele').concat(ieleCommands).concat(utils).sort();}function help() {opn('https://github.com/input-output-hk/mallet/blob/master/README.md');}const mallet = new Mallet('iele', path.join(os.homedir()));

Kemudian import library express di atas file

var express = require('express');var app = express();

Selanjutnya tambahkan code berikut untuk membuat JSON API untuk menampilkan listAccounts, createAccount, requestFunds, dan getBalance.

var bodyParser = require("body-parser");app.use(bodyParser.urlencoded({ extended: false }));app.get('/getAccount', function (req, res) {var oMyOBject = mallet.listAccounts();var arrayJson = [];for(var i in oMyOBject) {var item = oMyOBject[i];var x = 1;var z = parseInt(i) + x;arrayJson.push({"name" : "Account " + z,"address" : item});}res.json(arrayJson);});app.post('/newAccount', function(req, res) {var password = req.body.password;mallet.newAccount(password);var oMyOBject = {response:'success'};res.json(oMyOBject);});app.post('/getBalance', function(req, res) {var address = req.body.address;mallet.requestFunds(address);var oMyOBject = {response:'success'};res.json(oMyOBject);});app.post('/detailAccount', function (req, res) {var address = req.body.address;var arrayJson = [];arrayJson.push({"address" : mallet.selectAccount(address),"balance" : mallet.getBalance(address)});res.json(arrayJson);});var server = app.listen(5000, function () {console.log('Node server is running..');});

Setelah itu save file tersebut, dan running file tersebut dengan menggunakan node.js atau pm2 (agar tetap running dalam background).

Image for post

Bisa dilihat App name post statusnya sudah online dan siap digunakan.

Tes API Dengan Postman

Selanjutnya kita akan mengetes API yang sudah kita buat melalui postman, kita akan mencoba menampilkan listAccounts melalui postman.

Buka postman dan masukkan url server anda

Image for post

Baiklah, API sudah bisa digunakan. Dan sekarang waktunya membuat aplikasi android dan menghubungkan aplikasi android dengan IELE testnet melalui API tersebut.

Membuat Aplikasi Android dan menghubungkan dengan IELE Testnet

Aplikasi android yang akan dibuat menggunakan flutter dan menggunakan bahasa pemrograman dart.

Aplikasi yang akan dibuat cukup sederhana, yaitu akan mencoba menampilkan list account, membuat account baru, melihat saldo, dan me-request coin dan di akhir cerita kita akan melihat semua account yang kita buat dari aplikasi akan muncul pada IELE testnet explore.

Buka aplikasi android studio dan buatlah sebuah project flutter baru, setelah itu beri nama project dengan iele_testnet.

Pertama kita akan membuat sebuah splash screen untuk pembukaan agar tidak membosankan. Berikut screenshot pada mobile.

Image for post
import 'dart:async';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:iele_testnet/util/constants.dart';
class SplashScreen extends StatefulWidget {
@override
SplashScreenState createState() => new SplashScreenState();
}
class SplashScreenState extends State<SplashScreen> with SingleTickerProviderStateMixin {
var _visible = true;
AnimationController animationController;
Animation<double> animation;
SharedPreferences sharedPreferences;
String loginToken;
bool loginSuccess;
String _token;
String userLogin, userPassword;
int idUser;
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); startTime() async {
var _duration = new Duration(seconds: 3);
return new Timer(_duration, navigationPage);
}
void navigationPage() async {
sharedPreferences = await SharedPreferences.getInstance();
setState(() {
Navigator.of(context).pushReplacementNamed(LIST_ACCOUNT);
});
}
@override
void initState() {
super.initState();
animationController = new AnimationController(
vsync: this, duration: new Duration(seconds: 2));
animation =
new CurvedAnimation(parent: animationController, curve: Curves.easeOut);
animation.addListener(() => this.setState(() {}));
animationController.forward();
setState(() {
_visible = !_visible;
});
startTime();
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: Stack(
fit: StackFit.expand,
children: <Widget>[
new Container(
decoration: new BoxDecoration(color: new Color(0xFF003366))
),
new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Image.asset(
'assets/logo.png',
width: animation.value * 100,
height: animation.value * 100,
),
],
),
],
),
);
}
}

Kita masukkan logo Emurgo pada splash screen dan set waktu animasi logo 2 detik dan durasi splash screen 3 detik, setelah 3 detik maka akan masuk ke menu list account.

Get List Account

Image for post
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter/cupertino.dart';
import 'package:iele_testnet/model/listaccountsmodel.dart';
import 'package:iele_testnet/util/separators.dart';
import 'package:iele_testnet/util/constants.dart';
import 'package:flutter/services.dart';
import 'package:iele_testnet/activity/SecondPage.dart';
class ListAcountsRoute extends CupertinoPageRoute {
ListAcountsRoute() : super(builder: (BuildContext context) => new ListAcounts());
@override
Widget buildPage(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return new FadeTransition(opacity: animation, child: new ListAcounts());
}
}
class ListAcounts extends StatefulWidget {
static const String routeName = "/ListAcounts";
@override
ListAcountsState createState() => ListAcountsState();
}class ListAcountsState extends State<ListAcounts> with WidgetsBindingObserver {
String url;
ListAccountsModel data;
bool downloading = false; final _passwordController = TextEditingController();
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
String _mySelection; Future<String> makeRequest() async {
downloading = true;
url = "http://52.221.142.167:5000/getAccount";
var response = await http
.get(Uri.encodeFull(url), headers: {"Accept": "application/json"});
setState(() {
final extractdata = json.decode(response.body);
data = ListAccountsModel.fromJson(extractdata);
});
downloading = false;
}
@override
void initState() {
this.makeRequest();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
didPopRoute() {
} @override
Widget build(BuildContext context) {
final makeBody = Container(
child:
new Container(
child: new Stack(
children: <Widget>[
new Center(
child: downloading ? Container(
child: new Padding(
padding: const EdgeInsets.all(4.0),
child: new SizedBox(
child: CircularProgressIndicator(),
height: 20.0,
width: 20.0,
)
)
): Text(""),
),
ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: data == null ? 0 : data.listAccounts.length,
itemBuilder: (BuildContext context, int i) {
return new Card(
elevation: 8.0,
margin: new EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0),
child: Container(
decoration: BoxDecoration(
// Box decoration takes a gradient
gradient: LinearGradient(
// Where the linear gradient begins and ends
begin: Alignment.topRight,
end: Alignment.bottomLeft,
// Add one stop for each color. Stops should increase from 0 to 1
stops: [0.1, 0.5, 0.7, 0.9],
colors: [
// Colors are easy thanks to Flutter's Colors class.
new Color(0xFF0E0B17),
new Color(0xE60E0B17),
new Color(0xCC0E0B17),
new Color(0x990E0B17),
],
)),
child: new ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0),
leading: Container(
padding: EdgeInsets.only(right: 12.0),
decoration: new BoxDecoration(
border: new Border(
right: new BorderSide(width: 2.0, color: new Color(0xff00c6ff)))
),
child: Text((i+1).toString(), style: TextStyle(color: Colors.red, fontSize: 18.0))
),
title:
Column(
children: <Widget>[
Text(data.listAccounts[i].name, style: TextStyle(color: Colors.orangeAccent, fontWeight: FontWeight.bold, fontSize: 14.0)),
new Separators()
],
),
subtitle: Column(
children: <Widget> [
new Text(data.listAccounts[i].address, style: TextStyle(color: Colors.orangeAccent, fontSize: 12.0)),
]
),
onTap: () {
Navigator.push(context, new MaterialPageRoute(builder: (BuildContext context) => new SecondPage(data.listAccounts[i])));
//_persistentBottomSheet(data.suratMasuk[i]);
}
)
),
);
},
)
],
)
)
);
return new Scaffold(
key: _scaffoldKey,
appBar: new AppBar(
elevation: 0.1,
backgroundColor: Color.fromRGBO(58, 66, 86, 1.0),
automaticallyImplyLeading: false,
title: Text("IELE"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add_circle),
onPressed: () {
_modalBottomSheet();
},
)
],
),
body: makeBody,
);
}
void _modalBottomSheet(){ final password = TextFormField(
autofocus: false,
controller: _passwordController,
obscureText: true,
decoration: InputDecoration(
hintText: 'Create Password',
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),
),
);
final submit = Padding(
padding: EdgeInsets.symmetric(vertical: 5.0),
child: MaterialButton(
onPressed: () {
//Navigator.of(context).pushReplacementNamed(HOME_SCREEN);
createAccount();
},
color: new Color(0xFF003366),
child: Text('Submit', style: TextStyle(color: Colors.white)),
),
);
showModalBottomSheet(
context: context,
builder: (builder){
return new Container(
color: Colors.white,
child: Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: new Center(
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.only(left: 24.0, right: 24.0),
children: <Widget>[
new Center(
child: Text("New Account"),
),
Container(
height: 10.0,
),
password,
SizedBox(height: 24.0),
submit
],
)
)
)
);
}
);
}
createAccount() async {
Map datas = {
'password': _passwordController.text
};
var url = 'http://52.221.142.167:5000/newAccount';
http.post(url, body: datas)
.then((response) {
final data = json.decode(response.body);
String responses = data['response'];
if (responses == "success") {
print('response '+responses);
_passwordController.clear();
Navigator.pop(context);
Navigator.of(context).pushReplacementNamed(LIST_ACCOUNT);
} else {
}
});
}
}

Penjelasan code diatas adalah, kita membuat sebuah listview pada widget dan menghubungkannya dengan API dengan meng-import library http. Code yang digunakan terdapat dalam function makeRequest()

Future<String> makeRequest() async {
downloading = true;
url = "http://52.221.142.167:5000/getAccount";
var response = await http
.get(Uri.encodeFull(url), headers: {"Accept": "application/json"});
setState(() {
final extractdata = json.decode(response.body);
data = ListAccountsModel.fromJson(extractdata);
});
downloading = false;
}

Bisa dilihat url pada function ini terhubung dengan function yang kita buat dalam file post.js tadi

app.get('/getAccount', function (req, res) {var oMyOBject = mallet.listAccounts();var arrayJson = [];for(var i in oMyOBject) {var item = oMyOBject[i];var x = 1;var z = parseInt(i) + x;arrayJson.push({"name" : "Account " + z,"address" : item});}res.json(arrayJson);});

Dalam function makeRequest kita melalukan request GET pada url tersebut dan menampilkan response lis account dalam bentuk JSON. File JSON tersebut ditarik untuk dijadikan list pada aplikasi.

Kemudian bisa dilihat pada screenshot tadi terdapat icon + yang berfungsi untuk membuat akun address baru.

Create New Account

Image for post

Seperti yang sudah saya jelaskan tadi jika ingin membuat akun address baru anda diharuskan membuat password, maka dari itu kita buat sebuah textfield untuk memasukkan password dan button submit untuk membuat request POST. Function untuk membuat akun baru terdapat dalam function berikut.

createAccount() async {
Map datas = {
'password': _passwordController.text
};
var url = 'http://52.221.142.167:5000/newAccount';
http.post(url, body: datas)
.then((response) {
final data = json.decode(response.body);
String responses = data['response'];
if (responses == "success") {
print('response '+responses);
_passwordController.clear();
Navigator.pop(context);
Navigator.of(context).pushReplacementNamed(LIST_ACCOUNT);
} else {
}
});
}

Function tersebut juga terhubung dengan function dalam post.js

app.post('/newAccount', function(req, res) {var password = req.body.password;mallet.newAccount(password);var oMyOBject = {response:'success'};res.json(oMyOBject);});

Dalam function create account, kita melakukan request POST dan memasukkan variable password. Dan jika berhasil maka akan menampilkan response success dalam bentuk JSON. setelah itu account address yang baru dibuat tersebut akan tampil pada list paling bawah.

Image for post

Get Balance & Request Funds

Sekarang kita akan lanjut ke code berikut nya yaitu untuk melihat saldo dan merequest saldo. kita akan melihat detail account yang baru kita buat tadi, jika anda kita klik account 7 maka akan tampil saldo dalam account tersebut.

import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter/cupertino.dart';
import 'package:iele_testnet/model/detailaccount.dart';
import 'package:iele_testnet/util/separators.dart';
import 'package:iele_testnet/util/constants.dart';
import 'package:flutter/services.dart';
import 'package:iele_testnet/activity/TransferMoney.dart';
class SecondPage extends StatefulWidget {
final data;
final bool horizontal;
SecondPage(this.data, {this.horizontal = true}); SecondPage.vertical(this.data): horizontal = false; SecondPageState createState() => new SecondPageState(data);
}
class SecondPageState extends State<SecondPage> { final data;
final bool horizontal;
bool downloading = false;
SecondPageState(this.data, {this.horizontal = true}); SecondPageState.vertical(this.data): horizontal = false; final key = new GlobalKey<ScaffoldState>(); Future<List<DetailAccount>> fetchDetailAccount() async {
final response = await http.post('http://52.221.142.167:5000/detailAccount', body: {'address': data.address.toString()});
print(response.body);
List responseJson = json.decode(response.body.toString());
List<DetailAccount> detailList = createDetailList(responseJson);
return detailList;
}
List<DetailAccount> createDetailList(List data){
List<DetailAccount> list = new List();
for (int i = 0; i < data.length; i++) {
String address = data[i]["address"];
String balance = data[i]["balance"];
int txCount = data[i]["transactionCount"];
DetailAccount user = new DetailAccount(
address: address,balance: balance, txCount: txCount);
list.add(user);
}
return list;
}
@override
initState() {
this.fetchDetailAccount();
}
@override
Widget build(BuildContext context) {
final pThumbnail =
new Container(
margin: new EdgeInsets.symmetric(
vertical: 16.0
),
alignment: horizontal ? FractionalOffset.centerLeft : FractionalOffset.center,
child: new Hero(
tag: "HK",
child: new Image(
image: new AssetImage("assets/logo.png"),
height: 72.0,
width: 72.0,
),
),
);
final pCardContent = new Container(
margin: new EdgeInsets.fromLTRB(horizontal ? 76.0 : 16.0, horizontal ? 16.0 : 42.0, 16.0, 16.0),
constraints: new BoxConstraints.expand(),
child: new Column(
crossAxisAlignment: horizontal ? CrossAxisAlignment.start : CrossAxisAlignment.center,
children: <Widget>[
new Container(height: 4.0),
new Text("Acount", style: TextStyle(color: Colors.white, fontSize: 18.0, fontWeight: FontWeight.w600)),
new Container(height: 12.0),
new Text(data.name, style: TextStyle(color: Colors.white, fontSize: 14.0, fontWeight: FontWeight.w400)),
new Separator(),
],
),
);
final pCard = new Container(
child: pCardContent,
height: horizontal ? 124.0 : 154.0,
margin: horizontal
? new EdgeInsets.only(left: 46.0)
: new EdgeInsets.only(top: 72.0),
decoration: new BoxDecoration(
color: new Color(0xFF333366),
shape: BoxShape.rectangle,
borderRadius: new BorderRadius.circular(8.0),
boxShadow: <BoxShadow>[
new BoxShadow(
color: Colors.black,
blurRadius: 10.0,
offset: new Offset(0.0, 10.0),
),
],
),
);
final gesture = new GestureDetector(
onTap: horizontal ? () => Navigator.of(context).push(new PageRouteBuilder(
pageBuilder: (_, __, ___) => new SecondPage(data),
transitionsBuilder: (context, animation, secondaryAnimation, child) =>
new FadeTransition(opacity: animation, child: child),
) ,
) : null,
child: new Container(
margin: const EdgeInsets.symmetric(
vertical: 16.0,
horizontal: 24.0,
),
child: new Stack(
children: <Widget>[
pCard,
pThumbnail,
],
),
)
);
Container _getGradient() {
return new Container(
margin: new EdgeInsets.only(top: 190.0),
height: 110.0,
decoration: new BoxDecoration(
gradient: new LinearGradient(
colors: <Color>[
new Color(0xFFFFFFFF),
new Color(0xFFFFFFFF)
],
stops: [0.0, 0.9],
begin: const FractionalOffset(0.0, 0.0),
end: const FractionalOffset(0.0, 1.0),
),
),
);
}
final submit =
Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 1.0),
child: MaterialButton(
onPressed: () {
//Navigator.of(context).pushReplacementNamed(HOME_SCREEN);
getBalance();
},
color: new Color(0xFF003366),
child: Text('Get Balance', style: TextStyle(color: Colors.white)),
),
)
);
String _copy = data.address; return new Scaffold(
key: key,
appBar: AppBar(
title: Text("Detail Account"),
),
body: new Container(
constraints: new BoxConstraints.expand(),
color: Colors.white,
child: new Stack (
children: <Widget>[
_getGradient(),
new Container(
child: new FutureBuilder<List<DetailAccount>>(
future: fetchDetailAccount(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Container(
child: new ListView(
padding: new EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 32.0),
children: <Widget>[
gesture,
new Container(
padding: new EdgeInsets.symmetric(horizontal: 32.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
new Text("Detail Account", style: TextStyle(color: Colors.black, fontSize: 16.0, fontWeight: FontWeight.w400)),
Padding(
padding: new EdgeInsets.only(left: 10.0),
child: new Icon(Icons.contact_mail, color: Colors.black, size: 16.0)
)
],
),
new Separator(),
new Column(
children: <Widget>[
new Row (
children: <Widget>[
new Text("Address :", style: TextStyle(color: Colors.black, fontSize: 14.0, fontWeight: FontWeight.w400))
],
),
new Container(height: 10.0),
new Row(
children: <Widget>[
Expanded (
child: new GestureDetector(
child: new Text(_copy),
onLongPress: () {
Clipboard.setData(new ClipboardData(text: _copy));
key.currentState.showSnackBar(
new SnackBar(content: new Text("Copied to Clipboard"),));
},
)
)
],
),
new Row(
children: <Widget>[
new Separator()
],
),
new Row (
children: <Widget>[
new Text("Transaction Count :", style: TextStyle(color: Colors.black, fontSize: 14.0, fontWeight: FontWeight.w400))
],
),
new Container(height: 10.0),
new Row(
children: <Widget>[
Expanded (
child: new Text(snapshot.data[0].txCount.toString(), style: TextStyle(color: Colors.black, fontSize: 14.0, fontWeight: FontWeight.w400))
)
],
)
],
),
new Container(height: 10.0),
new Separators(),
new Container(height: 10.0),
Row(
children: <Widget>[
new Text("Balance", style: TextStyle(color: Colors.black, fontSize: 16.0, fontWeight: FontWeight.w400)),
Padding(
padding: new EdgeInsets.only(left: 10.0),
child: new Icon(Icons.monetization_on, color: Colors.black, size: 16.0)
)
],
),
new Separator(),
new Column(
children: <Widget>[
new Row(
children: <Widget>[
new Text("Your Balance :", style: TextStyle(color: Colors.black, fontSize: 14.0, fontWeight: FontWeight.w400))
],
),
new Container(height: 10.0),
new Row(
children: <Widget>[
Expanded (
child: new Text(snapshot.data[0].balance, style: TextStyle(color: Colors.black, fontSize: 14.0, fontWeight: FontWeight.w400))
)
],
),
new Container(height: 5.0),
submit,
new Container(height: 2.0),
Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 1.0),
child: MaterialButton(
onPressed: () {
Navigator.push(context, new MaterialPageRoute(builder: (BuildContext context) => new TransferMoney(snapshot.data[0].address)));
},
color: new Color(0xFF003366),
child: Text('Transfer Money', style: TextStyle(color: Colors.white)),
),
)
)
],
)
],
),
),
],
),
);
} else if (snapshot.hasError) {
return new Text("${snapshot.error}");
}
return new Center(
child: new Padding(
padding: const EdgeInsets.all(4.0),
child: new SizedBox(
child: CircularProgressIndicator(),
height: 20.0,
width: 20.0,
)
),
);
// By default, show a loading spinner
},
),
)
],
),
),
);
}
getBalance() async {
Map datas = {
'address': data.address
};
var url = 'http://52.221.142.167:5000/getBalance';
http.post(url, body: datas)
.then((response) {
final data = json.decode(response.body);
String responses = data['response'];
if (responses == "success") {
print('response '+responses);
Navigator.pop(context);
} else {
}
});
}
}

Berikut tampilan nya

Image for post

Bisa dilihat tampilan untuk menu detail account. Terdapat informasi address dan saldo pada akun tersebut. Dan terdapat button get balance untuk merequest saldo. Function untuk menampilkan detail account tersebut terdapat dalam function berikut.

Future<List<DetailAccount>> fetchDetailAccount() async {
final response = await http.post('http://52.221.142.167:5000/detailAccount', body: {'address': data.address.toString()});
print(response.body);
List responseJson = json.decode(response.body.toString());
List<DetailAccount> detailList = createDetailList(responseJson);
return detailList;
}
List<DetailAccount> createDetailList(List data){
List<DetailAccount> list = new List();
for (int i = 0; i < data.length; i++) {
String address = data[i]["address"];
String balance = data[i]["balance"];
int txCount = data[i]["transactionCount"];
DetailAccount user = new DetailAccount(
address: address,balance: balance, txCount: txCount);
list.add(user);
}
return list;
}

Sekali lagi kita melakukan request POST untuk menampilkan address dan saldo pada akun tersebut. Dan variable yang di POST adalah address, karena untuk melihat saldo dibutuhkan address. Function tersebut terhubung dengan function dalam post.js.

app.post('/detailAccount', function (req, res) {
var address = req.body.address;
var txCount = mallet.getNonce(address);
var arrayJson = [];
arrayJson.push({
"address" : mallet.selectAccount(address),
"balance" : mallet.getBalance(address),
"transactionCount" : txCount
//"balance" : balance
});
res.json(arrayJson);});

Sekarang kita akan merequest saldo untuk account tersebut. Dengan menekan button submit. Saat menekan button submit tersebut, kita melakukan request POST dengan function berikut

getBalance() async {
Map datas = {
'address': data.address
};
var url = 'http://52.221.142.167:5000/getBalance';
http.post(url, body: datas)
.then((response) {
final data = json.decode(response.body);
String responses = data['response'];
if (responses == "success") {
print('response '+responses);
Navigator.pop(context);
} else {
}
});
}

Pada function tersebut kita melakukan request POST dan memasukkan variable address. Karena dibutuhkan address untuk merequest saldo. Berikut function yang terhubung pada post.js

app.post('/getBalance', function(req, res) {var address = req.body.address;mallet.requestFunds(address);var oMyOBject = {response:'success'};res.json(oMyOBject);});

Sekarang kita cek kembali pada menu detail account apakah saldo nya bertambah atau tidak. Dibutuhkan waktu beberapa detik sampai saldo anda bertambah.

Image for post

Bisa dilihat saldo sudah bertambah menjadi 30000000000000000 wei atau setara dengan 0,03 ETH.

Dan sekarang mari kita lihat pada IELE EXPLORE. Buka IELE explorer pada browser anda dengan memasukkan link berikut https://testnet.iohkdev.io/iele/explorer/

Kemudian copy address tadi dengan menekan address dan paste pada IELE EXPLORER.

Image for post

Bisa dilihat account dan saldo tampil dalam Cardano Blockchain IELE.

Transfer Coin

Sekarang kita akan mencoba menambahkan feature untuk mengirim koin dari 1 akun ke akun yang lain.

Pertama, tambahkan Button pada menu detail account untuk menuju ke menu transfer coin. Anda cukup menambahkan beberapa code pada file dart anda di bagian widget.

Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 1.0),
child: MaterialButton(
onPressed: () {
Navigator.push(context, new MaterialPageRoute(builder: (BuildContext context) => new TransferMoney(snapshot.data[0].address)));
},
color: new Color(0xFF003366),
child: Text('Transfer Money', style: TextStyle(color: Colors.white)),
),
)
)

Berikut tampilan nya

Image for post

Jika diklik button transfer money maka akan menuju ke menu untuk mengirim koin.

Image for post
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter/cupertino.dart';
import 'package:iele_testnet/model/listaccountsmodel.dart';
import 'package:iele_testnet/util/separators.dart';
import 'package:iele_testnet/util/constants.dart';
import 'package:flutter/services.dart';
class TransferMoney extends StatefulWidget {
final data;
final bool horizontal;
TransferMoney(this.data, {this.horizontal = true}); TransferMoney.vertical(this.data): horizontal = false; TransferMoneyState createState() => new TransferMoneyState(data);
}
class TransferMoneyState extends State<TransferMoney> { final dataAddress;
final bool horizontal;
TransferMoneyState(this.dataAddress, {this.horizontal = true}); TransferMoneyState.vertical(this.dataAddress): horizontal = false;
String url;
ListAccountsModel dataList;
final _passwordController = TextEditingController();
final _gasController = TextEditingController();
final _valueController = TextEditingController();
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); String _mySelection; Future<List<ListAccountDropDown>> makeRequest() async {
final response = await http.get('http://52.221.142.167:5000/getAccount');
//print(response.body);
List responseJson = json.decode(response.body.toString());
List<ListAccountDropDown> detailList = createDetailList(responseJson);
return detailList;
}
List<ListAccountDropDown> createDetailList(List data){
List<ListAccountDropDown> list = new List();
for (int i = 0; i < data.length; i++) {
String name = data[i]["name"];
String address = data[i]["address"];
if (address == dataAddress.toString()) { } else {
ListAccountDropDown user = new ListAccountDropDown(
name: name,address: address);
list.add(user);
}
}
return list;
}
@override
void initState() {
this.makeRequest();
}
@override
Widget build(BuildContext context) {
return new Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text("Transfer Money"),
),
body: new FutureBuilder<List<ListAccountDropDown>>(
future: makeRequest(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Container(
child: new Column(
children: <Widget>[
new ListTile(
leading: const Icon(Icons.monetization_on),
title: new TextField(
autofocus: false,
controller: _valueController,
keyboardType: TextInputType.number,
decoration: new InputDecoration(
hintText: "Value",
),
),
),
new ListTile(
leading: const Icon(Icons.attach_money),
title: new TextField(
autofocus: false,
controller: _gasController,
keyboardType: TextInputType.number,
decoration: new InputDecoration(
hintText: "Gas",
),
),
),
new ListTile(
leading: const Icon(Icons.vpn_key),
title: new TextField(
autofocus: false,
controller: _passwordController,
obscureText: true,
decoration: new InputDecoration(
hintText: "Your Password",
),
),
),
new ListTile(
leading: const Icon(Icons.email),
title: new DropdownButton(
isDense: true,
hint: new Text("Select Account"),
items: snapshot.data.map((item) {
return new DropdownMenuItem(
child: new Text(item.name),
value: item.address,
);
}).toList(),
onChanged: (newVal) {
setState(() {
_mySelection = newVal;
});
},
value: _mySelection,
)
),
Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 5.0),
child: MaterialButton(
onPressed: () {
//Navigator.of(context).pushReplacementNamed(HOME_SCREEN);
if (_gasController.text == "") {
_scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Gas Cannot empty"), duration: Duration(seconds: 3),));
} else if (_valueController.text == "") {
_scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Value Cannot empty"), duration: Duration(seconds: 3),));
} else if (_passwordController.text == "") {
_scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Password Cannot empty"), duration: Duration(seconds: 3),));
} else if (_mySelection == null) {
_scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Please select receiver address"), duration: Duration(seconds: 3),));
} else {
print("Select : " +_mySelection);
Map datas = {
'address': dataAddress.toString(),
'addressTo': _mySelection,
'gas': _gasController.text,
'value': _valueController.text,
'password': _passwordController.text
};
var url = 'http://52.221.142.167:5000/simpleTransfer';
http.post(url, body: datas).then((response) {
if (response.statusCode == 500) {
_scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Wrong Password"), duration: Duration(seconds: 3),));
} else if (response.statusCode == 200) {
final data = json.decode(response.body);
String responses = data['response'];
if (responses == "success") {
print('response '+responses);
_passwordController.clear();
_gasController.clear();
_valueController.clear();
Navigator.pop(context);
}
} else {
_scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Unknown Error"), duration: Duration(seconds: 3),));
}
});
}
},
color: new Color(0xFF003366),
child: Text('Send Money', style: TextStyle(color: Colors.white)),
),
)
)
],
)
);
} else if (snapshot.hasError) {
return new Text("${snapshot.error}");
}
return new Center(
child: new Padding(
padding: const EdgeInsets.all(4.0),
child: new SizedBox(
child: CircularProgressIndicator(),
height: 20.0,
width: 20.0,
)
),
);
// By default, show a loading spinner
},
)
);
}
}

Kita akan mengirim koin sebanyak 1000 dari Account 1 ke Account 6. Mari kita lihat terlebih dahulu ada berapa saldo pada Account 6.

Image for post
Image for post

Bisa dilihat saldo pada Account 6 adalah 0.

Pada saat akan mengirim koin, kita melakukan POST beberapa variabel yang berada dalam function berikut

Image for post
Map datas = {
'address': dataAddress.toString(),
'addressTo': _mySelection,
'gas': _gasController.text,
'value': _valueController.text,
'password': _passwordController.text
};
var url = 'http://52.221.142.167:5000/simpleTransfer';
http.post(url, body: datas).then((response) {
if (response.statusCode == 500) {
_scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Wrong Password"), duration: Duration(seconds: 3),));
} else if (response.statusCode == 200) {
final data = json.decode(response.body);
String responses = data['response'];
if (responses == "success") {
print('response '+responses);
_passwordController.clear();
_gasController.clear();
_valueController.clear();
Navigator.pop(context);
}
} else {
_scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Unknown Error"), duration: Duration(seconds: 3),));
}
});

Function tersebut mengarah ke post.js

app.post('/simpleTransfer', function(req, res) {var address = req.body.address;var addressTo = req.body.addressTo;var gas = parseInt(req.body.gas);var value = parseInt(req.body.value);var password = req.body.password;mallet.selectAccount(address);var hash = mallet.iele.simpleTransfer({to: addressTo, gas: gas, value: value}, password);var oMyOBject = {response:'success', hash: hash};res.json(oMyOBject);});

Variabel yang di post pertama adalah addres karena kita akan memanggil function mallet.selectAccount(addressPenerima); untuk memilih account pengirim.

Setelah akun pengirim sudah ditentukan, maka selanjutnya menjalankan function simpleTransfer pada mallet. Yang didalam function tersebut berisi value, gas, dan address penerima.Setelah sukses mengirim koin, mari kita cek apakah koin tersebut sampai kepada address penerima atau tidak.

Image for post
Image for post

Saldo pada address tersebut sudah bertambah menjadi 1e-15 ETH yang berarti 1000 wei sesuai dengan value yang dikirim.

Terima Kasih

Source code for Mallet : https://github.com/input-output-hk/mallet

Source code for Android : https://github.com/dimasprase/FlutterMallet.git

Written by

EMURGO Solusi Indonesia

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store