mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-11 01:08:07 +00:00
198 lines
6.9 KiB
TypeScript
198 lines
6.9 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 { 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;
|
|
|
|
if (id === "aster") {
|
|
const credentials = resolveAsterCredentials();
|
|
return createExchangeAdapter({ exchange: id, symbol, aster: credentials });
|
|
}
|
|
|
|
if (id === "lighter") {
|
|
const credentials = resolveLighterCredentials(symbol);
|
|
return createExchangeAdapter({ exchange: id, symbol, lighter: credentials });
|
|
}
|
|
|
|
if (id === "backpack") {
|
|
const credentials = resolveBackpackCredentials(symbol);
|
|
return createExchangeAdapter({ exchange: id, symbol, backpack: credentials });
|
|
}
|
|
|
|
if (id === "paradex") {
|
|
const credentials = resolveParadexCredentials();
|
|
return createExchangeAdapter({ exchange: id, symbol, paradex: credentials });
|
|
}
|
|
|
|
if (id === "nado") {
|
|
const credentials = resolveNadoCredentials(symbol);
|
|
return createExchangeAdapter({ exchange: id, symbol, nado: credentials });
|
|
}
|
|
|
|
if (id === "standx") {
|
|
const credentials = resolveStandxCredentials(symbol);
|
|
return createExchangeAdapter({ exchange: id, symbol, standx: credentials });
|
|
}
|
|
|
|
return createExchangeAdapter({ exchange: id, symbol, grvt: { symbol } });
|
|
}
|
|
|
|
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 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;
|
|
}
|