APIPAYEZ
สมาชิกใหม่
สมาชิกรุ่นแรก
สมาชิก
LV
0
- คะแนนธุระกิจ
- 0
- เครดิต
- $0.02
เนื้อหาบทความนี้ แบ่งออกเป็น
- ทำความรู้จัก Bitkub
- ทดสอบเรียก Bitkub RESTful API ด้วย Postman tool
- เขียน Flutter App ต่อเรียกข้อมูล Bitkub API
ทำความรู้จัก Bitkub
Bitkub ก่อตั้งเมื่อเดือนกุมภาพันธ์ พ.ศ. 2561 และเป็น Exchange Platform รุ่นใหม่สำหรับการซื้อขาย Cryptocurrency และสินทรัพย์ Digital โดยให้บริการเหนือระดับแก่บุคคลทั่วไปให้สามารถซื้อ, ขายและเก็บ Cryptocurrency ได้ตามต้องการ บริษัทเราได้จดทะเบียนอย่างถูกต้องตามกฎหมายด้วยทุนจดทะเบียน 80 ล้านบาท และมีที่ตั้งสำนักงานอยู่ในกรุงเทพฯ ประเทศไทยBitkub มี Public API ให้เรียกใช้งาน
Bitkub มีบริการ API ให้เหล่านักพัฒนาได้ลองเรียกใช้งาน จะเป็นแบบ RESful API สามารถเข้าไปดูได้ที่ https://www.bitkub.com/publicapi แบ่งตัว API 2 กลุ่ม คือ- แบบ Non-Secure Endpoint API คือ Public API ที่ไม่ต้องใช้ authentication และเป็น http method แบบ GET ตัวอย่างข้อมูล เช่น
- GET /api/servertime
- GET /api/market/symbols
- GET /api/market/ticker
- GET /api/market/trades
- GET /api/market/bids
- GET /api/market/asks
- GET /api/market/books
- GET /api/market/trading-view
- แบบ Secure Endpoint API คือ Secure API ที่ต้องมีการส่ง Server Time ,API Key, API Secret โดยต้องมีการ encode request payload ด้วย Function Hash อย่าง HMAC SHA-256 ในส่วของ Request Body ก่อนการเรียกใช้งาน Secure API ตรงส่วนนี้ ตัวอย่างข้อมูล เช่น
- POST /api/market/wallet
- POST /api/market/balances
- POST /api/market/place-bid
- POST /api/market/place-ask
- POST /api/market/cancel-order
- POST /api/market/my-open-orders
- POST /api/market/my-order-history
- POST /api/market/order-info
- POST /api/crypto/addresses
- POST /api/crypto/withdraw
- POST /api/crypto/deposit-history
- POST /api/crypto/withdraw-history
- POST /api/fiat/accounts
- POST /api/fiat/withdraw
- POST /api/fiat/deposit-history
- POST /api/fiat/withdraw-history
- POST /api/market/wstoken
- POST /api/user/limits
ทดสอบเรียก Public API ผ่าน REST Client Tool (Postman)
ลองเรียก API endpoint ที่เป็นแบบ Non-secure (GET) เรียกข้อมูล crypto symbols ของแต่ละเหรียญมาเริ่มเขียน Flutter App ให้เรียก Bitkub API กัน
- สมัครสมาชิก bitkub.com ทำการยืนยันการสมัครสมาชิกให้เรียบร้อย
- เข้าไปสร้าง bitkub public APIกำหนด permission role ตามที่ต้องการ หรือจะใส่ค่า default ก็ได้ เพราะเราจะ read data อย่างเดียว
api authenticate management
3. สร้าง Flutter Project กำหนดโครงสร้าง Project ตามข้างล่างนี้
3.1 เพิ่ม dependencies ในไฟล์ pubspec.yaml
YAML:
name: medium_bitkub
description: Example App Call RESTful API from Bitkub
# 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.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
# https://pub.dev/packages/http#-installing-tab-
http: ^0.12.0+2
# https://pub.dev/packages/json_serializable#-installing-tab-
json_serializable: ^3.2.3
# https://pub.dev/packages/crypto#-installing-tab-
crypto: ^2.1.3
dev_dependencies:
flutter_test:
sdk: flutter
# 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:
- config.json
# 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: Varela Round
fonts:
- asset: fonts/VarelaRound-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
- http: ^0.12.0+2 : สำหรับใช้เป็น lib จัดการเรื่อง http request ,response ใน code การเรียก API
- json_serializable: ^3.2.3 : parser JSON Data to Object Class
- crypto: ^2.1.3 : เพื่อใช้ function HMAC
- BitkubMenu เพื่อใช้เป็น widget เมนูที่จะมี button 2 buttons
Code:
import 'package:flutter/material.dart';
import 'package:medium_bitkub/pages/non_secure_api.dart';
import 'package:medium_bitkub/pages/secure_api.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Call Bitkub RESTful API',
theme: ThemeData(
primarySwatch: Colors.blue,
fontFamily: 'Varela Round'
),
home: BitkubMenu(),
);
}
}
class BitkubMenu extends StatelessWidget {
@override
Widget build(BuildContext context) {
TextStyle textStyle = new TextStyle(
fontSize: 20.0, color: Colors.white,
fontWeight: FontWeight.bold,
);
return Scaffold(
appBar: AppBar(
title: Text("Bitkub Api List"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new RaisedButton(
child: new Text("Non Secure Api (GET)",style: textStyle),
color: Colors.blue,
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => NonSecureApiPage()),
),
),
new RaisedButton(
child: new Text("Secure Api (POST)",style: textStyle),
color: Colors.green,
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecureApiPage()),
),
),
],
),
),
);
}
}
- NonSecureApiPage เพื่อใช้เป็น Page ดึง Non Secure API ในที่นี้คือดึง symbol ของ crypto มาแสดง (API ไม่ต้อง authen)
Code:
import 'package:flutter/material.dart';
import '../dto/symbol.dart';
import '../services/bitkub_service.dart';
class NonSecureApiPage extends StatefulWidget {
@override
_NonSecureApiState createState() => _NonSecureApiState();
}
class _NonSecureApiState extends State<NonSecureApiPage> {
Future fetchCoins() async {
print('fetchCoins');
return await BitkubService().getSymbols();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Non Secure API"),
),
body: FutureBuilder(
future: fetchCoins(),
builder: (BuildContext contex, AsyncSnapshot tickerSnap) {
switch (tickerSnap.connectionState) {
case ConnectionState.none:
return new Text('Input a URL to start');
break;
case ConnectionState.waiting:
return new Center(child: new CircularProgressIndicator());
break;
case ConnectionState.active:
return new Text('');
break;
case ConnectionState.done:
if (tickerSnap.hasError) {
return new Text('${tickerSnap.error}', style: TextStyle(color: Colors.red),);
} else {
return ListView.builder(
itemCount: (tickerSnap.data == null) ? 0 : tickerSnap.data.length,
itemBuilder: (contex, index) {
Ticker ticker = tickerSnap.data[index];
return Column(
children: <Widget>[
ListTile(
leading: Icon(Icons.monetization_on),
title: Text(ticker.key),
subtitle: Text(ticker.baseVolume.toString()),
),
],
);
});
}
break;
default:
return Container();
}
},
),
);
}
}
- SecureApiPage เพื่อใช้เป็น Page ดึง Secure API ในที่นี้คือดึง crypto balance ของ crypto มาแสดง (API จำเป็นต้องส่งข้อมูล authentication แนบไปด้วย)
Code:
import 'package:flutter/material.dart';
import '../dto/balance.dart';
import '../services/bitkub_service.dart';
class SecureApiPage extends StatefulWidget{
@override
_NonSecureApiState createState() => _NonSecureApiState();
}
class _NonSecureApiState extends State<SecureApiPage>{
Future fetchCoinBalances() async {
return await BitkubService().getCoinsBalance();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Secure API"),
),
body: FutureBuilder(
future: fetchCoinBalances(),
builder: (BuildContext contex, AsyncSnapshot balanceSnap) {
switch (balanceSnap.connectionState) {
case ConnectionState.none:
return new Text('Input a URL to start');
case ConnectionState.waiting:
return new Center(child: new CircularProgressIndicator());
case ConnectionState.active:
return new Text('');
case ConnectionState.done:
if (balanceSnap.hasError) {
return new Text(
'${balanceSnap.error}',
style: TextStyle(color: Colors.red),
);
} else {
return ListView.builder(
itemCount: (balanceSnap.data == null) ? 0 : balanceSnap.data.length,
itemBuilder: (contex, index) {
Balance ticker = balanceSnap.data[index];
return Column(
children: <Widget>[
ListTile(
leading: Icon(Icons.monetization_on),
title: Text(ticker.key),
subtitle: Text(ticker.available.toString()),
),
],
);
},
);
}
break;
default:
return Container();
}
},
),
);
}
}
3.3 สร้าง dto (data transfer object) เอาไว้ mapping json to object class 3 files
- balance.dart
Code:
class Balance {
final String key;
final double available;
final double reserved;
Balance({this.key, this.available, this.reserved});
factory Balance.fromJson(String balanceKey, Map<String, dynamic> json){
return Balance(
key: balanceKey,
available: json['available'].toDouble(),
reserved: json['reserved'].toDouble(),
);
}
}
- symbol.dart
Code:
class Ticker {
final String key;
final int id;
final double last;
final double lowestAsk;
final double highestBid;
final double percentChange;
final double baseVolume;
final double quoteVolume;
final double isFrozen;
final double high24hr;
final double low24hr;
Ticker(
{this.key,
this.id,
this.last,
this.lowestAsk,
this.highestBid,
this.percentChange,
this.baseVolume,
this.quoteVolume,
this.isFrozen,
this.high24hr,
this.low24hr});
factory Ticker.fromJson(String tickerKey,Map<String, dynamic> json) {
return Ticker(
key :tickerKey,
id: json['id'],
last: json['last'].toDouble(),
lowestAsk: json['lowestAsk'].toDouble(),
highestBid: json['highestBid'].toDouble(),
percentChange: json['percentChange'].toDouble(),
baseVolume: json['baseVolume'].toDouble(),
quoteVolume: json['quoteVolume'].toDouble(),
isFrozen: json['isFrozen'].toDouble(),
high24hr: json['high24hr'].toDouble(),
low24hr: json['low24hr'].toDouble(),
);
}
}
- config.dart
Code:
class Config {
final String bitkubEndpoint;
final String bitkubApikey;
final String bitkubApiSecret;
Config({this.bitkubEndpoint, this.bitkubApikey, this.bitkubApiSecret});
factory Config.fromJson(Map<String, dynamic> json) {
return Config(
bitkubEndpoint: json['bitkub.endpoint'],
bitkubApikey: json['bitkub.apikey'],
bitkubApiSecret: json['bitkub.apisecret'],
);
}
}
3.4 สร้าง service class เพื่อใช้เป็น service handle http client ต่อ Bitkub API ให้ชื่อว่า bitkub_service.dart
Code:
import 'dart:convert' as convert;
import 'dart:convert' show utf8;
import 'package:flutter/cupertino.dart';
import 'package:http/http.dart' as http;
import '../dto/symbol.dart';
import '../dto/balance.dart';
import '../dto/config.dart';
import '../app_config.dart';
import '../util/coin_security.dart';
class BitkubService{
Map<String, String> headers = {
"content-type": "application/json",
"Accept": "application/json",
};
Future<String> getServerTime() async {
Config _config = await ApplicationConfig().loadAsset();
final response = await http.get(_config.bitkubEndpoint + "/api/servertime", headers: headers);
if (response.statusCode == 200) {
return response.body;
} else {
throw Exception('Failed to load Server Time');
}
}
Future<List<Balance>> getCoinsBalance() async {
Config _config = await ApplicationConfig().loadAsset();
String timestamp = await this.getServerTime();
print('timestamp ::==$timestamp');
var payload = {'ts': timestamp};
print('payload ::==${convert.jsonEncode(payload)}');
headers['X-BTK-APIKEY'] = _config.bitkubApikey;
String hamcSing = new CoinSecurity().coinHmacSign(payload, _config.bitkubApiSecret);
//print('payloadHmac ::==${convert.jsonEncode(hamcSing)}');
payload['sig'] = hamcSing;
String payLoadJson = convert.jsonEncode(payload);
print('payLoadJson ::==$payLoadJson');
final response =
await http.post(_config.bitkubEndpoint + '/api/market/balances', headers: headers, body: payLoadJson);
debugPrint('response headers ::==${convert.jsonEncode(response.headers)}',wrapWidth: 1024);
debugPrint('response.statusCode :: ${response.statusCode}');
if (response.statusCode == 200) {
List<Balance> balances = new List();
try {
String _body = convert.utf8.decode(response.bodyBytes);
print('response _body::==$_body');
Map<String, dynamic> mapBodyRes = convert.jsonDecode(_body);
if(mapBodyRes['error'] == 1){
throw new Exception('error :: ${mapBodyRes['error']}');
}else{
Map<String,dynamic> mapResult = mapBodyRes['result'];
mapResult.forEach((balanceName, balanceDetail) {
balances.add(Balance.fromJson(balanceName, balanceDetail));
});
return balances;
}
} catch (e) {
print('e::==$e');
throw e;
}
} else {
throw Exception('Failed to load fetchCoinBalances');
}
}
Future<List<Ticker>> getSymbols() async {
Config _config = await ApplicationConfig().loadAsset();
print('_config ::==${_config.bitkubEndpoint}');
final response = await http.get(_config.bitkubEndpoint + "/api/market/ticker");
print('headers ::==${convert.jsonEncode(response.headers)}');
if (response.statusCode == 200) {
List<Ticker> tickers = new List();
String _body = utf8.decode(response.bodyBytes);
print('_body ::== $_body');
try {
Map<String, dynamic> mapTickets = convert.jsonDecode(_body);
mapTickets.forEach((coinName, coinDetail) {
//print('coinName :: $coinName');
//print('coinDetail :: $coinDetail');
tickers.add(Ticker.fromJson(coinName, coinDetail));
});
return tickers;
} catch (e) {
print('e::==$e');
throw e;
}
} else {
throw Exception('Failed to load Coins');
}
}
}
3.5 สร้าง coin_security.dart ใช้สำหรับ hash HMAC authen
Code:
import 'package:crypto/crypto.dart';
import 'dart:convert' as convert;
class CoinSecurity {
String coinHmacSign(var timestamp, String apiSecret) {
try {
var timestampEncode = convert.utf8.encode(convert.jsonEncode(timestamp));
var secretEncode = convert.utf8.encode(apiSecret);
var hmacSha256 = new Hmac(sha256, secretEncode); // HMAC-SHA256
var digest = hmacSha256.convert(timestampEncode);
return digest.toString();
} catch (e) {
print('$e');
throw e;
}
}
}
3.6 app_config.dart ใช้เป็น config handle เรื่อง read config file มาใช้ใน project
Code:
import 'dart:convert' as convert;
import 'package:flutter/services.dart' show rootBundle;
import 'dto/config.dart';
class ApplicationConfig{
Future<Config> loadAsset() async{
String _json = await rootBundle.loadString('assets/config.json');
//print('assets/config json::==$_json');
return Config.fromJson(convert.jsonDecode(_json));
}
}
4. อธิบายส่วนการเรียก Bitkub API
- Non Secure API ส่วนนี้ไม่ยุ่งยากอะไรเพราะมองเป็น API ที่ไม่ต้องใส่ authenticate secure เรียกผ่าน method GET ได้เลย ขั้นตอนส่วนนี้จะอยู่ใน function getSymbols()
- Secure API ส่วนนี้จะมีความยุ่งยากนิดนึง เพราะจำเป็นต้องแนบ authen ไปด้วย
1. เรียก function getServerTime() เก็บค่า time server ของ API ไว้ก่อน (ใช้ในขั้นตอนถัดไป)
2. เพิ่ม header “X-BTK-APIKEY” ใส่ value เป็น bitkub api key
3. เข้ากระบวนการ hash HMAC โดยใช้ข้อมูล servertime (ได้จาก 1.) จะถูกสร้างเป็น request body ที่ชื่อว่า “ts” พร้อมใส่ api secret ผลลัพธ์ที่ได้จะเป็น HASH String 1 ชุด เก็บไว้เป็น request body ที่ชื่อว่า “sig”
4. ส่งข้อมูล request เหล่านี้ไปแบบ method POST จากนั้นจะได้ response กับมาเป็นข้อมูล market balance ของ crypto ณ ปัจจุบัน code ส่วนนี้จะอยู่ใน function getCoinsBalance()
4.1 API Error Code ที่จะคอยบอกเราได้ว่า call API เกิด Error ทางฝั่ง server ว่าอย่างไร มี Error Code จาก server ที่คอยบอกเราได้
error code เพิ่มเติม
สำหรับเพื่อน ๆ คนใดอยากได้ sourcecode เพื่อลองเอาไป run ทดสอบเอง ผมจะแปะลิ้ง github ไว้ที่ตรงนี้ bitkubapi-flutter ก่อน run แก้ให้ไปแก้ config.json ตรงส่วน api key , api secret ก่อนรันด้วยนะจร้า
แก้ไขล่าสุด: