Skip to content

Commit

Permalink
API client - initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
dstranz committed Apr 10, 2019
0 parents commit 66ae311
Show file tree
Hide file tree
Showing 79 changed files with 4,258 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TestCredentials.swift
42 changes: 42 additions & 0 deletions ChangellyAPI.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#
# Be sure to run `pod lib lint ChangellyAPI.podspec' to ensure this is a
# valid spec before submitting.
#
# Any lines starting with a # are optional, but their use is encouraged
# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
#

Pod::Spec.new do |s|
s.name = 'ChangellyAPI'
s.version = '0.1.0'
s.summary = 'A short description of ChangellyAPI.'

# This description is used to generate tags and improve search results.
# * Think: What does it do? Why did you write it? What is the focus?
# * Try to keep it short, snappy and to the point.
# * Write the description between the DESC delimiters below.
# * Finally, don't worry about the indent, CocoaPods strips it!

s.description = <<-DESC
TODO: Add long description of the pod here.
DESC

s.homepage = 'https://github.com/coinpaprika/changelly-api-swift-client'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'Dominique Stranz' => '[email protected]' }
s.source = { :git => 'https://github.com/coinpaprika/changelly-api-swift-client.git', :tag => s.version.to_s }
s.source = { :git => 'https://github.com/Dominique Stranz/ChangellyAPI.git', :tag => s.version.to_s }
s.social_media_url = 'https://twitter.com/coinpaprika'

s.ios.deployment_target = '10.0'

s.source_files = 'ChangellyAPI/Classes/**/*'

# s.resource_bundles = {
# 'ChangellyAPI' => ['ChangellyAPI/Assets/*.png']
# }

# s.public_header_files = 'Pod/Classes/**/*.h'
# s.frameworks = 'UIKit', 'MapKit'
s.dependency 'CoinpaprikaAPI/Networking'
end
Empty file added ChangellyAPI/Assets/.gitkeep
Empty file.
Empty file added ChangellyAPI/Classes/.gitkeep
Empty file.
58 changes: 58 additions & 0 deletions ChangellyAPI/Classes/ChangellyAPI.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// ChangellyAPI.swift
// ChangellyAPI
//
// Created by Dominique Stranz on 10/04/2019.
//

import Foundation

public struct ChangellyAPI {
private let credentials: Credentials
private let baseUrl: URL = URL(string: "https://api.changelly.com")!

public init(key: String, secret: String) {
self.credentials = Credentials(key: key, secret: secret)
}

public func getCurrencies() -> JsonRpcRequest<[String]> {
return JsonRpcRequest<[String]>(baseUrl: baseUrl, method: "getCurrencies", params: [:], credentials: credentials)
}

public func getCurrenciesFull() -> JsonRpcRequest<[Currency]> {
return JsonRpcRequest<[Currency]>(baseUrl: baseUrl, method: "getCurrenciesFull", params: [:], credentials: credentials)
}

public func getMinAmount(from: String, to: String) -> JsonRpcRequest<Decimal> {
return JsonRpcRequest<Decimal>(baseUrl: baseUrl, method: "getMinAmount", params: ["from": from, "to": to], credentials: credentials)
}

public func getExchangeAmount(from: String, to: String, amount: Decimal) -> JsonRpcRequest<String> {
return JsonRpcRequest<String>(baseUrl: baseUrl, method: "getExchangeAmount", params: ["from": from, "to": to, "amount": amount], credentials: credentials)
}

public func createTransaction(from: String, to: String, amount: Decimal, address: String, extraId: String? = nil, refundAddress: String? = nil, refundExtraId: String? = nil) -> JsonRpcRequest<Transaction> {
var params: [String : Any] = ["from": from, "to": to, "amount": amount, "address": address]
params["extraId"] = extraId
params["refundAddress"] = refundAddress
params["refundExtraId"] = refundExtraId
return JsonRpcRequest<Transaction>(baseUrl: baseUrl, method: "createTransaction", params: params, credentials: credentials)
}

public func getTransactions(currency: String? = nil, address: String? = nil, extraId: String? = nil, limit: Int? = nil, offset: Int? = nil) -> JsonRpcRequest<[TransactionLog]> {
var params = [String : Any]()
params["currency"] = currency
params["address"] = address
params["extraId"] = extraId
params["limit"] = limit
params["offset"] = offset
return JsonRpcRequest<[TransactionLog]>(baseUrl: baseUrl, method: "getTransactions", params: params, credentials: credentials)
}

public func getStatus(id: String) -> JsonRpcRequest<Transaction.Status> {
return JsonRpcRequest<Transaction.Status>(baseUrl: baseUrl, method: "getStatus", params: ["id": id], credentials: credentials)
}



}
21 changes: 21 additions & 0 deletions ChangellyAPI/Classes/Models/Currency.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// Currency.swift
// ChangellyAPI
//
// Created by Dominique Stranz on 10/04/2019.
//

