我如何与 Coinbase 的 API 交互?为啥总是失败?

Posted

技术标签:

【中文标题】我如何与 Coinbase 的 API 交互?为啥总是失败?【英文标题】:How do I interact with Coinbase's API? Why does it always fail?我如何与 Coinbase 的 API 交互?为什么总是失败? 【发布时间】:2014-03-10 21:12:45 【问题描述】:

访问 Coinbase API 过去非常简单:您只需要一个 API 密钥。现在你需要一个叫做“nonce”和“signature”的东西。我在请求中传递了我的新 API“秘密”、随机数和密钥,但它返回了“无效密钥”错误。什么给了?

编辑 3 月 12 日: Added a tutorial on interacting with the API via OAuth.

【问题讨论】:

+1,但如果您以后可以像提问一样提问,然后将大部分材料放入答案中,这有助于遵循问答格式。它还允许其他人回答可能有更好答案的问题。 (您始终可以在问题的末尾添加一个后记,说明这将是一个自我回答的问题,然后在您添加/编辑一个您满意的答案后将其删除)。 @halfer 谢谢!我只是对其进行了编辑以反映这一点。 太好了,谢谢。小问题:如果您发现自己使用“它不起作用”这个短语,即使是在教程问题中,也希望它会关闭或吸引反对票。这里的读者经常说“不工作”是“可能的最没有帮助的故障报告”;)。因此,您可能想解释具体出了什么问题。但除此之外,感谢您希望添加有用的信息! 啊。我也修好了。显然我有一些学习要做! 没问题,干得好! 【参考方案1】:

YE GREAT OAUTH 教程

<?php

/*OAuth is great. It's also complicated. Or rather, it LOOKS complicated.

This whole script is just one big long function. It's a really, really ugly
function. I broke down everything "Barney-style" to demonstrate all the steps
in the process, and because there are some things you have to decide -- how to
record the user data, for instance.

Let's get this train wreck a rollin'.*/

function oauthRequest($apiPath,$getOrPost,$parameters)

/*You get this info from https://coinbase.com/oauth/applications*/
$clientId = "#####";
$clientSecret = "#####";
$callbackUrl = "http://www.blah.com/oauth.php";

function curling($url,$getpost,$params)
    if($params != "")
        $params = http_build_query(json_decode($params), true);
    
    if($getpost == "get")
        $ispost = false;
        $url .= $params;
    
    $ch = curl_init();
    curl_setopt_array($ch, array(
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true
    ));
    if($getpost == "post")
        curl_setopt_array($ch, array(
            CURLOPT_POST => $ispost,
            CURLOPT_POSTFIELDS => $params
        ));
    
    $results = curl_exec($ch);
    curl_close($ch);
    return $results;


/*There are two people involved here: the Client (you), and the User (the
person accessing your app or site).

You'll need 3 pieces of data for each user before your app can access their
Coinbase account: a User Code, an Access Token, and a Refresh Token.

For the purposes of this demonstration, I'm recording all of the user data in
a .txt file on my server. THIS IS PROBABLY A BAD IDEA in real life because .txt
files aren't secure at all, and this text file will only store the data for one
user (it gets overwritten every time). This is the kind of stuff you'd put in an
SQL database if you have one, or maybe in the user's cookies.*/

if(!file_exists("userdata.txt") || file_get_contents("userdata.txt") == "")
    file_put_contents("userdata.txt",json_encode(array(
        "userCode" => "",
        "accessToken" => "",
        "refreshToken" => ""
    )), LOCK_EX);

$userData = json_decode(file_get_contents("userdata.txt"), true);

/*Okay. So. First thing we're going to do is see if we have a User Code for
this user. These are big alphanumeric strings that are 64 characters long. If
we have one, it'll either be in the URL of this page (the $_GET array), or
it'll be in that userdata.txt file.*/

if(array_key_exists("code",$_GET) && $_GET["code"] != "")
    $userCode = $_GET["code"];
else if(array_key_exists("userCode",$userData) && $userData["userCode"] != "")
    $userCode = $userData["userCode"];
else

/*If we don't have a User Code, then this next snippet of code will fire. It'll
return the link for a special user-specific Coinbase page to which the user
will need to go to authorize your app to access their Coinbase account (by
signing into Coinbase and clicking a green "Authorize" button).

After authorizing your app, they'll be automatically taken to the Redirect URL
you specified, with their User Code added to the end of the URL. So if your
Redirect URL is www.blah.com/oauth.php, they'll go to www.blah.com/oauth.php?
code=123451234512345 .

This User Code never expires, and so theoretically the user should only need to
go to the authorization link once. However, if you don't make a way of getting
the User Code in the future (my fancy "userdata.txt" in this case) or they de-
authorized your app from within their Coinbase account, then they'll need to go
to the link again and re-authorize your app from the beginning.

I have it set up so my Redirect URL and the rest of my OAuth script are all on
the same page: www.blah.com/oauth.php . So the user will just start back at the
beginning of this script, and THIS time the script will see the User Code in
the URL (the $_GET array), and so will skip this next bit.

Whew. You with me so far?*/
    return ("https:/*coinbase.com/oauth/authorize?" . http_build_query(array(
        "response_type" => "code",
        "client_id" => $clientId,
        "redirect_uri" => $callbackUrl
    )));
    die;


