@renegade-fi/core
Version:
VanillaJS library for Renegade
255 lines (234 loc) • 8.29 kB
text/typescript
import axios from "axios";
import invariant from "tiny-invariant";
import {
RENEGADE_AUTH_HEADER_NAME,
RENEGADE_SDK_VERSION_HEADER,
RENEGADE_SIG_EXPIRATION_HEADER_NAME,
SIG_EXPIRATION_BUFFER_MS,
} from "../constants.js";
import type { BaseConfig, Config, RenegadeConfig } from "../createConfig.js";
import { BaseError } from "../errors/base.js";
import { parseBigJSON } from "./bigJSON.js";
import { getVersionNumber } from "./getVersion.js";
import type { AuthType } from "./websocket.js";
export async function postRelayerRaw(url: string, body: any, headers = {}) {
try {
const response = await axios.post(url, body, {
headers,
validateStatus: null, // Allow any status code to pass through
transformResponse: (data) => {
try {
return parseBigJSON(data);
} catch {
// If parsing fails, return raw data
return data;
}
},
});
// For non-2xx responses, throw error with raw data
if (response.status < 200 || response.status >= 300) {
throw new BaseError(response.data);
}
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
throw new BaseError(error.response.data);
}
if (error.request) {
// The request was made but no response was received
throw new BaseError("Request error: No response received");
} else {
// Something happened in setting up the request that triggered an Error
throw new BaseError(error.message);
}
}
// Non-Axios error
throw error; // Rethrow the error for further handling or logging
}
}
export async function getRelayerRaw(url: string, headers = {}) {
try {
const response = await axios.get(url, {
headers,
transformResponse: (data) => {
try {
if (
url.includes("/order-history") ||
url.includes("/open-orders") ||
url.includes("/metadata")
) {
// We use ts-ignore here because TypeScript doesn't recognize the
// `context` argument in the JSON.parse reviver
// @ts-expect-error
return JSON.parse(data, (key, value, context) => {
if (typeof value === "number" && key !== "price") {
if (context?.source === undefined) {
return BigInt(value);
}
return BigInt(context.source);
}
return value;
});
}
if (url.includes("/price")) {
return JSON.parse(data);
}
return parseBigJSON(data);
} catch (_error) {
return data;
}
},
});
// Process the response data as needed
return response.data; // Assuming the function should return the response data
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
throw new BaseError(error.response.data);
} else if (error.request) {
// The request was made but no response was received
throw new BaseError("Request error: No response received");
} else {
// Something happened in setting up the request that triggered an Error
throw new BaseError(error.message);
}
}
// Non-Axios error
throw new BaseError(error instanceof Error ? error.message : String(error));
}
}
export async function postRelayerWithAuth(
config: RenegadeConfig,
url: string,
body?: string,
requestType?: AuthType,
) {
const symmetricKey = config.getSymmetricKey(requestType);
invariant(symmetricKey, "Failed to derive symmetric key");
const path = getPathFromUrl(url);
const headers = {
"Content-Type": "application/json",
};
const headersWithAuth = addExpiringAuthToHeaders(
config,
path,
headers,
body ?? "",
symmetricKey,
SIG_EXPIRATION_BUFFER_MS,
);
return await postRelayerRaw(url, body, headersWithAuth);
}
export async function postRelayerWithAdmin(config: Config, url: string, body?: string) {
const { adminKey } = config;
invariant(adminKey, "Admin key is required");
const symmetricKey = config.utils.b64_to_hex_hmac_key(adminKey);
const path = getPathFromUrl(url);
const headers = {
"Content-Type": "application/json",
};
const headersWithAuth = addExpiringAuthToHeaders(
config,
path,
headers,
body ?? "",
symmetricKey,
SIG_EXPIRATION_BUFFER_MS,
);
return await postRelayerRaw(url, body, headersWithAuth);
}
export async function getRelayerWithAuth(config: RenegadeConfig, url: string) {
const symmetricKey = config.getSymmetricKey();
invariant(symmetricKey, "Failed to derive symmetric key");
const path = getPathFromUrl(url);
const headers = {
"Content-Type": "application/json",
};
const headersWithAuth = addExpiringAuthToHeaders(
config,
path,
headers,
"", // Body
symmetricKey,
SIG_EXPIRATION_BUFFER_MS,
);
return await getRelayerRaw(url, headersWithAuth);
}
export async function getRelayerWithAdmin(config: Config, url: string) {
const { adminKey } = config;
invariant(adminKey, "Admin key is required");
const symmetricKey = config.utils.b64_to_hex_hmac_key(adminKey);
const path = getPathFromUrl(url);
const headers = {
"Content-Type": "application/json",
};
const headersWithAuth = addExpiringAuthToHeaders(
config,
path,
headers,
"", // Body
symmetricKey,
SIG_EXPIRATION_BUFFER_MS,
);
return await getRelayerRaw(url, headersWithAuth);
}
export async function postWithSymmetricKey(
config: BaseConfig,
{
body,
headers = {},
key,
url,
}: {
body?: string;
headers?: Record<string, string>;
key: string;
url: string;
},
) {
const path = getPathFromUrl(url);
const headersWithAuth = addExpiringAuthToHeaders(
config,
path,
headers,
body ?? "",
key,
SIG_EXPIRATION_BUFFER_MS,
);
return await postRelayerRaw(url, body, headersWithAuth);
}
/// Get the path from a URL
function getPathFromUrl(url: string): string {
try {
const parsedUrl = new URL(url);
return parsedUrl.pathname + parsedUrl.search || "/";
} catch {
return url.startsWith("/") ? url : `/${url}`;
}
}
/// Add an auth expiration and signature to a set of headers
export function addExpiringAuthToHeaders(
config: BaseConfig,
path: string,
headers: Record<string, string>,
body: string,
key: string,
expiration: number,
): Record<string, string> {
// Add a timestamp
const expirationTs = Date.now() + expiration;
const versionString = `typescript-v${getVersionNumber()}`;
const headersWithExpiration = {
...headers,
[RENEGADE_SIG_EXPIRATION_HEADER_NAME]: expirationTs.toString(),
[RENEGADE_SDK_VERSION_HEADER]: versionString,
};
// Add the signature
const auth = config.utils.create_request_signature(path, headersWithExpiration, body, key);
return { ...headersWithExpiration, [RENEGADE_AUTH_HEADER_NAME]: auth };
}