import Foundation

public struct Currency: Codable {
public let name: String
public let fullName: String
public let enabled: Bool
public let fixRateEnabled: Bool
public let payinConfirmations: Int
public let extraIdName: String?
public let addressUrl: String?
public let transactionUrl: String?
public let image: URL
public let fixedTime: Int
}
74 changes: 74 additions & 0 deletions ChangellyAPI/Classes/Models/Transaction.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//
// Transaction.swift
// ChangellyAPI
//
// Created by Dominique Stranz on 10/04/2019.
//

import Foundation
import CoinpaprikaAPI

public struct Transaction: Codable, CodableModel {
public let id: String
public let apiExtraFee: String
public let changellyFee: String
public let payinExtraId: String?
public let payoutExtraId: String?
public let amountExpectedFrom: Decimal
public let amountExpectedTo: String
public let status: Status
public let currencyFrom: String
public let currencyTo: String
public let payinAddress: String
public let payoutAddress: String
public let createdAt: Date
public let kycRequired: Bool

public enum Status: String, Codable {
case new
case waiting
case confirming
case exchanging
case sending
case finished
case failed
case refunded
case overdue
case hold

var localizedDescription: String {
switch self {
case .new:
return "Freshly created transaction."
case .waiting:
return "Transaction is waiting for an incoming payment."
case .confirming:
return "We have received payin and are waiting for certain amount of confirmations depending of incoming currency."
case .exchanging:
return "Payment was confirmed and is being exchanged."
case .sending:
return "Coins are being sent to the recipient address."
case .finished:
return "Coins were successfully sent to the recipient address."
case .failed:
return "Transaction has failed. In most cases, the amount was less than the minimum. Please contact support and provide a transaction id."
case .refunded:
return "Exchange failed and coins were refunded to user's wallet. The wallet address should be provided by user."
case .overdue:
return "We did not receive any payment since 36 hours from transaction creation."
case .hold:
return "Due to AML/KYC procedure, exchange may be delayed"
}
}

static var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
return formatter
}

static var dateDecodingStrategy: JSONDecoder.DateDecodingStrategy? {
return .formatted(dateFormatter)
}
}
}
37 changes: 37 additions & 0 deletions ChangellyAPI/Classes/Models/TransactionLog.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// TransactionLog.swift
// ChangellyAPI
//
// Created by Dominique Stranz on 10/04/2019.
//

import Foundation
import CoinpaprikaAPI

public struct TransactionLog: Codable, CodableModel {
public let id: String
public let createdAt: Date
public let moneyReceived: Decimal
public let moneySent: Decimal
public let payinConfirmations: String
public let status: Transaction.Status
public let currencyFrom: String
public let currencyTo: String
public let payinAddress: String
public let payinExtraId: String?
public let payinHash: String?
public let amountExpectedFrom: String
public let payoutAddress: String
public let payoutExtraId: String?
public let payoutHash: String?
public let refundHash: String?
public let amountFrom: String
public let amountTo: String
public let networkFee: String?
public let changellyFee: String
public let apiExtraFee: String

static var dateDecodingStrategy: JSONDecoder.DateDecodingStrategy? {
return .secondsSince1970
}
}
21 changes: 21 additions & 0 deletions ChangellyAPI/Classes/Request/Credentials.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// Credentials.swift
// ChangellyAPI
//
// Created by Dominique Stranz on 10/04/2019.
//

