Bitcoin Forum

Bitcoin => Project Development => Topic started by: BitPirate on August 02, 2013, 09:32:35 AM



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:

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.

Code:
<?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"&#65292; "&#20154;&#30431;&#27604;&#29305;&#24065;", 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&gt;Pq&lt;_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], 26);
              
            
// 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->secrettrue))
            );
        } 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($chCURLOPT_RETURNTRANSFERtrue);
            
curl_setopt($chCURLOPT_USERAGENT'Mozilla/4.0 (compatible; RMBTB PHP client; '.php_uname('s').'; PHP/'.phpversion().')');
        }
          
         
          
        if(
$http_method == 'GET') {
            
$url .= '?' $data;
        } else {
            
curl_setopt($chCURLOPT_POSTFIELDS$data);
        }
  
        
curl_setopt($chCURLOPT_URL$url);
          
        
curl_setopt($chCURLOPT_HTTPHEADER$headers);
        
curl_setopt($chCURLOPT_SSL_VERIFYPEERTRUE);
        
curl_setopt($chCURLOPT_CAINFORMBTB_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($responsetrue);
 
        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

Code:
/**
 *
 *  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

Code:
#!/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.

Code:
/**
 * 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();
}
}