-3

I am trying to access coinbase's Send Money api endpoint documented here but when I try it out, I am getting back an html response which seems to indicate cloudflare is blocking me but I'm not sure:

<html>
<head><title>400 Bad Request</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<hr><center>cloudflare</center>
</body>

Coinbase's error documention states I should be getting back actual error codes but clearly that isn't the case

What I've tried:

  1. There's no info I can find in headers, trying a VPN to another place doesn't do anything.
  2. Taking away my api key/secret headers leads to another error message as a JSON response stating I am not authenticated
  3. I have successfully connected to other endpoints (but only GET request so far)
  4. I have confirmed the scope of my api key covers the one needed for this endpoint (wallet:transactions:send)
  5. I attempted making this request both from a primary account and from a specific account id, both give the same error.
  6. I've tried modifying the order of the post request payload so that it matches documentation, or so that it is in alphabetical order since I've hit that issue before with other apis in the past

I must be missing something really obvious here, but I can't seem to figure it out or find any mention of this issue elsewhere

Here's what I'm using to make my requests:

var (
    cbAPIKey    = os.Getenv("CB_API_KEY")
    cbAPISecret = os.Getenv("CB_API_SECRET")
    ethAccountID = os.Getenv("CB_ETH_ACCOUNT_ID")

    version = "2023-04-01" // https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/versioning
)

func TestSendEth(t *testing.T) {
    destination := "..."
    transaction, err := SendEth(destination, "0.1")
    assert.Nil(t, err)
    assert.Equal(t, destination, transaction.To.Address)
}

type sendPayload struct {
    Type        string  `json:"type"` // must be "send"
    To          string  `json:"to"`
    Amount      string  `json:"amount"`
    Currency    string  `json:"currency"`
    Description *string `json:"description,omitempty"`
    Idem        *string `json:"idem,omitempty"`
    // For select currencies, destination_tag or memo indicates the beneficiary or destination of a
    // payment for select currencies. Example:
    // { "type" : "send", "to": "address", "destination_tag" : "memo", "amount": "", "currency": "" }
    DestinationTag *string `json:"destination_tag,omitempty"`
}

func SendEth(to, amount string) (transaction Transaction, err error) {
    payload := sendPayload{
        Type:     "send",
        To:       to,
        Amount:   amount,
        Currency: "ETH",
    }

    // serialize payload
    b, _ := json.Marshal(payload)
    reader := bytes.NewReader(b)

    url := fmt.Sprintf(transactionsURL, ethAccountID)
    req, _ := http.NewRequest("POST", url, reader)
    req.Header.Set("Content-Type", "application/json")

    // sign request
    err = signRequest(req)
    if err != nil {
        return
    }

    // send request
    c := http.Client{}
    response, err := c.Do(req)
    if err != nil {
        return
    }

    var cbResponse Response[Transaction]
    responseBytes, _ := ioutil.ReadAll(response.Body)
    err = json.Unmarshal(responseBytes, &cbResponse)
    if err != nil {
        log.Warn(string(responseBytes))
        log.WithError(err).Error("Failed to unmarshal response")
        return
    }

    transaction = cbResponse.Data
    return
}

// signRequest modifies a http.Request to include the Coinbase-Auth-Signature
// headers. You can find more information about these headers here:
// https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/api-key-authentication
func signRequest(req *http.Request) (err error) {
    reqTime := time.Now().Unix()

    /*
        The CB-ACCESS-SIGN header is generated by creating a sha256 HMAC using the secret key on the
        prehash string timestamp + method + requestPath + body (where + represents string concatenation).

        timestamp is the same as the X-CB-ACCESS-TIMESTAMP header.
        method should be UPPER CASE.
        requestPath is the full path and query parameters of the URL, e.g.: /v2/exchange-rates?currency=USD.
    */
    body := ""

    if req.Body != nil {
        var bytes []byte
        bytes, err = ioutil.ReadAll(req.Body)
        if err != nil {
            return
        }
        body = string(bytes)
    }

    message := fmt.Sprintf("%d%s%s%s", reqTime, req.Method, req.URL.Path, body)

    // generate a sha256 HMAC using the secret key on the prehash string
    signature := generateHMAC(message, cbAPISecret)

    req.Header.Set("CB-ACCESS-KEY", cbAPIKey)
    req.Header.Set("CB-ACCESS-SIGN", signature)
    req.Header.Set("CB-ACCESS-TIMESTAMP", fmt.Sprintf("%d", reqTime))
    req.Header.Set("CB-VERSION", version)

    return
}

I'd expect that if I'm doing something wrong, I should at least get error messages back to indicate some kind of failure. But instead I get the mentioned opaque cloudflare failure. Any help would be greatly appreciated!

adk992
  • 9
  • 1

1 Answers1

1

It's possible that Cloudflare is blocking your request due to a missing or incorrect User-Agent header. To fix this issue, try setting a User-Agent header for your HTTP request:

req.Header.Set("User-Agent", "MyAppName/1.0")

Replace MyAppName/1.0 with a suitable value for your app. Including a User-Agent can help avoid being blocked by Cloudflare.

Additionally, double-check the endpoint URL to ensure it is correct and complete.

For further details on Cloudflare issues and troubleshooting, please refer to Cloudflare Support.

If the issue persists, it might be helpful to contact Coinbase API support directly. You can reach out to their support through the https://www.coinbase.com/cloud

DicDimi
  • 11
  • 1