Bitcoin Forum
November 07, 2024, 01:58:55 AM *
News: Latest Bitcoin Core release: 28.0 [Torrent]
 
   Home   Help Search Login Register More  
Pages: [1]
  Print  
Author Topic: RMBTB Trade API -- Overview & Sample Python,PHP, JS,Ruby & Java trade wrappers  (Read 1202 times)
BitPirate (OP)
Full Member
***
Offline Offline

Activity: 238
Merit: 100


RMBTB.com: The secure BTC:CNY exchange. 0% fee!


View Profile
August 02, 2013, 09:32:35 AM
Last edit: August 12, 2013, 04:28:37 PM by BitPirate
 #1

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

Sample Python wrapper

Sample PHP client wrapper

Sample JavaScript (Node.js) wrapper

Sample Ruby wrapper

Sample Java wrapper

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!




BitPirate (OP)
Full Member
***
Offline Offline

Activity: 238
Merit: 100


RMBTB.com: The secure BTC:CNY exchange. 0% fee!


View Profile
August 06, 2013, 02:11:49 PM
Last edit: August 08, 2013, 04:43:35 PM by BitPirate
 #2

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")

BitPirate (OP)
Full Member
***
Offline Offline

Activity: 238
Merit: 100


RMBTB.com: The secure BTC:CNY exchange. 0% fee!


View Profile
August 06, 2013, 02:13:11 PM
Last edit: August 06, 2013, 02:37:44 PM by BitPirate
 #3

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));
    }
}

BitPirate (OP)
Full Member
***
Offline Offline

Activity: 238
Merit: 100


RMBTB.com: The secure BTC:CNY exchange. 0% fee!


View Profile
August 06, 2013, 02:15:10 PM
Last edit: August 08, 2013, 04:47:33 PM by BitPirate
 #4

... 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);
};

daybyter
Legendary
*
Offline Offline

Activity: 965
Merit: 1000


View Profile
August 06, 2013, 02:29:35 PM
 #5

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...

BitPirate (OP)
Full Member
***
Offline Offline

Activity: 238
Merit: 100


RMBTB.com: The secure BTC:CNY exchange. 0% fee!


View Profile
August 06, 2013, 02:36:39 PM
Last edit: August 12, 2013, 04:38:58 PM by BitPirate
 #6

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 :-)

BitPirate (OP)
Full Member
***
Offline Offline

Activity: 238
Merit: 100


RMBTB.com: The secure BTC:CNY exchange. 0% fee!


View Profile
August 08, 2013, 02:49:01 PM
Last edit: August 08, 2013, 04:49:36 PM by BitPirate
 #7

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

BitPirate (OP)
Full Member
***
Offline Offline

Activity: 238
Merit: 100


RMBTB.com: The secure BTC:CNY exchange. 0% fee!


View Profile
August 12, 2013, 04:26:41 PM
 #8

... 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();
}
}


Pages: [1]
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.19 | SMF © 2006-2009, Simple Machines Valid XHTML 1.0! Valid CSS!