import crypto from 'node:crypto'; import { pairKey } from './assets.mjs'; export const NEAR_INTENTS_VENUE = 'near-intents'; export const ONE_CLICK_TOKENS_URL = 'https://1click.chaindefuser.com/v0/tokens'; export const CURRENT_NBTC_ASSET_ID = 'nep141:nbtc.bridge.near'; export const LEGACY_OMFT_BTC_ASSET_ID = 'nep141:btc.omft.near'; export const CURRENT_EURE_ASSET_ID = 'nep141:gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near'; export const CURRENT_USDC_ASSET_ID = 'nep141:gnosis-0x2a22f9c3b484c3629090feed35f17ff8f88f76f0.omft.near'; export const CURRENT_PAIR_KEY = pairKey(CURRENT_NBTC_ASSET_ID, CURRENT_EURE_ASSET_ID); export const CURRENT_REVERSE_PAIR_KEY = pairKey(CURRENT_EURE_ASSET_ID, CURRENT_NBTC_ASSET_ID); export const CURRENT_EDGE_BPS = 49; export const CURRENT_STRATEGY_MAX_NOTIONAL = '150'; export const CURRENT_REQUEST_DEFAULT_NOTIONAL_EURE = '5'; export const CURRENT_REQUEST_MAX_NOTIONAL_EURE = '5'; export const CURRENT_SLIPPAGE_BPS = 200; export const CURRENT_MIN_DEADLINE_MS = 60_000; export const CURRENT_PRICE_MAX_AGE_MS = 30_000; export const CURRENT_INVENTORY_MAX_AGE_MS = 30_000; export const PAIR_MODES = new Set(['observe_only', 'maker', 'taker', 'both']); export const PAIR_STATUSES = new Set(['disabled', 'observe_only', 'maker', 'taker', 'both']); export function normalizeOneClickToken(token) { if (!isRecord(token)) throw new Error('token record must be an object'); const assetId = stringField(token.assetId ?? token.asset_id ?? token.defuseAssetId, 'assetId'); const symbol = stringField(token.symbol, 'symbol'); const decimals = integerField(token.decimals, 'decimals'); const blockchain = stringField(token.blockchain ?? token.chain, 'blockchain'); const chain = normalizeBridgeChain({ assetId, blockchain }); const contractAddress = optionalString(token.contractAddress ?? token.contract_address); const latestPrice = token.price == null ? null : String(token.price); const priceUpdatedAt = optionalTimestamp(token.priceUpdatedAt ?? token.price_updated_at); return { assetId, venue: NEAR_INTENTS_VENUE, symbol, label: symbol, decimals, blockchain, chain, contractAddress, latestPrice, priceUpdatedAt, supported: true, rawPayload: token, }; } export function normalizeOneClickTokenResponse(response) { const tokens = Array.isArray(response) ? response : Array.isArray(response?.tokens) ? response.tokens : Array.isArray(response?.result) ? response.result : null; if (!tokens) throw new Error('supported token response must be an array'); return tokens.map((token) => normalizeOneClickToken(token)); } export function hashJson(value) { return crypto.createHash('sha256') .update(JSON.stringify(value)) .digest('hex'); } export function buildSeedAssets() { return [ { assetId: CURRENT_NBTC_ASSET_ID, venue: NEAR_INTENTS_VENUE, symbol: 'BTC', label: 'BTC / nBTC reserve', decimals: 8, blockchain: 'near', chain: 'btc:mainnet', contractAddress: 'nbtc.bridge.near', latestPrice: null, priceUpdatedAt: null, supported: true, enabledForInventory: true, role: 'trading', rawPayload: { source: 'repo_seed', assetId: CURRENT_NBTC_ASSET_ID }, }, { assetId: LEGACY_OMFT_BTC_ASSET_ID, venue: NEAR_INTENTS_VENUE, symbol: 'BTC', label: 'BTC / legacy OMFT', decimals: 8, blockchain: 'btc', chain: 'btc:mainnet', contractAddress: null, latestPrice: null, priceUpdatedAt: null, supported: true, enabledForInventory: true, role: 'legacy', rawPayload: { source: 'repo_seed', assetId: LEGACY_OMFT_BTC_ASSET_ID }, }, { assetId: CURRENT_EURE_ASSET_ID, venue: NEAR_INTENTS_VENUE, symbol: 'EURe', label: 'EURe', decimals: 18, blockchain: 'gnosis', chain: 'eth:100', contractAddress: '0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430', latestPrice: null, priceUpdatedAt: null, supported: true, enabledForInventory: true, role: 'trading', withdrawAddress: '0x6C40267e03A97B2132e7a7d3159C88534eBEfdFb', rawPayload: { source: 'repo_seed', assetId: CURRENT_EURE_ASSET_ID }, }, { assetId: CURRENT_USDC_ASSET_ID, venue: NEAR_INTENTS_VENUE, symbol: 'USDC', label: 'USDC', decimals: 6, blockchain: 'gnosis', chain: 'eth:100', contractAddress: '0x2a22f9c3b484c3629090feed35f17ff8f88f76f0', latestPrice: null, priceUpdatedAt: null, supported: true, enabledForInventory: false, role: null, rawPayload: { source: 'repo_seed', assetId: CURRENT_USDC_ASSET_ID }, }, ]; } export function buildSeedPairs() { return [ { pairId: CURRENT_PAIR_KEY, venue: NEAR_INTENTS_VENUE, assetIn: CURRENT_NBTC_ASSET_ID, assetOut: CURRENT_EURE_ASSET_ID, mode: 'both', enabled: true, status: 'both', }, { pairId: CURRENT_REVERSE_PAIR_KEY, venue: NEAR_INTENTS_VENUE, assetIn: CURRENT_EURE_ASSET_ID, assetOut: CURRENT_NBTC_ASSET_ID, mode: 'both', enabled: true, status: 'both', }, ]; } export function buildSeedStrategyConfig(pairId, { version = 1, active = true, createdBy = 'repo_seed', reason = 'seed current nBTC/EURe production config', } = {}) { return { configId: `${pairId}:v${version}`, pairId, version, active, edgeBps: CURRENT_EDGE_BPS, maxNotional: CURRENT_STRATEGY_MAX_NOTIONAL, minNotional: '0', slippageBps: CURRENT_SLIPPAGE_BPS, minDeadlineMs: CURRENT_MIN_DEADLINE_MS, priceMaxAgeMs: CURRENT_PRICE_MAX_AGE_MS, inventoryMaxAgeMs: CURRENT_INVENTORY_MAX_AGE_MS, requestDefaultNotional: CURRENT_REQUEST_DEFAULT_NOTIONAL_EURE, requestMaxNotional: CURRENT_REQUEST_MAX_NOTIONAL_EURE, requestMaxSlippageBps: CURRENT_SLIPPAGE_BPS, createdBy, reason, }; } export function buildSeedPriceRoute(pairId) { return { routeId: `${pairId}:btc-eur-reference`, pairId, source: 'btc_eur_reference', baseAssetId: CURRENT_NBTC_ASSET_ID, quoteAssetId: CURRENT_EURE_ASSET_ID, routeConfig: { reference_pair: 'BTC/EUR', eure_per_eur_assumption: '1', }, maxAgeMs: CURRENT_PRICE_MAX_AGE_MS, enabled: true, }; } export function buildBtcUsdcPriceRoute(pairId) { return { routeId: `${pairId}:btc-usdc-reference`, pairId, source: 'btc_usdc_reference', baseAssetId: CURRENT_NBTC_ASSET_ID, quoteAssetId: CURRENT_USDC_ASSET_ID, routeConfig: { reference_pair: 'BTC/USDC', kraken_pair: 'XBTUSDC', coingecko_id: 'bitcoin', coingecko_vs_currency: 'usd', usdc_per_usd_assumption: '1', }, maxAgeMs: CURRENT_PRICE_MAX_AGE_MS, enabled: true, }; } export function pairCanObserve(pair) { return Boolean(pair?.enabled) && PAIR_MODES.has(pair.mode) && pair.status !== 'disabled'; } export function pairCanMake(pair) { return pairCanObserve(pair) && ['maker', 'both'].includes(pair.mode); } export function pairCanTake(pair) { return pairCanObserve(pair) && ['taker', 'both'].includes(pair.mode); } export function normalizePairMode(mode) { const normalized = String(mode || '').trim().toLowerCase(); if (!PAIR_MODES.has(normalized)) throw new Error(`unsupported pair mode: ${mode}`); return normalized; } function stringField(value, field) { const normalized = optionalString(value); if (!normalized) throw new Error(`${field} is required`); return normalized; } function optionalString(value) { const normalized = String(value ?? '').trim(); return normalized || null; } function normalizeBridgeChain({ assetId, blockchain }) { const normalizedBlockchain = String(blockchain || '').trim(); const normalizedAssetId = String(assetId || '').trim(); if (normalizedBlockchain === 'gnosis' || normalizedAssetId.startsWith('nep141:gnosis-')) { return 'eth:100'; } return normalizedBlockchain; } function integerField(value, field) { const number = Number(value); if (!Number.isInteger(number) || number < 0) { throw new Error(`${field} must be a non-negative integer`); } return number; } function optionalTimestamp(value) { if (value == null || value === '') return null; const date = new Date(value); if (Number.isNaN(date.getTime())) throw new Error('priceUpdatedAt must be a timestamp'); return date.toISOString(); } function isRecord(value) { return Boolean(value) && typeof value === 'object' && !Array.isArray(value); }