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:
- There's no info I can find in headers, trying a VPN to another place doesn't do anything.
- Taking away my api key/secret headers leads to another error message as a JSON response stating I am not authenticated
- I have successfully connected to other endpoints (but only GET request so far)
- I have confirmed the scope of my api key covers the one needed for this endpoint (
wallet:transactions:send
) - I attempted making this request both from a primary account and from a specific account id, both give the same error.
- 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!