Versie 0.2.0
This commit is contained in:
commit
e77d8fd373
495 files changed
+81026
No files matched your search
@@ -0,0 +1,871 @@
|
||||
// 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.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user