/*Here I am, recording the User Code for future use in userdata.txt*/
$userData["userCode"] = $userCode;
file_put_contents("userdata.txt",json_encode($userData),LOCK_EX);

/*Alright, we've got the User Code. Now we need the Access Token -- another 64-
character string. The difference is that the Access Token expires every 2 hours
(7200 seconds). Let's see if we already have one in the userdata.txt file.*/
if(array_key_exists("accessToken",$userData) && $userData["accessToken"] != "")
    $accessToken = $userData["accessToken"];
    $refreshToken = $userData["refreshToken"];
else

/*If we're here, it's because we don't have an Access Token for this user. We
get one by making this POST request:*/
    $authorization = json_decode(curling(
        "https:/*coinbase.com/oauth/token" . "?" . http_build_query(array(
            "grant_type" => "authorization_code",
            "code" => $userCode,
            "redirect_uri" => $callbackUrl,
            "client_id" => $clientId,
            "client_secret" => $clientSecret
        )), "post", ""), true);
    if(array_key_exists("error",$authorization))

/*If something goes wrong here, I'm going to clean out userdata.txt and ask the
user to try again.*/
        file_put_contents("userdata.txt","",LOCK_EX);
        die("Something went wrong. Please refresh the page and try again.");
    
    $accessToken = $authorization["access_token"];
    $refreshToken = $authorization["refresh_token"];


/*The Refresh Token is what you use to get a new Access Token once the current
Access Token has expired. The Refresh Token never expires, but can only be used
once. Anytime you get an Access Token, you'll also be given a Refresh Token.

If you don't have the Refresh Token and a working Access Token for the user,
they'll need to re-authorize your app all over again.

I'm backing up the Access Token and Refresh Token to userdata.txt*/
$userData["accessToken"] = $accessToken;
$userData["refreshToken"] = $refreshToken;
file_put_contents("userdata.txt",json_encode($userData),LOCK_EX);

/*Alright! At this point, we should have the three bits of user data we need:
the User Code, the Access Token, and the Refresh Token. So now lets try
actually making an API request.

This whole script is really just one big function called "oauthRequest". You
pass three parameters to the function: the path of the API request (everything
after https:/*coinbase.com/api/v1/), whether this API query is a GET or a POST,
and any parameters that go along with that GET or POST request. These params
first come into play here.

Let's make the API request:*/
$results = curling("https:/*coinbase.com/api/v1/" . $apiPath . "?" . http_build_query(array(
        "access_token" => $accessToken
    )), $getOrPost, $parameters);

/*Now we're going to make sure the request actually worked, and didn't get
rejected because the Access Token was expired. If it WAS expired, the
results should be blank. (It'll return a 401 if you want to get fancy.)*/
$resultsArray = json_decode($results);
if(count($resultsArray) < 1)

/*Looks like it did expire, so now we make a POST request using the Refresh
token, which will return a new Access Token AND a new Refresh Token.*/
    $reAuthorization = json_decode(curling(
        "https:/*coinbase.com/oauth/token?" . http_build_query(array(
            "grant_type" => "refresh_token",
            "code" => $userCode,
            "refresh_token" => $refreshToken
        )), "post", ""), true);
    $accessToken = $reAuthorization["access_token"];
    $refreshToken = $reAuthorization["refresh_token"];

/*Let's back those up to userdata.txt...*/
    $userData["accessToken"] = $accessToken;
    $userData["refreshToken"] = $refreshToken;
    file_put_contents("userdata.txt",json_encode($userData),LOCK_EX);

/*...and try the API request all over again:*/
    $results = curling("https:/*coinbase.com/api/v1/" . $apiPath . "?" . http_build_query(array(
            "access_token" => $accessToken
        )), $getOrPost, $parameters);

/*If it doesn't work THIS time, I'm going to clean out userdata.txt and ask
the user to try again. One of the codes probably got all mungled up.*/
    $resultsArray = json_decode($results);
    if(array_key_exists("error",$resultsArray))
        file_put_contents("userdata.txt","",LOCK_EX);
        die("Something went wrong. Please refresh the page and try again.");
    


/*If, however, everything went right, then this function will return the JSON
string with the data from the API! Hooray!*/
return $results;



/*Here are 4 different example requests you can make.*/

/*
echo oauthRequest("account/generate_receive_address","post","");

echo oauthRequest("buttons","post",'
    "button": 
        "name": "test",
        "type": "buy_now",
        "price_string": ".01",
        "price_currency_iso": "USD"
    
');

echo oauthRequest("prices/buy","get",'
    "qty": 1,
    "currency": "USD"
');

echo oauthRequest("account/balance","get","");
*/

?>

【讨论】:

【参考方案2】:

API 过去如此简单——只需要一个密钥——这一事实意味着它非常不安全。所以他们在一周前加强了安全措施。这是博文:

http://blog.coinbase.com/post/75936737678/more-security-and-granular-control-with-the-new-api

