unrip/src/lib/config.mjs
philipp 860471f267
Some checks failed
deploy / deploy (push) Failing after 2s
Add pre-credit funding visibility and durable alerts
Proof: Implement the active turn for pre-credit funding visibility and durable operator alerts while keeping spendable inventory truth limited to bridge/verifier credit.

Assumptions: The BTC deposit handle can be observed through a mempool.space-compatible API, bridge recent_deposits remains the credit truth for correlation, and pausing market-reference-ingest or inventory-sync briefly for alert validation is safe without disarming strategy or executor.

Still fake: Gnosis pre-credit observation is not implemented, executor failure alert validation may still depend on an existing real failure unless a separate live failure is explicitly approved, and a new live deposit is still required to prove a fresh pre-credit-to-credit path if no suitable recent funding exists.
2026-04-03 17:50:39 +02:00

335 lines
14 KiB
JavaScript

import { loadDotenv } from './env.mjs';
import { DEFAULT_NEAR_INTENTS_PAIR_FILTER } from '../core/pair-filter.mjs';
const DEFAULTS = {
nearIntentsWsUrl: 'wss://solver-relay-v2.chaindefuser.com/ws',
nearIntentsRpcUrl: 'https://solver-relay-v2.chaindefuser.com/rpc',
nearBridgeRpcUrl: 'https://bridge.chaindefuser.com/rpc',
nearRpcUrl: 'https://rpc.fastnear.com',
nearVerifierContract: 'intents.near',
nearIntentsPairFilter: DEFAULT_NEAR_INTENTS_PAIR_FILTER,
nearIntentsPairFilterReloadMs: 5_000,
nearIntentsControlApiEnabled: true,
nearIntentsControlHost: '0.0.0.0',
nearIntentsControlPort: 8081,
marketReferenceControlPort: 8082,
inventorySyncControlPort: 8083,
liquidityManagerControlPort: 8084,
historyWriterControlPort: 8085,
strategyEngineControlPort: 8086,
tradeExecutorControlPort: 8087,
opsSentinelControlPort: 8088,
kafkaBrokers: ['127.0.0.1:9092'],
kafkaClientId: 'unrip',
kafkaTopicRawNearIntentsQuote: 'raw.near_intents.quote',
kafkaTopicNormSwapDemand: 'norm.swap_demand',
kafkaTopicRefMarketPrice: 'ref.market_price',
kafkaTopicStateIntentInventory: 'state.intent_inventory',
kafkaTopicOpsLiquidityAction: 'ops.liquidity_action',
kafkaTopicOpsFundingObservation: 'ops.funding_observation',
kafkaTopicOpsAlert: 'ops.alert',
kafkaTopicDecisionTradeDecision: 'decision.trade_decision',
kafkaTopicCmdExecuteTrade: 'cmd.execute_trade',
kafkaTopicExecTradeResult: 'exec.trade_result',
kafkaConsumerGroupHistory: 'history-writer-v1',
kafkaConsumerGroupInventory: 'inventory-sync-v1',
kafkaConsumerGroupStrategy: 'strategy-engine-v1',
kafkaConsumerGroupExecutor: 'trade-executor-v1',
kafkaConsumerGroupOpsSentinel: 'ops-sentinel-v1',
executorStateDir: './var/executor-state',
liquidityStateDir: './var/liquidity-state',
postgresUrl: 'postgresql://unrip:unrip@127.0.0.1:5432/unrip',
projectName: 'unrip',
projectNamespace: 'unrip',
tradingBtcAssetId: 'nep141:btc.omft.near',
tradingBtcSymbol: 'BTC',
tradingBtcDecimals: 8,
tradingBtcChain: 'btc:mainnet',
tradingBtcWithdrawAddress: '',
tradingEureAssetId: 'nep141:gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near',
tradingEureSymbol: 'EURe',
tradingEureDecimals: 18,
tradingEureChain: 'eth:100',
tradingEureWithdrawAddress: '',
marketReferenceRefreshMs: 5_000,
marketReferenceCoinGeckoRefreshMs: 15_000,
marketReferenceMaxAgeMs: 30_000,
marketReferenceKrakenTickerUrl: 'https://api.kraken.com/0/public/Ticker?pair=XBTEUR',
marketReferenceCoinGeckoUrl:
'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=eur',
inventorySyncRefreshMs: 15_000,
liquidityRefreshMs: 30_000,
strategyGrossThresholdPct: 2,
strategyInitialArmed: false,
strategyMaxNotionalEure: 5,
strategyPriceMaxAgeMs: 30_000,
strategyInventoryMaxAgeMs: 30_000,
executorInitialArmed: false,
executorResponseTimeoutMs: 10_000,
withdrawalsFrozen: true,
btcFundingObserverEnabled: true,
btcFundingObserverBaseUrl: 'https://mempool.space/api',
fundingObservationStuckMs: 60 * 60 * 1000,
opsSentinelEvaluationMs: 5_000,
opsSentinelFundingCreditPendingMs: 5 * 60 * 1000,
};
function splitCsv(value) {
return String(value || '')
.split(',')
.map((part) => part.trim())
.filter(Boolean);
}
function parseNumber(value, fallback) {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : fallback;
}
function parseBoolean(value, fallback) {
if (value == null || value === '') return fallback;
const normalized = String(value).trim().toLowerCase();
if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
return fallback;
}
function buildAsset({ assetId, symbol, decimals, chain, withdrawAddress = '' }) {
return {
assetId,
symbol,
decimals,
chain,
withdrawAddress,
};
}
export function loadConfig({ envPath = '.env' } = {}) {
loadDotenv(envPath);
const tradingBtc = buildAsset({
assetId: process.env.TRADING_BTC_ASSET_ID || DEFAULTS.tradingBtcAssetId,
symbol: process.env.TRADING_BTC_SYMBOL || DEFAULTS.tradingBtcSymbol,
decimals: parseNumber(process.env.TRADING_BTC_DECIMALS, DEFAULTS.tradingBtcDecimals),
chain: process.env.TRADING_BTC_CHAIN || DEFAULTS.tradingBtcChain,
withdrawAddress:
process.env.TRADING_BTC_WITHDRAW_ADDRESS || DEFAULTS.tradingBtcWithdrawAddress,
});
const tradingEure = buildAsset({
assetId: process.env.TRADING_EURE_ASSET_ID || DEFAULTS.tradingEureAssetId,
symbol: process.env.TRADING_EURE_SYMBOL || DEFAULTS.tradingEureSymbol,
decimals: parseNumber(process.env.TRADING_EURE_DECIMALS, DEFAULTS.tradingEureDecimals),
chain: process.env.TRADING_EURE_CHAIN || DEFAULTS.tradingEureChain,
withdrawAddress:
process.env.TRADING_EURE_WITHDRAW_ADDRESS || DEFAULTS.tradingEureWithdrawAddress,
});
const projectName = process.env.PROJECT_NAME || DEFAULTS.projectName;
const projectNamespace =
process.env.PROJECT_NAMESPACE || projectName || DEFAULTS.projectNamespace;
return {
nearIntentsApiKey: process.env.NEAR_INTENTS_API_KEY || '',
nearIntentsAccountId: process.env.NEAR_INTENTS_ACCOUNT_ID || '',
nearIntentsSignerPrivateKey: process.env.NEAR_INTENTS_SIGNER_PRIVATE_KEY || '',
nearIntentsWsUrl: process.env.NEAR_INTENTS_WS_URL || DEFAULTS.nearIntentsWsUrl,
nearIntentsRpcUrl: process.env.NEAR_INTENTS_RPC_URL || DEFAULTS.nearIntentsRpcUrl,
nearBridgeRpcUrl: process.env.NEAR_INTENTS_BRIDGE_RPC_URL || DEFAULTS.nearBridgeRpcUrl,
nearRpcUrl: process.env.NEAR_RPC_URL || DEFAULTS.nearRpcUrl,
nearVerifierContract:
process.env.NEAR_INTENTS_VERIFIER_CONTRACT || DEFAULTS.nearVerifierContract,
nearIntentsPairFilter:
process.env.NEAR_INTENTS_PAIR_FILTER || DEFAULTS.nearIntentsPairFilter,
nearIntentsPairFilterFile: process.env.NEAR_INTENTS_PAIR_FILTER_FILE || '',
nearIntentsPairFilterReloadMs: parseNumber(
process.env.NEAR_INTENTS_PAIR_FILTER_RELOAD_MS,
DEFAULTS.nearIntentsPairFilterReloadMs,
),
nearIntentsControlApiEnabled: parseBoolean(
process.env.NEAR_INTENTS_CONTROL_API_ENABLED,
DEFAULTS.nearIntentsControlApiEnabled,
),
nearIntentsControlHost:
process.env.NEAR_INTENTS_CONTROL_HOST || DEFAULTS.nearIntentsControlHost,
nearIntentsControlPort: parseNumber(
process.env.NEAR_INTENTS_CONTROL_PORT,
DEFAULTS.nearIntentsControlPort,
),
marketReferenceControlHost:
process.env.MARKET_REFERENCE_CONTROL_HOST || DEFAULTS.nearIntentsControlHost,
marketReferenceControlPort: parseNumber(
process.env.MARKET_REFERENCE_CONTROL_PORT,
DEFAULTS.marketReferenceControlPort,
),
inventorySyncControlHost:
process.env.INVENTORY_SYNC_CONTROL_HOST || DEFAULTS.nearIntentsControlHost,
inventorySyncControlPort: parseNumber(
process.env.INVENTORY_SYNC_CONTROL_PORT,
DEFAULTS.inventorySyncControlPort,
),
liquidityManagerControlHost:
process.env.LIQUIDITY_MANAGER_CONTROL_HOST || DEFAULTS.nearIntentsControlHost,
liquidityManagerControlPort: parseNumber(
process.env.LIQUIDITY_MANAGER_CONTROL_PORT,
DEFAULTS.liquidityManagerControlPort,
),
historyWriterControlHost:
process.env.HISTORY_WRITER_CONTROL_HOST || DEFAULTS.nearIntentsControlHost,
historyWriterControlPort: parseNumber(
process.env.HISTORY_WRITER_CONTROL_PORT,
DEFAULTS.historyWriterControlPort,
),
opsSentinelControlHost:
process.env.OPS_SENTINEL_CONTROL_HOST || DEFAULTS.nearIntentsControlHost,
opsSentinelControlPort: parseNumber(
process.env.OPS_SENTINEL_CONTROL_PORT,
DEFAULTS.opsSentinelControlPort,
),
strategyEngineControlHost:
process.env.STRATEGY_ENGINE_CONTROL_HOST || DEFAULTS.nearIntentsControlHost,
strategyEngineControlPort: parseNumber(
process.env.STRATEGY_ENGINE_CONTROL_PORT,
DEFAULTS.strategyEngineControlPort,
),
tradeExecutorControlHost:
process.env.TRADE_EXECUTOR_CONTROL_HOST || DEFAULTS.nearIntentsControlHost,
tradeExecutorControlPort: parseNumber(
process.env.TRADE_EXECUTOR_CONTROL_PORT,
DEFAULTS.tradeExecutorControlPort,
),
kafkaBrokers: splitCsv(process.env.KAFKA_BROKERS).length
? splitCsv(process.env.KAFKA_BROKERS)
: DEFAULTS.kafkaBrokers,
kafkaClientId: process.env.KAFKA_CLIENT_ID || DEFAULTS.kafkaClientId,
kafkaTopicRawNearIntentsQuote:
process.env.KAFKA_TOPIC_RAW_NEAR_INTENTS_QUOTE || DEFAULTS.kafkaTopicRawNearIntentsQuote,
kafkaTopicNormSwapDemand:
process.env.KAFKA_TOPIC_NORM_SWAP_DEMAND || DEFAULTS.kafkaTopicNormSwapDemand,
kafkaTopicRefMarketPrice:
process.env.KAFKA_TOPIC_REF_MARKET_PRICE || DEFAULTS.kafkaTopicRefMarketPrice,
kafkaTopicStateIntentInventory:
process.env.KAFKA_TOPIC_STATE_INTENT_INVENTORY || DEFAULTS.kafkaTopicStateIntentInventory,
kafkaTopicOpsLiquidityAction:
process.env.KAFKA_TOPIC_OPS_LIQUIDITY_ACTION || DEFAULTS.kafkaTopicOpsLiquidityAction,
kafkaTopicOpsFundingObservation:
process.env.KAFKA_TOPIC_OPS_FUNDING_OBSERVATION || DEFAULTS.kafkaTopicOpsFundingObservation,
kafkaTopicOpsAlert:
process.env.KAFKA_TOPIC_OPS_ALERT || DEFAULTS.kafkaTopicOpsAlert,
kafkaTopicDecisionTradeDecision:
process.env.KAFKA_TOPIC_DECISION_TRADE_DECISION || DEFAULTS.kafkaTopicDecisionTradeDecision,
kafkaTopicCmdExecuteTrade:
process.env.KAFKA_TOPIC_CMD_EXECUTE_TRADE || DEFAULTS.kafkaTopicCmdExecuteTrade,
kafkaTopicExecTradeResult:
process.env.KAFKA_TOPIC_EXEC_TRADE_RESULT || DEFAULTS.kafkaTopicExecTradeResult,
kafkaConsumerGroupHistory:
process.env.KAFKA_CONSUMER_GROUP_HISTORY || DEFAULTS.kafkaConsumerGroupHistory,
kafkaConsumerGroupInventory:
process.env.KAFKA_CONSUMER_GROUP_INVENTORY || DEFAULTS.kafkaConsumerGroupInventory,
kafkaConsumerGroupStrategy:
process.env.KAFKA_CONSUMER_GROUP_STRATEGY || DEFAULTS.kafkaConsumerGroupStrategy,
kafkaConsumerGroupExecutor:
process.env.KAFKA_CONSUMER_GROUP_EXECUTOR || DEFAULTS.kafkaConsumerGroupExecutor,
kafkaConsumerGroupOpsSentinel:
process.env.KAFKA_CONSUMER_GROUP_OPS_SENTINEL || DEFAULTS.kafkaConsumerGroupOpsSentinel,
executorStateDir: process.env.EXECUTOR_STATE_DIR || DEFAULTS.executorStateDir,
liquidityStateDir: process.env.LIQUIDITY_STATE_DIR || DEFAULTS.liquidityStateDir,
postgresUrl: process.env.POSTGRES_URL || DEFAULTS.postgresUrl,
projectName,
projectNamespace,
tradingBtc,
tradingEure,
activePair: `${tradingBtc.assetId}->${tradingEure.assetId}`,
activeAssetIds: [tradingBtc.assetId, tradingEure.assetId],
assetRegistry: new Map([
[tradingBtc.assetId, tradingBtc],
[tradingEure.assetId, tradingEure],
]),
marketReferenceRefreshMs: parseNumber(
process.env.MARKET_REFERENCE_REFRESH_MS,
DEFAULTS.marketReferenceRefreshMs,
),
marketReferenceCoinGeckoRefreshMs: parseNumber(
process.env.MARKET_REFERENCE_COINGECKO_REFRESH_MS,
DEFAULTS.marketReferenceCoinGeckoRefreshMs,
),
marketReferenceMaxAgeMs: parseNumber(
process.env.MARKET_REFERENCE_MAX_AGE_MS,
DEFAULTS.marketReferenceMaxAgeMs,
),
marketReferenceKrakenTickerUrl:
process.env.MARKET_REFERENCE_KRAKEN_TICKER_URL || DEFAULTS.marketReferenceKrakenTickerUrl,
marketReferenceCoinGeckoUrl:
process.env.MARKET_REFERENCE_COINGECKO_URL || DEFAULTS.marketReferenceCoinGeckoUrl,
inventorySyncRefreshMs: parseNumber(
process.env.INVENTORY_SYNC_REFRESH_MS,
DEFAULTS.inventorySyncRefreshMs,
),
liquidityRefreshMs: parseNumber(
process.env.LIQUIDITY_REFRESH_MS,
DEFAULTS.liquidityRefreshMs,
),
strategyGrossThresholdPct: parseNumber(
process.env.STRATEGY_GROSS_THRESHOLD_PCT,
DEFAULTS.strategyGrossThresholdPct,
),
strategyInitialArmed: parseBoolean(
process.env.STRATEGY_INITIAL_ARMED,
DEFAULTS.strategyInitialArmed,
),
strategyMaxNotionalEure: parseNumber(
process.env.STRATEGY_MAX_NOTIONAL_EURE,
DEFAULTS.strategyMaxNotionalEure,
),
strategyPriceMaxAgeMs: parseNumber(
process.env.STRATEGY_PRICE_MAX_AGE_MS,
DEFAULTS.strategyPriceMaxAgeMs,
),
strategyInventoryMaxAgeMs: parseNumber(
process.env.STRATEGY_INVENTORY_MAX_AGE_MS,
DEFAULTS.strategyInventoryMaxAgeMs,
),
executorInitialArmed: parseBoolean(
process.env.EXECUTOR_INITIAL_ARMED,
DEFAULTS.executorInitialArmed,
),
executorResponseTimeoutMs: parseNumber(
process.env.EXECUTOR_RESPONSE_TIMEOUT_MS,
DEFAULTS.executorResponseTimeoutMs,
),
withdrawalsFrozen: parseBoolean(
process.env.LIQUIDITY_WITHDRAWALS_FROZEN,
DEFAULTS.withdrawalsFrozen,
),
btcFundingObserverEnabled: parseBoolean(
process.env.BTC_FUNDING_OBSERVER_ENABLED,
DEFAULTS.btcFundingObserverEnabled,
),
btcFundingObserverBaseUrl:
process.env.BTC_FUNDING_OBSERVER_BASE_URL || DEFAULTS.btcFundingObserverBaseUrl,
fundingObservationStuckMs: parseNumber(
process.env.FUNDING_OBSERVATION_STUCK_MS,
DEFAULTS.fundingObservationStuckMs,
),
opsSentinelEvaluationMs: parseNumber(
process.env.OPS_SENTINEL_EVALUATION_MS,
DEFAULTS.opsSentinelEvaluationMs,
),
opsSentinelPriceStaleMs: parseNumber(
process.env.OPS_SENTINEL_PRICE_STALE_MS,
DEFAULTS.marketReferenceMaxAgeMs,
),
opsSentinelInventoryStaleMs: parseNumber(
process.env.OPS_SENTINEL_INVENTORY_STALE_MS,
DEFAULTS.strategyInventoryMaxAgeMs,
),
opsSentinelFundingCreditPendingMs: parseNumber(
process.env.OPS_SENTINEL_FUNDING_CREDIT_PENDING_MS,
DEFAULTS.opsSentinelFundingCreditPendingMs,
),
opsSentinelFundingStuckMs: parseNumber(
process.env.OPS_SENTINEL_FUNDING_STUCK_MS,
DEFAULTS.fundingObservationStuckMs,
),
};
}