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; }