871 lines
39 KiB
JavaScript
871 lines
39 KiB
JavaScript
// Generated, do not edit. sha256:79db5d8cc5706ad987b207d98e1c67ca389284a24121f65b199acbe48bce76ec
|
|||
|
|
const GRAMS_PER_TROY_OUNCE = 31.1034768;
|
||
|
|
|
||
|
|
export const CONFIG = Object.freeze({
|
||
|
|
PRICE_SOURCES: Object.freeze({
|
||
|
|
MEMPOOL_HISTORICAL: '/api/v1/historical-price',
|
||
|
|
MEMPOOL_PRICES: '/api/v1/prices',
|
||
|
|
MEMPOOL_PUBLIC_BASE: 'https://mempool.space',
|
||
|
|
BITVAVO_BASE: 'https://api.bitvavo.com',
|
||
|
|
BITVAVO_CANDLES: '/v2/{market}/candles',
|
||
|
|
BITVAVO_TICKER: '/v2/ticker/price',
|
||
|
|
BITVAVO_ACCOUNT: '/v2/account',
|
||
|
|
COINGECKO_BASE: 'https://api.coingecko.com/api/v3',
|
||
|
|
COINGECKO_PRICE: '/simple/price',
|
||
|
|
COINGECKO_CHART: '/coins/{id}/market_chart',
|
||
|
|
COINGECKO_PING: '/ping',
|
||
|
|
GOLDAPI_BASE: 'https://www.goldapi.io',
|
||
|
|
GOLDAPI_SPOT: '/api/{metal}/{currency}',
|
||
|
|
GOLDAPI_HISTORICAL: '/api/{metal}/{currency}/{date}',
|
||
|
|
EULERPOOL_BASE: 'https://api.eulerpool.com',
|
||
|
|
EULERPOOL_QUOTES: '/api/1/{external_id}/quotes/{symbol}',
|
||
|
|
METALSDEV_BASE: 'https://api.metals.dev',
|
||
|
|
METALSDEV_SPOT: '/v1/metal/spot',
|
||
|
|
METALSDEV_TIMESERIES: '/v1/timeseries',
|
||
|
|
FRANKFURTER_BASE: 'https://api.frankfurter.dev',
|
||
|
|
FRANKFURTER_LATEST: '/v1/latest',
|
||
|
|
FRANKFURTER_SERIES: '/v1/1999-01-04..',
|
||
|
|
}),
|
||
|
|
|
||
|
|
CHART_LIBS: Object.freeze({
|
||
|
|
CHARTJS: 'vendor/chartjs/chart.umd.js',
|
||
|
|
HAMMER: 'vendor/hammerjs/hammer.min.js',
|
||
|
|
ZOOM: 'vendor/chartjs-plugin-zoom/chartjs-plugin-zoom.min.js',
|
||
|
|
}),
|
||
|
|
|
||
|
|
SCHEMA_VERSION: 128,
|
||
|
|
|
||
|
|
MAX_ASSET_DECIMALS: 9,
|
||
|
|
|
||
|
|
LOCALE_TAGS: Object.freeze({ nl: 'nl-NL', en: 'en-US' }),
|
||
|
|
|
||
|
|
INSTALLATION: Object.freeze({ SETTINGS_FILE_NAME: 'installation.json', DEFAULT_LOCALE: 'en' }),
|
||
|
|
|
||
|
|
METALS: Object.freeze({
|
||
|
|
BASE_UNIT: 'g',
|
||
|
|
BASE_DECIMALS: 7,
|
||
|
|
GRAMS_PER_TROY_OUNCE,
|
||
|
|
DISPLAY_DECIMALS: 4,
|
||
|
|
UNITS: Object.freeze({ g: 1, kg: 1000, ozt: GRAMS_PER_TROY_OUNCE }),
|
||
|
|
SPOT_DEVIATION_WARN: 0.10,
|
||
|
|
}),
|
||
|
|
|
||
|
|
DB_TABLES: Object.freeze([
|
||
|
|
'Asset', 'Account', 'Event', 'Valuation', 'Label',
|
||
|
|
'WalletSource', 'WalletAddress', 'WalletTransaction', 'ScanLog',
|
||
|
|
'ReconcileProposal', 'ImportBatch', 'PriceHistory', 'WalletTransactionEventLink',
|
||
|
|
'InterestSchedule', 'InterestPeriod', 'InterestCredit', 'Notification',
|
||
|
|
'MetalProduct',
|
||
|
|
'Provider', 'ProviderAuthenticationHmac', 'ProviderEndpoint', 'ProviderFixedRate', 'ProviderCallLimit',
|
||
|
|
'ProviderInputMap', 'ProviderOutputMap', 'ProviderCoverage', 'ApiCallMetric',
|
||
|
|
'PriceSource', 'Connection',
|
||
|
|
'DashboardProfile', 'DashboardWidget',
|
||
|
|
'AiToolCall', 'AiToolCallMessage', 'BusSignal',
|
||
|
|
'Command', 'AiProposal',
|
||
|
|
'Preference', 'Schema', 'ChangeLog', 'HygieneAcceptance', 'IgnoredPriceGap', 'AssetNote',
|
||
|
|
'Portfolio', 'PriceAbsence', 'IntegrationCall',
|
||
|
|
]),
|
||
|
|
|
||
|
|
DB_DELETABLE_TABLES: Object.freeze([
|
||
|
|
'ImportBatch', 'PriceHistory', 'ReconcileProposal', 'WalletTransactionEventLink',
|
||
|
|
'Valuation', 'WalletAddress', 'InterestCredit', 'Notification',
|
||
|
|
'PriceSource', 'ProviderCoverage', 'Preference', 'BusSignal',
|
||
|
|
'PriceAbsence',
|
||
|
|
'IntegrationCall',
|
||
|
|
]),
|
||
|
|
|
||
|
|
DB_HISTORY: Object.freeze({
|
||
|
|
TRACKED: Object.freeze([
|
||
|
|
'Account', 'Asset', 'Event', 'Valuation', 'Label', 'MetalProduct',
|
||
|
|
'InterestSchedule', 'InterestPeriod', 'InterestCredit',
|
||
|
|
'WalletSource', 'Connection',
|
||
|
|
]),
|
||
|
|
UNTRACKED: Object.freeze({
|
||
|
|
AiToolCall: 'logboek', AiToolCallMessage: 'logboek', BusSignal: 'logboek',
|
||
|
|
Command: 'logboek', ScanLog: 'logboek', ApiCallMetric: 'logboek', Notification: 'logboek',
|
||
|
|
WalletTransaction: 'scanketen', WalletAddress: 'scanketen', WalletTransactionEventLink: 'scanketen',
|
||
|
|
PriceHistory: 'volume', PriceAbsence: 'volume',
|
||
|
|
AiProposal: 'herkomst', ReconcileProposal: 'herkomst', ImportBatch: 'herkomst',
|
||
|
|
Provider: 'configuratie', ProviderAuthenticationHmac: 'configuratie',
|
||
|
|
ProviderEndpoint: 'configuratie', ProviderFixedRate: 'configuratie', ProviderCallLimit: 'configuratie',
|
||
|
|
ProviderInputMap: 'configuratie', ProviderOutputMap: 'configuratie',
|
||
|
|
ProviderCoverage: 'configuratie', PriceSource: 'configuratie',
|
||
|
|
DashboardProfile: 'weergave', DashboardWidget: 'weergave', Preference: 'weergave',
|
||
|
|
Schema: 'systeem', Portfolio: 'systeem',
|
||
|
|
ChangeLog: 'logboek',
|
||
|
|
IntegrationCall: 'logboek',
|
||
|
|
HygieneAcceptance: 'weergave',
|
||
|
|
IgnoredPriceGap: 'weergave',
|
||
|
|
AssetNote: 'weergave',
|
||
|
|
}),
|
||
|
|
}),
|
||
|
|
|
||
|
|
PEILDATUM: Object.freeze({
|
||
|
|
MONTH: 1, DAY: 1, HOUR: 0, MINUTE: 0,
|
||
|
|
TIMEZONE: 'Europe/Amsterdam',
|
||
|
|
}),
|
||
|
|
|
||
|
|
START_YEAR: Object.freeze({
|
||
|
|
AUTO: 0,
|
||
|
|
MIN: 1900,
|
||
|
|
MAX: 2200,
|
||
|
|
}),
|
||
|
|
|
||
|
|
WEALTH_RETURN: Object.freeze({ DUST_SHARE: 0.005, REOPEN_MULTIPLE: 10, ROLLING_DAYS: Object.freeze([1, 7, 30, 365]) }),
|
||
|
|
|
||
|
|
AUTO_BLUR: Object.freeze({
|
||
|
|
PRESETS_MIN: Object.freeze([5, 15, 30]),
|
||
|
|
NEVER_MIN: 525600,
|
||
|
|
}),
|
||
|
|
|
||
|
|
GLOW_TEMPO: Object.freeze({
|
||
|
|
brisk: Object.freeze({ MOVE_S: 11, PAUSE_MIN_S: 18, PAUSE_MAX_S: 40, ALWAYS_MULT: 1 }),
|
||
|
|
calm: Object.freeze({ MOVE_S: 24, PAUSE_MIN_S: 45, PAUSE_MAX_S: 90, ALWAYS_MULT: 2 }),
|
||
|
|
slow: Object.freeze({ MOVE_S: 36, PAUSE_MIN_S: 80, PAUSE_MAX_S: 140, ALWAYS_MULT: 3 }),
|
||
|
|
}),
|
||
|
|
GLOW_CORE_CHANCE: 0.35,
|
||
|
|
ATTENTION_RING: Object.freeze({ ROUND_MS: 3500, ROUNDS: 3 }),
|
||
|
|
|
||
|
|
TIMERS: Object.freeze({
|
||
|
|
OFF_SECONDS: 31536000,
|
||
|
|
SPOT_REFRESH: Object.freeze({
|
||
|
|
MIN_SECONDS: 60,
|
||
|
|
MAX_SECONDS: 3600,
|
||
|
|
PRESETS_SECONDS: Object.freeze([60, 300, 900]),
|
||
|
|
}),
|
||
|
|
BUS_POLL: Object.freeze({
|
||
|
|
MIN_SECONDS: 5,
|
||
|
|
MAX_SECONDS: 300,
|
||
|
|
PRESETS_SECONDS: Object.freeze([5, 15, 60]),
|
||
|
|
}),
|
||
|
|
}),
|
||
|
|
|
||
|
|
PRICE_HISTORY: Object.freeze({
|
||
|
|
NEAREST_MAX_SECONDS: 60,
|
||
|
|
CARRY_FORWARD_DAYS: Object.freeze({ crypto: 7, metal: 14, fiat: null }),
|
||
|
|
CLOSED_MARKET_DAYS: Object.freeze({ crypto: 0, metal: 4, fiat: 4 }),
|
||
|
|
WEEKEND_PRICE: Object.freeze({ crypto: true, metal: false, fiat: false }),
|
||
|
|
RECENT_CLOSE_DAYS: 14,
|
||
|
|
FX_MAX_AGE_DAYS: 7,
|
||
|
|
}),
|
||
|
|
|
||
|
|
API_USAGE: Object.freeze({ WARN_SHARE: 0.8 }),
|
||
|
|
|
||
|
|
HYGIENE: Object.freeze({
|
||
|
|
PRICE_GAP_DAYS: 7,
|
||
|
|
MAX_ROWS_PER_FINDING: 200,
|
||
|
|
OUTLIER_FACTOR: 3,
|
||
|
|
TROY_OUNCE_TOLERANCE: 0.2,
|
||
|
|
SOURCES_DISAGREE_SHARE: 0.08,
|
||
|
|
BOOKING_PRICE_DEVIATION: 0.2,
|
||
|
|
}),
|
||
|
|
|
||
|
|
BUS: Object.freeze({
|
||
|
|
RETENTION_HOURS: 24,
|
||
|
|
}),
|
||
|
|
|
||
|
|
INTEGRATION_LOG: Object.freeze({
|
||
|
|
RETENTION_DAYS: 90,
|
||
|
|
MAX_ROWS: 10_000,
|
||
|
|
DETAIL_MAX_CHARS: 300,
|
||
|
|
WINDOW_DAYS: 7,
|
||
|
|
AI_MAX_ROWS: 20,
|
||
|
|
}),
|
||
|
|
|
||
|
|
COMMANDS: Object.freeze({
|
||
|
|
CLAIM_SECONDS: 20,
|
||
|
|
COUNTDOWN_SECONDS: 5,
|
||
|
|
NOTICE_SECONDS: 3,
|
||
|
|
RETENTION_HOURS: 24,
|
||
|
|
}),
|
||
|
|
|
||
|
|
BACKUP: Object.freeze({
|
||
|
|
REMIND_AFTER_DAYS: 14,
|
||
|
|
}),
|
||
|
|
|
||
|
|
HEALTH_CHECK: Object.freeze({
|
||
|
|
REMIND_AFTER_DAYS: 30,
|
||
|
|
}),
|
||
|
|
|
||
|
|
AUTH: Object.freeze({
|
||
|
|
SESSION_TTL_MS: 12 * 60 * 60 * 1000,
|
||
|
|
COOKIE_NAME: 'assetapp_session',
|
||
|
|
LOGIN_PATH: '/login',
|
||
|
|
LOGIN_ATTEMPTS: Object.freeze({
|
||
|
|
FREE: 3,
|
||
|
|
BASE_LOCK_MS: 2000,
|
||
|
|
MAX_LOCK_MS: 5 * 60 * 1000,
|
||
|
|
}),
|
||
|
|
}),
|
||
|
|
|
||
|
|
ATTENTION: Object.freeze({
|
||
|
|
FADE_AFTER_DAYS: 1,
|
||
|
|
HIDE_AFTER_DAYS: 2,
|
||
|
|
}),
|
||
|
|
|
||
|
|
FOCUS: Object.freeze({
|
||
|
|
INTEREST_ACCOUNT: 'interest.focusAccount',
|
||
|
|
ROW: 'focus.row',
|
||
|
|
ROW_TTL_MS: 30_000,
|
||
|
|
}),
|
||
|
|
|
||
|
|
LICENSE: Object.freeze({
|
||
|
|
FILE_NAME: 'license.json',
|
||
|
|
FREE_FILE_NAME: 'free-license.json',
|
||
|
|
BUYER_FILE_NAME: 'buyer.json',
|
||
|
|
SUPPORT: Object.freeze({
|
||
|
|
ATTACHMENT_TYPES: Object.freeze(['image/png', 'image/jpeg', 'image/webp']),
|
||
|
|
MAX_ATTACHMENT_BYTES: 2 * 1024 * 1024,
|
||
|
|
RECIPIENT: '72304ea4d273d6c685aaa76342975e7a001190c9f15369ed3d3b303b79c8c74c',
|
||
|
|
RECIPIENT_NAME: 'Atreides Assets',
|
||
|
|
RELAYS: Object.freeze(['wss://nos.lol']),
|
||
|
|
MAX_TEXT: 4000,
|
||
|
|
MAX_EVENT_BYTES: 64 * 1024,
|
||
|
|
PUBLISH_TIMEOUT_MS: 10_000,
|
||
|
|
}),
|
||
|
|
INFO_LINKS: Object.freeze([
|
||
|
|
Object.freeze({ id: 'nostrHow', url: 'https://nostr.how/' }),
|
||
|
|
Object.freeze({ id: 'nostrOrg', url: 'https://nostr.org/' }),
|
||
|
|
Object.freeze({ id: 'alby', url: 'https://getalby.com/alby-extension' }),
|
||
|
|
Object.freeze({ id: 'nos2x', url: 'https://github.com/fiatjaf/nos2x' }),
|
||
|
|
Object.freeze({ id: 'lightning', url: 'https://lightning.network/' }),
|
||
|
|
]),
|
||
|
|
PAY_APPS: Object.freeze([
|
||
|
|
Object.freeze({ id: 'phoenix', name: 'Phoenix', group: 'easy', url: 'https://phoenix.acinq.co/' }),
|
||
|
|
Object.freeze({ id: 'pocket', name: 'Pocket', group: 'easy', url: 'https://pocketbitcoin.com/lightning' }),
|
||
|
|
Object.freeze({ id: 'strike', name: 'Strike', group: 'other', url: 'https://strike.me/' }),
|
||
|
|
Object.freeze({ id: 'zeus', name: 'Zeus', group: 'other', url: 'https://zeusln.com/' }),
|
||
|
|
Object.freeze({ id: 'bringin', name: 'Bringin', group: 'other', url: 'https://bringin.app/' }),
|
||
|
|
]),
|
||
|
|
FEATURE_IMAGES: 'img/features',
|
||
|
|
FEATURE_GROUPS: Object.freeze(['overview', 'records', 'prices', 'app', 'bitcoin']),
|
||
|
|
FEATURES: Object.freeze([
|
||
|
|
{ id: 'dashboard', group: 'overview', free: null }, { id: 'wealth', group: 'overview', free: null },
|
||
|
|
{ id: 'returns', group: 'overview', free: null }, { id: 'taxes', group: 'overview', has: 'tax' },
|
||
|
|
{ id: 'balances', group: 'records', free: null }, { id: 'interest', group: 'records', free: null },
|
||
|
|
{ id: 'import', group: 'records', has: 'import' }, { id: 'data', group: 'records', has: 'data' },
|
||
|
|
{ id: 'health', group: 'records', has: 'health' }, { id: 'metals', group: 'records', free: false },
|
||
|
|
{ id: 'priceHistory', group: 'prices', free: null },
|
||
|
|
{ id: 'sourceChoice', group: 'prices', has: 'sourceMatrix' }, { id: 'providerKeys', group: 'prices', has: 'providerKeys' },
|
||
|
|
{ id: 'portfolios', group: 'app', has: 'portfolios' }, { id: 'ai', group: 'app', has: 'ai' },
|
||
|
|
{ id: 'kiosk', group: 'app', has: 'kiosk' },
|
||
|
|
{ id: 'tracking', group: 'bitcoin', free: null }, { id: 'lightning', group: 'bitcoin', has: 'lightning' },
|
||
|
|
{ id: 'ownMempool', group: 'bitcoin', has: 'ownMempool' }, { id: 'signals', group: 'bitcoin', has: 'signals' },
|
||
|
|
{ id: 'netshare', group: 'bitcoin', has: 'netshare' },
|
||
|
|
].map(f => Object.freeze(f))),
|
||
|
|
SELLER_KEY: 'cfc6e67daaf080abbdcbacdbca587d54a3c18b119f2f05d7b087b859caa96a2f',
|
||
|
|
FORMAT: 1,
|
||
|
|
FORMATS: Object.freeze([1, 2]),
|
||
|
|
MESSAGE_PREFIX: 'assetapp-license-v1',
|
||
|
|
ISSUER_PREFIX: 'assetapp-issuer-v1',
|
||
|
|
UNLIMITED: 'unlimited',
|
||
|
|
LEVELS: Object.freeze(['full', 'free']),
|
||
|
|
|
||
|
|
FREE: Object.freeze({
|
||
|
|
MAX: Object.freeze({
|
||
|
|
assets: 1,
|
||
|
|
fiatAccounts: 1,
|
||
|
|
portfolios: 1,
|
||
|
|
valuations: 1,
|
||
|
|
}),
|
||
|
|
ASSETS: Object.freeze(['BTC']),
|
||
|
|
HAS: Object.freeze({
|
||
|
|
ai: true,
|
||
|
|
sourceMatrix: false,
|
||
|
|
lightning: false,
|
||
|
|
tax: false,
|
||
|
|
providerKeys: false,
|
||
|
|
ownMempool: false,
|
||
|
|
signals: false,
|
||
|
|
netshare: false,
|
||
|
|
portfolios: false,
|
||
|
|
import: false,
|
||
|
|
data: false,
|
||
|
|
health: false,
|
||
|
|
kiosk: false,
|
||
|
|
}),
|
||
|
|
}),
|
||
|
|
}),
|
||
|
|
|
||
|
|
EDITION: Object.freeze({
|
||
|
|
CLIENT: Object.freeze({
|
||
|
|
entry: 'client/edition.js',
|
||
|
|
starts: Object.freeze(['client/app.js', 'client/boot-gate.js', 'client/login.js']),
|
||
|
|
}),
|
||
|
|
SERVER: Object.freeze({
|
||
|
|
entry: 'server/edition.mjs',
|
||
|
|
starts: Object.freeze(['server/index.mjs', 'server/supervisor.mjs', 'server/mcp/stdio.mjs']),
|
||
|
|
}),
|
||
|
|
}),
|
||
|
|
|
||
|
|
PORTFOLIO: Object.freeze({
|
||
|
|
RESTART_EXIT_CODE: 75,
|
||
|
|
DEFAULT_BASE_CURRENCY: 'EUR',
|
||
|
|
BASE_CURRENCIES: Object.freeze([
|
||
|
|
'AUD', 'BRL', 'CAD', 'CHF', 'CNY', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD', 'HUF', 'IDR', 'ILS', 'INR', 'ISK',
|
||
|
|
'JPY', 'KRW', 'MXN', 'MYR', 'NOK', 'NZD', 'PHP', 'PLN', 'RON', 'SEK', 'SGD', 'THB', 'TRY', 'USD', 'ZAR',
|
||
|
|
]),
|
||
|
|
ACTIVE_KEY: 'portfolio_active',
|
||
|
|
SWITCH_KEY: 'portfolio_switching',
|
||
|
|
}),
|
||
|
|
|
||
|
|
SPLASH: Object.freeze({
|
||
|
|
MIN_MS: 3200,
|
||
|
|
FADE_MS: 900,
|
||
|
|
QUOTE_START_MS: 1800,
|
||
|
|
QUOTE_CHAR_MS: 40,
|
||
|
|
QUOTE_WORD_PAUSE_MS: 110,
|
||
|
|
}),
|
||
|
|
|
||
|
|
CONNECTIONS: Object.freeze({
|
||
|
|
TEST_TIMEOUT_MS: 8000,
|
||
|
|
RESULT_HOLD_MS: 6000,
|
||
|
|
RESULT_FADE_MS: 600,
|
||
|
|
}),
|
||
|
|
|
||
|
|
NWC: Object.freeze({
|
||
|
|
REQUEST_TIMEOUT_MS: 15000,
|
||
|
|
CLIENT_TIMEOUT_MS: 20000,
|
||
|
|
}),
|
||
|
|
|
||
|
|
LND: Object.freeze({
|
||
|
|
REQUEST_TIMEOUT_MS: 15000,
|
||
|
|
CLIENT_TIMEOUT_MS: 20000,
|
||
|
|
CHANNEL_LABEL: 'Lightning-kanaal',
|
||
|
|
}),
|
||
|
|
|
||
|
|
MARKET_SERVICE: Object.freeze({
|
||
|
|
SETTINGS_FILE_NAME: 'market-service.json',
|
||
|
|
URL: 'https://atreides.kamenier-hamer.nl',
|
||
|
|
APP_NAME: 'Atreides Services',
|
||
|
|
REQUEST_TIMEOUT_MS: 10000,
|
||
|
|
CLIENT_TIMEOUT_MS: 15000,
|
||
|
|
REGISTER_TTL_MS: 10 * 60_000,
|
||
|
|
MAX_SERIES_PAGES: 10,
|
||
|
|
INTRADAY_MAX_DAYS: 7,
|
||
|
|
MOMENT_WINDOW_MS: 10 * 60_000,
|
||
|
|
HISTORY_MAX_DAYS: 3650,
|
||
|
|
PROVIDER_KEY: 'atreides_services',
|
||
|
|
SPOT_TTL_MS: 30_000,
|
||
|
|
RETRY_PAUSE_MS: 5 * 60_000,
|
||
|
|
MAX_INSTRUMENTS_PER_SPOT: 100,
|
||
|
|
}),
|
||
|
|
|
||
|
|
AI: Object.freeze({
|
||
|
|
SERVER_NAME: 'assetapp',
|
||
|
|
SERVER_VERSION: '3',
|
||
|
|
PROTOCOL_VERSION: '2025-06-18',
|
||
|
|
AUDIT_PAGE_SIZE: 100,
|
||
|
|
PROPOSAL_PAGE_SIZE: 50,
|
||
|
|
|
||
|
|
DESTRUCTIVE_PROPOSAL_KINDS: Object.freeze(['account_delete']),
|
||
|
|
|
||
|
|
FIELD_CLASSES: Object.freeze(['plain', 'rate', 'quantityFiat', 'quantity', 'handle', 'chain', 'drop']),
|
||
|
|
|
||
|
|
BUCKET_LADDERS: Object.freeze({
|
||
|
|
COARSE: Object.freeze([1, 1.5, 2, 3, 5, 7]),
|
||
|
|
FINE: Object.freeze([1, 1.25, 1.6, 2, 2.5, 3.15, 4, 5, 6.3, 8]),
|
||
|
|
}),
|
||
|
|
|
||
|
|
BUCKET_MIN_VALUES: Object.freeze({ COARSE: 1000, FINE: 0 }),
|
||
|
|
|
||
|
|
SHARE_BUCKETS: Object.freeze([2, 4, 6, 8, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75,
|
||
|
|
80, 85, 90, 95, 100]),
|
||
|
|
|
||
|
|
SHARED_PREF_SCOPES: Object.freeze(['app', 'timers']),
|
||
|
|
|
||
|
|
PROPOSABLE_PREF_SCOPES: Object.freeze(['app', 'timers']),
|
||
|
|
|
||
|
|
LIMITS: Object.freeze({
|
||
|
|
MAX_ROWS_PER_LIST: 50,
|
||
|
|
MAX_TEXT_CHARS: 10000,
|
||
|
|
MAX_LOG_CHARS: 10000,
|
||
|
|
METRICS_WINDOW_HOURS: 24,
|
||
|
|
}),
|
||
|
|
}),
|
||
|
|
|
||
|
|
ROW_GUARD: false,
|
||
|
|
});
|
||
|
|
|
||
|
|
export const AUTH_KINDS = Object.freeze({
|
||
|
|
none: Object.freeze({ secret_fields: Object.freeze([]) }),
|
||
|
|
header_key: Object.freeze({ secret_fields: Object.freeze(['api_key']) }),
|
||
|
|
query_key: Object.freeze({ secret_fields: Object.freeze(['api_key']) }),
|
||
|
|
bearer: Object.freeze({ secret_fields: Object.freeze(['api_key']) }),
|
||
|
|
basic: Object.freeze({ secret_fields: Object.freeze(['api_key', 'api_secret']) }),
|
||
|
|
hmac: Object.freeze({ secret_fields: Object.freeze(['api_key', 'api_secret']) }),
|
||
|
|
});
|
||
|
|
|
||
|
|
export const ENUMS = Object.freeze({
|
||
|
|
ASSET_KIND: Object.freeze(['crypto', 'metal', 'fiat']),
|
||
|
|
ASSET_DISPLAY_UNIT: Object.freeze(['unit', 'subunit']),
|
||
|
|
METAL_FORM: Object.freeze(['coin', 'bar', 'other']),
|
||
|
|
ACCOUNT_KIND: Object.freeze(['exchange', 'wallet', 'lightning', 'card', 'bank', 'vault']),
|
||
|
|
TAX_CATEGORY: Object.freeze(['bank', 'overig']),
|
||
|
|
TAX_SCOPE: Object.freeze(['all', 'category', 'none']),
|
||
|
|
WEALTH_SCOPE: Object.freeze(['managed', 'none']),
|
||
|
|
EVENT_TYPE: Object.freeze(['buy', 'sell', 'transfer', 'interest', 'reservation']),
|
||
|
|
LEDGER_EVENT_TYPE: Object.freeze(['buy', 'sell', 'transfer', 'interest']),
|
||
|
|
EVENT_SOURCE: Object.freeze(['manual', 'import', 'reconcile', 'lightning']),
|
||
|
|
EVENT_PRICE_ORIGIN: Object.freeze(['paid', 'market_day', 'market_moment', 'none']),
|
||
|
|
VALUATION_SOURCE: Object.freeze(['onchain', 'manual', 'statement']),
|
||
|
|
CHANGE_OPERATION: Object.freeze(['update', 'delete']),
|
||
|
|
CHANGE_ORIGIN: Object.freeze(['manual', 'import', 'reconcile', 'interest', 'lightning',
|
||
|
|
'ai_proposal', 'merge', 'system']),
|
||
|
|
INTEREST_CADENCE: Object.freeze(['term_end', 'annual', 'quarterly', 'monthly', 'weekly']),
|
||
|
|
INTEREST_RATE_KIND: Object.freeze(['fixed', 'variable']),
|
||
|
|
INTEREST_CREDIT_STATUS: Object.freeze(['due', 'booked', 'dismissed']),
|
||
|
|
WALLET_SOURCE_KIND: Object.freeze(['xpub', 'address', 'nwc', 'lnd']),
|
||
|
|
ONCHAIN_SOURCE_KIND: Object.freeze(['xpub', 'address']),
|
||
|
|
SCRIPT_TYPE: Object.freeze(['p2tr', 'p2wpkh', 'p2sh-p2wpkh', 'p2pkh']),
|
||
|
|
SCAN_STATUS: Object.freeze(['success', 'partial', 'failed']),
|
||
|
|
RECONCILE_DIRECTION: Object.freeze(['in', 'out']),
|
||
|
|
WALLET_TX_LAYER: Object.freeze(['onchain', 'lightning']),
|
||
|
|
WALLET_TX_KIND: Object.freeze(['channel', 'self']),
|
||
|
|
RECONCILE_SUGGESTED: Object.freeze(['trade', 'transfer']),
|
||
|
|
RECONCILE_STATUS: Object.freeze(['pending', 'accepted_trade', 'accepted_transfer', 'accepted_interest', 'ignored', 'auto_linked']),
|
||
|
|
LINK_METHOD: Object.freeze(['exact', 'soft', 'manual']),
|
||
|
|
NOTIFICATION_KIND: Object.freeze(['reconcile', 'interest', 'valuation', 'schedule_ended', 'source_proposal', 'ai_proposal', 'reservation']),
|
||
|
|
CONNECTION_KIND: Object.freeze(['btc_explorer', 'lnd', 'nwc']),
|
||
|
|
THEME: Object.freeze(['dark', 'light']),
|
||
|
|
ACCENT: Object.freeze(['orange', 'magenta', 'lila', 'indigo', 'blue', 'cyan', 'teal', 'slate', 'sand']),
|
||
|
|
DEPTH: Object.freeze(['flat', 'deep']),
|
||
|
|
PROFILE: Object.freeze(['business', 'glass']),
|
||
|
|
GLOW: Object.freeze(['off', 'on']),
|
||
|
|
GLOW_SHAPE: Object.freeze(['blobs', 'bands']),
|
||
|
|
MOTION: Object.freeze(['off', 'step', 'always']),
|
||
|
|
GLOW_TEMPO: Object.freeze(['brisk', 'calm', 'slow']),
|
||
|
|
ROW_SELECTION: Object.freeze(['light', 'tint', 'pill']),
|
||
|
|
GLASS_RENDER: Object.freeze(['real', 'mimic']),
|
||
|
|
MWR_METHOD: Object.freeze(['dietz', 'irr']),
|
||
|
|
PERSONA: Object.freeze(['bitcoin', 'broad']),
|
||
|
|
MARKET_SPOT: Object.freeze(['off', 'fallback', 'first']),
|
||
|
|
REPEAT_ENTRY_KEY: Object.freeze(['ctrl_enter', 'enter']),
|
||
|
|
RESULT_SIGNS: Object.freeze(['none', 'negative', 'both']),
|
||
|
|
PRICE_RESOLUTION: Object.freeze(['day', 'timestamp']),
|
||
|
|
CONNECTION_STATUS: Object.freeze(['ok', 'fail']),
|
||
|
|
PRICE_SOURCE_STATUS: Object.freeze(['proposed', 'confirmed', 'manual']),
|
||
|
|
HMAC_HASH_ALGORITHM: Object.freeze(['SHA-256']),
|
||
|
|
HMAC_ENCODING: Object.freeze(['hex', 'base64']),
|
||
|
|
AI_CALL_OUTCOME: Object.freeze(['ok', 'error', 'refused']),
|
||
|
|
INTEGRATION_TARGET: Object.freeze(['service', 'provider']),
|
||
|
|
INTEGRATION_OUTCOME: Object.freeze(['fail', 'empty', 'refused']),
|
||
|
|
AI_MESSAGE_KIND: Object.freeze(['request', 'response']),
|
||
|
|
AI_SCOPE: Object.freeze(['none', 'read', 'write', 'control']),
|
||
|
|
COMMAND_KIND: Object.freeze(['navigate', 'locate']),
|
||
|
|
|
||
|
|
AI_PROPOSAL_KIND: Object.freeze(['price_point', 'account', 'event', 'account_delete', 'valuation', 'portfolio', 'preference', 'source_choice']),
|
||
|
|
SOURCE_CHOICE_ACTION: Object.freeze(['first', 'remove']),
|
||
|
|
QUOTE_UNIT: Object.freeze(['ozt']),
|
||
|
|
AI_PROPOSAL_STATUS: Object.freeze(['pending', 'accepted', 'rejected']),
|
||
|
|
COMMAND_STATE: Object.freeze(['open', 'claimed', 'done', 'rejected', 'expired']),
|
||
|
|
});
|
||
|
|
|
||
|
|
export const SYSTEM_PAGE = Object.freeze({
|
||
|
|
ROUTE: 'system',
|
||
|
|
TABS: Object.freeze({
|
||
|
|
PORTFOLIOS: 'portfolios',
|
||
|
|
SETTINGS: 'settings',
|
||
|
|
ASSETS: 'assets',
|
||
|
|
ACCOUNTS: 'accounts',
|
||
|
|
INTEGRATIONS: 'integrations',
|
||
|
|
IMPORT: 'import',
|
||
|
|
DATA: 'data',
|
||
|
|
HEALTH: 'health',
|
||
|
|
AI: 'ai',
|
||
|
|
LICENSE: 'license',
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
|
||
|
|
export const EVENTS_PAGE = Object.freeze({
|
||
|
|
ROUTE: 'events',
|
||
|
|
TABS: Object.freeze({
|
||
|
|
ACCOUNTS: 'accounts',
|
||
|
|
VALUATIONS: 'valuations',
|
||
|
|
TRADES: 'trades',
|
||
|
|
TRANSFERS: 'transfers',
|
||
|
|
INTEREST: 'interest',
|
||
|
|
RESERVATIONS: 'reservations',
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
|
||
|
|
export const CHARTS_PAGE = Object.freeze({
|
||
|
|
ROUTE: 'charts',
|
||
|
|
TABS: Object.freeze({
|
||
|
|
PRICE: 'price',
|
||
|
|
POWERLAW: 'pl',
|
||
|
|
WMA: 'wma',
|
||
|
|
MAYER: 'mayer',
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
|
||
|
|
export const ROUTE_KEYS = Object.freeze({
|
||
|
|
HOME: 'home',
|
||
|
|
ACTIONS: 'actions',
|
||
|
|
EVENTS: EVENTS_PAGE.ROUTE,
|
||
|
|
WEALTH: 'wealth',
|
||
|
|
TAXES: 'taxes',
|
||
|
|
VALIDATION: 'validation',
|
||
|
|
TRACKING: 'tracking',
|
||
|
|
CONVERSION_BITCOIN: 'conversion/bitcoin',
|
||
|
|
PRICE_HISTORY: 'price-history',
|
||
|
|
CHARTS: CHARTS_PAGE.ROUTE,
|
||
|
|
SYSTEM: SYSTEM_PAGE.ROUTE,
|
||
|
|
GUIDE: 'guide',
|
||
|
|
});
|
||
|
|
|
||
|
|
const doel = (route, tab, sub) => Object.freeze({ route, tab: tab ?? null, sub: sub ?? null, element: null });
|
||
|
|
export const TARGETS = Object.freeze({
|
||
|
|
HOME: doel(ROUTE_KEYS.HOME),
|
||
|
|
ACTIONS: doel(ROUTE_KEYS.ACTIONS),
|
||
|
|
CHARTS: doel(ROUTE_KEYS.CHARTS),
|
||
|
|
TAXES: doel(ROUTE_KEYS.TAXES),
|
||
|
|
WEALTH: doel(ROUTE_KEYS.WEALTH),
|
||
|
|
TRACKING: doel(ROUTE_KEYS.TRACKING),
|
||
|
|
PRICE_HISTORY: doel(ROUTE_KEYS.PRICE_HISTORY),
|
||
|
|
CONVERSION: doel(ROUTE_KEYS.CONVERSION_BITCOIN),
|
||
|
|
GUIDE: doel(ROUTE_KEYS.GUIDE),
|
||
|
|
PORTFOLIOS: doel(ROUTE_KEYS.SYSTEM, SYSTEM_PAGE.TABS.PORTFOLIOS),
|
||
|
|
ASSETS: doel(ROUTE_KEYS.SYSTEM, SYSTEM_PAGE.TABS.ASSETS),
|
||
|
|
ACCOUNTS: doel(ROUTE_KEYS.SYSTEM, SYSTEM_PAGE.TABS.ACCOUNTS),
|
||
|
|
INTEGRATIONS: doel(ROUTE_KEYS.SYSTEM, SYSTEM_PAGE.TABS.INTEGRATIONS),
|
||
|
|
AI_ACTIVITY: doel(ROUTE_KEYS.SYSTEM, SYSTEM_PAGE.TABS.AI),
|
||
|
|
SETTINGS: doel(ROUTE_KEYS.SYSTEM, SYSTEM_PAGE.TABS.SETTINGS),
|
||
|
|
LICENSE: doel(ROUTE_KEYS.SYSTEM, SYSTEM_PAGE.TABS.LICENSE),
|
||
|
|
IMPORT: doel(ROUTE_KEYS.SYSTEM, SYSTEM_PAGE.TABS.IMPORT),
|
||
|
|
DATA: doel(ROUTE_KEYS.SYSTEM, SYSTEM_PAGE.TABS.DATA),
|
||
|
|
HEALTH: doel(ROUTE_KEYS.SYSTEM, SYSTEM_PAGE.TABS.HEALTH),
|
||
|
|
PROVIDER_KEYS: doel(ROUTE_KEYS.SYSTEM, SYSTEM_PAGE.TABS.INTEGRATIONS, 'providers'),
|
||
|
|
SOURCE_PROPOSALS: doel(ROUTE_KEYS.SYSTEM, SYSTEM_PAGE.TABS.INTEGRATIONS, 'sources'),
|
||
|
|
CONNECTIONS: doel(ROUTE_KEYS.SYSTEM, SYSTEM_PAGE.TABS.INTEGRATIONS, 'connections'),
|
||
|
|
CHART_PRICE: doel(ROUTE_KEYS.CHARTS, CHARTS_PAGE.TABS.PRICE),
|
||
|
|
CHART_POWERLAW: doel(ROUTE_KEYS.CHARTS, CHARTS_PAGE.TABS.POWERLAW),
|
||
|
|
CHART_WMA: doel(ROUTE_KEYS.CHARTS, CHARTS_PAGE.TABS.WMA),
|
||
|
|
CHART_MAYER: doel(ROUTE_KEYS.CHARTS, CHARTS_PAGE.TABS.MAYER),
|
||
|
|
EVENT_ACCOUNTS: doel(ROUTE_KEYS.EVENTS, EVENTS_PAGE.TABS.ACCOUNTS),
|
||
|
|
VALUATIONS: doel(ROUTE_KEYS.EVENTS, EVENTS_PAGE.TABS.VALUATIONS),
|
||
|
|
TRADES: doel(ROUTE_KEYS.EVENTS, EVENTS_PAGE.TABS.TRADES),
|
||
|
|
TRANSFERS: doel(ROUTE_KEYS.EVENTS, EVENTS_PAGE.TABS.TRANSFERS),
|
||
|
|
INTEREST: doel(ROUTE_KEYS.EVENTS, EVENTS_PAGE.TABS.INTEREST),
|
||
|
|
RESERVATIONS: doel(ROUTE_KEYS.EVENTS, EVENTS_PAGE.TABS.RESERVATIONS),
|
||
|
|
});
|
||
|
|
|
||
|
|
export const NOTIFICATION_TARGETS = Object.freeze({
|
||
|
|
reconcile: TARGETS.ACTIONS,
|
||
|
|
interest: TARGETS.ACTIONS,
|
||
|
|
valuation: TARGETS.ACTIONS,
|
||
|
|
schedule_ended: TARGETS.ACCOUNTS,
|
||
|
|
source_proposal: TARGETS.SOURCE_PROPOSALS,
|
||
|
|
ai_proposal: TARGETS.ACTIONS,
|
||
|
|
reservation: TARGETS.ACTIONS,
|
||
|
|
});
|
||
|
|
|
||
|
|
export const NOTIFICATION_PRIORITY = Object.freeze([
|
||
|
|
'reconcile', 'valuation', 'reservation', 'interest', 'ai_proposal', 'source_proposal', 'schedule_ended',
|
||
|
|
]);
|
||
|
|
|
||
|
|
export const PLACE_LABELS = Object.freeze({
|
||
|
|
providers: 'integrations.tabProviders',
|
||
|
|
sources: 'integrations.tabSources',
|
||
|
|
connections: 'integrations.tabConnections',
|
||
|
|
[CHARTS_PAGE.TABS.PRICE]: 'signals.tabPrice',
|
||
|
|
[CHARTS_PAGE.TABS.POWERLAW]: 'signals.tabPl',
|
||
|
|
[CHARTS_PAGE.TABS.WMA]: 'signals.tabWma',
|
||
|
|
[CHARTS_PAGE.TABS.MAYER]: 'signals.tabMayer',
|
||
|
|
});
|
||
|
|
|
||
|
|
export function targetHash(target) {
|
||
|
|
if (!target || !target.route) throw new Error('A target without a route has no address.');
|
||
|
|
return `#/${[target.route, target.tab, target.sub].filter(Boolean).join('/')}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function hashFromLocation(hash) {
|
||
|
|
const pad = String(hash || '').replace(/^#\/?/, '');
|
||
|
|
return pad ? `#/${pad}` : targetHash(TARGETS.HOME);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function commandTooLate(expiresAt, nu = Date.now()) {
|
||
|
|
if (!expiresAt) return false;
|
||
|
|
const einde = Date.parse(expiresAt);
|
||
|
|
if (Number.isNaN(einde)) return false;
|
||
|
|
return einde - nu < CONFIG.COMMANDS.COUNTDOWN_SECONDS * 1000;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function targetTab(target) {
|
||
|
|
if (!target || !target.tab) return null;
|
||
|
|
return [target.tab, target.sub].filter(Boolean).join('/');
|
||
|
|
}
|
||
|
|
|
||
|
|
export const CAPABILITIES = Object.freeze(['spot', 'daily_history', 'history_point']);
|
||
|
|
|
||
|
|
export const SOURCE_CAPABILITIES = Object.freeze(['spot', 'history']);
|
||
|
|
export const SOURCE_ENDPOINTS = Object.freeze({
|
||
|
|
spot: Object.freeze(['spot']),
|
||
|
|
history: Object.freeze(['daily_history', 'history_point']),
|
||
|
|
});
|
||
|
|
export function sourceCapabilityOf(capability) {
|
||
|
|
return SOURCE_CAPABILITIES.find(c => SOURCE_ENDPOINTS[c].includes(capability)) ?? null;
|
||
|
|
}
|
||
|
|
|
||
|
|
export const OUTPUT_FIELDS = Object.freeze(['time', 'price']);
|
||
|
|
export const TRANSFORMS = Object.freeze(['none', 'ms_to_s', 'to_number', 'last_per_day', 'troy_ounce_to_gram', 'iso_date_to_s', 'ms_to_s_local_close']);
|
||
|
|
export const HTTP_METHODS = Object.freeze(['GET', 'POST']);
|
||
|
|
|
||
|
|
export const ACCOUNT_SOURCE_KINDS = Object.freeze({
|
||
|
|
exchange: Object.freeze([]),
|
||
|
|
wallet: Object.freeze(['xpub', 'address']),
|
||
|
|
lightning: Object.freeze(['xpub', 'address', 'nwc', 'lnd']),
|
||
|
|
card: Object.freeze([]),
|
||
|
|
bank: Object.freeze([]),
|
||
|
|
vault: Object.freeze([]),
|
||
|
|
});
|
||
|
|
|
||
|
|
export const ACCOUNT_INTEREST_KINDS = Object.freeze(['exchange', 'card', 'bank']);
|
||
|
|
|
||
|
|
export const ACCOUNT_ASSET_KINDS = Object.freeze({
|
||
|
|
exchange: Object.freeze(['crypto', 'fiat']),
|
||
|
|
wallet: Object.freeze(['crypto']),
|
||
|
|
lightning: Object.freeze(['crypto']),
|
||
|
|
card: Object.freeze(['fiat']),
|
||
|
|
bank: Object.freeze(['fiat']),
|
||
|
|
vault: Object.freeze(['metal']),
|
||
|
|
});
|
||
|
|
|
||
|
|
export const TAX_CATEGORY_BY_ASSET_KIND = Object.freeze({ crypto: 'overig', metal: 'overig', fiat: 'bank' });
|
||
|
|
|
||
|
|
export const BALANCE_CAPTURE_KINDS = Object.freeze(['fiat']);
|
||
|
|
|
||
|
|
export function balanceCaptureOn(asset) {
|
||
|
|
if (!asset) return false;
|
||
|
|
if (asset.balanceCapture === 0 || asset.balanceCapture === 1) return asset.balanceCapture === 1;
|
||
|
|
return BALANCE_CAPTURE_KINDS.includes(asset.kind);
|
||
|
|
}
|
||
|
|
|
||
|
|
export const CONNECTION_PROVIDER = Object.freeze({ btc_explorer: 'mempool' });
|
||
|
|
|
||
|
|
export const ASSET_DEFINITIONS = Object.freeze({
|
||
|
|
BTC: Object.freeze({ symbol: 'BTC', name: 'Bitcoin', kind: 'crypto', unit: 'BTC', subunit: 'sat', decimals: 8, displayUnit: 'subunit' }),
|
||
|
|
ETH: Object.freeze({ symbol: 'ETH', name: 'Ethereum', kind: 'crypto', unit: 'ETH', subunit: null, decimals: 8, displayUnit: 'unit' }),
|
||
|
|
XAU: Object.freeze({ symbol: 'XAU', name: 'Goud', kind: 'metal', unit: 'ozt', subunit: 'g', decimals: 7, displayUnit: 'unit' }),
|
||
|
|
XAG: Object.freeze({ symbol: 'XAG', name: 'Zilver', kind: 'metal', unit: 'ozt', subunit: 'g', decimals: 7, displayUnit: 'unit' }),
|
||
|
|
});
|
||
|
|
|
||
|
|
export const PREFERENCES = Object.freeze({
|
||
|
|
app: Object.freeze({
|
||
|
|
theme: Object.freeze({ type: 'enum', allowed: ENUMS.THEME, initial: 'dark' }),
|
||
|
|
accent: Object.freeze({ type: 'enum', allowed: ENUMS.ACCENT, initial: 'blue' }),
|
||
|
|
depth: Object.freeze({ type: 'enum', allowed: ENUMS.DEPTH, initial: 'deep' }),
|
||
|
|
profile: Object.freeze({ type: 'enum', allowed: ENUMS.PROFILE, initial: 'business' }),
|
||
|
|
glow: Object.freeze({ type: 'enum', allowed: ENUMS.GLOW, initial: 'on' }),
|
||
|
|
glowshape: Object.freeze({ type: 'enum', allowed: ENUMS.GLOW_SHAPE, initial: 'blobs' }),
|
||
|
|
motion: Object.freeze({ type: 'enum', allowed: ENUMS.MOTION, initial: 'step' }),
|
||
|
|
rowsel: Object.freeze({ type: 'enum', allowed: ENUMS.ROW_SELECTION, initial: 'light' }),
|
||
|
|
glow_tempo: Object.freeze({ type: 'enum', allowed: ENUMS.GLOW_TEMPO, initial: 'calm' }),
|
||
|
|
glass_transparency: Object.freeze({ type: 'int', allowed: null, initial: 65, min: 0, max: 100 }),
|
||
|
|
glass_frost: Object.freeze({ type: 'int', allowed: null, initial: 3, min: 0, max: 50 }),
|
||
|
|
glass_blur: Object.freeze({ type: 'int', allowed: null, initial: 30, min: 0, max: 100 }),
|
||
|
|
glass_render: Object.freeze({ type: 'enum', allowed: ENUMS.GLASS_RENDER, initial: 'mimic' }),
|
||
|
|
blur: Object.freeze({ type: 'bool', allowed: null, initial: 0 }),
|
||
|
|
auto_blur_min: Object.freeze({ type: 'int', allowed: null, initial: 5, min: 1, max: CONFIG.AUTO_BLUR.NEVER_MIN }),
|
||
|
|
asset_show_name: Object.freeze({ type: 'bool', allowed: null, initial: 1 }),
|
||
|
|
asset_show_symbol: Object.freeze({ type: 'bool', allowed: null, initial: 1 }),
|
||
|
|
show_page_title: Object.freeze({ type: 'bool', allowed: null, initial: 1 }),
|
||
|
|
show_page_hint: Object.freeze({ type: 'bool', allowed: null, initial: 1 }),
|
||
|
|
kiosk: Object.freeze({ type: 'bool', allowed: null, initial: 0 }),
|
||
|
|
last_backup_at: Object.freeze({ type: 'string', allowed: null, initial: '' }),
|
||
|
|
last_health_check_at: Object.freeze({ type: 'string', allowed: null, initial: '' }),
|
||
|
|
start_year: Object.freeze({ type: 'int', allowed: null, initial: CONFIG.START_YEAR.AUTO, min: CONFIG.START_YEAR.AUTO, max: CONFIG.START_YEAR.MAX }),
|
||
|
|
mwr_method: Object.freeze({ type: 'enum', allowed: ENUMS.MWR_METHOD, initial: 'dietz' }),
|
||
|
|
repeat_entry_key: Object.freeze({ type: 'enum', allowed: ENUMS.REPEAT_ENTRY_KEY, initial: 'ctrl_enter' }),
|
||
|
|
persona: Object.freeze({ type: 'enum', allowed: ENUMS.PERSONA, initial: 'bitcoin' }),
|
||
|
|
setup_done: Object.freeze({ type: 'bool', allowed: null, initial: 0 }),
|
||
|
|
market_spot: Object.freeze({ type: 'enum', allowed: ENUMS.MARKET_SPOT, initial: 'off' }),
|
||
|
|
result_signs: Object.freeze({ type: 'enum', allowed: ENUMS.RESULT_SIGNS, initial: 'negative' }),
|
||
|
|
}),
|
||
|
|
|
||
|
|
timers: Object.freeze({
|
||
|
|
spot_refresh_seconds: Object.freeze({ type: 'int', allowed: null, initial: CONFIG.TIMERS.SPOT_REFRESH.MIN_SECONDS, min: CONFIG.TIMERS.SPOT_REFRESH.MIN_SECONDS, max: CONFIG.TIMERS.OFF_SECONDS }),
|
||
|
|
bus_poll_seconds: Object.freeze({ type: 'int', allowed: null, initial: CONFIG.TIMERS.BUS_POLL.MIN_SECONDS, min: CONFIG.TIMERS.BUS_POLL.MIN_SECONDS, max: CONFIG.TIMERS.OFF_SECONDS }),
|
||
|
|
}),
|
||
|
|
|
||
|
|
ai: Object.freeze({
|
||
|
|
enabled: Object.freeze({ type: 'bool', allowed: null, initial: 1 }),
|
||
|
|
allow_read: Object.freeze({ type: 'bool', allowed: null, initial: 1 }),
|
||
|
|
allow_raw: Object.freeze({ type: 'bool', allowed: null, initial: 0 }),
|
||
|
|
allow_write: Object.freeze({ type: 'bool', allowed: null, initial: 0 }),
|
||
|
|
seen_call_id: Object.freeze({ type: 'int', allowed: null, initial: 0, min: 0 }),
|
||
|
|
allow_control: Object.freeze({ type: 'bool', allowed: null, initial: 0 }),
|
||
|
|
|
||
|
|
bucket_shares: Object.freeze({ type: 'bool', allowed: null, initial: 1 }),
|
||
|
|
bucket_totals: Object.freeze({ type: 'bool', allowed: null, initial: 1 }),
|
||
|
|
bucket_amounts: Object.freeze({ type: 'bool', allowed: null, initial: 1 }),
|
||
|
|
bucket_quantities: Object.freeze({ type: 'bool', allowed: null, initial: 1 }),
|
||
|
|
}),
|
||
|
|
|
||
|
|
});
|
||
|
|
|
||
|
|
export const NETWORK = Object.freeze({ MAX_SUPPLY_BTC: 21_000_000 });
|
||
|
|
|
||
|
|
export const METRICS = Object.freeze({
|
||
|
|
WINDOW_MINUTES: 60,
|
||
|
|
FAIL_WARN: 0.05,
|
||
|
|
FAIL_BAD: 0.20,
|
||
|
|
});
|
||
|
|
|
||
|
|
export const CLOCKS = Object.freeze({
|
||
|
|
LIST_MAX: 4,
|
||
|
|
OPENING_WINDOW_MINUTES: 15,
|
||
|
|
CITIES: Object.freeze([
|
||
|
|
Object.freeze({ key: 'amsterdam', name: 'Amsterdam', tz: 'Europe/Amsterdam' }),
|
||
|
|
Object.freeze({ key: 'london', name: 'Londen', tz: 'Europe/London' }),
|
||
|
|
Object.freeze({ key: 'frankfurt', name: 'Frankfurt', tz: 'Europe/Berlin' }),
|
||
|
|
Object.freeze({ key: 'newyork', name: 'New York', tz: 'America/New_York' }),
|
||
|
|
Object.freeze({ key: 'losangeles', name: 'Los Angeles', tz: 'America/Los_Angeles' }),
|
||
|
|
Object.freeze({ key: 'saopaulo', name: 'São Paulo', tz: 'America/Sao_Paulo' }),
|
||
|
|
Object.freeze({ key: 'tokyo', name: 'Tokyo', tz: 'Asia/Tokyo' }),
|
||
|
|
Object.freeze({ key: 'hongkong', name: 'Hongkong', tz: 'Asia/Hong_Kong' }),
|
||
|
|
Object.freeze({ key: 'shanghai', name: 'Shanghai', tz: 'Asia/Shanghai' }),
|
||
|
|
Object.freeze({ key: 'singapore', name: 'Singapore', tz: 'Asia/Singapore' }),
|
||
|
|
Object.freeze({ key: 'dubai', name: 'Dubai', tz: 'Asia/Dubai' }),
|
||
|
|
Object.freeze({ key: 'mumbai', name: 'Mumbai', tz: 'Asia/Kolkata' }),
|
||
|
|
Object.freeze({ key: 'sydney', name: 'Sydney', tz: 'Australia/Sydney' }),
|
||
|
|
]),
|
||
|
|
EXCHANGES: Object.freeze([
|
||
|
|
Object.freeze({ key: 'tse', name: 'Tokyo (TSE)', tz: 'Asia/Tokyo', open: '09:00', close: '15:00', days: [1, 2, 3, 4, 5] }),
|
||
|
|
Object.freeze({ key: 'hkex', name: 'Hongkong (HKEX)', tz: 'Asia/Hong_Kong', open: '09:30', close: '16:00', days: [1, 2, 3, 4, 5] }),
|
||
|
|
Object.freeze({ key: 'sse', name: 'Shanghai (SSE)', tz: 'Asia/Shanghai', open: '09:30', close: '15:00', days: [1, 2, 3, 4, 5] }),
|
||
|
|
Object.freeze({ key: 'lse', name: 'Londen (LSE)', tz: 'Europe/London', open: '08:00', close: '16:30', days: [1, 2, 3, 4, 5] }),
|
||
|
|
Object.freeze({ key: 'euronext', name: 'Euronext (AMS)', tz: 'Europe/Amsterdam', open: '09:00', close: '17:30', days: [1, 2, 3, 4, 5] }),
|
||
|
|
Object.freeze({ key: 'xetra', name: 'Xetra (FRA)', tz: 'Europe/Berlin', open: '09:00', close: '17:30', days: [1, 2, 3, 4, 5] }),
|
||
|
|
Object.freeze({ key: 'nyse', name: 'New York (NYSE)', tz: 'America/New_York', open: '09:30', close: '16:00', days: [1, 2, 3, 4, 5] }),
|
||
|
|
Object.freeze({ key: 'nasdaq', name: 'Nasdaq', tz: 'America/New_York', open: '09:30', close: '16:00', days: [1, 2, 3, 4, 5] }),
|
||
|
|
]),
|
||
|
|
});
|
||
|
|
|
||
|
|
export const DASHBOARD = Object.freeze({
|
||
|
|
GRID_COLUMNS: 12,
|
||
|
|
MIN_SPAN: 1,
|
||
|
|
MAX_SPAN: 12,
|
||
|
|
MIN_H: 1,
|
||
|
|
MAX_H: 16,
|
||
|
|
HEIGHT_STEP_REM: 3,
|
||
|
|
SETTLE_MS: 250,
|
||
|
|
LONG_PRESS_MS: 500,
|
||
|
|
LONG_PRESS_SLOP_PX: 6,
|
||
|
|
HEIGHT_MODEL: 6,
|
||
|
|
LAYOUT_MODEL: 1,
|
||
|
|
SKELETON_STEPS: 2,
|
||
|
|
REVEAL_MAX_MS: 4000,
|
||
|
|
SIZE_SPAN: Object.freeze({ s: 3, m: 4, l: 6, xl: 12 }),
|
||
|
|
WIDGETS: Object.freeze({
|
||
|
|
signal: Object.freeze({ defaultSpan: 4, instanceable: false, persona: 'bitcoin', label_key: 'dashboard.widget.signal', defaultH: 4 }),
|
||
|
|
notifications: Object.freeze({ defaultSpan: 4, instanceable: false, persona: null, label_key: 'dashboard.widget.notifications', defaultH: 3 }),
|
||
|
|
standing: Object.freeze({ defaultSpan: 4, instanceable: false, persona: null, label_key: 'dashboard.widget.standing', defaultH: 3 }),
|
||
|
|
metrics: Object.freeze({ defaultSpan: 3, instanceable: false, persona: null, label_key: 'dashboard.widget.metrics', defaultH: 1 }),
|
||
|
|
netshare: Object.freeze({ defaultSpan: 3, instanceable: false, persona: 'bitcoin', label_key: 'dashboard.widget.netshare', defaultH: 2 }),
|
||
|
|
spot: Object.freeze({ defaultSpan: 2, instanceable: true, persona: null, label_key: 'dashboard.widget.spot', defaultH: 2 }),
|
||
|
|
clocks: Object.freeze({ defaultSpan: 4, instanceable: false, persona: null, label_key: 'dashboard.widget.clocks', defaultH: 5 }),
|
||
|
|
return: Object.freeze({ defaultSpan: 4, instanceable: true, persona: null, label_key: 'dashboard.widget.return', defaultH: 4 }),
|
||
|
|
}),
|
||
|
|
DEFAULT_LAYOUT: Object.freeze([
|
||
|
|
Object.freeze({ instId: 'notifications', type: 'notifications', x: 8 }),
|
||
|
|
]),
|
||
|
|
});
|
||
|
|
|
||
|
|
export function assertConfig() {
|
||
|
|
for (const cap of CAPABILITIES) {
|
||
|
|
if (SOURCE_CAPABILITIES.filter(c => SOURCE_ENDPOINTS[c].includes(cap)).length !== 1) {
|
||
|
|
throw new Error(`Capability '${cap}' must fall under exactly one source choice. Startup aborted.`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for (const kind of ENUMS.ONCHAIN_SOURCE_KIND) {
|
||
|
|
if (!ENUMS.WALLET_SOURCE_KIND.includes(kind)) {
|
||
|
|
throw new Error(`ONCHAIN_SOURCE_KIND '${kind}' is not in WALLET_SOURCE_KIND. Startup aborted.`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for (const kind of ACCOUNT_INTEREST_KINDS) {
|
||
|
|
if (!ENUMS.ACCOUNT_KIND.includes(kind)) {
|
||
|
|
throw new Error(`ACCOUNT_INTEREST_KINDS '${kind}' is not an account kind. Startup aborted.`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for (const kind of Object.keys(ACCOUNT_SOURCE_KINDS)) {
|
||
|
|
if (!ENUMS.ACCOUNT_KIND.includes(kind)) {
|
||
|
|
throw new Error(`ACCOUNT_SOURCE_KINDS '${kind}' is not an account kind. Startup aborted.`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for (const kind of ENUMS.ACCOUNT_KIND) {
|
||
|
|
const soorten = ACCOUNT_ASSET_KINDS[kind];
|
||
|
|
if (!soorten || !soorten.length || soorten.some(s => !ENUMS.ASSET_KIND.includes(s))) {
|
||
|
|
throw new Error(`ACCOUNT_ASSET_KINDS '${kind}' is missing or names an unknown asset kind. Startup aborted.`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (!CONFIG.PORTFOLIO.BASE_CURRENCIES.includes(CONFIG.PORTFOLIO.DEFAULT_BASE_CURRENCY)) {
|
||
|
|
throw new Error(`PORTFOLIO.DEFAULT_BASE_CURRENCY '${CONFIG.PORTFOLIO.DEFAULT_BASE_CURRENCY}' is not in BASE_CURRENCIES. Startup aborted.`);
|
||
|
|
}
|
||
|
|
const essential = [
|
||
|
|
['PRICE_SOURCES.MEMPOOL_PUBLIC_BASE', CONFIG.PRICE_SOURCES.MEMPOOL_PUBLIC_BASE],
|
||
|
|
['PRICE_SOURCES.BITVAVO_BASE', CONFIG.PRICE_SOURCES.BITVAVO_BASE],
|
||
|
|
['PRICE_SOURCES.COINGECKO_BASE', CONFIG.PRICE_SOURCES.COINGECKO_BASE],
|
||
|
|
['PRICE_SOURCES.GOLDAPI_BASE', CONFIG.PRICE_SOURCES.GOLDAPI_BASE],
|
||
|
|
['PRICE_SOURCES.EULERPOOL_BASE', CONFIG.PRICE_SOURCES.EULERPOOL_BASE],
|
||
|
|
['PRICE_SOURCES.METALSDEV_BASE', CONFIG.PRICE_SOURCES.METALSDEV_BASE],
|
||
|
|
['PRICE_SOURCES.FRANKFURTER_BASE', CONFIG.PRICE_SOURCES.FRANKFURTER_BASE],
|
||
|
|
['METALS.GRAMS_PER_TROY_OUNCE', CONFIG.METALS.GRAMS_PER_TROY_OUNCE],
|
||
|
|
['METALS.SPOT_DEVIATION_WARN', CONFIG.METALS.SPOT_DEVIATION_WARN],
|
||
|
|
['CHART_LIBS.CHARTJS', CONFIG.CHART_LIBS.CHARTJS],
|
||
|
|
['CHART_LIBS.HAMMER', CONFIG.CHART_LIBS.HAMMER],
|
||
|
|
['CHART_LIBS.ZOOM', CONFIG.CHART_LIBS.ZOOM],
|
||
|
|
['SCHEMA_VERSION', CONFIG.SCHEMA_VERSION],
|
||
|
|
['MAX_ASSET_DECIMALS', CONFIG.MAX_ASSET_DECIMALS],
|
||
|
|
['PRICE_HISTORY.NEAREST_MAX_SECONDS', CONFIG.PRICE_HISTORY.NEAREST_MAX_SECONDS],
|
||
|
|
['PRICE_HISTORY.CARRY_FORWARD_DAYS.crypto', CONFIG.PRICE_HISTORY.CARRY_FORWARD_DAYS.crypto],
|
||
|
|
['PRICE_HISTORY.RECENT_CLOSE_DAYS', CONFIG.PRICE_HISTORY.RECENT_CLOSE_DAYS],
|
||
|
|
['PRICE_HISTORY.FX_MAX_AGE_DAYS', CONFIG.PRICE_HISTORY.FX_MAX_AGE_DAYS],
|
||
|
|
['API_USAGE.WARN_SHARE', CONFIG.API_USAGE.WARN_SHARE],
|
||
|
|
['START_YEAR.MIN', CONFIG.START_YEAR.MIN],
|
||
|
|
['START_YEAR.MAX', CONFIG.START_YEAR.MAX],
|
||
|
|
['TIMERS.SPOT_REFRESH.MIN_SECONDS', CONFIG.TIMERS.SPOT_REFRESH.MIN_SECONDS],
|
||
|
|
['TIMERS.BUS_POLL.MIN_SECONDS', CONFIG.TIMERS.BUS_POLL.MIN_SECONDS],
|
||
|
|
['BUS.RETENTION_HOURS', CONFIG.BUS.RETENTION_HOURS],
|
||
|
|
['COMMANDS.CLAIM_SECONDS', CONFIG.COMMANDS.CLAIM_SECONDS],
|
||
|
|
['COMMANDS.COUNTDOWN_SECONDS', CONFIG.COMMANDS.COUNTDOWN_SECONDS],
|
||
|
|
['TIMERS.OFF_SECONDS', CONFIG.TIMERS.OFF_SECONDS],
|
||
|
|
['LND.REQUEST_TIMEOUT_MS', CONFIG.LND.REQUEST_TIMEOUT_MS],
|
||
|
|
['LND.CLIENT_TIMEOUT_MS', CONFIG.LND.CLIENT_TIMEOUT_MS],
|
||
|
|
['NWC.REQUEST_TIMEOUT_MS', CONFIG.NWC.REQUEST_TIMEOUT_MS],
|
||
|
|
['NWC.CLIENT_TIMEOUT_MS', CONFIG.NWC.CLIENT_TIMEOUT_MS],
|
||
|
|
['MARKET_SERVICE.SETTINGS_FILE_NAME', CONFIG.MARKET_SERVICE.SETTINGS_FILE_NAME],
|
||
|
|
['MARKET_SERVICE.URL', CONFIG.MARKET_SERVICE.URL],
|
||
|
|
['LICENSE.FREE_FILE_NAME', CONFIG.LICENSE.FREE_FILE_NAME],
|
||
|
|
['MARKET_SERVICE.APP_NAME', CONFIG.MARKET_SERVICE.APP_NAME],
|
||
|
|
['MARKET_SERVICE.REQUEST_TIMEOUT_MS', CONFIG.MARKET_SERVICE.REQUEST_TIMEOUT_MS],
|
||
|
|
['MARKET_SERVICE.CLIENT_TIMEOUT_MS', CONFIG.MARKET_SERVICE.CLIENT_TIMEOUT_MS],
|
||
|
|
['MARKET_SERVICE.REGISTER_TTL_MS', CONFIG.MARKET_SERVICE.REGISTER_TTL_MS],
|
||
|
|
['MARKET_SERVICE.MAX_SERIES_PAGES', CONFIG.MARKET_SERVICE.MAX_SERIES_PAGES],
|
||
|
|
['MARKET_SERVICE.INTRADAY_MAX_DAYS', CONFIG.MARKET_SERVICE.INTRADAY_MAX_DAYS],
|
||
|
|
['MARKET_SERVICE.HISTORY_MAX_DAYS', CONFIG.MARKET_SERVICE.HISTORY_MAX_DAYS],
|
||
|
|
['MARKET_SERVICE.PROVIDER_KEY', CONFIG.MARKET_SERVICE.PROVIDER_KEY],
|
||
|
|
['MARKET_SERVICE.SPOT_TTL_MS', CONFIG.MARKET_SERVICE.SPOT_TTL_MS],
|
||
|
|
['MARKET_SERVICE.MAX_INSTRUMENTS_PER_SPOT', CONFIG.MARKET_SERVICE.MAX_INSTRUMENTS_PER_SPOT],
|
||
|
|
['AI.SERVER_NAME', CONFIG.AI.SERVER_NAME],
|
||
|
|
['AI.SERVER_VERSION', CONFIG.AI.SERVER_VERSION],
|
||
|
|
['AI.PROTOCOL_VERSION', CONFIG.AI.PROTOCOL_VERSION],
|
||
|
|
['AI.AUDIT_PAGE_SIZE', CONFIG.AI.AUDIT_PAGE_SIZE],
|
||
|
|
['AI.PROPOSAL_PAGE_SIZE', CONFIG.AI.PROPOSAL_PAGE_SIZE],
|
||
|
|
['AI.LIMITS.MAX_ROWS_PER_LIST', CONFIG.AI.LIMITS.MAX_ROWS_PER_LIST],
|
||
|
|
['AI.LIMITS.MAX_TEXT_CHARS', CONFIG.AI.LIMITS.MAX_TEXT_CHARS],
|
||
|
|
['AI.LIMITS.MAX_LOG_CHARS', CONFIG.AI.LIMITS.MAX_LOG_CHARS],
|
||
|
|
['AI.LIMITS.METRICS_WINDOW_HOURS', CONFIG.AI.LIMITS.METRICS_WINDOW_HOURS],
|
||
|
|
['AUTH.SESSION_TTL_MS', CONFIG.AUTH.SESSION_TTL_MS],
|
||
|
|
['AUTH.COOKIE_NAME', CONFIG.AUTH.COOKIE_NAME],
|
||
|
|
['AUTH.LOGIN_PATH', CONFIG.AUTH.LOGIN_PATH],
|
||
|
|
['AUTH.LOGIN_ATTEMPTS.FREE', CONFIG.AUTH.LOGIN_ATTEMPTS.FREE],
|
||
|
|
['AUTH.LOGIN_ATTEMPTS.BASE_LOCK_MS', CONFIG.AUTH.LOGIN_ATTEMPTS.BASE_LOCK_MS],
|
||
|
|
['AUTH.LOGIN_ATTEMPTS.MAX_LOCK_MS', CONFIG.AUTH.LOGIN_ATTEMPTS.MAX_LOCK_MS],
|
||
|
|
];
|
||
|
|
for (const [name, val] of essential) {
|
||
|
|
if (val === undefined || val === null || val === '') {
|
||
|
|
throw new Error(`Essential config missing: ${name}. Startup aborted.`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|