Coinbase API-继续获得无效的API密钥,尽管遵循文档



我试图设置一个应用程序来与Coinbase API交互,但我在API密钥/机密方面遇到了困难。

基于我的代码:

import { readFile } from 'fs';
import crypto from 'crypto';
import request from 'request';
import { request as rqu } from 'undici';
import fetch, { Headers } from 'node-fetch';
// Import { getJSON } from './sys-handler.mjs';
/**
* Read Local JSON file
* No real error management but that should be fine
*
* @param {string} file - JSON file to read
* @returns {object}
*/
function getJSON( file ) {
return new Promise( ( resolve, reject ) => {
readFile( file, 'utf8', ( err, data ) => {
if ( err ) {
reject( err );
}
else {
resolve( JSON.parse( data ) );
}
} );
} );
}
/**
* Base64 Decode
* @param {string} str
* @returns {string}
*/
function atob( str ) {
return `${ Buffer.from( str, 'base64' ) }`;
}
/**
* Base64 Encode
* @param {string} str
* @returns {string}
*/
function btoa( str ) {
return `${ Buffer.from( str ).toString( 'base64' ) }`;
}
/**
* Get API Credentials from base64 Hash
*
* Credentials are stored in a string that is composed of the following:
* - Key
* - Secret
* - Passphrase
*
* Each is base64 encoded, then they are joined with a colon (:), then the whole string is base64 encoded again.
*
* @param {string} file JSON File containing API Credentials
* @returns {Promise<string[]>}
*/
async function getApiCredentialsFromBase64( file ) {
try {
const keys = await getJSON( file );
return keys.map(
str => atob( str )
.split( ':' )
.map( atob )
);
}
catch ( error ) {
throw error;
}
}
/**
* Get API Credentials from plain text
*
* Credentials are stored in clear plain text as they were generated initially in a JSON object as follows:
* ```javascript
* {
*     "key":        "................",
*     "secret":     "...........................",
*     "passphrase": "............."
* }
* ```
*
* @param {string} file JSON File containing API Credentials
* @returns {Promise<string[]>}
*/
async function getApiCredentialsFromPlainText( file ) {
try {
const { key, secret, passphrase } = await getJSON( file );
return {
key,
secret,
passphrase
};
}
catch ( error ) {
throw error;
}
}
/**
* Ensure path does not start with /
*
* @param {string} path
* @returns {string}
*/
function sanitizePath( path ) {
return `${ path.replace( /^/*/gu, '' ) }`;
}
/**
* Ensure url does not end with /
*
* @param {string} url
* @returns {string}
*/
function sanitizeURL( url ) {
return `${ url.replace( //*$/gu, '' ) }`;
}
/**
* @typedef {Object} SignatureOptions
* @property {string} method - HTTP Method
* @property {string} endpoint - API Endpoint
* @property {string} body - Request Body
* @property {number} timestamp - Timestamp
* @property {string} version - API Version
* @property {string} credentials - API Credentials File
*/
/**
* Build Coinbase-formatted Signature
*
* A timestamp, the method, the path and the body are all concatenated together to create the `message` string.
* The `message` string is then signed using the base64 decoded API Secret through an HMAC-sha256.
*
* Then, `timestamp`, sanitized `path`, `passphrase`, API Key, the base64 encoded HMAC-sha256 signature and the trailing-slash-free endpoint are returned within an object.
*
*
* @param {SignatureOptions} options - Options
*/
async function buildSignature( {
timestamp = Math.floor( Date.now() / 1000 ),
method = 'GET',
endpoint,
body,
credentials,
version
} ) {
// Sanitize the path
const _endpoint = sanitizePath( endpoint );
let key; let passphrase; let
secret;
// Get API Credentials
if ( credentials.endsWith( '.base64.json' ) ) {
[ [ key, secret, passphrase ] ] = await getApiCredentialsFromBase64( credentials );
secret = atob( secret );
}
else {
( { key, secret, passphrase } = await getApiCredentialsFromPlainText( credentials ) );
}
// Build Message
let message;
// If a version is provided, it is prepended to the endpoint
if ( version ) {
message = `${ timestamp }${ method.toUpperCase() }/${ version }/${ _endpoint }${ body || '' }`;
}
else {
message = `${ timestamp }${ method.toUpperCase() }/${ _endpoint }${ body || '' }`;
}
return {
signature: crypto.createHmac( 'sha256', secret )
.update( message )
.digest( 'base64' ),
timestamp,
passphrase,
key,
endpoint: _endpoint
};
}

