Closed mitch-1211 closed 5 years ago
Hi,
So I have also tried using v3 of the API in Swift as follows:
func getHoldingsData(Coin: String, CoinURL: String, data: String){
var transactionArray = [Transaction]()
transactionArray.removeAll()
let privateAPIKey = savedPrivateKey!
let publicAPIKey = savedPublicKey!
guard let payload = Data(base64Encoded: privateAPIKey) else {errorEncountered()
return}
let decodedPrivateAPIKey = convert64EncodedToHex(payload)
let timestamp = "\(Int64(Date().timeIntervalSince1970*1000))"
let CoinURL = "/v3/trades"
let stringToSign = "GET" + CoinURL + timestamp
print("string to sign: \(stringToSign)")
let decodedPrivateAPIKeyByteArray = decodedPrivateAPIKey.hexa2Bytes
let stringToSignData: [UInt8] = Array(stringToSign.utf8)
var signature = ""
do {
let signatureArray = try HMAC(key: decodedPrivateAPIKeyByteArray, variant: .sha512).authenticate(stringToSignData)
if let conversion = signatureArray.toBase64(){
signature = conversion
}
}catch{
print(error)
}
let jsonUrlString = "https://api.btcmarkets.net" + CoinURL
let url = URL(string: jsonUrlString)!
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("UTF-8", forHTTPHeaderField: "Accept-Charset")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue(publicAPIKey, forHTTPHeaderField: "BM-AUTH-APIKEY")
request.setValue(timestamp, forHTTPHeaderField: "BM-AUTH-TIMESTAMP")
request.setValue(signature, forHTTPHeaderField: "BM-AUTH-SIGNATURE")
print("holdings: signature \(signature)")
print("holdings: apikey \(publicAPIKey)")
print("holdings: timestamp \(timestamp)")
session.dataTask(with: request as URLRequest) { (data, response, err) in
if err != nil {
print(err)
}else {
print("holdings: have received website response")
let dataAsString = String(data: data!, encoding: .utf8)
print("Website Response: " + dataAsString!)
guard let data = data, let jsonObj = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) else { self.errorEncountered()
self.URLdispatchGroup.leave()
return }
guard let dict = jsonObj as? NSDictionary else {self.errorEncountered()
self.URLdispatchGroup.leave()
return }
guard let dataArray = dict.value(forKey: "trades") as? [NSDictionary] else {self.errorEncountered()
//self.URLdispatchGroup.leave()
return }
for object in dataArray {
//print("\(object.value(forKey: "price")) \(object.value(forKey: "volume"))")
let type = object.value(forKey: "side") as! String
if type == "Bid" {
var price = object.value(forKey: "price") as! Double
var volume = object.value(forKey: "volume") as! Double
let date = object.value(forKey: "creationTime") as! Double
price = price/1e8
volume = volume/1e8
var icon = UIImage(named: "bitcoin")
if Coin == "BTC" {
icon = UIImage(named: "bitcoin")
}else if Coin == "BCHSV" {
icon = UIImage(named: "bitcoincash")
}else if Coin == "BCHABC" {
icon = UIImage(named: "bitcoincash")
}else if Coin == "ETH" {
icon = UIImage(named: "ethereum")
}else if Coin == "ETC" {
icon = UIImage(named: "ethclassic")
}else if Coin == "LTC" {
icon = UIImage(named: "litecoin")
}else if Coin == "XRP" {
icon = UIImage(named: "ripple")
}else if Coin == "OMG" {
icon = UIImage(named: "OMG")
}else if Coin == "POWR" {
icon = UIImage(named: "POWR")
}else if Coin == "BAT" {
icon = UIImage(named: "BAT")
}else if Coin == "XLM" {
icon = UIImage(named: "XLM")
}else if Coin == "GNT" {
icon = UIImage(named: "GNT")
}
let transaction = Transaction(crypto: Coin, amount: volume, buyPrice: price, icon: icon, date: date)
print(transaction!)
transactionArray.append(transaction!)
}
}
print("count1: \(transactionArray.count)")
self.transactions += transactionArray
print("count2: \(self.transactions.count)")
self.URLdispatchGroup.leave()
}
}.resume()
}
I get the following message returned from the API:
Website Response: {"code":"InvalidAuthSignature","message":"invalid signature"}
I have previously validated the signing using the example in v1/v2 of the API and able to recreate the example signature. Am I missing something in my implementation?
Hi @dev-duzza
Thanks for your feedback.
Authentication between v3 and v2 are very similar (except for how the message is created to be signed).
Our team will try to provide a working sample of authentication using Swift language in the coming days and I'm hoping that helps with your project.
In the meantime, please note we have sample working code (covers authentication and http handling ) in few programming languages including Java, Javascript, .NET, Python, Php, etc so please feel free to take a look as they might be helpful for Swift as well. https://github.com/BTCMarkets
Thanks.
Regards, Martin
Hi @martin-nginio
Thanks for getting back to me. A working example for Swift would be amazing. If possible can it be for a GET request and POST request to see the difference between?
I’ve had a browse of the other example code but am struggling to replicate in Swift.
Thanks, Mitch
In other languages we have provided GET, POST and DELETE and I'm hoping we can provide the same for Swift as well.
Thanks.
Thanks Martin, much appreciated.
By the way, I am using cryptoSwift library for the HMAC signing. It can be installed using cocoaPods
Thanks @dev-duzza. I will let our team know.
Just to add a little more information, this issue seems to only occur when using Xcode 11 and iOS13
Hi @dev-duzza
The following code (main body of a Swift command line application using CryptoSwift library) was provided by a team member. It command line app works for http get and post. Please feel free to check this out. Later on we will add this to the repo with the rest of the Swift project files (e.g. main.swft, etc).
import Foundation
import SystemConfiguration
let apiKey = "";
let privateKey = "";
let baseUrl = "";
public class CredentialsHTTP : NSObject, URLSessionDelegate
{
static let sharedInstance : CredentialsHTTP = {
let instance = CredentialsHTTP()
return instance
}()
override init() {
super.init()
}
func processSessionLoad(_ data: Data) -> Array<Any>
{
let jsonResults = try? JSONSerialization.jsonObject(with: data, options: []) as? Array<Any>
return jsonResults!
}
func get_order_request()
{
let method = "GET"
let path = "/v3/orders"
let dataObj = "?status=open"
let timestamp = Date().currentTimeMillis()
let message = method + path + "\(timestamp)";
let configuration = URLSessionConfiguration.ephemeral
configuration.timeoutIntervalForRequest = 30
let localOperationQueue = OperationQueue.main
localOperationQueue.maxConcurrentOperationCount = 20
let session = Foundation.URLSession(configuration: configuration, delegate: self, delegateQueue: localOperationQueue)
let request = self.buildAuthHeaders(method: method, path: path, message: message, timestamp: "\(timestamp)", dataObj: dataObj, jsonObj: nil)
session.dataTask(with: request, completionHandler:
{
(data, response, error) -> Void in
guard error == nil else {
print(error)
return
}
if let data = data
{
let responsePrint = response as? HTTPURLResponse
session.finishTasksAndInvalidate()
if (responsePrint?.statusCode)! == 200
{
print("--------- Printing Get Order Success ---------")
print(self.processSessionLoad(data))
print("--------- End Printing Get Order Success ---------")
} else {
let payloadJSONHttpCode = self.errorJson(data)
print("--------- Printing Get Order Fail ---------")
print(payloadJSONHttpCode["message"])
print("--------- End Printing Get Order Fail ---------")
}
exit(EXIT_SUCCESS)
}
}).resume()
dispatchMain()
}
func place_order_request()
{
let method = "POST"
let path = "/v3/orders"
let dataObj = ""
let jsonObj: [String:String] = ["marketId": "XRP-AUD", "price": "0.1", "amount": "0.1", "side": "Bid", "type": "Limit"]
let timestamp = Date().currentTimeMillis()
let message = method + path + "\(timestamp)" + self.jsonToString(json: jsonObj as AnyObject);
let configuration = URLSessionConfiguration.ephemeral
configuration.timeoutIntervalForRequest = 30
let localOperationQueue = OperationQueue.main
localOperationQueue.maxConcurrentOperationCount = 20
let session = Foundation.URLSession(configuration: configuration, delegate: self, delegateQueue: localOperationQueue)
let request = self.buildAuthHeaders(method: method, path: path, message: message, timestamp: "\(timestamp)", dataObj: dataObj, jsonObj: jsonObj as AnyObject)
let datatask = session.dataTask(with: request, completionHandler:
{
(data, response, error) -> Void in
guard error == nil else {
return
}
if let data = data
{
let responsePrint = response as? HTTPURLResponse
session.finishTasksAndInvalidate()
if (responsePrint?.statusCode)! == 200
{
print("--------- Printing Place Order Success ---------")
let jsonResults = try? JSONSerialization.jsonObject(with: data, options: []) as? Dictionary<String, Any>
print(jsonResults)
print("--------- End Printing Place Order Success ---------")
} else {
let payloadJSONHttpCode = self.errorJson(data)
print("--------- Printing Place Order Fail ---------")
print(payloadJSONHttpCode["message"])
print("--------- End Printing Place Order Fail ---------")
}
exit(EXIT_SUCCESS)
}
})
datatask.resume()
dispatchMain()
}
func cancel_order_request()
{
let method = "DELETE"
let path = "/v3/orders/1228743"
let dataObj = ""
let timestamp = Date().currentTimeMillis()
let message = method + path + "\(timestamp)";
let configuration = URLSessionConfiguration.ephemeral
configuration.timeoutIntervalForRequest = 30
let localOperationQueue = OperationQueue.main
localOperationQueue.maxConcurrentOperationCount = 20
let session = Foundation.URLSession(configuration: configuration, delegate: self, delegateQueue: localOperationQueue)
let request = self.buildAuthHeaders(method: method, path: path, message: message, timestamp: "\(timestamp)", dataObj: dataObj, jsonObj: nil)
let datatask = session.dataTask(with: request, completionHandler:
{
(data, response, error) -> Void in
guard error == nil else {
return
}
if let data = data
{
let responsePrint = response as? HTTPURLResponse
session.finishTasksAndInvalidate()
if (responsePrint?.statusCode)! == 200
{
print("--------- Printing Cancel Order Success ---------")
let jsonResults = try? JSONSerialization.jsonObject(with: data, options: []) as? Dictionary<String, Any>
print(jsonResults)
print("--------- End Printing Cancel Order Success ---------")
} else {
let payloadJSONHttpCode = self.errorJson(data)
print("--------- Printing Cancel Order Fail ---------")
print(payloadJSONHttpCode["message"])
print("--------- End Printing Cancel Order Fail ---------")
}
exit(EXIT_SUCCESS)
}
})
datatask.resume()
dispatchMain()
}
func buildAuthHeaders(method: String, path: String, message: String, timestamp: String, dataObj: String, jsonObj: AnyObject?) -> URLRequest {
let preppedUrlString = String(format: "%@", baseUrl + path + dataObj)
let url: URL = URL(string: preppedUrlString)!
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("UTF-8", forHTTPHeaderField: "Accept-Charset")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue(apiKey, forHTTPHeaderField: "BM-AUTH-APIKEY")
request.setValue("\(timestamp)", forHTTPHeaderField: "BM-AUTH-TIMESTAMP")
request.setValue(signMessage(message: message), forHTTPHeaderField: "BM-AUTH-SIGNATURE")
request.httpMethod = method
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
if let actualJsonObj = jsonObj {
if (JSONSerialization.isValidJSONObject(jsonObj)) {
do {
request.httpBody = try JSONSerialization.data(withJSONObject: jsonObj, options: .prettyPrinted)
} catch {
return request
}
}
}
return request
}
func signMessage(message: String) -> String {
guard let payload = Data(base64Encoded: privateKey) else {
return ""}
let decodedPrivateAPIKey = convert64EncodedToHex(payload)
let decodedPrivateAPIKeyByteArray = decodedPrivateAPIKey.hexa
let stringToSignData: [UInt8] = Array(message.utf8)
var signature = ""
do {
let signatureArray = try HMAC(key: decodedPrivateAPIKeyByteArray, variant: .sha512).authenticate(stringToSignData)
if let conversion = signatureArray.toBase64(){
signature = conversion
}
} catch {
print(error)
}
return signature
}
func convert64EncodedToHex(_ data:Data) -> String {
return data.map{ String(format: "%02x", $0) }.joined()
}
func errorJson(_ data: Data)->Dictionary<String, Any> {
let jsonResults = try? JSONSerialization.jsonObject(with: data, options: []) as! Dictionary<String, Any>
return jsonResults!
}
func jsonToString(json: AnyObject) -> String {
do {
let data1 = try JSONSerialization.data(withJSONObject: json, options: JSONSerialization.WritingOptions.prettyPrinted) // first of all convert json to the data
let convertedString = String(data: data1, encoding: String.Encoding.utf8) // the data will be converted to the string
return convertedString!
} catch let myJSONError {
print(myJSONError)
return ""
}
}
}
extension Date {
func currentTimeMillis() -> Int64 {
return Int64(self.timeIntervalSince1970 * 1000)
}
}
extension String {
func fromBase64() -> String? {
guard let data = Data(base64Encoded: self) else {
return nil
}
return String(data: data, encoding: .utf8)
}
func toBase64() -> String {
return Data(self.utf8).base64EncodedString()
}
}
extension StringProtocol {
var hexa: [UInt8] {
var startIndex = self.startIndex
return stride(from: 0, to: count, by: 2).compactMap { _ in
let endIndex = index(startIndex, offsetBy: 2, limitedBy: self.endIndex) ?? self.endIndex
defer { startIndex = endIndex }
return UInt8(self[startIndex..<endIndex], radix: 16)
}
}
}
Regards, Martin
Hi martin,
Thanks very much for that! Can you please confirm the correct value of baseUrl
as it never seems to be define. Is it just https://api.btcmarkets.net
Hi @dev-duzza
Yes. that's the correct baseUrl for the Swift sample code.
Regards, Martin
closing the issue and the sample client can be found here: https://github.com/BTCMarkets/api-v3-client-swift
Thanks.
Regards, Martin
Hi team,
I am having an issue authenticating a request for list of trades. I am aware that the v3 API has been released and includes updated functionality, however I need to get this code working with v2 of the API for now.
The below code has previously been working fine. Testing tonight (7/10/19) and am receiving {"success":false,"errorCode":1,"errorMessage":"Authentication failed."}
I have tested my code with the sample API key provided in the v2 API docs and I am able to recreate the expected signature.
Code is as follows:
getHoldingsData(Coin: "BTC", CoinURL: "/v2/order/trade/history/BTC/AUD")