@coinbase/cdp-sdk
Version:
SDK for interacting with the Coinbase Developer Platform Wallet API
212 lines • 10.8 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.cdpApiClient = exports.configure = exports.config = void 0;
// eslint-disable-next-line import/no-named-as-default
const axios_1 = __importDefault(require("axios"));
const axios_retry_1 = __importStar(require("axios-retry"));
const index_js_1 = require("../auth/hooks/axios/index.js");
const constants_js_1 = require("../constants.js");
const errors_js_1 = require("./errors.js");
let axiosInstance;
exports.config = undefined;
/**
* Configures the CDP client with the given options.
*
* @param {CdpOptions} options - The CDP options.
*/
const configure = (options) => {
const baseURL = options.basePath || "https://api.cdp.coinbase.com/platform";
exports.config = {
...options,
basePath: baseURL,
};
axiosInstance = axios_1.default.create({
baseURL,
});
(0, axios_retry_1.default)(axiosInstance, {
retryDelay: axios_retry_1.exponentialDelay,
});
axiosInstance = (0, index_js_1.withAuth)(axiosInstance, {
apiKeyId: options.apiKeyId,
apiKeySecret: options.apiKeySecret,
source: options.source || "sdk-openapi-client",
sourceVersion: options.sourceVersion,
walletSecret: options.walletSecret,
expiresIn: options.expiresIn,
debug: options.debugging,
});
};
exports.configure = configure;
/**
* Adds an idempotency key to request config if provided
*
* @param config - The Axios request configuration.
* @param idempotencyKey - The idempotency key.
* @returns The Axios request configuration with the idempotency key.
*/
const addIdempotencyKey = (config, idempotencyKey) => {
if (!idempotencyKey) {
return config;
}
return {
...config,
headers: {
...(config.headers || {}),
"X-Idempotency-Key": idempotencyKey,
},
};
};
/**
* Mutates the given Axios request configuration to add the CDP API key signature
* to the request headers.
*
* @param {AxiosRequestConfig} config - The Axios request configuration.
* @param idempotencyKey - The idempotency key.
* @returns {Promise<T>} A promise that resolves to the response data.
* @throws {APIError} If the request fails.
*/
const cdpApiClient = async (config, idempotencyKey) => {
validateCall(config);
// Add idempotency key to the request headers if provided
const configWithIdempotencyKey = addIdempotencyKey(config, idempotencyKey);
try {
const response = await axiosInstance(configWithIdempotencyKey);
return response.data;
}
catch (error) {
// eslint-disable-next-line import/no-named-as-default-member
if (axios_1.default.isAxiosError(error) && !error.response) {
// Network-level errors (no response received)
const errorMessage = (error.message || "").toLowerCase();
const errorCode = error.code?.toLowerCase();
// Categorize network errors based on error messages and codes
if (errorCode === "econnrefused" || errorMessage.includes("connection refused")) {
throw new errors_js_1.NetworkError("network_connection_failed", "Unable to connect to CDP service. The service may be unavailable.", { code: error.code, message: error.message, retryable: true }, error.cause);
}
else if (errorCode === "etimedout" ||
errorCode === "econnaborted" ||
errorMessage.includes("timeout")) {
throw new errors_js_1.NetworkError("network_timeout", "Request timed out. Please try again.", { code: error.code, message: error.message, retryable: true }, error.cause);
}
else if (errorCode === "enotfound" || errorMessage.includes("getaddrinfo")) {
throw new errors_js_1.NetworkError("network_dns_failure", "DNS resolution failed. Please check your network connection.", { code: error.code, message: error.message, retryable: false }, error.cause);
}
else if (errorMessage.includes("network error") || errorMessage.includes("econnreset")) {
throw new errors_js_1.NetworkError("network_connection_failed", "Network error occurred. Please check your connection and try again.", { code: error.code, message: error.message, retryable: true }, error.cause);
}
else {
// Generic network error
throw new errors_js_1.NetworkError("unknown", error.cause instanceof Error ? error.cause.message : error.message, { code: error.code, message: error.message, retryable: true }, error.cause);
}
}
// eslint-disable-next-line import/no-named-as-default-member
if (axios_1.default.isAxiosError(error) && error.response) {
if ((0, errors_js_1.isOpenAPIError)(error.response.data)) {
throw new errors_js_1.APIError(error.response.status, error.response.data.errorType, error.response.data.errorMessage, error.response.data.correlationId, error.response.data.errorLink, error.cause);
}
else {
const statusCode = error.response.status;
const responseData = error.response.data;
// Check for gateway-level errors that might indicate network issues
const isGatewayError = responseData &&
typeof responseData === "string" &&
(responseData.toLowerCase().includes("forbidden") ||
responseData.toLowerCase().includes("ip") ||
responseData.toLowerCase().includes("blocked") ||
responseData.toLowerCase().includes("gateway"));
switch (statusCode) {
case 401:
throw new errors_js_1.APIError(statusCode, "unauthorized", "Unauthorized.", undefined, `${constants_js_1.ERROR_DOCS_PAGE_URL}#unauthorized`, error.cause);
case 403:
// Special handling for IP blocklist and other gateway-level 403s
if (isGatewayError) {
throw new errors_js_1.NetworkError("network_ip_blocked", "Access denied. Your IP address may be blocked or restricted.", {
code: "IP_BLOCKED",
message: typeof responseData === "string" ? responseData : JSON.stringify(responseData),
retryable: false,
}, error.cause);
}
// Regular 403 forbidden error
throw new errors_js_1.APIError(statusCode, "unauthorized", "Forbidden. You don't have permission to access this resource.", undefined, `${constants_js_1.ERROR_DOCS_PAGE_URL}#forbidden`, error.cause);
case 404:
throw new errors_js_1.APIError(statusCode, "not_found", "API not found.", undefined, `${constants_js_1.ERROR_DOCS_PAGE_URL}#not_found`, error.cause);
case 502:
throw new errors_js_1.APIError(statusCode, "bad_gateway", "Bad gateway.", undefined, `${constants_js_1.ERROR_DOCS_PAGE_URL}`, error.cause);
case 503:
throw new errors_js_1.APIError(statusCode, "service_unavailable", "Service unavailable. Please try again later.", undefined, `${constants_js_1.ERROR_DOCS_PAGE_URL}`, error.cause);
default: {
let errorText = "";
if (error.response.data) {
try {
errorText = JSON.stringify(error.response.data);
}
catch {
errorText = String(error.response.data);
}
}
const errorMessage = errorText
? `An unexpected error occurred: ${errorText}`
: "An unexpected error occurred.";
throw new errors_js_1.APIError(statusCode, "unexpected_error", errorMessage, undefined, `${constants_js_1.ERROR_DOCS_PAGE_URL}`, error.cause);
}
}
}
}
throw new errors_js_1.UnknownError("Something went wrong. Please reach out at https://discord.com/channels/1220414409550336183/1271495764580896789 for help.", error instanceof Error ? error : undefined);
}
};
exports.cdpApiClient = cdpApiClient;
/**
* Validates the call to the cdpApiClient.
*
* @param {AxiosRequestConfig} config - The Axios request configuration.
* @throws {Error} If the call is not valid.
*/
const validateCall = (config) => {
if (!axiosInstance.getUri() || axiosInstance.getUri() === "") {
throw new Error("CDP client URI not configured. Call configure() first.");
}
if (!config.url || config.url === "") {
throw new Error("AxiosRequestConfig URL is empty. This should never happen.");
}
if (!config.method || config.method === "") {
throw new Error("AxiosRequestConfig method is empty. This should never happen.");
}
};
//# sourceMappingURL=cdpApiClient.js.map