UNPKG

@zufans/coinbase-advanced-trade

Version:
874 lines (787 loc) 25 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var axios = require('axios'); var jwt = require('jsonwebtoken'); var crypto = require('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); } } exports.CoinbaseAdvancedTrade = CoinbaseAdvancedTrade; exports.cancelOrders = cancelOrders; exports.cbToken = cbToken; exports.coinbaseAxios = coinbaseAxios; exports.createOrder = createOrder; exports.default = CoinbaseAdvancedTrade; exports.editOrder = editOrder; exports.editOrderPreview = editOrderPreview; exports.fetchAccountsData = fetchAccountsData; exports.fetchKeyPermissions = fetchKeyPermissions; exports.fetchProductData = fetchProductData; exports.getAccount = getAccount; exports.getBestBidAsk = getBestBidAsk; exports.getCandles = getCandles; exports.getCurrency = getCurrency; exports.getMarketTrades = getMarketTrades; exports.getOrder = getOrder; exports.getProduct = getProduct; exports.getProductBook = getProductBook; exports.keyPermissions = keyPermissions; exports.listAccounts = listAccounts; exports.listOrders = listOrders; exports.listProducts = listProducts;