Some checks failed
deploy / deploy (push) Failing after 2s
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.
335 lines
14 KiB
JavaScript
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,
|
|
),
|
|
};
|
|
}
|