import Foundation

public struct Credentials {
public let key: String
public let secret: String

internal func signature(for data: Data) -> String? {
guard let jsonString = String(data: data, encoding: .utf8) else {
return nil
}

return jsonString.hmac(key: secret)
}
}
89 changes: 89 additions & 0 deletions ChangellyAPI/Classes/Request/JsonRpcRequest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//
// Request+JSON-RPC.swift
// Coinpaprika
//
// Created by Dominique Stranz on 06.09.2018.
// Copyright © 2018 Grey Wizard sp. z o.o. All rights reserved.
//

import Foundation
import CoinpaprikaAPI

public struct JsonRpcRequest<Model: Codable> {

let request: Request<JsonRpcResponseEvelope<Model>>

init(baseUrl: URL, method: String, params: [String: Any], credentials: Credentials) {
let requestId = JsonRpcIdentifier.next()
let rpcBody = ["jsonrpc": "2.0", "method": method, "id": requestId, "params": params] as [String: Any]
request = Request<JsonRpcResponseEvelope<Model>>(baseUrl: baseUrl, method: .post, path: "", params: rpcBody, userAgent: "Changelly API Client - Swift", authorisation: .dynamic(signer: { (request) in
guard let jsonData = request.httpBody, let signature = credentials.signature(for: jsonData) else {
return
}

request.addValue(credentials.key, forHTTPHeaderField: "api-key")
request.addValue(signature, forHTTPHeaderField: "sign")
}))
}

public func perform(responseQueue: DispatchQueue? = nil, cachePolicy: URLRequest.CachePolicy? = nil, _ callback: @escaping (Result<Model, Error>) -> Void) {
request.perform(responseQueue: responseQueue, cachePolicy: cachePolicy) { (response) in
switch response {
case .success(let envelope):
if let result = envelope.result {
callback(Result.success(result))
} else if let error = envelope.error {
callback(Result.failure(JsonRpcError.serviceError(code: error.code, message: error.message)))
} else {
callback(Result.failure(JsonRpcError.unableToDecodeEnvelope))
}
case .failure(let error):
callback(Result.failure(error))
}
}
}
}

struct JsonRpcIdentifier {
private static var currentId: Int = 1

static func next() -> Int {
defer { currentId += 1 }
return currentId
}
}

enum JsonRpcError: Error {
case unableToDecodeEnvelope
case serviceError(code: Int, message: String)
}

extension JsonRpcError: LocalizedError {
public var errorDescription: String? {
switch self {
case .unableToDecodeEnvelope:
return "Unable to decode response"
case .serviceError(_, let message):
return "\(message)"
}
}
}

struct JsonRpcResponseEvelope<Model: Codable>: Codable, CodableModel {
let id: Int
let result: Model?
let error: JsonRpcErrorEnvelope?

static var dateDecodingStrategy: JSONDecoder.DateDecodingStrategy? {
if codableModel = model as? CodableModel {
return Model.dateDecodingStrategy
}

return nil
}
}

struct JsonRpcErrorEnvelope: Codable {
let code: Int
let message: String
}
18 changes: 18 additions & 0 deletions ChangellyAPI/Classes/Request/String+HMAC.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// String+HMAC.swift
// ChangellyAPI
//
// Created by Dominique Stranz on 10/04/2019.
//

import Foundation
import CommonCrypto

extension String {
func hmac(key: String) -> String {
var digest = [UInt8](repeating: 0, count: Int(CC_SHA512_DIGEST_LENGTH))
CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA512), key, key.count, self, self.count, &digest)
let data = Data(bytes: digest)
return data.map { String(format: "%02hhx", $0) }.joined()
}
}
Loading

0 comments on commit 66ae311

Please sign in to comment.