What is Amis Line Wallet?
Amis Line Wallet is a simple Mobile Dapp which uses Line Messaging API. You can inquire the balance, issue invoices and make payment with AMIS ERC20 Token and ETH.
- source: https://pragma-curry.com/2018/07/07/221/
- Works best with Metamask extension: install it metamask.io
- Check balance : https://impartial-ellipse.glitch.me/accountinfo.html
- Manage invoice(s) : https://impartial-ellipse.glitch.me/invoice
- Make transfer : https://impartial-ellipse.glitch.me/transfer
- Using the Line Messaging API, you can get Ethereum's balance or send money from the Line talk screen.
-
Ethereum Node - infura.io
-
Application Server - Glitch 1/ Setting up the Line Messaging API Register providers and channels with LINE Developers' Start using Messaging API .
-
For detailed procedures, refer to the last entry Entry Line Messaging API Flex Message .
2/ Acquire the API KEY of infura.io
-
By registering a mail address from the GET STARTED FOR FREE button of infura.io you can get API KEY.
-
For detailed procedures, please refer to the last entry ERC 223 token shortly to public on the public chain (for busy people) .
3/ Set the Glitch
-
Glitch is Paas that Node.js can use. Sign up can also be done with Github's account.
-
After logging in, create a new project from New Project.
-
I created a project named impartial-ellipse for that purpose.
4/ Install the required modules Next step consist of adding all necessary modules, the following modules will be added
- linebot - one of LineBotSDK
- web3 - The Javascript API
- nedb - NoSQL unnecessary for installation Add these 3 packages by Clicking the package.json in the Glitch project folder and add it from Add Packages.
5/ Define the class of LineBot In order to create Reply messages and read / write databases frequently in building LineBot applications, we make the series of processing systems into a class to make it easy to use. (To be exact, it is a pseudo class of ES5.) Reply There are many kinds of messages, but we define only text and buttons that are frequently used. Although it is a little long, it will be easier later if you put it together.
const Linebot = function(app) {
const linebot = require('linebot'); //linebot sdkの読み込み
const Database = require('nedb'); //nedbの読み込み
//各ユーザー端末のLineBotの状態を格納するためのデータベースファイルを指定
const db={};
db.botstatus = new Database({
filename: '.database/botstatus',
autoload: true
});
//LineBotのチャンネルID等をコンストラクタに渡しthis.botに代入
this.bot = linebot({
channelId: process.env.CHANNEL_ID,
channelSecret: process.env.CHANNEL_SECRET,
channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN,
verify: true
});
app.post('/', this.bot.parser());
//Lineからのメッセージやポストバックのイベントを受取り、コールバック関数にイベントを渡す
this.onMessageEvent = function (callback){
this.bot.on('message',event => {
this.setMessageTemplate(event);
callback(event);
});
this.bot.on('postback',event => {
this.setMessageTemplate(event);
callback(event);
});
}
//ReplyのたびにJSONをいちいち書くのは面倒なので、よく使うテキストやボタンだけ定義。
//ボタンは4つまで設置できるので、可変長引数にしておく。
this.setMessageTemplate = function(event){
event.replyText = function(message){
event.reply([{
"type": "text",
"text": message
}]).then(data => {
console.log('Success', data);
}).catch(error => {
console.log('Failed', error);
});
}
event.replyButton = function(/*title,message,button,postback,...*/){
var obj = {
"type": "template",
"altText": arguments[0],
"template": {
"type": "buttons",
"thumbnailImageUrl": null,
"title": arguments[0],
"text": arguments[1],
"actions": [
{
"type": "postback",
"label": arguments[2],
"data": arguments[3],
}
]
}
}
for (var i = 0; i < 3; i++) { if(arguments.length > 2*i+4){
obj.template.actions[i+1]={
"type": "postback",
"label": arguments[2*i+4],
"data": arguments[2*i+5]
};
}
}
event.reply([obj]).then(data => {
console.log('Success', data);
}).catch(error => {
console.log('Failed', error);
});
}
//ユーザー端末のLineBotの状態をデータベースから読み込む
this.readDatabase = function(lineID){
return new Promise((resolve, reject) => {
db.botstatus.findOne({ lineid: lineID }, (err, obj) =>{
if(err == null && obj!=null){
resolve(obj.status);
}
else if(err == null && obj==null){
resolve(null);
}
else{
reject(err);
}
});
});
}
//ユーザー端末のLineBotの状態をデータベースに書き込む
this.writeDatabase = function(lineID,status){
db.botstatus.findOne({ lineid: lineID }, (err, obj) => {
if(obj==null){
db.botstatus.insert({'lineid':lineID,'status':status});
}
else{
db.botstatus.update({ 'lineid': lineID }, { $set: { status: status } }, { multi: true });
}
});
}
}
If you prepare the above class
const express = require('express');
const app = express();
const bot = new Linebot(app);
bot.onMessageEvent(someFunction);
someFunction(event){
event.replyButton(
'選んでください','どれが好き?',
'ビーフカレー','answer=beef',
'ポークカレー','answer=pork',
'チキンカレー','answer=chicken'
);
event.replyText('テストだよ');
}
- Result in:
6/ Define the class handling Ethereum's web3.js Since only the balance is acquired, I think that it is sufficient if selection of a chain and validation of an Ethernet address are sufficient.
const Ether = function(){
const Web3 = require('web3'); //web3.jsの読み込み
//mainnet,ropsten,rinkeby,kovanのどれかを指定してnodeとchain idを返す
//infura.ioで取得したAPIアクセスキーを環境変数に入れて呼び出している。
this.setChain = function(chain){
switch(chain){
case 'mainnet':
return {'node':'https://mainnet.infura.io/'+ process.env.INFURA_KEY, 'id':1};
break;
case 'ropsten':
return {'node':'https://ropsten.infura.io/'+ process.env.INFURA_KEY, 'id':3};
break;
case 'rinkeby':
return {'node':'https://rinkeby.infura.io/'+ process.env.INFURA_KEY, 'id':4};
break;
case 'kovan':
return {'node':'https://kovan.infura.io/'+ process.env.INFURA_KEY, 'id':42};
break;
default:
return {'node':'https://mainnet.infura.io/'+ process.env.INFURA_KEY, 'id':1};
break;
}
}
//正しいイーサリアムアドレスの形式になっているか確認する
this.validateAddress = function(address){
var valid = true;
if(String(address) == ''){
valid = false;
}
else if(
String(address).length != 42
|| !String(address).match(/^[0-9a-zA-Z]/)
|| !(String(address).slice(0,2)=='0x')
){
valid = false;
}
return valid;
}
//単位Etherで残高取得する
this.getBalance = function(chain,address){
const web3 = new Web3(new Web3.providers.HttpProvider(this.setChain(chain).node));
return new Promise((resolve, reject) => {
if(this.validateAddress(address)){
resolve(web3.fromWei(web3.eth.getBalance(address), "ether").toNumber());
}
else{
reject('Invalid address.');
}
});
}
}
7/ Write the main processing Using the methods created in 5 and 6 above,
1 user sends a LINE message 2 User state and messages are stored in nedb 3 Line Messaging API returns Reply (Question) 4 User sends a LINE message Get information from the Ethernet based on the message stored in 5 2 and the message sent in 4. 6 The state of the user is initialized 7 Line Messaging API returns Reply (user wants information)
Implement the basic flow of:
var Main = function(app){
const bot = new Linebot(app);
const ether = new Ether();
//botインスタンスにgetActionメソッドを追加。
//DBにユーザーの状態が保存されていない場合(初期状態)では、LINEで送られたクエリ形式のアクション
// (action=showBalance 等)を解析して次のアクションを行う。
bot.getAction = function(event,message){
try{
if(typeof(message['action']) == 'undefined' || message['action']==''){
throw 'No Action Detected.';
}
switch(message['action']){
case 'showBalance':
bot.writeDatabase(event.source.userId,'action=showbalance&listen=chain');
event.replyButton(
'Select Chain','Select Ethereum chain.',
'Mainnet','chain=mainnet',
'Ropsten','chain=ropsten',
'Rinkeby','chain=rinkeby',
'Kovan','chain=kovan'
);
break;
default:
throw 'No Action Detected.';
break;
}
}catch(e){
event.replyText(e);
}
}
//メッセージかポストバックイベントを受け取ったら、まずどちらのイベントか判断する
this.onMessageEvent = function(event){
switch(event.type){
case 'message':
var message = functions.queryParse(event.message.text);
break;
case 'postback':
var message = functions.queryParse(event.postback.data);
break;
default:
event.replyText('The event type is not supported.');
break;
}
//ユーザーのLINE IDをキーにDBファイルからクエリ形式で状態を取得
bot.readDatabase(event.source.userId).then(function(statusQuery) {
//クエリ文字列をオブジェクトに変換する関数queryParseを定義しておく
//action=shobalance等のクエリ形式を解析
var status = queryParse(statusQuery);
if(status['action']=='showbalance' && status['listen']=='chain'){
if(typeof message['chain'] !== 'undefined'){
bot.writeDatabase(event.source.userId, 'action=showbalance&listen=address&chain=' + message['chain']);
//chainの選択が終わったら、次はアドレスを取得するよう、ユーザー状態を変化させる。
event.replyText('Send '+ message['chain']+ ' Address.');
}
else{
bot.writeDatabase(event.source.userId, null); //ユーザー状態を初期化
event.replyText('Failed to select chain.');
}
}
else if(status['action']=='showbalance' && status['listen']=='address' && typeof status['chain'] !== 'undefined'){
ether.getBalance(status['chain'] , event.message.text).then(function(data){
event.replyText(data);
})
.catch(function(err){
event.replyText(err);
});
bot.writeDatabase(event.source.userId, null); //ユーザー状態を初期化
}
else{
bot.getAction(event,message); //ユーザー状態を初期化
}
}).catch(function (err) {
event.replyText(err);
});
}
bot.onMessageEvent(this.onMessageEvent);
}
const express = require('express');
new Main(express());
- When you do the above:
And I got the Ether balance of Ropsten from the LINE Talk screen.
Non production ready. I have to copy and paste the address. Especially because it assumes operation on a smartphone, it is considerably troublesome.
Main caveat
Since javascript can not be embedded on the LINE talk screen, in the case of remittance etc., it will be a dangerous act of sending a private key to the application server and then signing etc for that purpose we recommend using Metamask.
Line@ https://line.me/R/ti/p/%40crb2330m
Thx @snst-lab