/**
* @typedef {Object} APIRequestOptions
* @property {string} api - API URL
* @property {string} version - API Version
* @property {string} endpoint - API Endpoint
* @property {string} credentials - API Key File
* @property {string|Object} body - Request Body
* @property {string} method - HTTP Method
* @property {string} lib - Library used to send request
* @returns
*/
/**
* Send API Request
*
* @param {APIRequestOptions} options
* @returns {Promise<*>}
*/
const getAccountInfo = async ( {
endpoint,
body,
method = 'GET',
version = 'v2',
sandbox = false,
credentials = './api.json',
api,
lib = 'undici'
} ) => {
let _api;
if ( !api ) {
// Choose the right URL depending on the sandbox mode
if ( sandbox ) {
_api = `https://api-public.sandbox.exchange.coinbase.com`;
}
else {
_api = `https://api.exchange.coinbase.com`;
}
}
else {
_api = `${ sanitizeURL( api ) }`;
}
if ( version ) {
_api = `${ _api }/${ version }`;
}
// Build Request Signature
const {
key,
signature,
passphrase,
timestamp,
endpoint: _endpoint
} = await buildSignature( { method, endpoint, body, credentials, version } );
// Full Endpoint
const fullEndpoint = `${ _api }/${ _endpoint }`;
// Send Request
return await new Promise( async ( resolve, reject ) => {
// Craft Request Headers
const headers = {
'User-Agent': 'aemi-node-client',
'CB-ACCESS-KEY': key,
'CB-ACCESS-SIGN': signature,
'CB-ACCESS-TIMESTAMP': timestamp,
'CB-ACCESS-PASSPHRASE': passphrase,
Accept: 'application/json',
'Content-Type': 'application/json'
};
const options = {};
// Try different libraries to send the request
switch ( lib ) {
case 'node-fetch': {
/*
* Craft Request Options
* Use object assign as official Coinbase Pro Node Client does
* Using the Headers object from node-fetch library
*/
Object.assign( options, {
headers: new Headers( headers ),
method,
uri: fullEndpoint,
...body ? { body } : {}
} );
// Parse the JSON request or display an error
try {
const response = await fetch( fullEndpoint, options );
resolve( await response.json() );
}
catch ( error ) {
console.error( error );
reject( error );
}
break;
}
case 'request': {
/*
* Craft Request Options
* Use object assign as official Coinbase Pro Node Client does
*/
Object.assign( options, {
headers,
method,
uri: fullEndpoint,
...body ? { body } : {}
} );
// Send Request
request( options, async ( error, response, data ) => {
// Parse the JSON request or display an error
try {
if ( error ) {
reject( error );
}
else {
if ( data ) {
resolve( JSON.parse( data ) );
}
else {
resolve( await response.toJSON() );
}
}
}
catch ( error ) {
reject( error );
}
} );
break;
}
case 'undici':
default: {
/*
* Craft Request Options
* Use object assign as official Coinbase Pro Node Client does
*/
Object.assign( options, {
headers,
method,
uri: fullEndpoint,
...body ? { body } : {}
} );
// Parse the JSON request or display an error
try {
const response = await rqu( fullEndpoint, options );
resolve( await response.body.json() );
}
catch ( error ) {
console.error( error );
reject( error );
}
break;
}
}
} );
};
// API URLs tested
const apis = [
'https://api.pro.coinbase.com',
'https://api.exchange.coinbase.com',
'https://api-public.sandbox.exchange.coinbase.com',
'https://api-public.sandbox.pro.coinbase.com'
];
// API Key files
const apiKeys = [
'./api.base64.json', // View Rights on Coinbase Pro (1st key generated)
'./api.test.base64.json', // View Rights on Coinbase Pro (2nd Key generated)
'./api.sandbox.base64.json', // View Rights on Coinbase Pro Sandbox Mode
'./api.plain.json' // View Rights on Coinbase Pro (same as api.test.base64)
];
// Libraries tested to send request
const libs = [
'undici',
'request',
'node-fetch'
];
// Test Endpoint that requires authentication
const endpoint = '/accounts';
// Avoid ESLint parsing problem about await outside an async function
( async () => {
for ( const lib of libs ) {
for ( const version of [ '', 'v2' ] ) {
for ( const key of apiKeys ) {
for ( const api of apis ) {
const options = { api, version, endpoint, credentials: key, lib };
console.log( `// ---- ----- ---- //` );
try {
const response = await getAccountInfo( options );
options.return = JSON.stringify( response, 0, 0 );
}
catch ( error ) {
console.error( `//      Error      //` );
let errorString;
try {
errorString = JSON.stringify( error, 0, 0 );
}
catch ( _ ) {
errorString = error;
}
options.return = errorString;
}
console.log( options );
console.log( `// ---- ----- ---- //` );
}
}
}
}
} )();

