├── .gitignore ├── README.md ├── .eslintrc.yml ├── package.json ├── test.js ├── LICENSE └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Node.js API frontend for the Cryptopia Crypto Currency Exchange (https://www.cryptopia.co.nz) 2 | 3 | 4 | Note: Credit goes to Nick Addison for his OKCoin (https://github.com/naddison36/okcoin) API Wrapper (another bitcoin exchange). The base HTTP Request structure was re-used for this exchange 5 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | browser: true 3 | es6: true 4 | node: true 5 | extends: 'eslint:recommended' 6 | parserOptions: 7 | sourceType: module 8 | rules: 9 | indent: 10 | - error 11 | - 4 12 | linebreak-style: 13 | - error 14 | - unix 15 | quotes: 16 | - error 17 | - double 18 | semi: 19 | - error 20 | - never 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cryptopia", 3 | "version": "0.0.1", 4 | "description": "Node.js API wrapper to the Cryptopia bitcoin exchange", 5 | "main": "index.js", 6 | "dependencies": { 7 | "request": "~2.67.0", 8 | "underscore": "~1.8.3", 9 | "crypto": "~0.0.3", 10 | "verror": "~1.6.0", 11 | "MD5": "~1.3.0" 12 | }, 13 | "keywords": [ 14 | "api", 15 | "exchange", 16 | "cryptocurrency", 17 | "bitcoin" 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/kwiksand/cryptopia" 22 | }, 23 | "author": "Shannon Carver", 24 | "license": "MIT" 25 | } 26 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | Cryptopia = require('./index.js') 2 | let logResponse = {} 3 | 4 | // Either pass your API key and secret as the first and second parameters to examples.js. eg 5 | // node examples.js your-api-key your-api-secret 6 | // 7 | // Or enter them below. 8 | // WARNING never commit your API keys into a public repository. 9 | var key = process.argv[2] || 'your-api-key'; 10 | var secret = process.argv[3] || 'your-api-secret'; 11 | 12 | // Test public data APIs 13 | var publicClient = new Cryptopia() 14 | var privateClient = new Cryptopia(key, secret) 15 | 16 | // get BTCUSD ticker 17 | publicClient.getTicker(function(err,data){ 18 | console.log(data) 19 | return true}, 'BTC_USDT') 20 | 21 | // get BTCUSD Order Book 22 | publicClient.getOrderBook(function(err,data){ 23 | console.log(data) 24 | console.log(JSON.stringify(data)) 25 | return true}, 'BTC_USDT') 26 | 27 | 28 | // get BTCUSD trades 29 | publicClient.getTrades(function(err,data){ 30 | console.log(data) 31 | return true}, 'BTC_USDT') 32 | 33 | // get Account Balance 34 | privateClient.getBalance(function(err,data){ 35 | console.log(data) 36 | return true}, {}) 37 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Shannon Carver 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var verbose = false 2 | 3 | var util = require("util"), 4 | _ = require("underscore"), 5 | request = require("request"), 6 | crypto = require("crypto"), 7 | VError = require("verror") 8 | // md5 = require("MD5") 9 | 10 | var Cryptopia = function Cryptopia(api_key, secret, hostname, timeout) 11 | { 12 | this.api_key = api_key 13 | this.secret = secret 14 | 15 | this.hostname = hostname || "www.cryptopia.co.nz" 16 | this.server = "https://" + this.hostname 17 | this.publicApiPath = "api" 18 | this.privateApiPath = "api" 19 | 20 | this.timeout = timeout || 20000 21 | } 22 | 23 | var headers = {"User-Agent": "nodejs-7.5-api-client"} 24 | 25 | Cryptopia.prototype.privateRequest = function(method, params, callback) 26 | { 27 | var functionName = "Cryptopia.privateRequest()", 28 | self = this 29 | 30 | var error 31 | 32 | if(!this.api_key || !this.secret) 33 | { 34 | error = new VError("%s must provide api_key and secret to make this API request.", functionName) 35 | return callback(error) 36 | } 37 | 38 | if(!_.isObject(params)) 39 | { 40 | error = new VError("%s second parameter %s must be an object. If no params then pass an empty object {}", functionName, params) 41 | return callback(error) 42 | } 43 | 44 | if (!callback || typeof(callback) != "function") 45 | { 46 | error = new VError("%s third parameter needs to be a callback function", functionName) 47 | return callback(error) 48 | } 49 | 50 | var uri = this.privateApiPath + "/" + method 51 | var url = this.server + "/" + uri 52 | 53 | var nonce = Math.floor(new Date().getTime() / 1000) 54 | var md5 = crypto.createHash("md5").update( JSON.stringify( params ) ).digest() 55 | var requestContentBase64String = md5.toString("base64") 56 | var signature = this.api_key + "POST" + encodeURIComponent( url ).toLowerCase() + nonce + requestContentBase64String 57 | var hmacsignature = crypto.createHmac("sha256", new Buffer( this.secret, "base64" ) ).update( signature ).digest().toString("base64") 58 | var header_value = "amx " + this.api_key + ":" + hmacsignature + ":" + nonce 59 | 60 | var headers = { 61 | "Authorization": header_value, 62 | "Content-Type":"application/json; charset=utf-8", 63 | "Content-Length" : Buffer.byteLength(JSON.stringify(params)), 64 | "User-Agent": "nodejs-7.5-api-client" 65 | } 66 | var options = { 67 | //host: this.hostname, 68 | //path: uri, 69 | url: url, 70 | method: "POST", 71 | headers: headers, 72 | form: JSON.stringify( params ) 73 | } 74 | 75 | var requestDesc = util.format("%s request to url %s with method %s and params %s", 76 | options.method, url, method, JSON.stringify(params)) 77 | 78 | executeRequest(options, requestDesc, callback) 79 | } 80 | 81 | Cryptopia.prototype.publicRequest = function(method, params, callback) 82 | { 83 | var functionName = "Cryptopia.publicRequest()" 84 | var error 85 | 86 | if(!_.isObject(params)) 87 | { 88 | error = new VError("%s second parameter %s must be an object. If no params then pass an empty object {}", functionName, params) 89 | return callback(error) 90 | } 91 | 92 | if (!callback || typeof(callback) != "function") 93 | { 94 | error = new VError("%s third parameter needs to be a callback function with err and data parameters", functionName) 95 | return callback(error) 96 | } 97 | 98 | var url = this.server + "/" + this.publicApiPath + "/" + method + "" 99 | if (verbose) console.log("Request URL is: " + url) 100 | 101 | var options = { 102 | url: url, 103 | method: "GET", 104 | headers: headers, 105 | timeout: this.timeout, 106 | qs: params, 107 | json: {} // request will parse the json response into an object 108 | } 109 | 110 | var requestDesc = util.format("%s request to url %s with parameters %s", 111 | options.method, options.url, JSON.stringify(params)) 112 | 113 | executeRequest(options, requestDesc, callback) 114 | } 115 | 116 | function executeRequest(options, requestDesc, callback) 117 | { 118 | var functionName = "Cryptopia.executeRequest()" 119 | 120 | request(options, function(err, response, data) 121 | { 122 | var error = null, // default to no errors 123 | returnObject = data 124 | 125 | if(err) 126 | { 127 | error = new VError(err, "%s failed %s", functionName, requestDesc) 128 | error.name = err.code 129 | } 130 | else if (response.statusCode < 200 || response.statusCode >= 300) 131 | { 132 | error = new VError("%s HTTP status code %s returned from %s", functionName, 133 | response.statusCode, requestDesc) 134 | error.name = response.statusCode 135 | } 136 | else if (options.form) 137 | { 138 | try { 139 | returnObject = JSON.parse(data) 140 | } 141 | catch(e) { 142 | error = new VError(e, "Could not parse response from server: " + data) 143 | } 144 | } 145 | // if json request was not able to parse json response into an object 146 | else if (options.json && !_.isObject(data) ) 147 | { 148 | error = new VError("%s could not parse response from %s\nResponse: %s", functionName, requestDesc, data) 149 | } 150 | 151 | if (_.has(returnObject, "error_code")) 152 | { 153 | var errorMessage = mapErrorMessage(returnObject.error_code) 154 | 155 | error = new VError("%s %s returned error code %s, message: '%s'", functionName, 156 | requestDesc, returnObject.error_code, errorMessage) 157 | 158 | error.name = returnObject.error_code 159 | } 160 | 161 | callback(error, returnObject) 162 | }) 163 | } 164 | 165 | // 166 | // Public Functions 167 | // 168 | 169 | 170 | Cryptopia.prototype.getCurrencies = function getCurrencies(callback) 171 | { 172 | this.publicRequest("GetCurrencies/", {}, callback) 173 | } 174 | 175 | Cryptopia.prototype.getTicker = function getTicker(callback, pair) 176 | { 177 | this.publicRequest("GetMarket/" + pair, {currencyPair: pair}, callback) 178 | } 179 | 180 | Cryptopia.prototype.getOrderBook = function getOrderBook(callback, pair, limit) 181 | { 182 | var params = { 183 | currencyPair: pair, 184 | limit: 1000, 185 | } 186 | 187 | if (!_.isUndefined(limit) ) params.limit = limit 188 | 189 | this.publicRequest("GetMarketOrders/" + pair + "/" + params.limit, params, callback) 190 | } 191 | 192 | Cryptopia.prototype.getTrades = function getTrades(callback, pair, hours) 193 | { 194 | var params = { 195 | currencyPair: pair, 196 | hours: 24, 197 | } 198 | 199 | if (hours) params.hours = hours 200 | 201 | this.publicRequest("GetMarketHistory/" + pair + "/" + params.hours, params, callback) 202 | } 203 | 204 | Cryptopia.prototype.getKline = function getKline(callback, symbol, type, size, since) 205 | { 206 | var params = {symbol: symbol} 207 | if (type) params.type = type 208 | if (size) params.size = size 209 | if (since) params.since = since 210 | 211 | this.publicRequest("kline", params, callback) 212 | } 213 | 214 | Cryptopia.prototype.getLendDepth = function getLendDepth(callback, symbol) 215 | { 216 | this.publicRequest("kline", {symbol: symbol}, callback) 217 | } 218 | 219 | // 220 | // Private Functions 221 | // 222 | 223 | Cryptopia.prototype.getBalance = function getBalance(callback) 224 | { 225 | this.privateRequest("GetBalance", {}, callback) 226 | } 227 | 228 | Cryptopia.prototype.addTrade = function addTrade(callback, symbol, type, amount, price) 229 | { 230 | var params = { 231 | symbol: symbol, 232 | type: type 233 | } 234 | 235 | if (amount) params.amount = amount 236 | if (price) params.price = price 237 | 238 | this.privateRequest("trade", params, callback) 239 | } 240 | 241 | Cryptopia.prototype.addBatchTrades = function addBatchTrades(callback, symbol, type, orders) 242 | { 243 | this.privateRequest("batch_trade", { 244 | symbol: symbol, 245 | type: type, 246 | orders_data: orders 247 | }, callback) 248 | } 249 | 250 | Cryptopia.prototype.cancelOrder = function cancelOrder(callback, symbol, order_id) 251 | { 252 | this.privateRequest("cancel_order", { 253 | symbol: symbol, 254 | order_id: order_id 255 | }, callback) 256 | } 257 | 258 | Cryptopia.prototype.getOrderInfo = function getOrderInfo(callback, symbol, order_id) 259 | { 260 | this.privateRequest("order_info", { 261 | symbol: symbol, 262 | order_id: order_id 263 | }, callback) 264 | } 265 | 266 | Cryptopia.prototype.getOrdersInfo = function getOrdersInfo(callback, symbol, type, order_id) 267 | { 268 | this.privateRequest("orders_info", { 269 | symbol: symbol, 270 | type: type, 271 | order_id: order_id 272 | }, callback) 273 | } 274 | 275 | Cryptopia.prototype.getAccountRecords = function getAccountRecords(callback, symbol, type, current_page, page_length) 276 | { 277 | this.privateRequest("account_records", { 278 | symbol: symbol, 279 | type: type, 280 | current_page: current_page, 281 | page_length: page_length 282 | }, callback) 283 | } 284 | 285 | Cryptopia.prototype.getTradeHistory = function getTradeHistory(callback, symbol, since) 286 | { 287 | this.privateRequest("trade_history", { 288 | symbol: symbol, 289 | since: since 290 | }, callback) 291 | } 292 | 293 | Cryptopia.prototype.getOrderHistory = function getOrderHistory(callback, symbol, status, current_page, page_length) 294 | { 295 | this.privateRequest("order_history", { 296 | symbol: symbol, 297 | status: status, 298 | current_page: current_page, 299 | page_length: page_length 300 | }, callback) 301 | } 302 | 303 | Cryptopia.prototype.addWithdraw = function addWithdraw(callback, symbol, chargefee, trade_pwd, withdraw_address, withdraw_amount) 304 | { 305 | this.privateRequest("withdraw", { 306 | symbol: symbol, 307 | chargefee: chargefee, 308 | trade_pwd: trade_pwd, 309 | withdraw_address: withdraw_address, 310 | withdraw_amount: withdraw_amount 311 | }, callback) 312 | } 313 | 314 | Cryptopia.prototype.cancelWithdraw = function cancelWithdraw(callback, symbol, withdraw_id) 315 | { 316 | this.privateRequest("cancel_withdraw", { 317 | symbol: symbol, 318 | withdraw_id: withdraw_id 319 | }, callback) 320 | } 321 | 322 | /** 323 | * Maps the Cryptopia error codes to error message 324 | * @param {Integer} error_code Cryptopia error code 325 | * @return {String} error message 326 | */ 327 | function mapErrorMessage(error_code) 328 | { 329 | var errorCodes = { 330 | 10000: "Required parameter can not be null", 331 | 10001: "Requests are too frequent", 332 | 10002: "System Error", 333 | 10003: "Restricted list request, please try again later", 334 | 10004: "IP restriction", 335 | 10005: "Key does not exist", 336 | 10006: "User does not exist", 337 | 10007: "Signatures do not match", 338 | 10008: "Illegal parameter", 339 | 10009: "Order does not exist", 340 | 10010: "Insufficient balance", 341 | 10011: "Order is less than minimum trade amount", 342 | 10012: "Unsupported symbol (not btc_usd or ltc_usd)", 343 | 10013: "This interface only accepts https requests", 344 | 10014: "Order price must be between 0 and 1,000,000", 345 | 10015: "Order price differs from current market price too much", 346 | 10016: "Insufficient coins balance", 347 | 10017: "API authorization error", 348 | 10026: "Loan (including reserved loan) and margin cannot be withdrawn", 349 | 10027: "Cannot withdraw within 24 hrs of authentication information modification", 350 | 10028: "Withdrawal amount exceeds daily limit", 351 | 10029: "Account has unpaid loan, please cancel/pay off the loan before withdraw", 352 | 10031: "Deposits can only be withdrawn after 6 confirmations", 353 | 10032: "Please enabled phone/google authenticator", 354 | 10033: "Fee higher than maximum network transaction fee", 355 | 10034: "Fee lower than minimum network transaction fee", 356 | 10035: "Insufficient BTC/LTC", 357 | 10036: "Withdrawal amount too low", 358 | 10037: "Trade password not set", 359 | 10040: "Withdrawal cancellation fails", 360 | 10041: "Withdrawal address not approved", 361 | 10042: "Admin password error", 362 | 10100: "User account frozen", 363 | 10216: "Non-available API", 364 | 503: "Too many requests (Http)"} 365 | 366 | if (!errorCodes[error_code]) 367 | { 368 | return "Unknown Cryptopia error code: " + error_code 369 | } 370 | 371 | return( errorCodes[error_code] ) 372 | } 373 | 374 | module.exports = Cryptopia 375 | --------------------------------------------------------------------------------