我如何与Coinbase的API进行交互?为什么总是失败?

时间:2014-02-12 13:45:41

标签: php ajax bitcoin coinbase-php coinbase-api

过去访问Coinbase API非常简单:您只需要一个API密钥即可。现在你需要一种叫做“nonce”和“signature”的东西。我在我的请求中传递了新的API“Secret”,nonce和Key,但它返回了“invalid_key”错误。是什么给了什么?

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

3 个答案:

答案 0 :(得分:3)

事实上,API曾经如此简单 - 只需要一个密钥 - 意味着它非常不安全。所以他们在一周前加强了安全性。这是博客文章:

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

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

  • 您的API密钥。
  • “nonce”,这是一个用于识别内容的唯一编号。在这种情况下,您所做的每个请求都需要有一个新的数字,并且每个请求的nonce必须大于之前的那个。
  • 您的API“签名”。这不是你的API“秘密”。

签名是您的随机数,紧接着是您发布请求,参数和所有内容的完整网址。这个URL也包含nonce,所以整个过程看起来像这样:

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

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

无论如何,我在解决所有问题时遇到了一些麻烦,所以我花了一些时间来编写这个脚本,这使得对API的GETing和POSTing变得非常简单。我很想听听别人的想法!

<?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”函数,该函数根据当前的microtime生成一个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>");
}

答案 1 :(得分:0)

由于Coinbase最近实施了OAuth2协议,因此无法正常运行。这可确保安全地传输用户的个人信息。 I referred to this implementation several months ago when writing my own OAuth class on another project

答案 2 :(得分:0)

YE GREAT OAUTH TUTORIAL

<?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","");
*/

?>