我一直在获取(由于堆栈溢出字符限制,日志被截断,并且我删除了组合(exchange|pro)-key/(exchange|pro)-sandbox-mode(:

// ---- ----- ---- //
{
api: 'https://api.pro.coinbase.com',
version: '',
endpoint: '/accounts',
credentials: './api.base64.json',
lib: 'undici',
return: '{"message":"invalid signature"}'
}
// ---- ----- ---- //
// ---- ----- ---- //
{
api: 'https://api.exchange.coinbase.com',
version: '',
endpoint: '/accounts',
credentials: './api.base64.json',
lib: 'undici',
return: '{"message":"invalid signature"}'
}
// ---- ----- ---- //
// ---- ----- ---- //
{
api: 'https://api.pro.coinbase.com',
version: '',
endpoint: '/accounts',
credentials: './api.test.base64.json',
lib: 'undici',
return: '{"message":"invalid signature"}'
}
// ---- ----- ---- //
// ---- ----- ---- //
{
api: 'https://api.exchange.coinbase.com',
version: '',
endpoint: '/accounts',
credentials: './api.test.base64.json',
lib: 'undici',
return: '{"message":"invalid signature"}'
}
// ---- ----- ---- //
// ---- ----- ---- //
{
api: 'https://api-public.sandbox.exchange.coinbase.com',
version: '',
endpoint: '/accounts',
credentials: './api.sandbox.base64.json',
lib: 'undici',
return: '{"message":"invalid signature"}'
}
// ---- ----- ---- //
// ---- ----- ---- //
{
api: 'https://api-public.sandbox.pro.coinbase.com',
version: '',
endpoint: '/accounts',
credentials: './api.sandbox.base64.json',
lib: 'undici',
return: '{"message":"invalid signature"}'
}
// ---- ----- ---- //
// ---- ----- ---- //
{
api: 'https://api.pro.coinbase.com',
version: '',
endpoint: '/accounts',
credentials: './api.plain.json',
lib: 'undici',
return: '{"message":"invalid signature"}'
}
// ---- ----- ---- //
// ---- ----- ---- //
{
api: 'https://api.exchange.coinbase.com',
version: '',
endpoint: '/accounts',
credentials: './api.plain.json',
lib: 'undici',
return: '{"message":"invalid signature"}'
}
// ---- ----- ---- //
// ---- ----- ---- //
{
api: 'https://api.pro.coinbase.com',
version: 'v2',
endpoint: '/accounts',
credentials: './api.base64.json',
lib: 'undici',
return: '{"message":"invalid signature"}'
}
// ---- ----- ---- //
// ---- ----- ---- //
{
api: 'https://api.exchange.coinbase.com',
version: 'v2',
endpoint: '/accounts',
credentials: './api.base64.json',
lib: 'undici',
return: '{"message":"invalid signature"}'
}
// ---- ----- ---- //
// ---- ----- ---- //
{
api: 'https://api.pro.coinbase.com',
version: 'v2',
endpoint: '/accounts',
credentials: './api.test.base64.json',
lib: 'undici',
return: '{"message":"invalid signature"}'
}
// ---- ----- ---- //
// ---- ----- ---- //
{
api: 'https://api.exchange.coinbase.com',
version: 'v2',
endpoint: '/accounts',
credentials: './api.test.base64.json',
lib: 'undici',
return: '{"message":"invalid signature"}'
}
// ---- ----- ---- //
// ---- ----- ---- //
{
api: 'https://api-public.sandbox.exchange.coinbase.com',
version: 'v2',
endpoint: '/accounts',
credentials: './api.sandbox.base64.json',
lib: 'undici',
return: '{"message":"invalid signature"}'
}
// ---- ----- ---- //
// ---- ----- ---- //
{
api: 'https://api-public.sandbox.pro.coinbase.com',
version: 'v2',
endpoint: '/accounts',
credentials: './api.sandbox.base64.json',
lib: 'undici',
return: '{"message":"invalid signature"}'
}
// ---- ----- ---- //
// ---- ----- ---- //
{
api: 'https://api.pro.coinbase.com',
version: 'v2',
endpoint: '/accounts',
credentials: './api.plain.json',
lib: 'undici',
return: '{"message":"invalid signature"}'
}
// ---- ----- ---- //
// ---- ----- ---- //
{
api: 'https://api.exchange.coinbase.com',
version: 'v2',
endpoint: '/accounts',
credentials: './api.plain.json',
lib: 'undici',
return: '{"message":"invalid signature"}'
}
// ---- ----- ---- //
// ... It goes the same way with other used libraries.

如果我很笨,请告诉我,但也请告诉我这个代码出了什么问题。我不确定API的URL是正确的,但Coinbase提供的每个URL都导致了不成功的试用。

编辑:代码已被精确地注释和详细说明,除非指定,否则不会操作提供的日志

谢谢大家,

致以最诚挚的问候

正如注释所示,您可能有一个无效的密钥。我看到CB突然使我的钥匙失效,我必须换一把新的。

如果你的签名有问题,你会得到更合适的错误。

类似于:

  • 无效签名
  • 时间戳已过期
  • 等等

最新更新