Title: RMBTB Trade API -- Overview & Sample Python,PHP, JS,Ruby & Java trade wrappers
Post by: BitPirate on August 02, 2013, 09:32:35 AM
Today RMBTB.com is releasing a new trading API, the "Secure API". Like other exchanges, this API allows you to monitor the market and/or trade remotely or using third-party apps. The implementation is broadly compatible with other competing APIs, but we went a little further, with advice from security experts, to ensure it is more secure. - Shared secrets used to sign API requests are IP-locked and rotated every two hours. API signing uses a shared secret. Most sites store this secret in the database. We don't like this -- so instead of issuing you a secret which is stored in the database, we issue you a long passphrase, which you can use to request a new IP-locked secret. This improves account security in the event of our site being compromised, as secrets are locked to your IP address, and passphrases are never stored.
- For each API key you create, you can enforce access rights. You can set what each key can do -- they can be limited (for viewing basic information), read-only (for viewing account balances and trades), or full (for trading). This means that you can grant access only to the applications that need it.
- IP/subnet whitelisting. You can choose which IP addresses or ranges can use your key/passphrase combo. You can specify multiple IPv4 or IPv6 ranges. This means that if someone stole your passphrase, they couldn't use it.
- API trade limits. You can limit the orders placed to the API. This limits the possible damage should your API credentials be compromised, or should your client run haywire.
- Split fee. Until further notice, orders placed via the API will receive a 50% trade fee discount! (that means a flat 0.15% fee!)
. . There is no API withdrawal function; As usual, all withdrawals are subject to prompt human review and processing. Documentation (https://www.rmbtb.com/help-secureapi-en/) Sample Python wrapper (https://www.rmbtb.com/help-secureapi-en/#code-python) Sample PHP client wrapper (https://www.rmbtb.com/help-secureapi-en/#code-php) Sample JavaScript (Node.js) wrapper (https://www.rmbtb.com/help-secureapi-en/#code-js) Sample Ruby wrapper (https://www.rmbtb.com/help-secureapi-en/#code-ruby) Sample Java wrapper (https://www.rmbtb.com/help-secureapi-en/#code-java) Please feel free to use this code as you wish, subject to the warnings and disclaimer on the documentation page ( THERE MAY BE BUGS). More examples in the works; watch this space! https://www.rmbtb.com/content/uploads/2013/08/dalek.jpg
Title: Re: RMBTB Trade API -- Overview & Sample Python & PHP trade wrappers
Post by: BitPirate on August 06, 2013, 02:11:49 PM
Here's the Python code: # -*- coding: utf-8 -*- # # PYTHON RMBTB SECURE API WRAPPER # # COPYRIGHT & LIMITATIONS # (c) 2013 rmbtb.com, released under the BSD license. You are free to copy, redistribute, and include this # code in your own application, for personal or commercial purposes. # # However, you may NOT brand yourself or any code or services as "RMBTB", "人盟比特币", or explicitly # or implicitly represent any services or software as being provided by or endorsed by RMBTB. # # # EXAMPLE USAGE # >>> rmbtb = RmbtbSecureAPI("API_KEY_HERE", "API_PASSPHRASE_HERE", "BTCCNY") # >>> rmbtb.ticker() # >>> rmbtb.add_order(type="ask", amount=3.1, price=650.00) # # // or... # >>> rmbtb = RmbtbSecureAPIInt("API_KEY_HERE", "API_PASSPHRASE_HERE", "BTCCNY") # >>> rmbtb.add_order(type="bid", amount=310000000, price=65000000) # import time, datetime, json, urllib, urllib2, base64, hmac, gzip from hashlib import sha512 from os import path class RmbtbSecureAPI(object): """The base RMBTB class""" #Set the following to a local, secure, writeable location to store temporary secrets. RMBTB_LOCAL_STORAGE_LOC = "rmbtb-store.dat" RMBTB_BASE_URL = "https://www.rmbtb.com/api/secure/" def __init__(self, key, passphrase, currencyPair="BTCCNY"): self.key = key self.passphrase = passphrase self.currencyPair = currencyPair self._load_secret(self.RMBTB_LOCAL_STORAGE_LOC) def get_info(self): """Get account information""" return self._request(api="getinfo", httpMethod="GET") def get_funds(self): """Get wallet balances""" return self._request(api="wallets", httpMethod="GET") def ticker(self): """Get ticker data""" return self._request(api="ticker", httpMethod="GET", auth=False) def get_orders(self): """Get my orders""" return self._request(api="orders", httpMethod="GET") def add_order(self, type, amount, price): """Place an order""" params = { 'type': type, 'amount': amount, 'price': price } return self._request(api="order/add", params=params, httpMethod="POST") def cancel_order(self, orderid): """Cancel an order""" params = { 'oid': orderid } return self._request(api="order/cancel", params=params, httpMethod="POST") def fetch_order(self, orderid): """Get order details""" params = { 'oid': orderid } return self._request(api="order/fetch", params=params, httpMethod="GET") def get_trades(self): """Get your recent trades""" return self._request(api="trades/mine", httpMethod="GET") def last_trades(self): """Get recent market trades data""" return self._request(api="trades/all", httpMethod="GET", auth=False) def get_depth(self): """Get market depth""" return self._request(api="depth", httpMethod="GET", auth=False) def _load_secret(self, loc): """Private method; loads secret from storage""" self.secret = False self.secretExpiryTime = False if path.exists(loc): storTime = datetime.datetime.fromtimestamp(path.getmtime(loc)) timeNow = datetime.datetime.now() self.secretExpiryTime = storTime + datetime.timedelta(hours=2) if (datetime.datetime.now() < self.secretExpiryTime): # secret has not expired self.secret = open(loc).read().strip() return True #secret has expired or we've never created one before return self._refresh_secret(loc) def _refresh_secret(self, loc): """Private method; refreshes the secret""" self.secret = str(self._obtain_new_secret(loc)) if self.secret != False: f = open(loc, 'w') f.write(self.secret) f.close() self.secretExpiryTime = datetime.datetime.now() + datetime.timedelta(hours=2) return self.secret def _obtain_new_secret(self, loc): """Private method; Gets the new secret""" postData = "api_passphrase=" + urllib2.quote(self.passphrase) headers = { "Rest-Key": self.key } data = self._curl_call(api="getsecret", paramStr=postData, headers=headers, httpMethod="POST") return data["data"]["secret"] def _request(self, api, params={}, httpMethod="GET", auth=True): """Private method; creates the API request parameters / auth headers""" 'GET' if (httpMethod == 'GET') else 'POST' if auth: #refresh secret if necessary if (self.secret == False) or (datetime.datetime.now() > (self.secretExpiryTime - datetime.timedelta(seconds=60))): self._refresh_secret(self.RMBTB_LOCAL_STORAGE_LOC) params[u"nonce"] = str(int(time.time()*1e6)) sendParams = urllib.urlencode(params) mac = hmac.new(self.secret, str(sendParams), sha512) sig = base64.b64encode(str(mac.digest())) headers = { "Rest-Key": self.key, "Rest-Sign": sig, "Content-Type": "application/x-www-form-urlencoded", } else: sendParams = urllib.urlencode(params) headers = {} data = self._curl_call(api=api, paramStr=sendParams, headers=headers, httpMethod=httpMethod) return data def _curl_call(self, api, paramStr=None, httpMethod="GET", headers={}, timeout=8): """Private method; performs the request""" url = self.RMBTB_BASE_URL + self.currencyPair + "/" + api headers.update({ 'User-Agent': "Mozilla/4.0 (compatible; RMBTB Python client)", 'Accept-Encoding': 'GZIP', }) if httpMethod == "POST": headers["Content-Type"] = "application/x-www-form-urlencoded" sendParams = paramStr else: url = url + "?" + paramStr sendParams = False if sendParams: request = urllib2.Request(url, sendParams, headers=headers) else: request = urllib2.Request(url, headers=headers) response = urllib2.urlopen(request, timeout=timeout) data = json.loads(response.read()) if not(u"data" in data): if(u"error" in data): raise Exception(u"Error received: " + data[u"error"]) else: raise Exception("An error occurred") return data class RmbtbSecureAPIInt(RmbtbSecureAPI): """Use this class if you prefer to deal with integers""" def add_order(self, type_int, amount_int, price): """Place an order""" params = { 'type': type, 'amount_int': amount_int, 'price_int': price_int } return self._request(api="order/add", params=params, httpMethod="POST")
Title: Re: RMBTB Trade API -- Overview & Sample Python & PHP trade wrappers
Post by: BitPirate on August 06, 2013, 02:13:11 PM
PHP. This code also checks our SSL certificate. You can download this yourself (in Firefox, click the padlock on the rmbtb.com URL bar, More Information… → View Certificate… → Details → Save) and save it in a secure location. <?php /** * PHP RMBTB SECURE API WRAPPER * * COPYRIGHT & LIMITATIONS * (c) 2013 rmbtb.com, released under the BSD license. You are free to copy, redistribute, and include this * code in your own application, for personal or commercial purposes. * * However, you may NOT brand yourself or any code or services as "RMBTB", "人盟比特币", or explicitly * or implicitly represent any services or software as being provided by or endorsed by RMBTB. * * INSTRUCTIONS * This wrapper should be mostly compatible with other PHP API implementations, and so you can pretty much just * drop this in as a replacement for other API access layers in PHP applications, with some small changes. * * You can use either the default (float) or the integer wrapper. * Please read the configuration section and security suggestions below. * * EXAMPLE USAGE * $rmbtbApi = new Rmbtb_Secure_API('BTCCNY', RMBTB_API_PUBKEY, RMBTB_API_PASSPHRASE); * print_r($rmbtbApi->get_info()); * print_r($rmbtbApi->add_order('bid', 5, 650)); * * // or... * $rmbtbApiInt = new Rmbtb_Secure_API_Integer('BTCCNY', RMBTB_API_PUBKEY, RMBTB_API_PASSPHRASE); * print_r($rmbtbApiInt->add_order('ask', 500000000, 65000000)); */ /** * Configuration * * Set your configuration options here. * It is recommended that you store these in a separate file, in a more secure location on your server, and include() the * file. * e.g. require('/path/to/rmbtb/config.php'); */ // Your API username and passphrase define('RMBTB_API_PUBKEY', 'r-api-e888a-1111d-181818-6fafa7-fa18fa-8888a-9fsx'); define('RMBTB_API_PASSPHRASE', 'n4ajpX/df*2A%%xxT>Pq<_24pxcpH|^Q5nhQab==!=IIh%/x-'); /* * Location to a copy of the rmbtb.com SSL certificate. This is used to * verify the connection and ensure you are not communicating with an impostor. * You can download and export it using your browser. */ define('RMBTB_CERT_LOC', 'rmbtb-cert.pem'); /* * A secure, writable location where this script will store your temporary API secrets. * The file does not need to exist, but the folder must, and it must be writable. * Do not put this anywhere accessible from the web! */ define('RMBTB_LOCAL_STORAGE_LOC', 'rmbtb-store.dat'); /** * End of configuration section */ /** * * The base API class. This is extended below if you need to deal with integers rather than floats. */ class Rmbtb_Secure_API { private $urlBase = 'https://www.rmbtb.com/api/secure/', $currencyPair, $key, $passphrase, $secret, $secretExpiryTime, $debug; /** * Constructor * @param string $key your RMBTB Secure API public key * @param string $passphrase the passphrase given to you with your key */ public function __construct($currencyPair, $key, $passphrase, $debug = false) { $this->debug = $debug; $this->currencyPair = $currencyPair; $this->key = $key; $this->passphrase = $passphrase; $this->load_secret(RMBTB_LOCAL_STORAGE_LOC); } /** * get_info() * Get useful information about your account * Authentication required. * @return Array representation of JSON object */ public function get_info() { return $this->request('getinfo', array(), 'GET'); } /** * get_funds() * Get balance information * Authentication required. * @return Array representation of JSON object */ public function get_funds() { return $this->request('wallets', array(), 'GET'); } /** * ticker() * Get balance information * @return Array representation of JSON object */ public function ticker() { return $this->request('ticker', array(), 'GET', false); } /** * get_orders() * Get your 50 most recent orders * Authentication required. * @return Array representation of JSON object */ public function get_orders() { return $this->request('orders', array(), 'GET'); } /** * add_order() * Place a new order * Authentication required. * @param string bid|ask $type * @param float $amount amount of BTC to buy/sell * @param float $price bid or ask price * @return Array representation of JSON object */ public function add_order($type, $amount, $price) { return $this->request('order/add', array('type' => $type, 'amount' => $amount, 'price' => $price), 'POST'); } /** * add_order() * Cancel an order * Authentication required. * @param integer $orderid the Order ID to cancel * @return Array representation of JSON object */ public function cancel_order($orderid) { return $this->request('order/cancel', array('oid' => $orderid), 'POST'); } /** * fetch_order() * Fetch order details * Authentication required. * @param integer $orderid the Order ID to fetch * @return Array representation of JSON object */ public function fetch_order($orderid) { return $this->request('order/fetch', array('oid' => $orderid), 'GET'); } /** * get_trades() * Get your 50 most recent trades * Authentication required. * @return Array representation of JSON object */ public function get_trades() { return $this->request('trades/mine', array(), 'GET'); } /** * last_trades() * View the last 80 public trades * @return Array representation of JSON object */ public function last_trades() { return $this->request('trades/all', array(), 'GET', false); } /** * get_depth() * View the market depth * @return Array representation of JSON object */ public function get_depth() { return $this->request('depth', array(), 'GET', false); } /** * Performs the request. * @param string $method the API address * @param array $params the API method parameters * @param string GET|POST $http_method the HTTP method * @param bool $auth whether to sign the request * @return array with the returned data * @access protected */ protected function request($method, $params = array(), $http_method = 'GET', $auth = true) { $http_method = ($http_method == 'GET') ? 'GET' : 'POST'; if($auth) { // refresh secret if necessary $secretExpires = $this->secretExpiryTime - time(); if($secretExpires < 60) { $this->refresh_secret(RMBTB_LOCAL_STORAGE_LOC); } // generate an always-increasing nonce using microtime $mt = explode(' ', microtime()); $params['nonce'] = $mt[1].substr($mt[0], 2, 6); // generate the POST data string $data = http_build_query($params, '', '&'); // generate the extra headers for message verification $headers = array( 'Rest-Key: ' . $this->key, 'Rest-Sign: '. base64_encode(hash_hmac('sha512', $data, $this->secret, true)) ); } else { $data = http_build_query($params, '', '&'); $headers = array(); } $data = $this->do_curl($method, $data, $headers, $http_method); return $data; } /** * Loads the last API secret from your local storage file. * Secrets expire every two hours, so we only use the secret if it was stored less than two hours ago. * If it has expired, we load a new one. * @param string $loc the location where the last secret was stored. * @return bool false on failure * @access private */ private function load_secret($loc) { $this->secret = false; $this->secretExpiryTime = false; if(file_exists($loc)) { $storTime = @filemtime($loc); // Account for a bug in Windows where daylight saving is not reflected correctly $isDST = (date('I', $storTime) == 1); $systemDST = (date('I') == 1); $adjustment = 0; if($isDST == false && $systemDST == true) { $adjustment = 3600; } else if($isDST == true && $systemDST == false) { $adjustment = -3600; } $storTime += $adjustment; $elapsed = time() - $storTime; if($elapsed < 7200) { // secret has not yet expired $this->secret = trim(file_get_contents($loc)); $this->secretExpiryTime = $storTime + 7200; return true; } } // secret has expired or we've never created one before return $this->refresh_secret($loc); } /** * Fetch a new secret from the API * @param string $loc the location to store the secret * @return bool true on success * @access private */ private function refresh_secret($loc) { if($this->secret = $this->obtain_new_secret()) { file_put_contents($loc, $this->secret); $this->secretExpiryTime = time() + 7200; return true; } return false; } /** * Requests a new API secret, which will be tied to our IP and will * last for 2 hours. * @return string our new secret, or false on error. * @access private */ private function obtain_new_secret() { $postData = 'api_passphrase=' . urlencode($this->passphrase); $headers = array( 'Rest-Key: ' . $this->key ); $data = $this->do_curl('getsecret', $postData, $headers, 'POST'); return $data['data']['secret']; } /** * Performs the request * @param string $path the API method path * @param string $data the GET url string, or the POST body * @param array $headers headers to send -- e.g. our Rest-Key and Rest-Sign * @param string GET|POST $http_method the HTTP method to use * @return array representation of JSON response, or an error. * @access private */ private function do_curl($path, $data, $headers, $http_method) { static $ch = null; $url = $this->urlBase . $this->currencyPair . '/' . $path; if($this->debug) { echo "Sending request to $url\n"; } if (is_null($ch)) { $ch = curl_init(); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; RMBTB PHP client; '.php_uname('s').'; PHP/'.phpversion().')'); } if($http_method == 'GET') { $url .= '?' . $data; } else { curl_setopt($ch, CURLOPT_POSTFIELDS, $data); } curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, TRUE); curl_setopt($ch, CURLOPT_CAINFO, RMBTB_CERT_LOC); // run the query $response = curl_exec($ch); if($this->debug) { echo "Response: $response\n"; } if (empty($response)) { throw new Exception('Could not get reply: ' . curl_error($ch)); } $data = json_decode($response, true); if (!is_array($data)) { throw new Exception('Invalid data received, please make sure connection is working and requested API exists'); } $this->quit_on_error($data); return $data; } /** * Parses the returned data, and bails if it contains an error. * @param array $data an array representation of returned JSON data * @return void * @access private */ private function quit_on_error($data) { if($data['error'] !== false) { throw new Exception("\n\nError received from API: {$data['code']}\n-----------------------------------\n{$data['error']}\n\n"); exit(); } } } /** * * Use this if you prefer to use integers to order. */ class Rmbtb_Secure_API_Integer extends Rmbtb_Secure_API { /** * add_order() * Place a new order * Authentication required. * @param string bid|ask $type * @param integer $amount_int amount of BTC to buy/sell * @param integer $price_int bid or ask price * @return Array representation of JSON object */ public function add_order($type, $amount_int, $price_int) { return $this->request('order/add', array('type' => $type, 'amount_int' => $amount_int, 'price_int' => $price_int)); } }
Title: Re: RMBTB Trade API -- Overview & Sample Python & PHP trade wrappers
Post by: BitPirate on August 06, 2013, 02:15:10 PM
... and JavaScript. This is fully asynchronous and tested in Node.js /** * * JavaScript (Node.js) secure API wrapper * * (c) 2013 rmbtb.com, released under the BSD license. You are free to copy, redistribute, and include this * code in your own application, for personal or commercial purposes. * * However, you may NOT brand yourself or any code or services as "RMBTB", "人盟比特币", or explicitly * or implicitly represent any services or software as being provided by or endorsed by RMBTB. * * Usage: * All functions are fully asynchronous, so you need to provide a callback function. * * var rmbtb = new rmbtbSecureApi('BTCCNY', myPubKey, myPassPhrase); * * rmbtb.addOrder('bid', 3, 591, function(result) { * console.log('Result: %j', result); * }); * */ // Location where you will store temporary secrets: var rmbtbStoreLocation = 'rmbtb-secret.dat'; // Node.js dependencies var fs = require('fs'), https = require('https'), querystring = require('querystring'), crypto = require('crypto'); function rmbtbSecureApi(currPair, pubKey, passphrase) { this.currPair = currPair; this.pubKey = pubKey; this.passphrase = passphrase; this.storeLoc = rmbtbStoreLocation; this.apiHost = 'www.rmbtb.com'; this.apiPath = '/api/secure/' + currPair + '/'; this.secret = false; this.secretExpiryTime = false; var that = this; var twoHrs = 7200000; // fetches a new secret if necessary, before performing the main request this.apiCall = function(api, params, httpMethod, auth, callback) { var cb = function() { prepRequest(api, params, httpMethod, auth, callback); }; if(auth) { if((this.secret === false) || (this.secretExpiryTime === false)) { loadSecret(cb); } else if((this.secretExpiryTime.getTime() - 60000) > (new Date()).getTime()) { rereshSecret(cb); } else { cb.call(this); } } else { cb.call(this); } }; // prepares the request function prepRequest(api, params, httpMethod, auth, callback) { var data = '', headers = {}; if(this.secret === false) { throw 'No valid secret!'; } if(auth) { params.nonce = (new Date()).getTime() * 1000; data = querystring.stringify(params); var hmac = crypto.createHmac("sha512", that.secret); hmac.update(data); headers['Rest-Key'] = that.pubKey; headers['Rest-sign'] = hmac.digest('base64'); } else { data = querystring.stringify(params); } if(httpMethod == 'POST') { headers['Content-Type'] = 'application/x-www-form-urlencoded'; headers['Content-Length'] = data.length; } headers['User-Agent'] = 'Mozilla/4.0 (compatible; RMBTB JavaScript client)';
var opts = { method: httpMethod, headers: headers }; if(httpMethod == 'GET') { api += '?' + data; } doRequest(api, data, opts, callback); } // sends a prepared request to the API function doRequest(api, data, opts, callback) { opts.host = that.apiHost; opts.path = that.apiPath + api; var req = https.request(opts, function(response) { var resultStr = ''; response.setEncoding('utf8'); response.on('data', function(chunk) { resultStr += chunk; }); response.on('error', function(err) { throw err; }); response.on('end', function() { try { var obj = JSON.parse(resultStr); } catch(e) { throw 'Could not understand response'; } if(obj.error !== false) { throw 'API returned an error: ' + obj.error; } var result = obj.data; callback.call(that, result); }); }); req.on('error', function(err) { throw 'Error communicating: ' + err; }); if(opts.method == 'POST') { req.write(data); } req.end(); } // refreshes the temporary secret function refreshSecret(cb) { var data = querystring.stringify({'api_passphrase': that.passphrase}); var opts = { method: 'POST', headers: { 'Rest-Key': that.pubKey, 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': data.length, } }; doRequest('getsecret', data, opts, function(result) { that.secret = result.secret; that.secretExpiryTime = new Date((new Date()).getTime() + twoHrs); fs.writeFile(that.storeLoc, that.secret, 'utf8', function() { cb.call(that) }); }); } // loads the stored secret from the save file function loadSecret(cb) { fs.stat(that.storeLoc, function(err, stat) { if(!err) { that.secretExpiryTime = new Date(stat.mtime.getTime() + twoHrs); if((new Date()).getTime() < that.secretExpiryTime.getTime()) { fs.readFile(that.storeLoc, 'utf8', function(err, data) { if(!err) { that.secret = data; cb.call(that); } else { refreshSecret(cb); } }); } else { refreshSecret(cb); } } else { refreshSecret(cb); } }); } } /** * * The public, callable methods: * */ rmbtbSecureApi.prototype.getInfo = function(callback) { this.apiCall('getinfo', {}, 'GET', true, callback); }; rmbtbSecureApi.prototype.getFunds = function(callback) { this.apiCall('wallets', {}, 'GET', true, callback); }; rmbtbSecureApi.prototype.ticker = function(callback) { this.apiCall('ticker', {}, 'GET', false, callback); }; rmbtbSecureApi.prototype.getOrders = function(callback) { this.apiCall('orders', {}, 'GET', true, callback); }; rmbtbSecureApi.prototype.addOrder = function(type, amount, price, callback) { var params = { 'type': type, 'amount': amount, 'price': price }; this.apiCall('order/add', params, 'POST', true, callback); }; rmbtbSecureApi.prototype.addOrderInt = function(type, amount, price, callback) { var params = { 'type': type, 'amount_int': amount, 'price_int': price }; this.apiCall('order/add', params, 'POST', true, callback); }; rmbtbSecureApi.prototype.cancelOrder = function(orderid, callback) { var params = { 'oid': orderid, }; this.apiCall('order/cancel', params, 'POST', true, callback); }; rmbtbSecureApi.prototype.fetchOrder = function(orderid, callback) { var params = { 'oid': orderid, }; this.apiCall('order/fetch', params, 'POST', true, callback); }; rmbtbSecureApi.prototype.getTrades = function(callback) { this.apiCall('trades/mine', {}, 'GET', true, callback); }; rmbtbSecureApi.prototype.lastTrades = function(callback) { this.apiCall('trades/all', {}, 'GET', false, callback); }; rmbtbSecureApi.prototype.getDepth = function(callback) { this.apiCall('depth', {}, 'GET', false, callback); };
Title: Re: RMBTB Trade API -- Overview & Sample Python, PHP & JS trade wrappers
Post by: daybyter on August 06, 2013, 02:29:35 PM
I work on Java implementations of various exchanges:
https://github.com/ReAzem/cryptocoin-tradelib/tree/master/modules
Maybe a java implementation of the API would help, too?
Just an idea...
Title: Re: RMBTB Trade API -- Overview & Sample Python, PHP & JS trade wrappers
Post by: BitPirate on August 06, 2013, 02:36:39 PM
I work on Java implementations of various exchanges:
https://github.com/ReAzem/cryptocoin-tradelib/tree/master/modules
Maybe a java implementation of the API would help, too?
Just an idea...
That would be awesome, yes :-) EDIT: Java implementation added :-)
Title: Re: RMBTB Trade API -- Overview & Sample Python, PHP, JS & Ruby trade wrappers
Post by: BitPirate on August 08, 2013, 02:49:01 PM
Have you some Ruby #!/usr/bin/env ruby # # coding: utf-8 # # Ruby wrapper for the RMBTB.com Secure API # (c) RMBTB.com. Released under the BSD license. Modification & redistribution allowed, but you may not use the RMBTB brand or name. # # Example usage: # rmbtb = RmbtbSecureApi.new(pubkey, passphrase, 'BTCCNY') # p rmbtb.add_order('bid', 3.1, 570.2) # require 'base64' require 'openssl' require 'time' require 'uri' require 'cgi' require 'net/http' require 'net/https'
require 'rubygems' require 'json'
# RMBTB API layer class RmbtbSecureApi
def initialize(key, passphrase, currpair = 'BTCCNY') @base = 'https://www.rmbtb.com/api/secure/' @storloc = 'rmbtb-store.dat'
@key = key @passphrase = passphrase @currpair = currpair @secret = nil @expiry = nil end
# Public: Retrieve account information # # Returns a JSON object def get_info return prepare_request('getinfo', :httpMethod => 'GET', :auth => true) end # Public: Retrieve wallet balances # # Returns a JSON object of wallet balances def get_funds return prepare_request('wallets', :httpMethod => 'GET', :auth => true) end # Public: Retrieve market information # # Returns a JSON object def ticker return prepare_request('ticker', :httpMethod => 'GET') end # Public: Retrieve my last 50 orders # # Returns a JSON object of orders def get_orders return prepare_request('ticker', :httpMethod => 'GET', :auth => true) end # Public: Add an order, using floats # # type - String, 'bid' or 'ask' # amount - Float, amount to buy or sell # price - Float, the price # # Returns a JSON object containing the Order ID def add_order(type, amount, price) params = { :type => type, :amount => amount, :price => price } return prepare_request('order/add', :params => params, :httpMethod => 'POST', :auth => true) end # Public: Add an order, using integers # # type - String, 'bid' or 'ask' # amount - Float, amount to buy or sell # price - Float, the price # # Returns a JSON object containing the Order ID def add_order_int(type, amount, price) params = { :type => type, :amount_int => amount, :price_int => price } return prepare_request('order/add', :params => params, :httpMethod => 'POST', :auth => true) end # Public: Cancel an order # # orderid - Integer, the order ID to cancel # # Returns a JSON object containing the Order ID def cancel_order(orderid) params = { :oid => orderid } return prepare_request('order/cancel', :params => params, :httpMethod => 'POST', :auth => true) end # Public: Fetch information about an order # # orderid - Integer, the order ID to cancel # # Returns order information def fetch_order(orderid) params = { :oid => orderid } return prepare_request('order/fetch', :params => params, :httpMethod => 'GET', :auth => true) end # Public: Get my trades # # Returns my latest trades def get_trades return prepare_request('trades/mine', :httpMethod => 'GET', :auth => true) end # Public: Get all market trades # # Returns the latest market trades def last_trades return prepare_request('trades/all', :httpMethod => 'GET') end # Public: Get depth # # Returns the market depth def get_depth return prepare_request('depth', :httpMethod => 'GET') end
private # Prepares the request def prepare_request(api, options = { }) httpMethod = (options[:httpMethod] == 'GET') && 'GET' || 'POST' auth = options[:auth] ||= false params = options[:params] ||= { } if auth load_secret if !@secret || !@expiry || (@expiry < (Time.now - 60)) # Add nonce params = params.merge(:nonce => (Time.now.to_f * 1e6).to_i.to_s) sendParams = params.map{ |k,v| "#{ CGI::escape(k.to_s) }=#{ CGI::escape(v.to_s) }" }.join('&') mac = Base64.encode64(OpenSSL::HMAC.digest('sha512', @secret, sendParams)).gsub(/\n/, '') headers = { 'Rest-Key' => @key, 'Rest-Sign' => mac } else sendParams = params.map{ |k,v| "#{CGI::escape(k.to_s) }=#{ CGI::escape(v.to_s)}" }.join('&') headers = { } end return send_request(api, :paramStr => sendParams, :httpMethod => httpMethod, :headers => headers) end
# fetch or load/refresh the temporary secret def load_secret if !@secret && File.exists?(@storloc) @expiry = File.mtime(@storloc) + 7200 if @expiry > (Time.now - 60) @secret = File.open(@storloc).read return end end
result = send_request('getsecret', :paramStr => "api_passphrase=#{ CGI::escape(@passphrase) }", :httpMethod => 'POST', :headers => { 'Rest-Key' => @key }) @secret = result['secret'] File.open(@storloc, 'w') { |f| f.write(@secret) } @expiry = Time.now + 7200 end
# Send the request out def send_request(api, options={ })
httpMethod = (options[:httpMethod] == 'GET') && 'GET' || 'POST' headers = options[:headers] ||= { } data = options[:paramStr] ||= ''
url = "#{ @base }#{ @currpair }/#{ api }"
if httpMethod == 'POST' headers.merge({'Content-Type' => 'application/x-www-form-urlencoded'}) elsif data != '' url = "#{ url }?#{ data }" end
headers.merge({'User-Agent' => 'Mozilla/4.0 (compatible); RMBTB Ruby API Client'})
url = URI.parse(url) https = Net::HTTP.new(url.host, url.port) https.use_ssl = true result = nil begin req, body = ((httpMethod == 'POST') ? https.post(url.path, data, headers) : https.get(url.request_uri, headers)) result = JSON.parse(body) rescue raise 'Could not communicate with server' end raise "Error: #{ result['error'] }" if result['error'] return result['data']
end
end
Title: Re: RMBTB Trade API -- Overview & Sample Python, PHP, JS & Ruby trade wrappers
Post by: BitPirate on August 12, 2013, 04:26:41 PM
... And here is a Java implementation. Let me know how this works when included in other projects, or if I need to make any changes to make it less brittle. You'll obviously want to change the location where the secret is stored.. or (better), change the storage method altogether. /** * JAVA RMBTB SECURE API CLASS * * COPYRIGHT & LIMITATIONS * (c) 2013 rmbtb.com, released under the BSD license. You are free to copy, redistribute, and include this * code in your own application, for personal or commercial purposes. * * However, you may NOT brand yourself or any code or services as "RMBTB", "人盟比特币", or explicitly * or implicitly represent any services or software as being provided by or endorsed by RMBTB. * * USAGE * This class relies on the Apache commons codec and the json-simple modules. * * RmbtbSecureApi r = new RmbtbSecureApi("pubkey", "passphrase", "BTCCNY"); * Sytem.out.print(r.addOrder("bid", 1.1, 600.0)); * * Full documentation is at https://www.rmbtb.com/help-secureapi-en/ * */ import java.util.*; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.text.DecimalFormat; import javax.net.ssl.HttpsURLConnection; import java.net.ProtocolException; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException;
// http://commons.apache.org/proper/commons-codec/ import org.apache.commons.codec.binary.Base64;
// json-simple http://code.google.com/p/json-simple/ import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException;
/** * The RMBTB Secure API class. Set the constants here. */ interface RmbtbApi { // Set this to the location you want to store temporary secrets public static final String StorLocation = "rmbtb-secret-data.dat"; public static final String urlBaseLoc = "https://www.rmbtb.com/api/secure/";
/** * Gets useful information about your account * Authentication required. * @return JSONObject account details */ public JSONObject getInfo() throws Exception; /** * Gets your wallet balances * Authentication required. * @return JSONObject account balances */ public JSONObject getFunds() throws Exception; /** * Gets your wallet balances * Authentication required. * @return JSONObject account balances */ public JSONObject ticker() throws Exception; /** * Gets useful market information * @return JSONObject market info */ public JSONObject getOrders() throws Exception; /** * Adds an order -- double params * @param String type bid | ask * @param double amount the amount to buy/sell * @param souble price the price to buy/sell at * @return JSONObject containing the Order ID */ public JSONObject addOrder(String type, double amount, double price) throws Exception;
/** * Adds an order -- long integer params * @param String type bid | ask * @param long amount the amount to buy/sell * @param long price the price to buy/sell at * @return JSONObject containing the Order ID */ public JSONObject addOrder(String type, long amount, long price) throws Exception;
/** * Cancels an order * @param long orderId the Order ID to cancel * @return JSONObject containing the Order ID */ public JSONObject cancelOrder(long orderId) throws Exception; /** * fetches info about an order * @param long orderId the Order ID to cancel * @return JSONObject of info */ public JSONObject fetchOrder(long orderId) throws Exception; /** * Gets your 50 most recent trades * @return JSONObject of info */ public JSONObject getTrades() throws Exception; /** * returns the most recent market transactions * @return JSONObject of info */ public JSONObject lastTrades() throws Exception; /** * returns the market depth * @return JSONObject showing bids and asks */ public JSONObject getDepth() throws Exception; }
public class RmbtbSecureApi implements RmbtbApi { private String currPair; private String pubkey; private String passphrase; private String secret; private Calendar expires;
public RmbtbSecureApi(String pubkey, String passphrase) { this(pubkey, passphrase, "BTCCNY"); }
public RmbtbSecureApi(String pubkey, String passphrase, String currPair) {
this.currPair = currPair; this.pubkey = pubkey; this.passphrase = passphrase; this.secret = ""; this.expires = Calendar.getInstance();
} public JSONObject getInfo() throws Exception { return doRequest("getinfo", "GET", true); } public JSONObject getFunds() throws Exception { return doRequest("wallets", "GET", true); } public JSONObject ticker() throws Exception { return doRequest("ticker", "GET", false); } public JSONObject getOrders() throws Exception { return doRequest("orders", "GET", true); } public JSONObject addOrder(String type, double amount, double price) throws Exception { HashMap<String,String> params = new HashMap<String, String>(); params.put("type", type == "ask" ? "ask" : "bid"); params.put("amount", new DecimalFormat("00000000.00000000").format(amount)); params.put("price", new DecimalFormat("00000000.00000000").format(price));
return doRequest("order/add", "POST", true, params); } public JSONObject addOrder(String type, long amount, long price) throws Exception {
HashMap<String,String> params = new HashMap<String, String>(); params.put("type", type == "ask" ? "ask" : "bid"); params.put("amount_int", String.valueOf(amount)); params.put("price_int", String.valueOf(price));
return doRequest("order/add", "POST", true, params); } public JSONObject cancelOrder(long orderId) throws Exception { HashMap<String,String> params = new HashMap<String, String>(); params.put("oid", String.valueOf(orderId));
return doRequest("order/cancel", "POST", true, params); } public JSONObject fetchOrder(long orderId) throws Exception { HashMap<String,String> params = new HashMap<String, String>(); params.put("oid", String.valueOf(orderId));
return doRequest("order/fetch", "GET", true, params); } public JSONObject getTrades() throws Exception { return doRequest("trades/mine", "GET", true); } public JSONObject lastTrades() throws Exception { return doRequest("trades/all", "GET", false); } public JSONObject getDepth() throws Exception { return doRequest("depth", "GET", false); } private JSONObject doRequest(String api, String httpMethod, boolean auth) throws Exception {
HashMap<String,String> params = new HashMap<String, String>(); return doRequest(api, httpMethod, auth, params); }
private JSONObject doRequest(String api, String httpMethod, boolean auth, HashMap<String,String> params) throws Exception {
if(auth) { Calendar maxAge = Calendar.getInstance(); maxAge.add(Calendar.MINUTE, -1); if((this.secret == "") || this.expires.before(maxAge)) { this.loadSecret(); } params.put("nonce", String.valueOf(System.nanoTime())+"000"); } String paramStr = ""; int i = 0; for(Map.Entry<String, String> entry : params.entrySet()) { paramStr += entry.getKey() + "=" + URLEncoder.encode(entry.getValue(), "UTF-8"); i++; if(i < params.size()) { paramStr += "&"; } } HashMap<String,String> headers = new HashMap<String, String>(); if(auth) { headers.put("Rest-Key", this.pubkey); headers.put("Rest-Sign", this.getRequestSig(paramStr)); } JSONObject data = this.doHttpRequest(api, httpMethod, paramStr, headers); return data; } private void loadSecret() throws UnsupportedEncodingException, IOException, ParseException {
File dat = new File(this.StorLocation); // Load secret from file if(dat.exists()) { this.expires.setTimeInMillis(dat.lastModified()); this.expires.add(Calendar.HOUR, 2);
Calendar maxAge = Calendar.getInstance(); maxAge.add(Calendar.MINUTE, -1);
if(this.expires.after(maxAge)) { StringBuilder datContents = new StringBuilder((int)dat.length()); Scanner scanner = null; try { scanner = new Scanner(dat); while(scanner.hasNextLine()) { datContents.append(scanner.nextLine()); } } catch (FileNotFoundException e) { } finally { scanner.close(); } this.secret = datContents.toString(); this.expires = Calendar.getInstance(); this.expires.add(Calendar.HOUR, 2); return; } }
// Need to fetch a new secret HashMap<String,String> headers = new HashMap<String, String>(); headers.put("Rest-Key", this.pubkey); String params = "api_passphrase=" + URLEncoder.encode(this.passphrase, "UTF-8");
JSONObject data = this.doHttpRequest("getsecret", "POST", params, headers); this.secret = (String)data.get("secret"); this.expires = Calendar.getInstance(); this.expires.add(Calendar.HOUR, 2); FileWriter fw = new FileWriter(dat); try { fw.write(this.secret); } catch(IOException e) { System.out.println(e); } finally { fw.close(); } } // Perform the request private JSONObject doHttpRequest(String api, String httpMethod, String params, HashMap<String,String> headers) throws IOException, ProtocolException, ParseException { api = (httpMethod == "GET") ? api += "?" + params : api; URL uObj = new URL(this.urlBaseLoc + this.currPair + "/" + api); HttpsURLConnection conn = (HttpsURLConnection) uObj.openConnection(); conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; RMBTB Java client)"); for(Map.Entry<String, String> entry : headers.entrySet()) { conn.setRequestProperty(entry.getKey(), entry.getValue()); } if(httpMethod == "POST") { conn.setRequestMethod("POST"); conn.setDoOutput(true); DataOutputStream out = new DataOutputStream(conn.getOutputStream()); out.writeBytes(params); out.flush(); out.close(); } else { conn.setRequestMethod("GET"); } BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); String inLine; StringBuffer response = new StringBuffer();
while((inLine = in.readLine()) != null) { response.append(inLine); } in.close(); JSONParser parser = new JSONParser(); JSONObject respJSON = (JSONObject)(parser.parse(response.toString()));
if(respJSON.get("error").getClass().getName() != "java.lang.Boolean") { String strErr = (String)(respJSON.get("error")); throw new RuntimeException(strErr); } JSONObject data = (JSONObject)respJSON.get("data"); return data; } // Signs using HMAC private String getRequestSig(String data) throws NoSuchAlgorithmException, InvalidKeyException {
Mac mac = Mac.getInstance("HmacSHA512"); SecretKeySpec secret_spec = new SecretKeySpec(this.secret.getBytes(), "HmacSHA512"); mac.init(secret_spec); return Base64.encodeBase64String(mac.doFinal(data.getBytes())).toString(); } }
|