mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-09 08:18:07 +00:00
249 lines
9.5 KiB
TypeScript
249 lines
9.5 KiB
TypeScript
import type { ExchangeAdapter } from "./adapter";
|
|
import { createExchangeAdapter, resolveExchangeId, type SupportedExchangeId } from "./create-adapter";
|
|
import type { AsterCredentials } from "./aster/adapter";
|
|
import type { LighterCredentials } from "./lighter/adapter";
|
|
import type { BackpackCredentials } from "./backpack/adapter";
|
|
import type { ParadexCredentials } from "./paradex/adapter";
|
|
import type { NadoCredentials } from "./nado/adapter";
|
|
import type { StandxCredentials } from "./standx/adapter";
|
|
import type { BinanceCredentials } from "./binance/adapter";
|
|
import type { OndoperpsCredentials } from "./ondoperps/adapter";
|
|
import { t } from "../i18n";
|
|
import type { Address } from "viem";
|
|
|
|
interface BuildAdapterOptions {
|
|
symbol: string;
|
|
exchangeId?: string | SupportedExchangeId;
|
|
}
|
|
|
|
export function buildAdapterFromEnv(options: BuildAdapterOptions): ExchangeAdapter {
|
|
const id = resolveExchangeId(options.exchangeId);
|
|
const symbol = options.symbol;
|
|
|
|
switch (id) {
|
|
case "aster": {
|
|
const credentials = resolveAsterCredentials();
|
|
return createExchangeAdapter({ exchange: id, symbol, aster: credentials });
|
|
}
|
|
case "grvt":
|
|
return createExchangeAdapter({ exchange: id, symbol, grvt: { symbol } });
|
|
case "lighter": {
|
|
const credentials = resolveLighterCredentials(symbol);
|
|
return createExchangeAdapter({ exchange: id, symbol, lighter: credentials });
|
|
}
|
|
case "backpack": {
|
|
const credentials = resolveBackpackCredentials(symbol);
|
|
return createExchangeAdapter({ exchange: id, symbol, backpack: credentials });
|
|
}
|
|
case "paradex": {
|
|
const credentials = resolveParadexCredentials();
|
|
return createExchangeAdapter({ exchange: id, symbol, paradex: credentials });
|
|
}
|
|
case "nado": {
|
|
const credentials = resolveNadoCredentials(symbol);
|
|
return createExchangeAdapter({ exchange: id, symbol, nado: credentials });
|
|
}
|
|
case "standx": {
|
|
const credentials = resolveStandxCredentials(symbol);
|
|
return createExchangeAdapter({ exchange: id, symbol, standx: credentials });
|
|
}
|
|
case "binance": {
|
|
const credentials = resolveBinanceCredentials(symbol);
|
|
return createExchangeAdapter({ exchange: id, symbol, binance: credentials });
|
|
}
|
|
case "ondoperps": {
|
|
const credentials = resolveOndoperpsCredentials(symbol);
|
|
return createExchangeAdapter({ exchange: id, symbol, ondoperps: credentials });
|
|
}
|
|
}
|
|
}
|
|
|
|
function resolveAsterCredentials(): AsterCredentials {
|
|
const apiKey = process.env.ASTER_API_KEY;
|
|
const apiSecret = process.env.ASTER_API_SECRET;
|
|
if (!apiKey || !apiSecret) {
|
|
throw new Error(t("env.missingAster"));
|
|
}
|
|
return { apiKey, apiSecret };
|
|
}
|
|
|
|
function resolveLighterCredentials(symbol: string): LighterCredentials {
|
|
const accountIndexRaw = process.env.LIGHTER_ACCOUNT_INDEX;
|
|
const apiPrivateKey = process.env.LIGHTER_API_PRIVATE_KEY;
|
|
if (!accountIndexRaw || !apiPrivateKey) {
|
|
throw new Error(t("env.missingLighter"));
|
|
}
|
|
const accountIndex = Number(accountIndexRaw);
|
|
if (!Number.isInteger(accountIndex)) {
|
|
throw new Error(t("env.lighterIndexInteger"));
|
|
}
|
|
const credentials: LighterCredentials = {
|
|
displaySymbol: symbol,
|
|
accountIndex,
|
|
apiPrivateKey,
|
|
apiKeyIndex: process.env.LIGHTER_API_KEY_INDEX ? Number(process.env.LIGHTER_API_KEY_INDEX) : 0,
|
|
environment: process.env.LIGHTER_ENV,
|
|
baseUrl: process.env.LIGHTER_BASE_URL,
|
|
l1Address: process.env.LIGHTER_L1_ADDRESS,
|
|
marketSymbol: process.env.LIGHTER_SYMBOL,
|
|
marketId: process.env.LIGHTER_MARKET_ID ? Number(process.env.LIGHTER_MARKET_ID) : undefined,
|
|
priceDecimals: process.env.LIGHTER_PRICE_DECIMALS ? Number(process.env.LIGHTER_PRICE_DECIMALS) : undefined,
|
|
sizeDecimals: process.env.LIGHTER_SIZE_DECIMALS ? Number(process.env.LIGHTER_SIZE_DECIMALS) : undefined,
|
|
};
|
|
return credentials;
|
|
}
|
|
|
|
function resolveBackpackCredentials(symbol: string): BackpackCredentials {
|
|
const apiKey = process.env.BACKPACK_API_KEY;
|
|
const apiSecret = process.env.BACKPACK_API_SECRET;
|
|
if (!apiKey || !apiSecret) {
|
|
throw new Error(t("env.missingBackpack"));
|
|
}
|
|
const credentials: BackpackCredentials = {
|
|
apiKey,
|
|
apiSecret,
|
|
password: process.env.BACKPACK_PASSWORD,
|
|
subaccount: process.env.BACKPACK_SUBACCOUNT,
|
|
symbol: process.env.BACKPACK_SYMBOL ?? symbol,
|
|
sandbox: parseOptionalBoolean(process.env.BACKPACK_SANDBOX),
|
|
};
|
|
return credentials;
|
|
}
|
|
|
|
function resolveParadexCredentials(): ParadexCredentials {
|
|
const privateKey = process.env.PARADEX_PRIVATE_KEY;
|
|
const walletAddress = process.env.PARADEX_WALLET_ADDRESS;
|
|
|
|
if (!privateKey || !walletAddress) {
|
|
throw new Error(t("env.missingParadex"));
|
|
}
|
|
if (!isHex32(privateKey)) {
|
|
throw new Error(t("env.invalidParadexPrivateKey"));
|
|
}
|
|
if (!isHexAddress(walletAddress)) {
|
|
throw new Error(t("env.invalidParadexAddress"));
|
|
}
|
|
|
|
const credentials: ParadexCredentials = {
|
|
privateKey,
|
|
walletAddress,
|
|
sandbox: parseOptionalBoolean(process.env.PARADEX_SANDBOX),
|
|
usePro: parseOptionalBoolean(process.env.PARADEX_USE_PRO),
|
|
watchReconnectDelayMs: parseOptionalNumber(process.env.PARADEX_RECONNECT_DELAY_MS),
|
|
};
|
|
|
|
return credentials;
|
|
}
|
|
|
|
function resolveNadoCredentials(symbol: string): NadoCredentials {
|
|
const signerPrivateKey = process.env.NADO_SIGNER_PRIVATE_KEY;
|
|
const subaccountOwner = process.env.NADO_SUBACCOUNT_OWNER ?? process.env.NADO_EVM_ADDRESS;
|
|
|
|
if (!signerPrivateKey || !subaccountOwner) {
|
|
throw new Error(t("env.missingNado"));
|
|
}
|
|
if (!isHex32(signerPrivateKey)) {
|
|
throw new Error(t("env.invalidNadoPrivateKey"));
|
|
}
|
|
if (!isHexAddress(subaccountOwner)) {
|
|
throw new Error(t("env.invalidNadoAddress"));
|
|
}
|
|
|
|
const credentials: NadoCredentials = {
|
|
symbol: process.env.NADO_SYMBOL ?? symbol,
|
|
signerPrivateKey,
|
|
subaccountOwner: subaccountOwner as Address,
|
|
subaccountName: process.env.NADO_SUBACCOUNT_NAME ?? undefined,
|
|
env: process.env.NADO_ENV as any,
|
|
gatewayWsUrl: process.env.NADO_GATEWAY_WS_URL ?? undefined,
|
|
subscriptionsWsUrl: process.env.NADO_SUBSCRIPTIONS_WS_URL ?? undefined,
|
|
archiveUrl: process.env.NADO_ARCHIVE_URL ?? undefined,
|
|
triggerUrl: process.env.NADO_TRIGGER_URL ?? undefined,
|
|
marketSlippagePct: parseOptionalNumber(process.env.NADO_MARKET_SLIPPAGE_PCT),
|
|
stopTriggerSource: process.env.NADO_STOP_TRIGGER_SOURCE as any,
|
|
};
|
|
|
|
return credentials;
|
|
}
|
|
|
|
function resolveStandxCredentials(symbol: string): StandxCredentials {
|
|
const token = process.env.STANDX_TOKEN;
|
|
if (!token) {
|
|
throw new Error(t("env.missingStandx"));
|
|
}
|
|
return {
|
|
token,
|
|
symbol: process.env.STANDX_SYMBOL ?? symbol,
|
|
baseUrl: process.env.STANDX_BASE_URL ?? undefined,
|
|
wsUrl: process.env.STANDX_WS_URL ?? undefined,
|
|
sessionId: process.env.STANDX_SESSION_ID ?? undefined,
|
|
signingKey: process.env.STANDX_REQUEST_PRIVATE_KEY ?? undefined,
|
|
};
|
|
}
|
|
|
|
function resolveBinanceCredentials(symbol: string): BinanceCredentials {
|
|
const apiKey = process.env.BINANCE_API_KEY;
|
|
const apiSecret = process.env.BINANCE_API_SECRET;
|
|
if (!apiKey || !apiSecret) {
|
|
throw new Error("Missing BINANCE_API_KEY or BINANCE_API_SECRET environment variable");
|
|
}
|
|
|
|
const marketTypeRaw = process.env.BINANCE_MARKET_TYPE?.trim().toLowerCase();
|
|
const marketType: BinanceCredentials["marketType"] =
|
|
marketTypeRaw === "spot" ? "spot" : marketTypeRaw === "auto" ? "auto" : "perp";
|
|
|
|
return {
|
|
apiKey,
|
|
apiSecret,
|
|
symbol: process.env.BINANCE_SYMBOL ?? symbol,
|
|
marketType,
|
|
sandbox: parseOptionalBoolean(process.env.BINANCE_SANDBOX),
|
|
spotRestUrl: process.env.BINANCE_SPOT_REST_URL ?? undefined,
|
|
futuresRestUrl: process.env.BINANCE_FUTURES_REST_URL ?? undefined,
|
|
spotWsUrl: process.env.BINANCE_SPOT_WS_URL ?? undefined,
|
|
futuresWsUrl: process.env.BINANCE_FUTURES_WS_URL ?? undefined,
|
|
};
|
|
}
|
|
|
|
function resolveOndoperpsCredentials(symbol: string): OndoperpsCredentials {
|
|
const apiKeyId = process.env.ONDOPERPS_API_KEY_ID ?? process.env.ONDOPERP_API_KEY_ID ?? process.env.ONDO_KEY_ID;
|
|
const apiSecret = process.env.ONDOPERPS_API_SECRET ?? process.env.ONDOPERP_API_SECRET ?? process.env.ONDO_API_SECRET;
|
|
if (!apiKeyId || !apiSecret) {
|
|
throw new Error(t("env.missingOndoperps"));
|
|
}
|
|
const sandbox = parseOptionalBoolean(process.env.ONDOPERPS_SANDBOX ?? process.env.ONDOPERP_SANDBOX) === true;
|
|
return {
|
|
apiKeyId,
|
|
apiSecret,
|
|
symbol: process.env.ONDOPERPS_SYMBOL ?? process.env.ONDOPERP_SYMBOL ?? symbol,
|
|
baseUrl: process.env.ONDOPERPS_BASE_URL ?? process.env.ONDOPERP_BASE_URL ?? (sandbox ? "https://api.ondoperps-sandbox.xyz" : undefined),
|
|
wsUrl: process.env.ONDOPERPS_WS_URL ?? process.env.ONDOPERP_WS_URL ?? (sandbox ? "wss://api.ondoperps-sandbox.xyz/ws" : undefined),
|
|
builderCode: process.env.ONDOPERPS_BUILDER_CODE ?? process.env.ONDOPERP_BUILDER_CODE ?? undefined,
|
|
builderFeeRateBps: parseOptionalNumber(
|
|
process.env.ONDOPERPS_BUILDER_FEE_RATE_BPS ?? process.env.ONDOPERP_BUILDER_FEE_RATE_BPS,
|
|
),
|
|
};
|
|
}
|
|
|
|
function isHex32(value: string): boolean {
|
|
return /^0x[0-9a-fA-F]{64}$/.test(value.trim());
|
|
}
|
|
|
|
function isHexAddress(value: string): boolean {
|
|
return /^0x[0-9a-fA-F]{40}$/.test(value.trim());
|
|
}
|
|
|
|
function parseOptionalBoolean(value: string | undefined): boolean | undefined {
|
|
if (value == null) return undefined;
|
|
const normalized = value.trim().toLowerCase();
|
|
if (!normalized) return undefined;
|
|
if (["false", "0", "no", "off"].includes(normalized)) return false;
|
|
return true;
|
|
}
|
|
|
|
function parseOptionalNumber(value: string | undefined): number | undefined {
|
|
if (!value) return undefined;
|
|
const parsed = Number(value);
|
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
}
|