Swift) 업비트(Upbit) API 이용하기


업비트 API 가이드(https://docs.upbit.com)에 가면 upbit의 API를 활용할 수 있는 가이드가 있습니다.

아쉬운점은 Node, Python, Ruby, Java 에 대해서만 예제를 제공하고 있어서 swift로 예제를 작성 했습니다.


업비트의 API는 크게 EXCHANGE API, QUOTATION API로 2가지가 존재합니다. QUOTATION API의 경우 큰 문제없이 GET 메소드로 request를 요청하면 됩니다. 문제는 EXCHANGE API인데 주문 API의 경우 업비트를 통해서 access key와 secret key를 이용해 토큰을 생성해 API를 요청해야하기 때문에 다소 까다롭습니다.


업비트의 토큰 경우 JWT(https://jwt.io) 형식을 따릅니다. Swift에는 타 언어처럼 jwt라이브러리를 제공하고 있지 않아서 JWT토큰을 이용하기 위해서 IBM에서 개발된 Swift-JWT(https://github.com/Kitura/Swift-JWT)를 이용하고 API 요청을 위해서 Alamofire(https://github.com/Alamofire/Alamofire)을 이용합니다.

upbit에서 사용중이 Payload의 구성은 다음과 같기 때문에 Swift-JWT에서 제공하는 Claims protocol을 이용해서 payload를 만들어줍니다.

1
2
3
4
5
6
7
struct Payload: Claims {
    let access_key: String
    let nonce: String
    let query_hash: String
    let query_hash_alg: String
}
 
cs

- 전제 계좌 조회(Parameter가 없을 경우, method: GET)
    parameter가 없고 http method가 GET일 경우는 헤더의 'Authorization'만 잘 셋팅해서 보내주면 됩니다. 그리고 JWT의 sign함수를 통해서 해싱 알고리즘을 hs256로 정해서 해싱 시킵니다.(업비트에선 hs256을 권장)

1
2
3
4
5
6
7
8
9
10
11
1
13
14
15
16
17
func getAccounts() {        
        let accessKey = "UPBIT_OPEN_API_ACCESS_KEY"
        let secretKey = "UPBIT_OPEN_API_SECRET_KEY"
        let url = "https://api.upbit.com/v1/accounts"
        
        var jwt = JWT(claims: Payload(access_key: accessKey, 
                                      nonce: UUID().uuidString), 
                                      query_hash: ""
                                      query_hash_alg: "SHA512")
        let signedJWT = try! jwt.sign(using: .hs256(key: secretKey.data(using: .utf8)!))
        let authenticationToken = "Bearer " + signedJWT
        
        AF.request(url,
                   headers: ["Authorization": authenticationToken])
            .responseString { response in
                print(response)
            }
}
cs

- 주문 가능 정보(Parameter가 있을 경우, method: GET)
    parameter가 있고 http method가 GET일 경우는 Payload의 query_hash에 값을 넣어줘야 합니다. Swift-JWT에 보면 digest란 함수를 이용해서 query parameter값을 해시 시켜 Payload의 query_hash값에 사용하면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func ordersChance() {
        let accessKey = "UPBIT_OPEN_API_ACCESS_KEY"
        let secretKey = "UPBIT_OPEN_API_SECRET_KEY"
        let url = "https://api.upbit.com/v1/orders/chance"
        let query = ["market""KRW-BTC"]
        
        var components = URLComponents()
        components.queryItems = query.map { URLQueryItem(name: $0, value: $1) }
        let requestString = components.query!.digest(using: .sha512)
        
        var jwt = JWT(claims: Payload(access_key: accessKey,
                                       nonce: UUID().uuidString,
                                       query_hash: requestString,
                                       query_hash_alg: "SHA512"))
        let signedJWT = try! jwt.sign(using: .hs256(key: secretKey.data(using: .utf8)!))
        let authenticationToken = "Bearer " + signedJWT
        
        AF.request("https://api.upbit.com/v1/orders/chance",
                   parameters: query,
                   headers: ["Authorization": authenticationToken])
            .responseString { response in
                print(response)
            }
}
 
 
cs

- 주문하기(Parameter가 있을 경우,  method: POST)
    Post의 경우 위에 Get과 조금 다릅니다. URLRequest를 생성해서 httpBody에 query값을 넣어주고 다른 헤더를 지정해줘서 보내줘야 동작합니다. 기존 alamofire의 예제 'AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: URLEncodedFormParameterEncoder(destination: .httpBody))'을 이용할 경우 'invalid_query_payload' error를 받을 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
func order() {
        let accessKey = "UPBIT_OPEN_API_ACCESS_KEY"
        let secretKey = "UPBIT_OPEN_API_SECRET_KEY"
        let url = "https://api.upbit.com/v1/orders"
        let query = ["market""KRW-BTC",                     
                     "side""bid",
                     "volume""0.00228319",
                     "price""56219000",
                     "ord_type""limit"]        
        var components = URLComponents()
        components.queryItems = query.map { URLQueryItem(name: $0, value: $1) }
        let requestString = components.query!.digest(using: .sha512)
        
        var jwt = JWT(claims: Payload(access_key: accessKey,
                                      nonce: UUID().uuidString,
                                      query_hash: requestString,
                                      query_hash_alg: "SHA512"))
        let signedJWT = try! jwt.sign(using: .hs256(key: secretKey.data(using: .utf8)!))
        let authenticationToken = "Bearer " + signedJWT
        
        var request = URLRequest(url: URL(string: url)!)        
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.addValue(authenticationToken, forHTTPHeaderField: "Authorization")
        
        do {
            try request.httpBody = JSONSerialization.data(withJSONObject: query, options: [])
        } catch {
            print("http Body Error")
        }
 
        AF.request(request)
            .responseString { response in
                print(response.description)
            }
}
 
cs

- 주문 리스트 조회(Parameter가 있을 경우, Parameter중 array가 있을경우,  method: GET)
parameter중 array가 있을 경우 key값을 'key[]=value1&key[]=value2 ...'으로 구성해야합니다. 그리고 query를 기존처럼 request의 parameter로 넣지 말고 string으로 변환된 값을 url에 넣어줍니다. 그렇지 않으면 'invalid_query_payload' error를 받을 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func orders() {
        let accessKey = "UPBIT_OPEN_API_ACCESS_KEY"
        let secretKey = "UPBIT_OPEN_API_SECRET_KEY"
        let url = "https://api.upbit.com/v1/orders/chance"
        let query = ["market""KRW-BTC"]
        let uuids = ["216d955b-2f81-4776-9fc9-1b3f7af42e1a""c0752957-d7c1-4a9c-85bc-0072651b4f9f"]
        
        let uuidsString = uuids.compactMap({ "uuids[]=" + $0}).joined(separator: "&")
        let queryString = query.compactMap({ $0.key + "=" + $0.value }).joined(separator: "&")
        
        let queryHashString = queryString + "&" + uuidsString
        let requestString = queryHashString.digest(using: .sha512)
        var jwt = JWT(claims: Payload(access_key: accessKey,
                                      nonce: UUID().uuidString,
                                      query_hash: requestString,
                                      query_hash_alg: "SHA512"))
        
        let signedJWT = try? jwt.sign(using: .hs256(key: secretKey.data(using: .utf8)!))
        let authenticationToken = "Bearer " + signedJWT!
        
        AF.request(url + "?" + queryHashString,
                   headers: ["Content-Type""application/json",
                             "Authorization": authenticationToken])
            .responseString { response in
                print(response)
            }
}
 
 
cs

댓글 없음:

Powered by Blogger.