Search code examples
kotlincoinbase-apiktorktor-client

when calling Coinbase pro sandbox api, invalid: 401 Unauthorized. Text: "{"message":"invalid signature"}" Kotlin


What I wanted to do is call the Coinbase sandbox API to get all accounts for a profile.
I am following the official documentation https://docs.cloud.coinbase.com/exchange/reference/exchangerestapi_getaccounts
But keep getting this error
Client request(https://api-public.sandbox.exchange.coinbase.com/accounts) invalid: 401 Unauthorized. Text: "{"message":"invalid signature"}"

Am I missing something?

suspend fun getTradingAccounts(): String {
        val timestamp = getTimeStamp()
        val response: String = client.get("https://api-public.sandbox.exchange.coinbase.com/accounts") {
            headers {
                append("Accept", "application/json")
                append("Content-Type", "application/json")
                append(
                    "cb-access-key",
                    "MY_KEY..."
                )
                append("cb-access-passphrase", "MY_PASSPHRASE....")
                append("cb-access-sign", signMessage(
                    timestamp = timestamp,
                    method = "GET",
                    path = "https://api-public.sandbox.exchange.coinbase.com/accounts"
                ))
                append("cb-access-timestamp", timestamp)
            }
        }
        return response
    }

    private fun getTimeStamp(): String {
        val  time = LocalDateTime.now()
        val zoneId = ZoneId.of("Europe/London")
        val  epoch = time.atZone(zoneId).toEpochSecond()
        return epoch.toString()


    }
    @Throws(NoSuchAlgorithmException::class, InvalidKeyException::class)
    private fun signMessage(timestamp: String, method: String, path: String): String {
        val prehash = timestamp + method + path
        val sha256_HMAC = Mac.getInstance("HmacSHA256")
        val secretDecoded: ByteArray = Base64.getDecoder().decode("MY_API_KEY==")
        val secret_key = SecretKeySpec(secretDecoded, "HmacSHA256")
        sha256_HMAC.init(secret_key)
        return Base64.getEncoder().encodeToString(sha256_HMAC.doFinal(prehash.toByteArray()))
    }

Solution

  • As I see your code for signing messages doesn't use request body JSON string when concatenating components for a message. Here is my version for the function that returns the same string as in NodeJS script for the same inputs:

    import kotlinx.serialization.ExperimentalSerializationApi
    import kotlinx.serialization.Serializable
    import kotlinx.serialization.encodeToString
    import kotlinx.serialization.json.Json
    import java.util.*
    import javax.crypto.Mac
    import javax.crypto.spec.SecretKeySpec
    
    fun main() {
        val result = sign(
            secret = "TVlfQVBJX0tFWQ==",
            timestampMs = System.currentTimeMillis(),
            method = "POST",
            requestPath = "/orders",
            request = Req(
                price = "1.0",
                size = "1.0",
                side = "buy",
                product_id = "BTC-USD",
            )
        )
    
        println(result)
    }
    
    @OptIn(ExperimentalSerializationApi::class)
    fun sign(timestampMs: Long, method: String, requestPath: String, request: Req, secret: String): String {
        val accessTimestamp = timestampMs / 1000.0
        val message = "%.3f".format(accessTimestamp) + method + requestPath + Json.encodeToString(request)
    
        val sha256HMAC = Mac.getInstance("HmacSHA256")
        val key: ByteArray = Base64.getDecoder().decode(secret)
        sha256HMAC.init(SecretKeySpec(key, "HmacSHA256"))
    
        return Base64.getEncoder().encodeToString(
            sha256HMAC.doFinal(message.toByteArray())
        )
    }
    
    @Serializable
    data class Req(val price: String, val size: String, val side: String, val product_id: String)