现在,除了 API 密钥之外,每个人都会获得一个 API“秘密”。每当您向 API 发出请求时,都必须包含三个参数:

您的 API 密钥。 “nonce”,它是用于标识某物的唯一编号。在这种情况下,您发出的每个请求都需要有一个新编号,并且每个请求的 nonce 必须大于之前的。 您的 API“签名”。这不是您的 API“秘密”。

签名是您的 nonce,紧随其后的是您发布请求、参数和所有内容的完整 URL。这个 URL 也包含 nonce,所以整个事情看起来像这样:

12345https://coinbase.com/api/v1/buttons?nonce=12345&amp;name=Socks&amp;price=9.95

然后您将整个内容编码为“SHA256”哈希。如果您不知道这意味着什么,请不要惊慌——您可以使用 PHP 已经内置的函数在一行中完成。

无论如何,我在搞清楚这一切时遇到了一些麻烦,所以我花了一点时间整理了这个脚本,这使得 GET 和 POST 到 API 变得非常容易。我很想听听人们的想法!

<?php

function coinbaseRequest($what,$getOrPost,$parameters)

//Obviously, your API Key and Secret go here.
$apikey = "blahblahblah";
$apisecret = "blahblahblahblah";    
$nonce = file_get_contents("nonce.txt") + 1;
file_put_contents("nonce.txt", $nonce, LOCK_EX);

$url = "https://coinbase.com/api/v1/" . $what . "?nonce=" . $nonce;

if($parameters != "")
$parameters = http_build_query(json_decode($parameters), true);


//Here I go, hashing the Signature! Thanks, PHP, for making this easy!

$signature = hash_hmac("sha256", $nonce . $url . $parameters, $apisecret);

$ch = curl_init();

curl_setopt_array($ch, array(
    CURLOPT_URL => $url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => array(
        "ACCESS_KEY: " . $apikey,
        "ACCESS_NONCE: " . $nonce,
        "ACCESS_SIGNATURE: " . $signature
    )));

if($getOrPost == "post")
curl_setopt_array($ch, array(
    CURLOPT_POSTFIELDS => $parameters,
    CURLOPT_POST => true,
));


$results = curl_exec($ch);
curl_close($ch);

echo $results;


//This is a POST example.
coinbaseRequest("buttons", "post", '
    "button": 
    "name": "test",
    "price_string": "1.23",
    "price_currency_iso": "USD",
    "variable_price": true
    
');


//This is a GET example.
coinbaseRequest("account/balance", "get", false);

?>

注意事项:

我尝试使用(microtime(true)*100) 作为我的随机数。问题是它生成了一个十进制数,并且最后几位数字不断被丢弃或四舍五入,因此它不起作用。然后我想,“搞砸了”,创建了一个空白的nonce.txt 文件,并在其中写入了1,为了获得随机数,我刚刚得到了该文件的内容,添加了1,并用新文件替换了该文件数字。它的第二个目的是作为计数器显示我提出的总请求数。

然后有人向我指出PHP的“uniqid”函数,它根据当前的微时间生成一个ID。所以你也可以试试这个:

$nonce = hexdec(uniqid());

这具有不访问外部文件的优点。实际上,我真的很喜欢能够看到我提出了多少请求,因此可能会坚持使用(坏的)nonce.txt 方法。

coinbaseRequest() 函数有三个参数。第一个是您向其提出请求的目录——也就是说,应该在“https://coinbase.com/api/v1/”之后出现的任何内容。第二个参数是“get”或“post”,取决于它是 GET 还是 POST 请求。 (有意义吗?)

第三个参数是您在请求中传递的所有查询。这应该被格式化为 JSON,除非它是一个不带任何参数的 GET 请求(除了该函数为您提供的 Key、Nonce 和 Signature),在这种情况下,您应该将其保留为 false

编辑,3 月 3 日:

我做了一个小函数来获取 coinbaseRequest 返回的任何内容并将其变成一个按钮:

function makebutt($data)

$data = json_decode($data,true);
$buttoncode = $data["button"]["code"];

return ("<a class=\"coinbase-button\" data-code=\"" . $buttoncode . "\" href=\"https://coinbase.com/checkouts/" . $buttoncode . "\">Pay With Bitcoin</a><script src=\"https://coinbase.com/assets/button.js\" type=\"text/javascript\"></script>");

【讨论】:

【参考方案3】:

它不起作用,因为 Coinbase 最近实施了 OAuth2 协议。这可确保您的用户的个人信息安全传输。 I referred to this implementation several months ago when writing my own OAuth class on another project:

【讨论】:

实际上他们提供了两种不同的身份验证方法:OAuth2 和上面列出的一种:coinbase.com/docs/api/authentication

以上是关于我如何与 Coinbase 的 API 交互?为啥总是失败?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 coinbase api 和 php 获取具有新地址的字符串

如何在 SendMoney coinbase API 中发布数据

Python coinbase API 价格为浮动

Coinbase API:如何获取所有受支持硬币的现货价格

如何修复 react app 和 coinbase connect api 上的 cors 错误

如何通过 Coinbase Api v2 发送 ping 通知?