@zufans/coinbase-advanced-trade
Version:
A comprehensive Node.js SDK for Coinbase Advanced Trade API
848 lines (763 loc) • 24.4 kB
JavaScript
import axios from 'axios';
import jwt from 'jsonwebtoken';
import crypto from 'crypto';
// coinbaseAxios.js
// What is the difference between "https://api.exchange.coinbase.com" and "https://api.coinbase.com/
// Create Axios instance with base URL
const coinbaseAxios = axios.create({
baseURL: "https://api.coinbase.com/",
maxBodyLength: Infinity,
headers: {
"Content-Type": "application/json",
},
});
// Request Interceptor to attach Authorization token dynamically
coinbaseAxios.interceptors.request.use(
async (config) => {
try {
// If Authorization token is provided in config, use it
if (config.token) {
config.headers.Authorization = `Bearer ${config.token}`;
}
return config;
} catch (error) {
return Promise.reject(error);
}
},
(error) => {
return Promise.reject(error);
}
);
// Response Interceptor for handling responses
coinbaseAxios.interceptors.response.use(
(response) => {
return response.data;
},
(error) => {
// console.error("Error in response interceptor", error.response?.status);
return Promise.reject(error);
}
);
// import url from "url";
// functions
// import grabNestedObjectValues from "../grabNestedObjectValues";
// import { logErrorToFile } from "../Error/errorToDataBase";
// import { redisPublisher } from "../redis/redisClient";
function cbToken(method, request_path, key, secret) {
try {
const uri = method + " " + "api.coinbase.com" + request_path;
const privateKey = crypto.createPrivateKey(secret);
return jwt.sign(
{
iss: "cdp",
nbf: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 120,
sub: key,
uri: uri,
},
privateKey,
{
algorithm: "ES256",
header: {
kid: key,
nonce: crypto.randomBytes(16).toString("hex"),
},
}
);
} catch (error) {
throw error;
}
}
// let key_name = null;
// let key_secret = null;
// export async function cbSignWithJWT(message) {
// try {
// if (key_name === null || !key_secret === null) {
// // await redisSubscriber.connect();
// const adminAPIKey = JSON.parse(await redisPublisher.get("adminAPIKey"));
// key_name = adminAPIKey.cbAdminKey;
// key_secret = adminAPIKey.cbAdminSecret;
// }
// const header = {
// iss: "cdp",
// nbf: Math.floor(Date.now() / 1000),
// exp: Math.floor(Date.now() / 1000) + 120,
// sub: key_name,
// };
// const options = {
// algorithm: "ES256",
// header: {
// kid: key_name,
// nonce: crypto.randomBytes(16).toString("hex"),
// },
// };
// const jwtSigned = jwt.sign(header, key_secret, options);
// return { ...message, jwt: jwtSigned };
// } catch (error) {
// console.error(error);
// logErrorToFile(error);
// throw error;
// }
// }
// export function verify(token) {
// try {
// if (!token) throw new Error("Token is required");
// return jwt.verify(token, process.env.JWT_SECRET);
// } catch (error) {
// return { isError: true, name: error.name, message: error.message };
// }
// }
// export function sign(payload) {
// const expiresIn = "1h";
// // const expiresIn = "5000";
// try {
// if (!payload) throw new Error("Payload is required");
// return jwt.sign(payload, process.env.JWT_SECRET, {
// expiresIn: expiresIn,
// });
// } catch (error) {
// return error;
// }
// }
// export function confirmations(userInfo) {
// const verificationKeys = [
// ["email"],
// ["EmailVerification", "emailConfirmed"],
// ["SmsVerification", "smsConfirmed"],
// ["Account", "coinBaseAPIKeyName"],
// ["Account", "coinBaseSecret"],
// ["isAdmin"],
// ];
// const info = grabNestedObjectValues(verificationKeys, userInfo);
// const confirmation = {};
// confirmation["email"] = info?.email || false;
// confirmation["emailConfirmed"] = info?.emailConfirmed || false;
// confirmation["smsConfirmed"] = info?.smsConfirmed || false;
// confirmation["coinBaseAPIKey"] = info?.coinBaseAPIKeyName ? true : false;
// confirmation["coinBaseSecret"] = info?.coinBaseSecret ? true : false;
// confirmation["isAdmin"] = info?.isAdmin ? true : false;
// return confirmation;
// }
// export function socketCheckUserAuth(ws, req) {
// const parameters = url.parse(req.url, true);
// const authorization = parameters.query.authorization;
// const jwtUserInfo = verify(authorization);
// if (jwtUserInfo.hasOwnProperty("expiredAt")) {
// ws.send(JSON.stringify({ error: "sessionexpired" }));
// ws.close();
// return;
// }
// }
async function fetchAccountsData(
endpoint, // The API endpoint template (e.g., "/accounts/{account_uuid}")
cbAPIKeyName, // API Key
cbSecret, // API Secret
params = {}, // Query parameters (e.g., { limit: 250, cursor: "cursor" })
pathParams = {} // Path parameters (e.g., { account_uuid })
) {
try {
// Construct the full endpoint path and inject path parameters
let fullPath = `/api/v3/brokerage${endpoint}`;
Object.keys(pathParams).forEach((key) => {
fullPath = fullPath.replace(`{${key}}`, pathParams[key]);
});
// Generate the authorization token
const token = cbToken("GET", fullPath, cbAPIKeyName, cbSecret);
// Make the API request
const response = await coinbaseAxios.get(fullPath, {
params, // Axios will serialize the query parameters
token, // Attach the token in the request headers
});
return response; // Return the response data
} catch (error) {
throw error; // Re-throw any errors for further handling
}
}
// https://api.exchange.coinbase.com
// https://api.exchange.coinbase.com/accounts
// https://api.exchange.coinbase.com/accounts/{account_id}
// https://api.exchange.coinbase.com/accounts/{account_id}/holds
// https://api.exchange.coinbase.com/accounts/{account_id}/ledger
// https://api.exchange.coinbase.com/accounts/{account_id}/transfers
async function listAccounts(cbAPIKeyName, cbSecret, params = {}) {
// Query parameters could include limit, cursor, and retail_portfolio_id
const queryParams = {
limit: params.limit || 250, // Default limit to 250 if not provided
cursor: params.cursor || undefined, // Optional cursor for pagination
retail_portfolio_id: params.retail_portfolio_id || undefined, // Optional portfolio ID
};
return await fetchAccountsData(
"/accounts", // The accounts endpoint
cbAPIKeyName,
cbSecret,
queryParams, // Pass query parameters
{} // No path parameters for listing accounts
);
}
// Fetch details of a specific account
// https://api.coinbase.com/api/v3/brokerage/accounts/account_uuid
async function getAccount(cbAPIKeyName, cbSecret, accountUUID) {
return await fetchAccountsData(
`/accounts/${accountUUID}`, // The account details endpoint with path parameter
cbAPIKeyName,
cbSecret,
{}, // No query parameters for this request
{ account_uuid: accountUUID } // Path parameter: accountUUID
);
}
/**
* Create a new order on Coinbase
*
* @param {string} cbAPIKeyName - Coinbase API Key Name
* @param {string} cbSecret - Coinbase API Secret
* @param {string} client_order_id - Client Order ID (required)
* @param {string} product_id - Product ID (required, e.g., "BTC-USD")
* @param {string} side - Order side (required, possible values: ["BUY", "SELL"])
* @param {object} order_configuration - Order configuration object (required, one of the order types)
* @param {string} [leverage] - Optional leverage
* @param {string} [margin_type] - Optional margin type
* @param {string} [retail_portfolio_id] - Optional retail portfolio ID
* @param {string} [preview_id] - Optional preview ID
*
* @returns {Promise<object>} - The created order response
* @throws {Error} - If the request fails
*/
// https://api.coinbase.com
// /api/v3/brokerage/orders
async function createOrder(
cbAPIKeyName,
cbSecret,
client_order_id,
product_id,
side,
order_configuration,
leverage = null,
margin_type = null,
retail_portfolio_id = null,
preview_id = null
) {
try {
const token = cbToken(
"POST",
"/api/v3/brokerage/orders",
cbAPIKeyName,
cbSecret
);
const bodyParams = {
client_order_id,
product_id,
side,
order_configuration,
};
// Add optional parameters if provided
if (leverage) bodyParams.leverage = leverage;
if (margin_type) bodyParams.margin_type = margin_type;
if (retail_portfolio_id)
bodyParams.retail_portfolio_id = retail_portfolio_id;
if (preview_id) bodyParams.preview_id = preview_id;
const response = await coinbaseAxios.post(
"/api/v3/brokerage/orders",
bodyParams,
{
token: token,
}
);
return response;
} catch (error) {
return new Error(error);
}
}
/**
* Cancel multiple orders on Coinbase
*
* @param {string} cbAPIKeyName - Coinbase API Key Name
* @param {string} cbSecret - Coinbase API Secret
* @param {array} order_ids - Array of order IDs to be canceled (required, max length: 99)
*
* @returns {Promise<object>} - The response of the cancellation request
* @throws {Error} - If the request fails or order_ids array exceeds the limit
*/
// TESTED: Functions as expected
async function cancelOrders(cbAPIKeyName, cbSecret, order_ids) {
try {
if (!order_ids) throw new Error("order_ids is required");
if (Array.isArray(order_ids)) {
if (order_ids.length === 0 || order_ids.length > 99) {
throw new Error(
"The order_ids array must contain between 1 and 99 order IDs."
);
}
}
if (!Array.isArray(order_ids) && typeof order_ids === "string")
order_ids = [order_ids];
const token = cbToken(
"POST",
"/api/v3/brokerage/orders/batch_cancel",
cbAPIKeyName,
cbSecret
);
const bodyParams = {
order_ids, // Array of order IDs to be canceled
};
const response = await coinbaseAxios.post(
"/api/v3/brokerage/orders/batch_cancel",
bodyParams,
{
token: token,
}
);
return response;
} catch (error) {
// throw new Error(`Failed to cancel orders: ${error.message}`);
return new Error(error);
}
}
/**
* Edit an existing order on Coinbase
*
* @param {string} cbAPIKeyName - Coinbase API Key Name
* @param {string} cbSecret - Coinbase API Secret
* @param {string} order_id - The ID of the order to edit (required)
* @param {string} price - The new price for the order (required)
* @param {string} size - The new size for the order (required)
* @param {string} [client_order_id] - Optional client order ID for additional filtering
*
* @returns {Promise<object>} - The response of the edit request
* @throws {Error} - If the request fails or any required parameters are missing
*/
// TESTED: Functions as expected
async function editOrder(
cbAPIKeyName,
cbSecret,
order_id,
price,
size,
client_order_id = null
) {
try {
if (!order_id || !price || !size) {
throw new Error(
"The order_id, price, and size are required to edit an order."
);
}
const token = cbToken(
"POST",
"/api/v3/brokerage/orders/edit",
cbAPIKeyName,
cbSecret
);
price = typeof price === "number" ? price.toString() : price;
size = typeof size === "number" ? size.toString() : size;
const bodyParams = {
order_id, // Required order ID in the body
price, // New price for the order
size, // New size for the order
};
const response = await coinbaseAxios.post(
"/api/v3/brokerage/orders/edit",
bodyParams,
{
params: client_order_id ? { client_order_id } : {}, // Only include client_order_id if provided
headers: {
Authorization: `Bearer ${token}`,
},
}
);
return response;
} catch (error) {
// throw new Error(`Failed to edit order: ${error.message}`);
return new Error(error);
}
}
/**
* Preview an edited order on Coinbase
*
* @param {string} cbAPIKeyName - Coinbase API Key Name
* @param {string} cbSecret - Coinbase API Secret
* @param {string} order_id - The ID of the order to be previewed (required)
* @param {string} price - New price for the order (required)
* @param {string} size - New size for the order (required)
*
* @returns {Promise<object>} - The response of the preview request
* @throws {Error} - If the request fails or any required params are missing
*/
async function editOrderPreview(
cbAPIKeyName,
cbSecret,
order_id,
price,
size
) {
try {
if (!order_id || !price || !size) {
throw new Error(
"The order_id, price, and size are required to preview an edited order."
);
}
const token = cbToken(
"POST",
"/api/v3/brokerage/orders/edit_preview",
cbAPIKeyName,
cbSecret
);
const bodyParams = {
order_id, // Required order ID
price, // Required new price
size, // Required new size
};
const response = await coinbaseAxios.post(
"/api/v3/brokerage/orders/edit_preview",
bodyParams,
{
token: token,
}
);
return response;
} catch (error) {
throw new Error(`Failed to preview edited order: ${error.message}`);
}
}
/**
* List orders on Coinbase with optional filters
*
* @param {string} cbAPIKeyName - Coinbase API Key Name
* @param {string} cbSecret - Coinbase API Secret
* @param {object} filters - Optional query parameters for filtering the orders
*
* @returns {Promise<object>} - The response of the list request
* @throws {Error} - If the request fails
*/
// https://api.coinbase.com/api/v3/brokerage/orders/historical/batch
// https://api.coinbase.com
// /api/v3/brokerage/orders/historical/batch
async function listOrders(cbAPIKeyName, cbSecret, filters = {}) {
try {
const token = cbToken(
"GET",
"/api/v3/brokerage/orders/historical/batch",
cbAPIKeyName,
cbSecret
);
// const queryParams = { ...filters }; // Copy any filters passed into the function
const queryParams = filters.order_ids
? { order_ids: filters.order_ids }
: { ...filters };
const response = await coinbaseAxios.get(
"/api/v3/brokerage/orders/historical/batch",
{
params: queryParams,
token: token,
}
);
return response;
} catch (error) {
throw error;
}
}
// https://docs.cdp.coinbase.com/advanced-trade/reference/retailbrokerageapi_getfills
// GET: https://api.coinbase.com/api/v3/brokerage/orders/historical/fills
// MISSING listFills function
// https://docs.cdp.coinbase.com/advanced-trade/reference/retailbrokerageapi_gethistoricalorder
// GET: https://api.coinbase.com/api/v3/brokerage/orders/historical/{order_id}
/**
* Get details of a specific order on Coinbase
*
* @param {string} cbAPIKeyName - Coinbase API Key Name
* @param {string} cbSecret - Coinbase API Secret
* @param {string} order_id - The ID of the order to retrieve (required)
* @param {string} [client_order_id] - Optional client order ID for additional filtering
*
* @returns {Promise<object>} - The response containing the order details
* @throws {Error} - If the request fails or if order_id is missing
*/
async function getOrder(
cbAPIKeyName,
cbSecret,
order_id,
client_order_id = null
) {
try {
if (!order_id) {
throw new Error("The order_id is required to get order details.");
}
const token = cbToken(
"GET",
`/api/v3/brokerage/orders/historical/${order_id}`,
cbAPIKeyName,
cbSecret
);
const response = await coinbaseAxios.get(
`/api/v3/brokerage/orders/historical/${order_id}`,
{
params: client_order_id ? { client_order_id } : {}, // Only include client_order_id if provided
headers: {
Authorization: `Bearer ${token}`,
},
}
);
return response;
} catch (error) {
return new Error(error);
}
}
// fetchProductData.js
async function fetchProductData(
endpoint,
cbAPIKeyName,
cbSecret,
params = {}, // Query parameters
pathParams = {} // Path parameters (e.g., productId)
) {
try {
let fullPath = `/api/v3/brokerage${endpoint}`;
Object.keys(pathParams).forEach((key) => {
fullPath = fullPath.replace(`{${key}}`, pathParams[key]);
});
const token = cbToken(
"GET",
`/api/v3/brokerage${endpoint}`,
cbAPIKeyName,
cbSecret
);
const response = await coinbaseAxios.get(fullPath, {
params, // Optional query parameters
token: token,
});
return response;
} catch (error) {
throw error;
}
}
// Products.js
// endpoint, cbAPIKeyName, cbSecret, params, (productId = "");
async function getBestBidAsk(
cbAPIKeyName,
cbSecret,
productId = "", // Product ID to be passed in query params
params = {} // Additional query parameters, if any
) {
// Ensure productId is passed in query params as product_ids
if (params.product_ids) params.product_ids = productId;
// Fetch data from /best_bid_ask endpoint
return await fetchProductData(
`/best_bid_ask`, // Endpoint
cbAPIKeyName,
cbSecret,
params, // Query parameters
{} // No path parameters for this endpoint
);
}
async function getProductBook(
cbAPIKeyName,
cbSecret,
productId = "", // Product ID to be passed in query params
params = {} // Additional query parameters, if any
) {
// Ensure productId is passed in query params as product_id
params.product_id = productId;
// Fetch data from /product_book endpoint
return await fetchProductData(
`/product_book`, // Endpoint
cbAPIKeyName,
cbSecret,
params, // Query parameters
{} // No path parameters for this endpoint
);
}
async function listProducts(cbAPIKeyName, cbSecret, queryParams) {
return await fetchProductData(
"/products", // Endpoint
cbAPIKeyName,
cbSecret,
queryParams, // Query parameters
{} // No path parameters
);
}
// https://api.coinbase.com/api/v3/brokerage/products/BTC-USD
async function getProduct(cbAPIKeyName, cbSecret, productId) {
return await fetchProductData(
`/products/${productId}`, // Product ID as part of the endpoint path
cbAPIKeyName,
cbSecret,
{}, // No query parameters
{} // No path parameters
);
}
//https://api.coinbase.com/api/v3/brokerage/products/BTC-USD/candles?start=1725853715&end=1725925715&granularity=ONE_MINUTE&limit=350
async function getCandles(
cbAPIKeyName,
cbSecret,
productId,
queryParam
) {
try {
const validGranularities = [
"ONE_MINUTE",
"FIVE_MINUTE",
"FIFTEEN_MINUTE",
"THIRTY_MINUTE",
"ONE_HOUR",
"TWO_HOUR",
"SIX_HOUR",
"ONE_DAY",
];
if (!validGranularities.includes(queryParam.granularity))
throw new Error(
`Granularity ${
queryParam.granularity
} is not supported. Please use one of the following: ${validGranularities.join(
", "
)}`
);
const queryParams = {
start: queryParam.start,
end: queryParam.end,
granularity: queryParam.granularity, // ONE_MINUTE, FIVE_MINUTE, FIFTEEN_MINUTE, THIRTY_MINUTE, ONE_HOUR, TWO_HOUR, SIX_HOUR, ONE_DAY
limit: queryParam.limit,
};
return await fetchProductData(
`/products/${productId}/candles`, // Product ID in the endpoint path
cbAPIKeyName,
cbSecret,
queryParams, // Query parameters for candles
{} // No path parameters
);
} catch (error) {
throw error;
}
}
// https://api.coinbase.com/api/v3/brokerage/products/BTC-USD/ticker?limit=17&start=1725853715&end=1725925715
async function getMarketTrades(
cbAPIKeyName,
cbSecret,
productId,
queryParam
) {
const queryParams = {
limit: queryParam.limit,
start: queryParam.start,
end: queryParam.end,
};
return await fetchProductData(
`/products/${productId}/ticker`, // Product ID in the endpoint path
cbAPIKeyName,
cbSecret,
queryParams, // Query parameters for trades
{} // No path parameters
);
}
// https://api.coinbase.com/api/v3/brokerage/key_permissions
async function fetchKeyPermissions(cbAPIKeyName, cbSecret) {
try {
const fullPath = `/api/v3/brokerage/key_permissions`;
const token = cbToken("GET", fullPath, cbAPIKeyName, cbSecret);
const response = await coinbaseAxios.get(fullPath, {
token: token,
});
return response;
} catch (error) {
throw error;
}
}
// KeyPermissions
async function keyPermissions(cbAPIKeyName, cbSecret) {
try {
return await fetchKeyPermissions(cbAPIKeyName, cbSecret);
} catch (error) {
throw error;
}
}
async function getCurrency(base) {
try {
if (!base) throw new Error("base currency is not defined");
const baseCurrency = base.includes("-") ? base.split("-")[0] : base;
const response = await axios.request({
method: "get",
maxBodyLength: Infinity,
url: `https://api.exchange.coinbase.com/currencies/${base}`,
headers: {
"Content-Type": "application/json",
},
});
if (response.data) return response.data;
} catch (error) {
throw error;
}
}
// Main entry point for the Coinbase Advanced Trade SDK
/**
* Main CoinbaseAdvancedTrade class that encapsulates all API functionality
*/
class CoinbaseAdvancedTrade {
constructor(apiKeyName, apiSecret) {
if (!apiKeyName || !apiSecret) {
throw new Error('API Key Name and API Secret are required');
}
this.apiKeyName = apiKeyName;
this.apiSecret = apiSecret;
}
// Account methods
async listAccounts(params = {}) {
return listAccounts(this.apiKeyName, this.apiSecret, params);
}
async getAccount(accountUUID) {
return getAccount(this.apiKeyName, this.apiSecret, accountUUID);
}
// Order methods
async createOrder(clientOrderId, productId, side, orderConfiguration, options = {}) {
return createOrder(
this.apiKeyName,
this.apiSecret,
clientOrderId,
productId,
side,
orderConfiguration,
options.leverage,
options.marginType,
options.retailPortfolioId,
options.previewId
);
}
async cancelOrders(orderIds) {
return cancelOrders(this.apiKeyName, this.apiSecret, orderIds);
}
async editOrder(orderId, price, size, clientOrderId = null) {
return editOrder(this.apiKeyName, this.apiSecret, orderId, price, size, clientOrderId);
}
async editOrderPreview(orderId, price, size) {
return editOrderPreview(this.apiKeyName, this.apiSecret, orderId, price, size);
}
async listOrders(filters = {}) {
return listOrders(this.apiKeyName, this.apiSecret, filters);
}
async getOrder(orderId, clientOrderId = null) {
return getOrder(this.apiKeyName, this.apiSecret, orderId, clientOrderId);
}
// Product methods
async getBestBidAsk(productId = '', params = {}) {
return getBestBidAsk(this.apiKeyName, this.apiSecret, productId, params);
}
async getProductBook(productId = '', params = {}) {
return getProductBook(this.apiKeyName, this.apiSecret, productId, params);
}
async listProducts(queryParams = {}) {
return listProducts(this.apiKeyName, this.apiSecret, queryParams);
}
async getProduct(productId) {
return getProduct(this.apiKeyName, this.apiSecret, productId);
}
async getCandles(productId, queryParams) {
return getCandles(this.apiKeyName, this.apiSecret, productId, queryParams);
}
async getMarketTrades(productId, queryParams) {
return getMarketTrades(this.apiKeyName, this.apiSecret, productId, queryParams);
}
// Utility methods
async getKeyPermissions() {
return keyPermissions(this.apiKeyName, this.apiSecret);
}
async getCurrency(base) {
return getCurrency(base);
}
}
export { CoinbaseAdvancedTrade, cancelOrders, cbToken, coinbaseAxios, createOrder, CoinbaseAdvancedTrade as default, editOrder, editOrderPreview, fetchAccountsData, fetchKeyPermissions, fetchProductData, getAccount, getBestBidAsk, getCandles, getCurrency, getMarketTrades, getOrder, getProduct, getProductBook, keyPermissions, listAccounts, listOrders, listProducts };