Description
I am attempting to use the Coinbase API to convert between ETH and BTC within the Coinbase account that I have. I am unable to successfully complete this conversion as I would expect via the API docs. I am receiving responses like this:
{}
This is incredibly easy to do via the mobile Coinbase app and online in the browser, however appears impossible based on my experience working with the API.
What I have tried
I have created an API key for the profile containing my ETH and BTC balances and grated it all permissions. I am able to successfully execute a GET
request against the API and return info about my account. This aligns accurately with what is online and on the app. No issues there. Now, when I attempt a POST
request as outlined in the documentation here I am unable to actually perform a successful transfer. On the app it is very easy to transfer between BTC/ETH or ETH/BTC but with the API there is no clear way to accomplish this.
Minimum Reproducible Example
I am using Python 3.9.2 with the following requirements installed:
certifi==2021.10.8
charset-normalizer==2.0.12
idna==3.3
requests==2.27.1
urllib3==1.26.9
and this being the minimum code example to reproduce the issue I am experiencing:
import base64
import hashlib
import hmac
import os
import time
from datetime import datetime
import requests
from requests.auth import AuthBase
class CoinbaseAuth(AuthBase):
def __init__(self, api_key = None, secret_key = None):
self.api_key = api_key or os.getenv("API_KEY")
self.secret_key = secret_key or os.getenv("SECRET_KEY")
def __call__(self, request):
timestamp = str(int(time.time()))
message = (
timestamp
+ request.method
+ request.path_url
+ (request.body.decode("utf8") if request.body else "")
)
sk = self.secret_key.encode("utf8")
hmac_signature = hmac.new(
key=sk,
msg=message.encode("utf8),
digestmod=hashlib.sha256,
)
signature = hmac_signature.hexdigest()
headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"CB-ACCESS-KEY": self.api_key,
"CB-ACCESS-SIGN": signature,
"CB-ACCESS-TIMESTAMP": timestamp,
"CB-VERSION": str(datetime(2022, 1, 17).date()),
}
request.headers.update(headers)
return request
auth = CoinbaseAuth(
api_key='<my-api-key>',
secret_key='<my-secret-key>',
)
accounts_response = requests.get("https://api.coinbase.com/v2/accounts", auth=auth)
accounts = accounts_response.json()
eth_wallet = {}
btc_wallet = {}
for account in accounts['data']:
if account['currency']['code'] == 'BTC':
btc_wallet = account
elif account['currency']['code'] == 'ETH':
eth_wallet = account
# I only have 1 profile, but one could iterate through to find the default
eth_account_id = eth_wallet.get("id", "ETH")
btc_account_id = btc_wallet.get("id", "BTC")
transfer_response = requests.post(
f"https://api.coinbase.com/v2/accounts/{eth_account_id}/transactions/",
auth=auth,
json={
"amount": "0.002".
"currency": "ETH",
"description": "Trading ETH to BTC from the API.",
"idem": "testing123",
"to": btc_account_id,
"type": "transfer",
},
)
print(f"Status Code: {transfer_response.status_code}")
print(f'Response Body: {transfer_response.content.decode("utf8")}')
and the above code snippet should output the following (because it does for me):
Status Code: 200
Response Body: {}
I have tried using the {"type": "send"}
parameter as well instead of transfer
, but that yields the following:
Status Code: 400
Response Body: {"errors":[{"id":"validation_error","message":"Please enter a valid email or ethereum address","field":"base"}]}
This response makes no sense, because I am literally feeding the account id values back into the API call that I received from the other API call, so they must be valid OR you mean different things by the account id than the id’s returned from a GET
to /accounts
and the id expected with a POST
to /accounts/:account_id:/transactions/
NOTE
There is an incredibly similar issue over on the Exchange/API. I have tried to accomplish this simple task with both APIs and have not had success with either of them. Please advise what the correct way to do this would be, your documentation is incredibly unclear as to how one would accomplish this.