From 0bffa8e365104c3d84ef59443dddd84fc77755b5 Mon Sep 17 00:00:00 2001 From: Frank Jia Date: Sun, 15 Mar 2026 21:08:12 -0700 Subject: [PATCH 1/8] Some comments --- skills/nado/SKILL.md | 7 +++++++ src/commands/auth.ts | 18 ++++++++++++++++++ src/commands/market.ts | 2 ++ src/commands/trade.ts | 5 +++++ src/config.ts | 13 +++++++++++++ src/context.ts | 1 + src/handlers/account.ts | 9 +++++++++ src/handlers/funds.ts | 5 +++++ src/handlers/market.ts | 12 ++++++++++++ src/handlers/trade.ts | 16 ++++++++++++++++ src/output/orderbook.ts | 1 + src/utils/confirm.ts | 1 + src/utils/orderBuilder.ts | 20 ++++++++++++++++++++ src/utils/resolveMarket.ts | 8 ++++++++ src/ws/subscribe.ts | 1 + 15 files changed, 119 insertions(+) diff --git a/skills/nado/SKILL.md b/skills/nado/SKILL.md index 12375c2..ea1cba3 100644 --- a/skills/nado/SKILL.md +++ b/skills/nado/SKILL.md @@ -130,11 +130,14 @@ Spot products have **even** IDs (0, 2, 4…). Perp products have **odd** IDs (1, Markets can be referenced by symbol (`BTC`, `ETH`), full name (`BTC-PERP`, `ETH`), or numeric product ID. The `long`/`short` commands auto-resolve to perp markets; `buy`/`sell` auto-resolve to spot markets. ```bash +# nit - this reads very weirdly, `nado market list`? nado market markets # List all product IDs and names ``` ## Quick Command Reference +# is it a requirement that we need to list every command? can AI just figure it out? + ### Auth & Configuration ```bash @@ -169,6 +172,8 @@ All account commands use your configured wallet. Override with `--owner
{ const currentKey = toml.credentials?.private_key; const keyDisplay = currentKey ? 'set — press Enter to keep' : 'not set'; + // NOTE: `rl` is closed here before `askSecret` so that `setRawMode` can take + // over stdin. Afterwards a new `rl2` instance is created for the final prompt. + // This split-readline approach is fragile: if `askSecret` throws, `rl2` is + // never created or closed. Consider refactoring to use a single readline + // instance throughout, or use a dedicated secret-input library. rl.close(); const keyInput = await askSecret( `Private key ${chalk.dim(`[${keyDisplay}]`)}: `, @@ -260,6 +265,10 @@ function askSecret(prompt: string): Promise { process.stdout.write(prompt); const buf: Buffer[] = []; const wasRaw = process.stdin.isRaw; + // NOTE: `setRawMode` throws if stdin is not a TTY (e.g. when piping input + // or running in CI). There is no guard here, so the process will crash with + // an unhandled exception in non-interactive environments. Consider checking + // `process.stdin.isTTY` first and falling back to a regular readline prompt. process.stdin.setRawMode(true); process.stdin.resume(); @@ -433,6 +442,11 @@ async function setup1ct(): Promise { console.log(chalk.green(' Already linked! ✓')); autoLinked = true; } else if ( + // NOTE: After the `===` check above has already excluded the equal case, + // the second condition `linked.signer.toLowerCase() !== signerAddress.toLowerCase()` + // is always true here (it is the logical complement of the already-handled + // branch). The `linked.signer === ZERO_ADDR` sub-condition is therefore + // redundant — the block runs regardless. Simplify to a plain `else`. linked.signer === ZERO_ADDR || linked.signer.toLowerCase() !== signerAddress.toLowerCase() ) { @@ -462,6 +476,10 @@ async function setup1ct(): Promise { const account = privateKeyToAccount(hotPrivateKey as Address); signerAddress = account.address; + // NOTE: The generated private key is printed to stdout (not stderr). This + // means it can be captured by shell output redirection, CI log scrapers, + // or terminal scrollback. Consider printing it to stderr, or at least + // displaying a strong warning that it should be treated as sensitive. console.log( `\n Generated hot private key: ${chalk.cyan(hotPrivateKey)}`, ); diff --git a/src/commands/market.ts b/src/commands/market.ts index 26b9b00..91bff5f 100644 --- a/src/commands/market.ts +++ b/src/commands/market.ts @@ -19,6 +19,8 @@ export function createMarketCommand(): Command { market .command('price') .description('Get best bid/ask for a market') + // We never really call a perp ETH-PERP on the FE, the backend returned endpoint uses market names from the old Vertex days and was never changed + // We could ask them to change it to ETHUSDT0 etc if we want .argument('', 'Symbol, name, or product ID (e.g. BTC, ETH-PERP, 2)') .action(async (marketArg: string, _opts: unknown, cmd: Command) => { await run(cmd, async (ctx) => { diff --git a/src/commands/trade.ts b/src/commands/trade.ts index 86bfaaa..855aa1b 100644 --- a/src/commands/trade.ts +++ b/src/commands/trade.ts @@ -80,6 +80,11 @@ export function createTradeCommand(): Command { trade .command('close-all') .description('Close all open perp positions') + // NOTE: `--side` accepts free-form text with no `.choices(['long', 'short'])` + // constraint. An invalid value like `--side both` would pass silently and be + // cast to `'long' | 'short' | undefined` without runtime validation. + // Consider adding `.choices(['long', 'short'])` via an `addOption(new Option...)` + // call to enforce valid values at the CLI layer. .option('-s, --side ', 'Filter: close only long or short positions') .option( '--product-ids ', diff --git a/src/config.ts b/src/config.ts index 2797917..38f94a7 100644 --- a/src/config.ts +++ b/src/config.ts @@ -45,6 +45,7 @@ export interface TomlConfig { credentials?: { private_key?: string; subaccount_owner?: string; + // why do we need this to be a separate config? testnet?: { private_key?: string; subaccount_owner?: string; @@ -60,6 +61,7 @@ export function loadTomlConfig(): TomlConfig { const raw = fs.readFileSync(CONFIG_PATH, 'utf-8'); return TOML.parse(raw) as TomlConfig; } catch { + // nit: could perhaps log the error return {}; } } @@ -68,6 +70,10 @@ export function saveTomlConfig(config: TomlConfig): void { ensureConfigDir(); const lines: string[] = []; + // NOTE: This is a manual TOML serializer using string interpolation. + // If any value contains a double-quote character (e.g. a subaccount name + // like my"name), the resulting file will be invalid TOML. Consider using a + // proper TOML serialization library (e.g. @iarna/toml or smol-toml). lines.push('[default]'); lines.push(`data_env = "${config.default?.data_env ?? 'nadoMainnet'}"`); lines.push( @@ -123,6 +129,10 @@ export function loadConfig(flags: CliFlags = {}): CliConfig { const envConfig = getDataEnvConfig(dataEnv); const testnetCreds = toml.credentials?.testnet; + // NOTE: When dataEnv is 'nadoTestnet', the testnet credentials fall back to + // the top-level mainnet credentials if no testnet-specific ones exist. + // This could lead to accidentally signing testnet transactions with a + // mainnet private key. Intentional? Worth making explicit or disabling fallback. const tomlKey = isTestnet ? (testnetCreds?.private_key ?? toml.credentials?.private_key) : toml.credentials?.private_key; @@ -158,6 +168,9 @@ function resolveFormat(flag?: string, env?: string): OutputFormat { const raw = flag ?? env; if (raw === 'json') return 'json'; if (raw === 'table') return 'table'; + // NOTE: Any unrecognized value (e.g. NADO_FORMAT=csv) falls through silently + // to the TTY auto-detect instead of throwing an error. Consider warning the + // user if raw is set but doesn't match 'json' or 'table'. return process.stdout.isTTY ? 'table' : 'json'; } diff --git a/src/context.ts b/src/context.ts index a016c1e..66e7013 100644 --- a/src/context.ts +++ b/src/context.ts @@ -39,6 +39,7 @@ export function createNadoContext(config: ServerConfig): NadoContext { const rpcUrl = config.rpcUrl ?? chain.rpcUrls.default.http[0]; const publicClient = createPublicClient({ transport: http(rpcUrl) }); + // nit: private keys are `Hex`, not `Address` const account = config.privateKey ? privateKeyToAccount(config.privateKey as Address) : undefined; diff --git a/src/handlers/account.ts b/src/handlers/account.ts index 3929d1b..55f9437 100644 --- a/src/handlers/account.ts +++ b/src/handlers/account.ts @@ -67,6 +67,7 @@ export async function getSubaccountSummary( const rows = nonZeroBalances.map((b) => { const symbol = symbolByProductId.get(b.productId) ?? '-'; const isLong = b.amount.isPositive(); + // removeDecimals for ops like this const absAmount = b.amount.abs().dividedBy(1e18); const value = absAmount.times(b.oraclePrice); return [ @@ -123,6 +124,9 @@ export async function getFeeRates( // --------------------------------------------------------------------------- const PAGE_SIZE = 500; +// NOTE: MAX_PAGES caps the fetch at 20,000 events. If the user has more history, +// it is silently truncated with no warning to the user. Consider logging a +// warning when the page limit is reached so users know the stats are incomplete. const MAX_PAGES = 40; const SECONDS_PER_DAY = 86_400; @@ -231,6 +235,9 @@ async function fetchSnapshotVolume( return currentVolume - historicalVolume; } +// NOTE: This rounds to 2 decimal places, which is appropriate for USD display +// but loses precision for small-cap tokens where amounts might be e.g. 0.00123. +// Stats like fees or PnL on low-value trades could display as $0.00. function round(n: number): number { return Math.round(n * 100) / 100; } @@ -266,6 +273,7 @@ function computeStats( const ts = Number(event.timestamp); if (ts < cutoff) continue; + // Same as on MCP, these calcs may not be accurate with `Number` const quote = Math.abs(Number(event.quoteFilled)) / 1e18; const fee = Number(event.totalFee) / 1e18; const pnl = Number(event.realizedPnl ?? 0) / 1e18; @@ -300,6 +308,7 @@ function computeStats( dayAgg.set(date, d); } + // Same comment as on MCP server, not sure why we need this, and `quote` includes fee when you probably don't want it to const reportedVolume = snapshotVolume != null ? snapshotVolume : totalVolume; const byMarket: MarketStats[] = [...marketAgg.entries()] diff --git a/src/handlers/funds.ts b/src/handlers/funds.ts index 15ab2ee..16f0293 100644 --- a/src/handlers/funds.ts +++ b/src/handlers/funds.ts @@ -109,6 +109,11 @@ export async function transferQuote( const to = args.recipientSubaccountName ?? 'default'; try { + // NOTE: `addDecimals(args.amount)` is called with no second argument, + // so it uses the SDK's default decimal scale (likely 18). USDT0 uses 6 + // decimals, so if the default is 18 this would send 1e12x the intended + // amount. Verify what `addDecimals` defaults to, or pass the explicit + // decimals for the quote token (like `depositCollateral` does above). const data = await ctx.client.spot.transferQuote({ subaccountOwner: ctx.subaccountOwner!, subaccountName: from, diff --git a/src/handlers/market.ts b/src/handlers/market.ts index a0f98dc..c8be8aa 100644 --- a/src/handlers/market.ts +++ b/src/handlers/market.ts @@ -34,6 +34,9 @@ export async function getAllMarkets( String(m.productId), meta?.marketName ?? '-', meta?.symbol ?? '-', + // NOTE: Magic number comparison — use `ProductEngineType.PERP` (imported + // at the top of this file) instead of `=== 1` for readability and safety + // against future enum value changes. m.type === 1 ? 'perp' : 'spot', m.priceIncrement.toFixed(), m.sizeIncrement.toFixed(), @@ -316,6 +319,10 @@ export async function watchOrderbook( price: a.price.toNumber(), size: removeDecimals(a.liquidity).toNumber(), })); + // NOTE: `latestBids` and `latestAsks` are unbounded — as levels are added they + // grow without limit. For high-depth books or long-running sessions this could + // consume significant memory. Consider capping to `args.depth` levels after + // each merge so the arrays stay bounded. function draw() { const output = renderOrderbook(args.marketName, { @@ -350,6 +357,11 @@ export async function watchOrderbook( handle.close(); resolve(); }; + // NOTE: These signal listeners are added to the process but never removed + // after resolution. In the interactive shell, calling `market orderbook --watch` + // multiple times in one session would accumulate SIGINT/SIGTERM listeners, + // eventually triggering Node's MaxListenersExceededWarning. Consider calling + // `process.removeListener('SIGINT', onExit)` after the promise resolves. process.on('SIGINT', onExit); process.on('SIGTERM', onExit); }); diff --git a/src/handlers/trade.ts b/src/handlers/trade.ts index d5326dd..39498ed 100644 --- a/src/handlers/trade.ts +++ b/src/handlers/trade.ts @@ -75,6 +75,11 @@ export async function placeOrder( const orderList = Array.isArray(args.orders) ? args.orders : [args.orders]; try { + // NOTE: Market data (priceIncrement, sizeIncrement) is pre-fetched per unique + // product to avoid N redundant `getAllMarkets` calls inside `buildEngineOrder`. + // However `resolveMarketData` itself still issues an `getAllMarkets` RPC call + // on each invocation (no SDK-level cache). A module-level cache similar to + // the one in resolveMarket.ts would reduce round trips for batch orders. const uniqueProductIds = [...new Set(orderList.map((o) => o.productId))]; const marketDataMap = new Map( await Promise.all( @@ -601,6 +606,7 @@ export type TriggerType = export interface PlaceTriggerOrderArgs { productId: number; + // BalanceSide side: 'long' | 'short'; amount: number; price?: number; @@ -712,8 +718,18 @@ export async function placeTwapOrder( ); const perOrderSigned = isLong ? perOrderAmount : perOrderAmount.negated(); + // NOTE: `totalAmountX18` is derived by multiplying the *rounded* per-order + // amount back out, so it may differ from `args.amount` due to rounding. + // The summary displays `args.amount` (the user's requested total), but the + // protocol receives `totalAmountX18` (the rounded total). For small amounts + // with many sub-orders this discrepancy could be meaningful. const totalAmountX18 = perOrderSigned.times(numOrders); + // NOTE: The TWAP long price is set to `refPrice * 1000` as a sentinel "market + // buy" value — effectively placing a limit order so far above market that it + // fills immediately at whatever the prevailing ask is. For shorts, '0' serves + // the same purpose (always below bid). This is intentional protocol-level + // behaviour but is non-obvious without a comment. const orderPrice = isLong ? roundToIncrement(refPrice.times(1000), priceIncrement).toFixed() : '0'; diff --git a/src/output/orderbook.ts b/src/output/orderbook.ts index f203c27..2c65cb1 100644 --- a/src/output/orderbook.ts +++ b/src/output/orderbook.ts @@ -119,6 +119,7 @@ function padLeft(str: string, width: number): string { return str.padStart(width); } +// Why do we need to guess anything if we can just get the market config via a query? function guessPriceDecimals(levels: OrderbookLevel[]): number { let maxDec = 1; for (const l of levels) { diff --git a/src/utils/confirm.ts b/src/utils/confirm.ts index 1b4078a..7cc69af 100644 --- a/src/utils/confirm.ts +++ b/src/utils/confirm.ts @@ -14,6 +14,7 @@ export function getSharedReadline(): readline.Interface | null { return sharedReadline; } +// Is there no lib for building cli's? /** * Prompts the user for y/n confirmation in the terminal. * Returns true if the user confirms, false otherwise. diff --git a/src/utils/orderBuilder.ts b/src/utils/orderBuilder.ts index bad2f7b..b94c45f 100644 --- a/src/utils/orderBuilder.ts +++ b/src/utils/orderBuilder.ts @@ -99,6 +99,11 @@ async function resolvePrice( const slippageFrac = slippagePct / 100; if (isLong) { + // POTENTIAL BUG: Market buy slippage is applied to the *bid* price, but when + // buying you cross the *ask* (the price sellers are offering). Using + // `bid * (1 + slippage)` sets the limit price near the mid-market, which may + // sit below the ask in wide-spread markets and cause the IOC to miss entirely. + // Consider using `ask * (1 + slippage)` instead for more reliable fills. const bidPrice = marketPrice.bid; if (bidPrice.lte(0)) { throw new Error( @@ -111,6 +116,10 @@ async function resolvePrice( ).toFixed(); } + // POTENTIAL BUG: Market sell slippage is applied to the *ask* price, but when + // selling you cross the *bid* (the price buyers are offering). Using + // `ask * (1 - slippage)` may still sit above the bid in wide-spread markets, + // causing the IOC to miss. Consider using `bid * (1 - slippage)` instead. const askPrice = marketPrice.ask; if (askPrice.lte(0)) { throw new Error( @@ -136,6 +145,7 @@ function resolveIsolatedMargin( throw new Error('leverage is required when marginMode is "isolated".'); } return { + // Number is inaccurate when working with X18. anything doing these ops should be with BigNumber margin: addDecimals(Math.abs((amount * Number(resolvedPrice)) / leverage)), }; } @@ -151,6 +161,7 @@ export interface BuildEngineOrderInput { reduceOnly: boolean; marginMode?: 'cross' | 'isolated'; leverage?: number; + // Why not just cache the loaded mkts? /** Pre-resolved market data to avoid redundant getAllMarkets calls. */ marketData?: MarketData; } @@ -202,6 +213,13 @@ export async function buildEngineOrder( order: { price: resolvedPrice, amount: signedAmountX18.toFixed(0), + // NOTE: `expiration` is set to `Date.now()` (milliseconds since epoch), but + // `calculateTwapExpiration` returns seconds. These two code paths use + // inconsistent units. Does the protocol engine accept milliseconds here, + // or should this be `Math.floor(Date.now() / 1000)`? Worth verifying against + // the SDK contract to ensure orders don't immediately expire. + + // I think this just needs a comment to explain that this is effectively no expiration expiration: Date.now(), nonce: getOrderNonce(), appendix, @@ -272,6 +290,8 @@ export async function buildPriceTriggerOrder( order: { price: resolvedPrice, amount: signedAmountX18.toFixed(0), + // NOTE: Same milliseconds vs. seconds inconsistency as in `buildEngineOrder` + // above — see comment there. expiration: Date.now(), nonce: getOrderNonce(), appendix, diff --git a/src/utils/resolveMarket.ts b/src/utils/resolveMarket.ts index f86cfca..fe34058 100644 --- a/src/utils/resolveMarket.ts +++ b/src/utils/resolveMarket.ts @@ -95,6 +95,8 @@ export async function getMarkets( * Strips a single lowercase wrapper prefix from a token symbol so that * "kbtc" -> "btc", "weth" -> "eth", "kpepe" -> "pepe", etc. * Returns the original string if it doesn't look like a wrapped symbol. + * + * Not sure we need this - we should fuzzy match through all `altSearchTerms` + `marketName` + `symbol`. */ function stripWrapperPrefix(sym: string): string { if (sym.length > 1 && /^[a-z][A-Za-z]/.test(sym)) { @@ -180,6 +182,12 @@ export function resolveProduct( const best = candidates[0]; const samePriority = candidates.filter((c) => c.priority === best.priority); + // NOTE: When `samePriority.length <= 2`, the first candidate is returned + // without further disambiguation. The sort above places the PERP before SPOT + // on ties, so a 2-match case (one perp, one spot) silently returns the perp. + // This is intentional per the JSDoc, but a user typing 'BTC' expecting spot + // could be surprised. Consider logging which market was selected when the + // result is ambiguous between types. if (samePriority.length <= 2) { return samePriority[0].market; } diff --git a/src/ws/subscribe.ts b/src/ws/subscribe.ts index f356793..e435055 100644 --- a/src/ws/subscribe.ts +++ b/src/ws/subscribe.ts @@ -35,6 +35,7 @@ export function subscribeBookDepth( streamParams, ); + // if we continue to expand this project, we should create a `ReconnectingWebsocket`, and perhaps a singleton connection across the entire cli (if processes can run in background) function connect() { if (closed) return; From 6dfae3563311867c179d1a4bb82808ded78c4826 Mon Sep 17 00:00:00 2001 From: przemekink Date: Mon, 16 Mar 2026 11:11:11 +0100 Subject: [PATCH 2/8] address CR comments --- README.md | 7 +- package-lock.json | 4893 ++++++++++++++++++++++ package.json | 2 +- skills/nado/SKILL.md | 25 +- src/__tests__/utils/orderBuilder.test.ts | 37 +- src/commands/account.ts | 9 +- src/commands/auth.ts | 142 +- src/commands/helpers.ts | 24 + src/commands/market.ts | 21 +- src/commands/trade.ts | 46 +- src/config.ts | 74 +- src/context.ts | 4 +- src/handlers/account.ts | 73 +- src/handlers/funds.ts | 5 - src/handlers/market.ts | 64 +- src/handlers/trade.ts | 55 +- src/output/orderbook.ts | 31 +- src/utils/orderBuilder.ts | 72 +- src/utils/resolveMarket.ts | 57 +- src/ws/ReconnectingWebSocket.ts | 106 + src/ws/subscribe.ts | 83 +- 21 files changed, 5380 insertions(+), 450 deletions(-) create mode 100644 package-lock.json create mode 100644 src/ws/ReconnectingWebSocket.ts diff --git a/README.md b/README.md index dd96939..3caac90 100644 --- a/README.md +++ b/README.md @@ -62,9 +62,6 @@ subaccount_name = "default" [credentials] private_key = "0x..." subaccount_owner = "0x..." # only needed when using a linked signer - -[credentials.testnet] # optional override for testnet -private_key = "0x..." ``` ### Environment variables @@ -128,7 +125,7 @@ nado trade trigger BTC long 0.01 --trigger-price 60000 --trigger-type oracle_pri ### Market data ```bash -nado market markets # List all available markets +nado market list # List all available markets nado market price 2 # BTC-PERP best bid/ask nado market prices 2 4 8 # Multiple markets at once nado market orderbook 2 --depth 20 # Orderbook depth @@ -187,7 +184,7 @@ Shell-specific commands: `help`, `clear`, `exit`/`quit`. Spot products have even IDs (0, 2, 4…). Perp products have odd IDs (1, 3, 5…). Product 0 is USDT0 (the quote token). -Use `nado market markets` to list all product IDs. +Use `nado market list` to list all product IDs. ## Development diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..02d49ba --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4893 @@ +{ + "name": "@nadohq/nado-cli", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@nadohq/nado-cli", + "version": "0.1.0", + "dependencies": { + "@iarna/toml": "^2.2.5", + "@nadohq/client": "0.1.0-alpha.51", + "chalk": "^5.0.0", + "cli-table3": "^0.6.0", + "commander": "^13.0.0", + "viem": "^2.47.0", + "ws": "^8.19.0", + "zod": "^4.3.6" + }, + "bin": { + "nado": "dist/index.js" + }, + "devDependencies": { + "@eslint/js": "^9.39.2", + "@types/node": "^24.10.9", + "@types/ws": "^8.18.1", + "@typescript-eslint/eslint-plugin": "^8.54.0", + "@typescript-eslint/parser": "^8.54.0", + "eslint": "^9.39.2", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.5", + "husky": "^9.1.7", + "lint-staged": "^16.3.2", + "prettier": "^3.8.1", + "prettier-plugin-organize-imports": "^4.3.0", + "tsup": "^8.5.1", + "typescript": "^5.9.3", + "typescript-eslint": "^8.54.0", + "vitest": "^4.1.0" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "license": "MIT" + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@emnapi/core": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz", + "integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz", + "integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", + "license": "ISC" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nadohq/client": { + "version": "0.1.0-alpha.51", + "license": "ISC", + "dependencies": { + "@nadohq/engine-client": "0.1.0-alpha.51", + "@nadohq/indexer-client": "0.1.0-alpha.51", + "@nadohq/shared": "0.1.0-alpha.51", + "@nadohq/trigger-client": "0.1.0-alpha.51", + "ts-mixer": "*" + }, + "peerDependencies": { + "viem": "*" + } + }, + "node_modules/@nadohq/engine-client": { + "version": "0.1.0-alpha.51", + "license": "ISC", + "dependencies": { + "@nadohq/shared": "0.1.0-alpha.51", + "axios": "*", + "ts-mixer": "*" + }, + "peerDependencies": { + "viem": "*" + } + }, + "node_modules/@nadohq/indexer-client": { + "version": "0.1.0-alpha.51", + "license": "ISC", + "dependencies": { + "@nadohq/engine-client": "0.1.0-alpha.51", + "@nadohq/shared": "0.1.0-alpha.51", + "axios": "*", + "ts-mixer": "*" + }, + "peerDependencies": { + "viem": "*" + } + }, + "node_modules/@nadohq/shared": { + "version": "0.1.0-alpha.51", + "license": "ISC", + "dependencies": { + "abitype": "^1.2.3" + }, + "peerDependencies": { + "bignumber.js": "^9.3.0", + "viem": "*" + } + }, + "node_modules/@nadohq/trigger-client": { + "version": "0.1.0-alpha.51", + "license": "ISC", + "dependencies": { + "@nadohq/engine-client": "0.1.0-alpha.51", + "@nadohq/shared": "0.1.0-alpha.51", + "axios": "*", + "ts-mixer": "*" + }, + "peerDependencies": { + "viem": "*" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.1", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@oxc-project/runtime": { + "version": "0.115.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.115.0", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.9.tgz", + "integrity": "sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.9", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.9.tgz", + "integrity": "sha512-iwtmmghy8nhfRGeNAIltcNXzD0QMNaaA5U/NyZc1Ia4bxrzFByNMDoppoC+hl7cDiUq5/1CnFthpT9n+UtfFyg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.9.tgz", + "integrity": "sha512-DLFYI78SCiZr5VvdEplsVC2Vx53lnA4/Ga5C65iyldMVaErr86aiqCoNBLl92PXPfDtUYjUh+xFFor40ueNs4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.9.tgz", + "integrity": "sha512-CsjTmTwd0Hri6iTw/DRMK7kOZ7FwAkrO4h8YWKoX/kcj833e4coqo2wzIFywtch/8Eb5enQ/lwLM7w6JX1W5RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.9.tgz", + "integrity": "sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-aOKU9dJheda8Kj8Y3w9gnt9QFOO+qKPAl8SWd7JPHP+Cu0EuDAE5wokQubLzIDQWg2myXq2XhTpOVS07qqvT+w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-OalO94fqj7IWRn3VdXWty75jC5dk4C197AWEuMhIpvVv2lw9fiPhud0+bW2ctCxb3YoBZor71QHbY+9/WToadA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.9.tgz", + "integrity": "sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.9.tgz", + "integrity": "sha512-+6zoiF+RRyf5cdlFQP7nm58mq7+/2PFaY2DNQeD4B87N36JzfF/l9mdBkkmTvSYcYPE8tMh/o3cRlsx1ldLfog==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.9.tgz", + "integrity": "sha512-rgFN6sA/dyebil3YTlL2evvi/M+ivhfnyxec7AccTpRPccno/rPoNlqybEZQBkcbZu8Hy+eqNJCqfBR8P7Pg8g==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.9.tgz", + "integrity": "sha512-lHVNUG/8nlF1IQk1C0Ci574qKYyty2goMiPlRqkC5R+3LkXDkL5Dhx8ytbxq35m+pkHVIvIxviD+TWLdfeuadA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.9.tgz", + "integrity": "sha512-G0oA4+w1iY5AGi5HcDTxWsoxF509hrFIPB2rduV5aDqS9FtDg1CAfa7V34qImbjfhIcA8C+RekocJZA96EarwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.9", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@scure/base": { + "version": "1.2.6", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.12.0", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.57.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/type-utils": "8.57.0", + "@typescript-eslint/utils": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.57.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.57.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.57.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.57.0", + "@typescript-eslint/types": "^8.57.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.57.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.57.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.57.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/utils": "8.57.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.57.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.57.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.57.0", + "@typescript-eslint/tsconfig-utils": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.4", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch/node_modules/brace-expansion": { + "version": "5.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match": { + "version": "4.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.57.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.57.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.0", + "@vitest/utils": "4.1.0", + "chai": "^6.2.2", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.0", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.0", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.0", + "@vitest/utils": "4.1.0", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.0", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/abitype": { + "version": "1.2.3", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.6", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "license": "MIT", + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bundle-require": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-truncate": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "8.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/string-width/node_modules/strip-ansi": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/string-width/node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "13.1.0", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.1.8", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "license": "MIT" + }, + "node_modules/environment": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.4", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.5.5", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.12" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/expect-type": { + "version": "1.3.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.1", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/husky": { + "version": "9.1.7", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/ignore": { + "version": "7.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/isows": { + "version": "1.0.7", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "dev": true, + "license": "MIT" + }, + "node_modules/lint-staged": { + "version": "16.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^14.0.3", + "listr2": "^9.0.5", + "micromatch": "^4.0.8", + "string-argv": "^0.3.2", + "tinyexec": "^1.0.2", + "yaml": "^2.8.2" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "14.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/listr2": { + "version": "9.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mlly": { + "version": "1.8.1", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/onetime": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ox": { + "version": "0.14.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.2.3", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ox/node_modules/eventemitter3": { + "version": "5.0.1", + "license": "MIT" + }, + "node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/prettier-plugin-organize-imports": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "peerDependencies": { + "prettier": ">=2.0", + "typescript": ">=2.9", + "vue-tsc": "^2.1.0 || 3" + }, + "peerDependenciesMeta": { + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.115.0", + "@rolldown/pluginutils": "1.0.0-rc.9" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.9", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.9", + "@rolldown/binding-darwin-x64": "1.0.0-rc.9", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.9", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.9", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.9", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.9", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.9", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.9", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.9", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.9" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slice-ansi": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/string-argv": { + "version": "0.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/synckit": { + "version": "0.11.12", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/ts-mixer": { + "version": "6.0.4", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/tsup": { + "version": "8.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.27.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "^0.7.6", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/tsup/node_modules/tinyexec": { + "version": "0.3.2", + "dev": true, + "license": "MIT" + }, + "node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "devOptional": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.57.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.57.0", + "@typescript-eslint/parser": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/utils": "8.57.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/ufo": { + "version": "1.6.3", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.16.0", + "dev": true, + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/viem": { + "version": "2.47.2", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@noble/curves": "1.9.1", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.2.3", + "isows": "1.0.7", + "ox": "0.14.0", + "ws": "8.18.3" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/viem/node_modules/ws": { + "version": "8.18.3", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/vite": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@oxc-project/runtime": "0.115.0", + "lightningcss": "^1.32.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.9", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.0.0-alpha.31", + "esbuild": "^0.27.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.0", + "@vitest/mocker": "4.1.0", + "@vitest/pretty-format": "4.1.0", + "@vitest/runner": "4.1.0", + "@vitest/snapshot": "4.1.0", + "@vitest/spy": "4.1.0", + "@vitest/utils": "4.1.0", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.0", + "@vitest/browser-preview": "4.1.0", + "@vitest/browser-webdriverio": "4.1.0", + "@vitest/ui": "4.1.0", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi/node_modules/string-width/node_modules/emoji-regex": { + "version": "10.6.0", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ws": { + "version": "8.19.0", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yaml": { + "version": "2.8.2", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json index 56035ea..4f13b86 100644 --- a/package.json +++ b/package.json @@ -41,11 +41,11 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@iarna/toml": "^2.2.5", "@nadohq/client": "0.1.0-alpha.51", "chalk": "^5.0.0", "cli-table3": "^0.6.0", "commander": "^13.0.0", - "toml": "^3.0.0", "viem": "^2.47.0", "ws": "^8.19.0", "zod": "^4.3.6" diff --git a/skills/nado/SKILL.md b/skills/nado/SKILL.md index ea1cba3..ad366ca 100644 --- a/skills/nado/SKILL.md +++ b/skills/nado/SKILL.md @@ -81,9 +81,6 @@ subaccount_name = "default" [credentials] private_key = "0x..." subaccount_owner = "0x..." # only needed when using a linked signer - -[credentials.testnet] # optional override for testnet -private_key = "0x..." ``` Or use the interactive configurator: @@ -130,8 +127,7 @@ Spot products have **even** IDs (0, 2, 4…). Perp products have **odd** IDs (1, Markets can be referenced by symbol (`BTC`, `ETH`), full name (`BTC-PERP`, `ETH`), or numeric product ID. The `long`/`short` commands auto-resolve to perp markets; `buy`/`sell` auto-resolve to spot markets. ```bash -# nit - this reads very weirdly, `nado market list`? -nado market markets # List all product IDs and names +nado market list # List all product IDs and names ``` ## Quick Command Reference @@ -153,7 +149,7 @@ nado setup # 1-click trading setup wizard ### Market Data ```bash -nado market markets # List all available markets +nado market list # List all available markets nado market price BTC # Best bid/ask for one market nado market prices BTC ETH SOL # Bid/ask for multiple markets nado market orderbook BTC --depth 20 # Orderbook depth @@ -172,9 +168,7 @@ All account commands use your configured wallet. Override with `--owner
{ const AMOUNT_0_01_X18 = '10000000000000000'; describe('buildEngineOrder', () => { - it('builds a market long order (ioc, bid + slippage)', async () => { + beforeEach(() => _resetMarketDataCache()); + + it('builds a market long order (ioc, ask + slippage)', async () => { const client = mockClient(50000, 50100); const result = await buildEngineOrder({ client, @@ -199,11 +202,11 @@ describe('buildEngineOrder', () => { reduceOnly: false, }); - // bid 50000 * 1.02 = 51000, rounded to 0.1 increment = "51000" + // ask 50100 * 1.02 = 51102, rounded to 0.1 increment = "51102" expect(result).toEqual({ productId: 1, order: { - price: '51000', + price: '51102', amount: AMOUNT_0_01_X18, expiration: expect.any(Number), nonce: '0xdeadbeef', @@ -212,7 +215,7 @@ describe('buildEngineOrder', () => { }); }); - it('builds a market short order (ioc, ask - slippage)', async () => { + it('builds a market short order (ioc, bid - slippage)', async () => { const client = mockClient(50000, 50100); const result = await buildEngineOrder({ client, @@ -223,11 +226,11 @@ describe('buildEngineOrder', () => { reduceOnly: false, }); - // ask 50100 * 0.98 = 49098, rounded to 0.1 = "49098" + // bid 50000 * 0.98 = 49000, rounded to 0.1 = "49000" expect(result).toEqual({ productId: 1, order: { - price: '49098', + price: '49000', amount: `-${AMOUNT_0_01_X18}`, expiration: expect.any(Number), nonce: '0xdeadbeef', @@ -284,8 +287,8 @@ describe('buildEngineOrder', () => { }); }); - it('throws when bid is zero for a long market order', async () => { - const client = mockClient(0, 50100); + it('throws when ask is zero for a long market order', async () => { + const client = mockClient(50000, 0); await expect( buildEngineOrder({ client, @@ -294,11 +297,11 @@ describe('buildEngineOrder', () => { orderExecutionType: 'ioc', reduceOnly: false, }), - ).rejects.toThrow('No bid price'); + ).rejects.toThrow('No ask price'); }); - it('throws when ask is zero for a short market order', async () => { - const client = mockClient(50000, 0); + it('throws when bid is zero for a short market order', async () => { + const client = mockClient(0, 50100); await expect( buildEngineOrder({ client, @@ -307,7 +310,7 @@ describe('buildEngineOrder', () => { orderExecutionType: 'ioc', reduceOnly: false, }), - ).rejects.toThrow('No ask price'); + ).rejects.toThrow('No bid price'); }); it('throws for unknown product when no marketData provided', async () => { @@ -345,7 +348,9 @@ describe('buildEngineOrder', () => { // --------------------------------------------------------------------------- describe('buildPriceTriggerOrder', () => { - it('builds a stop-market long trigger (ioc, bid + slippage, reduceOnly)', async () => { + beforeEach(() => _resetMarketDataCache()); + + it('builds a stop-market long trigger (ioc, ask + slippage, reduceOnly)', async () => { const client = mockClient(50000, 50100); const result = await buildPriceTriggerOrder({ client, @@ -355,11 +360,11 @@ describe('buildPriceTriggerOrder', () => { reduceOnly: true, }); - // no price -> market order uses bid * (1 + slippage) = 51000 + // no price -> market order uses ask * (1 + slippage) = 50100 * 1.02 = 51102 expect(result).toEqual({ productId: 1, order: { - price: '51000', + price: '51102', amount: AMOUNT_0_01_X18, expiration: expect.any(Number), nonce: '0xdeadbeef', @@ -405,7 +410,7 @@ describe('buildPriceTriggerOrder', () => { expect(result).toEqual({ productId: 1, order: { - price: '51000', + price: '51102', amount: AMOUNT_0_01_X18, expiration: expect.any(Number), nonce: '0xdeadbeef', diff --git a/src/commands/account.ts b/src/commands/account.ts index 32eef7f..0a96b50 100644 --- a/src/commands/account.ts +++ b/src/commands/account.ts @@ -1,5 +1,6 @@ import { ProductEngineType, + removeDecimals, toPrintableObject, type BigDecimalish, toBigDecimal, @@ -25,6 +26,7 @@ import { getMarkets } from '../utils/resolveMarket.js'; import { requireOwner, resolveMarketArgs, + resolveMarketArgsAll, run, splitMarketArgs, } from './helpers.js'; @@ -188,7 +190,7 @@ export function createAccountCommand(): Command { const metaNameById = new Map(metaMarkets.map((m) => [m.productId, m.marketName])); if (marketArgs.length > 0) { - const resolved = await resolveMarketArgs(ctx, marketArgs); + const resolved = await resolveMarketArgsAll(ctx, marketArgs); productIds = resolved.map((m) => m.productId); nameById = new Map(resolved.map((m) => [m.productId, m.marketName])); } else { @@ -365,7 +367,7 @@ function buildPositionRow( mode: string, ): string[] { const amount = toBigDecimal(b.amount); - const size = amount.div('1e18'); + const size = removeDecimals(amount); const isLong = size.gt(0); const side = isLong ? 'long' : 'short'; const oracle = toBigDecimal(b.oraclePrice); @@ -386,8 +388,7 @@ function buildPositionRow( if (!amount.isZero()) { entry = netEntry.div(amount).abs().toFixed(2); } - // unrealizedPnl = (amount * exitPrice - netEntryUnrealized) / 1e18 - const pnl = amount.times(exitPrice).minus(netEntry).div('1e18'); + const pnl = removeDecimals(amount.times(exitPrice).minus(netEntry)); pnlStr = fmtPnl(pnl.toNumber()); } else { pnlStr = chalk.dim('-'); diff --git a/src/commands/auth.ts b/src/commands/auth.ts index 41d5a2f..2ab01f5 100644 --- a/src/commands/auth.ts +++ b/src/commands/auth.ts @@ -36,24 +36,17 @@ export function createAuthCommand(): Command { .description('Configure credentials and defaults') .argument('[key]', `Setting to configure: ${VALID_KEYS.join(', ')}`) .argument('[value]', 'Value to set') - .option('--testnet', 'Write credentials to the testnet section') - .action( - async ( - key: string | undefined, - value: string | undefined, - opts: { testnet?: boolean }, - ) => { - if (key && value) { - setKeyValue(key, value, opts.testnet ?? false); - } else if (key) { - console.error(chalk.red(`Usage: nado auth set `)); - console.error(chalk.dim(`Valid keys: ${VALID_KEYS.join(', ')}`)); - process.exitCode = 1; - } else { - await setInteractive(); - } - }, - ); + .action(async (key: string | undefined, value: string | undefined) => { + if (key && value) { + setKeyValue(key, value); + } else if (key) { + console.error(chalk.red(`Usage: nado auth set `)); + console.error(chalk.dim(`Valid keys: ${VALID_KEYS.join(', ')}`)); + process.exitCode = 1; + } else { + await setInteractive(); + } + }); auth .command('whoami') @@ -81,9 +74,7 @@ export function createAuthCommand(): Command { auth .command('link-signer') - .description( - 'Link a signer address to your subaccount for 1-click trading', - ) + .description('Link a signer address to your subaccount for 1-click trading') .argument( '
', `Signer address to link (use ${ZERO_ADDR} to revoke)`, @@ -95,9 +86,7 @@ export function createAuthCommand(): Command { const summary = [ '', chalk.bold( - isRevoke - ? ' Revoke linked signer' - : ' Link signer to subaccount', + isRevoke ? ' Revoke linked signer' : ' Link signer to subaccount', ), ` Signer: ${address}`, ` Subaccount: ${ctx.subaccountName}`, @@ -127,7 +116,7 @@ export function createSetupCommand(): Command { }); } -function setKeyValue(key: string, value: string, testnet: boolean): void { +function setKeyValue(key: string, value: string): void { const toml = loadTomlConfig(); switch (key) { @@ -138,22 +127,12 @@ function setKeyValue(key: string, value: string, testnet: boolean): void { return; } toml.credentials = toml.credentials ?? {}; - if (testnet) { - toml.credentials.testnet = toml.credentials.testnet ?? {}; - toml.credentials.testnet.subaccount_owner = value; - } else { - toml.credentials.subaccount_owner = value; - } + toml.credentials.subaccount_owner = value; break; } case 'private-key': { toml.credentials = toml.credentials ?? {}; - if (testnet) { - toml.credentials.testnet = toml.credentials.testnet ?? {}; - toml.credentials.testnet.private_key = value; - } else { - toml.credentials.private_key = value; - } + toml.credentials.private_key = value; break; } case 'network': { @@ -222,32 +201,19 @@ async function setInteractive(): Promise { return; } + const currentName = toml.default?.subaccount_name ?? 'default'; + const nameInput = await ask( + `Subaccount name ${chalk.dim(`[${currentName}]`)}: `, + ); + const subaccountName = nameInput.trim() || currentName; + const currentKey = toml.credentials?.private_key; const keyDisplay = currentKey ? 'set — press Enter to keep' : 'not set'; - // NOTE: `rl` is closed here before `askSecret` so that `setRawMode` can take - // over stdin. Afterwards a new `rl2` instance is created for the final prompt. - // This split-readline approach is fragile: if `askSecret` throws, `rl2` is - // never created or closed. Consider refactoring to use a single readline - // instance throughout, or use a dedicated secret-input library. rl.close(); const keyInput = await askSecret( `Private key ${chalk.dim(`[${keyDisplay}]`)}: `, ); const privateKey = keyInput.trim() || currentKey || undefined; - const rl2 = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - const ask2 = (prompt: string): Promise => - new Promise((resolve) => rl2.question(prompt, resolve)); - - const currentName = toml.default?.subaccount_name ?? 'default'; - const nameInput = await ask2( - `Subaccount name ${chalk.dim(`[${currentName}]`)}: `, - ); - const subaccountName = nameInput.trim() || currentName; - - rl2.close(); toml.default = { data_env: dataEnv, subaccount_name: subaccountName }; toml.credentials = toml.credentials ?? {}; @@ -261,26 +227,33 @@ async function setInteractive(): Promise { } function askSecret(prompt: string): Promise { + if (!process.stdin.isTTY) { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + return new Promise((resolve) => + rl.question(prompt, (answer) => { + rl.close(); + resolve(answer); + }), + ); + } + return new Promise((resolve) => { process.stdout.write(prompt); const buf: Buffer[] = []; const wasRaw = process.stdin.isRaw; - // NOTE: `setRawMode` throws if stdin is not a TTY (e.g. when piping input - // or running in CI). There is no guard here, so the process will crash with - // an unhandled exception in non-interactive environments. Consider checking - // `process.stdin.isTTY` first and falling back to a regular readline prompt. process.stdin.setRawMode(true); process.stdin.resume(); const onData = (key: Buffer): void => { const ch = key[0]; if (ch === 3) { - // Ctrl-C process.stdout.write('\n'); process.exit(130); } if (ch === 13 || ch === 10) { - // Enter process.stdout.write('\n'); process.stdin.setRawMode(wasRaw ?? false); process.stdin.pause(); @@ -289,7 +262,6 @@ function askSecret(prompt: string): Promise { return; } if (ch === 127 || ch === 8) { - // Backspace buf.pop(); } else { buf.push(key); @@ -336,11 +308,7 @@ async function setup1ct(): Promise { const chainEnv = envConfig.defaultChainEnv; // Step 2: Wallet address - const currentOwner = isTestnet - ? (toml.credentials?.testnet?.subaccount_owner ?? - toml.credentials?.subaccount_owner ?? - '') - : (toml.credentials?.subaccount_owner ?? ''); + const currentOwner = toml.credentials?.subaccount_owner ?? ''; const ownerInput = await ask( ` Your wallet address (EOA) ${chalk.dim(currentOwner ? `[${currentOwner}]` : '')}: `, ); @@ -437,21 +405,12 @@ async function setup1ct(): Promise { subaccountName, }); - const ZERO_ADDR = '0x0000000000000000000000000000000000000000'; if (linked.signer.toLowerCase() === signerAddress.toLowerCase()) { console.log(chalk.green(' Already linked! ✓')); autoLinked = true; - } else if ( - // NOTE: After the `===` check above has already excluded the equal case, - // the second condition `linked.signer.toLowerCase() !== signerAddress.toLowerCase()` - // is always true here (it is the logical complement of the already-handled - // branch). The `linked.signer === ZERO_ADDR` sub-condition is therefore - // redundant — the block runs regardless. Simplify to a plain `else`. - linked.signer === ZERO_ADDR || - linked.signer.toLowerCase() !== signerAddress.toLowerCase() - ) { + } else { console.log(chalk.dim(' Linking signer on-chain...')); - // EIP712 expects signer as bytes32 — use subaccountToHex (address + empty name) like the web app + // EIP712 expects signer as bytes32 - use subaccountToHex (address + empty name) like the web app const signerBytes32 = subaccountToHex({ subaccountOwner: signerAddress, subaccountName: '', @@ -476,14 +435,15 @@ async function setup1ct(): Promise { const account = privateKeyToAccount(hotPrivateKey as Address); signerAddress = account.address; - // NOTE: The generated private key is printed to stdout (not stderr). This - // means it can be captured by shell output redirection, CI log scrapers, - // or terminal scrollback. Consider printing it to stderr, or at least - // displaying a strong warning that it should be treated as sensitive. - console.log( - `\n Generated hot private key: ${chalk.cyan(hotPrivateKey)}`, + console.error( + chalk.yellow( + '\n ⚠ The key below is sensitive — do not share or log it.\n', + ), ); - console.log( + console.error( + ` Generated hot private key: ${chalk.cyan(hotPrivateKey)}`, + ); + console.error( chalk.yellow('\n ⚠ You must link this signer in the Nado web app:\n') + ` 1. Go to ${chalk.underline( isTestnet ? 'https://testnet.nado.xyz' : 'https://app.nado.xyz', @@ -498,14 +458,8 @@ async function setup1ct(): Promise { // Step 5: Save config toml.default = { data_env: dataEnv, subaccount_name: subaccountName }; toml.credentials = toml.credentials ?? {}; - if (isTestnet) { - toml.credentials.testnet = toml.credentials.testnet ?? {}; - toml.credentials.testnet.private_key = hotPrivateKey; - toml.credentials.testnet.subaccount_owner = owner; - } else { - toml.credentials.private_key = hotPrivateKey; - toml.credentials.subaccount_owner = owner; - } + toml.credentials.private_key = hotPrivateKey; + toml.credentials.subaccount_owner = owner; saveTomlConfig(toml); diff --git a/src/commands/helpers.ts b/src/commands/helpers.ts index cb44a4d..25b9630 100644 --- a/src/commands/helpers.ts +++ b/src/commands/helpers.ts @@ -12,6 +12,7 @@ import { type ProductEngineType } from '@nadohq/client'; import { getMarkets, type Market, + resolveAllProducts, resolveProduct, } from '../utils/resolveMarket.js'; @@ -136,6 +137,29 @@ export async function resolveMarketArgs( return inputs.map((input) => resolveProduct(markets, input, typeFilter)); } +/** + * Resolve multiple market identifiers, returning *all* matching markets per + * input (e.g. "BTC" → both the spot and perp product). Useful for queries + * where narrowing to a single type would silently hide results. + */ +export async function resolveMarketArgsAll( + ctx: NadoContext, + inputs: string[], +): Promise { + const markets = await getMarkets(ctx.dataEnv, ctx.chainEnv); + const seen = new Set(); + const result: Market[] = []; + for (const input of inputs) { + for (const m of resolveAllProducts(markets, input)) { + if (!seen.has(m.productId)) { + seen.add(m.productId); + result.push(m); + } + } + } + return result; +} + /** * Split a comma-separated option value into individual market tokens. * Used as a Commander argParser for --product-ids style options. diff --git a/src/commands/market.ts b/src/commands/market.ts index 91bff5f..927c46a 100644 --- a/src/commands/market.ts +++ b/src/commands/market.ts @@ -11,7 +11,12 @@ import { getTickers, watchOrderbook, } from '../handlers/market.js'; -import { resolveMarketArg, resolveMarketArgs, run, runWatch } from './helpers.js'; +import { + resolveMarketArg, + resolveMarketArgs, + run, + runWatch, +} from './helpers.js'; export function createMarketCommand(): Command { const market = new Command('market').description('Market data commands'); @@ -19,9 +24,7 @@ export function createMarketCommand(): Command { market .command('price') .description('Get best bid/ask for a market') - // We never really call a perp ETH-PERP on the FE, the backend returned endpoint uses market names from the old Vertex days and was never changed - // We could ask them to change it to ETHUSDT0 etc if we want - .argument('', 'Symbol, name, or product ID (e.g. BTC, ETH-PERP, 2)') + .argument('', 'Symbol, name, or product ID (e.g. BTC, ETH, 2)') .action(async (marketArg: string, _opts: unknown, cmd: Command) => { await run(cmd, async (ctx) => { const m = await resolveMarketArg(ctx, marketArg); @@ -42,7 +45,13 @@ export function createMarketCommand(): Command { const productIds = resolved.map((m) => m.productId); const result = await getMarketPrices(ctx, { productIds }); - const printed = toPrintableObject(result.data) as { marketPrices: Array<{ productId: number; bid?: string; ask?: string }> }; + const printed = toPrintableObject(result.data) as { + marketPrices: Array<{ + productId: number; + bid?: string; + ask?: string; + }>; + }; const prices = printed.marketPrices ?? []; const byId = new Map(prices.map((p) => [p.productId, p])); result.headers = ['Market', 'Product', 'Bid', 'Ask']; @@ -148,7 +157,7 @@ export function createMarketCommand(): Command { }); market - .command('markets') + .command('list') .description('List all available markets') .action(async (_opts: unknown, cmd: Command) => { await run(cmd, (ctx) => getAllMarkets(ctx)); diff --git a/src/commands/trade.ts b/src/commands/trade.ts index 855aa1b..f19c06e 100644 --- a/src/commands/trade.ts +++ b/src/commands/trade.ts @@ -43,17 +43,10 @@ export function createTradeCommand(): Command { trade .command('close') .description('Close an open position (cross or isolated)') - .argument( - '', - 'Symbol, name, or product ID (e.g. BTC, ETH-PERP, 1)', - ) + .argument('', 'Symbol, name, or product ID (e.g. BTC, ETH, 1)') .option('--slippage ', 'Slippage tolerance %', '2') .action( - async ( - marketArg: string, - opts: { slippage: string }, - cmd: Command, - ) => { + async (marketArg: string, opts: { slippage: string }, cmd: Command) => { await runConfirmed(cmd, async (ctx) => { const m = await resolveMarketArg(ctx, marketArg); const slippage = parseFloat(opts.slippage); @@ -80,12 +73,12 @@ export function createTradeCommand(): Command { trade .command('close-all') .description('Close all open perp positions') - // NOTE: `--side` accepts free-form text with no `.choices(['long', 'short'])` - // constraint. An invalid value like `--side both` would pass silently and be - // cast to `'long' | 'short' | undefined` without runtime validation. - // Consider adding `.choices(['long', 'short'])` via an `addOption(new Option...)` - // call to enforce valid values at the CLI layer. - .option('-s, --side ', 'Filter: close only long or short positions') + .addOption( + new Option( + '-s, --side ', + 'Filter: close only long or short positions', + ).choices(['long', 'short']), + ) .option( '--product-ids ', 'Filter by markets (comma-separated symbols or IDs)', @@ -221,8 +214,7 @@ export function createTradeCommand(): Command { return { summary, - execute: () => - cancelTriggerOrders(ctx, { productIds, digests }), + execute: () => cancelTriggerOrders(ctx, { productIds, digests }), }; }); }, @@ -330,7 +322,10 @@ export function createTradeCommand(): Command { .option('-m, --margin ', 'Margin mode: cross or isolated', 'cross') .option('--leverage ', 'Leverage for isolated margin') .option('--slippage ', 'Slippage tolerance %', '2') - .option('--no-reduce-only', 'Allow increasing position (default: reduce-only)') + .option( + '--no-reduce-only', + 'Allow increasing position (default: reduce-only)', + ) .action( async ( marketArg: string, @@ -377,9 +372,7 @@ export function createTradeCommand(): Command { triggerType: opts.triggerType as TriggerType, price, marginMode: opts.margin as 'cross' | 'isolated', - leverage: opts.leverage - ? parseFloat(opts.leverage) - : undefined, + leverage: opts.leverage ? parseFloat(opts.leverage) : undefined, slippagePct: parseFloat(opts.slippage), reduceOnly: opts.reduceOnly, }), @@ -408,17 +401,10 @@ function addPerpCommand( parent .command(name) .description(`Place a perp ${side} order`) - .argument( - '', - 'Symbol, name, or product ID (e.g. BTC, ETH-PERP, 1)', - ) + .argument('', 'Symbol, name, or product ID (e.g. BTC, ETH, 1)') .argument('', 'Order size in base units (e.g. 0.01)') .option('-p, --price ', 'Limit price (omit for market order)') - .option( - '-m, --margin ', - 'Margin mode: cross or isolated', - 'cross', - ) + .option('-m, --margin ', 'Margin mode: cross or isolated', 'cross') .option('--leverage ', 'Leverage for isolated margin') .option('--tif ', 'Time in force: gtc, ioc, fok, post_only') .option('--reduce-only', 'Reduce-only order') diff --git a/src/config.ts b/src/config.ts index 38f94a7..792ddbf 100644 --- a/src/config.ts +++ b/src/config.ts @@ -3,7 +3,7 @@ import * as os from 'node:os'; import * as path from 'node:path'; import type { ChainEnv } from '@nadohq/client'; -import TOML from 'toml'; +import * as TOML from '@iarna/toml'; import { ALL_DATA_ENVS, type DataEnv, getDataEnvConfig } from './dataEnv.js'; @@ -45,11 +45,6 @@ export interface TomlConfig { credentials?: { private_key?: string; subaccount_owner?: string; - // why do we need this to be a separate config? - testnet?: { - private_key?: string; - subaccount_owner?: string; - }; }; } @@ -61,46 +56,30 @@ export function loadTomlConfig(): TomlConfig { const raw = fs.readFileSync(CONFIG_PATH, 'utf-8'); return TOML.parse(raw) as TomlConfig; } catch { - // nit: could perhaps log the error return {}; } } export function saveTomlConfig(config: TomlConfig): void { ensureConfigDir(); - const lines: string[] = []; - - // NOTE: This is a manual TOML serializer using string interpolation. - // If any value contains a double-quote character (e.g. a subaccount name - // like my"name), the resulting file will be invalid TOML. Consider using a - // proper TOML serialization library (e.g. @iarna/toml or smol-toml). - lines.push('[default]'); - lines.push(`data_env = "${config.default?.data_env ?? 'nadoMainnet'}"`); - lines.push( - `subaccount_name = "${config.default?.subaccount_name ?? 'default'}"`, - ); - lines.push(''); + + const doc: Record> = { + default: { + data_env: config.default?.data_env ?? 'nadoMainnet', + subaccount_name: config.default?.subaccount_name ?? 'default', + }, + }; const creds = config.credentials; if (creds?.private_key || creds?.subaccount_owner) { - lines.push('[credentials]'); - if (creds.private_key) lines.push(`private_key = "${creds.private_key}"`); + const section: Record = {}; + if (creds.private_key) section.private_key = creds.private_key; if (creds.subaccount_owner) - lines.push(`subaccount_owner = "${creds.subaccount_owner}"`); - lines.push(''); + section.subaccount_owner = creds.subaccount_owner; + doc.credentials = section; } - const testnet = creds?.testnet; - if (testnet?.private_key || testnet?.subaccount_owner) { - lines.push('[credentials.testnet]'); - if (testnet.private_key) - lines.push(`private_key = "${testnet.private_key}"`); - if (testnet.subaccount_owner) - lines.push(`subaccount_owner = "${testnet.subaccount_owner}"`); - lines.push(''); - } - - fs.writeFileSync(CONFIG_PATH, lines.join('\n') + '\n'); + fs.writeFileSync(CONFIG_PATH, TOML.stringify(doc)); } function resolveDataEnv(flag?: string, env?: string, toml?: string): DataEnv { @@ -125,24 +104,13 @@ export function loadConfig(flags: CliFlags = {}): CliConfig { toml.default?.data_env, ); - const isTestnet = dataEnv === 'nadoTestnet'; const envConfig = getDataEnvConfig(dataEnv); - const testnetCreds = toml.credentials?.testnet; - // NOTE: When dataEnv is 'nadoTestnet', the testnet credentials fall back to - // the top-level mainnet credentials if no testnet-specific ones exist. - // This could lead to accidentally signing testnet transactions with a - // mainnet private key. Intentional? Worth making explicit or disabling fallback. - const tomlKey = isTestnet - ? (testnetCreds?.private_key ?? toml.credentials?.private_key) - : toml.credentials?.private_key; - const tomlOwner = isTestnet - ? (testnetCreds?.subaccount_owner ?? toml.credentials?.subaccount_owner) - : toml.credentials?.subaccount_owner; - - const privateKey = process.env.PRIVATE_KEY ?? tomlKey; + const privateKey = process.env.PRIVATE_KEY ?? toml.credentials?.private_key; const subaccountOwner = - flags.owner ?? process.env.SUBACCOUNT_OWNER ?? tomlOwner; + flags.owner ?? + process.env.SUBACCOUNT_OWNER ?? + toml.credentials?.subaccount_owner; const subaccountName = flags.subaccount ?? process.env.SUBACCOUNT_NAME ?? @@ -168,9 +136,11 @@ function resolveFormat(flag?: string, env?: string): OutputFormat { const raw = flag ?? env; if (raw === 'json') return 'json'; if (raw === 'table') return 'table'; - // NOTE: Any unrecognized value (e.g. NADO_FORMAT=csv) falls through silently - // to the TTY auto-detect instead of throwing an error. Consider warning the - // user if raw is set but doesn't match 'json' or 'table'. + if (raw != null) { + console.error( + `Warning: unrecognized format "${raw}", falling back to auto-detect.`, + ); + } return process.stdout.isTTY ? 'table' : 'json'; } diff --git a/src/context.ts b/src/context.ts index 66e7013..96ba873 100644 --- a/src/context.ts +++ b/src/context.ts @@ -10,6 +10,7 @@ import { http, isAddress, type Address, + type Hex, } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; @@ -39,9 +40,8 @@ export function createNadoContext(config: ServerConfig): NadoContext { const rpcUrl = config.rpcUrl ?? chain.rpcUrls.default.http[0]; const publicClient = createPublicClient({ transport: http(rpcUrl) }); - // nit: private keys are `Hex`, not `Address` const account = config.privateKey - ? privateKeyToAccount(config.privateKey as Address) + ? privateKeyToAccount(config.privateKey as Hex) : undefined; const walletClient = account diff --git a/src/handlers/account.ts b/src/handlers/account.ts index 55f9437..4c09a4f 100644 --- a/src/handlers/account.ts +++ b/src/handlers/account.ts @@ -1,7 +1,5 @@ import type { NadoClient } from '@nadohq/client'; -import { - ProductEngineType, -} from '@nadohq/client'; +import { ProductEngineType, removeDecimals } from '@nadohq/client'; import type { IndexerMatchEvent } from '@nadohq/indexer-client'; import type { NadoContext } from '../context.js'; @@ -45,8 +43,7 @@ export async function getSubaccountSummary( const enrichedIsolated = isolatedPositions.map((pos) => ({ ...pos, baseBalance: { - symbol: - symbolByProductId.get(pos.baseBalance.productId) ?? undefined, + symbol: symbolByProductId.get(pos.baseBalance.productId) ?? undefined, ...pos.baseBalance, }, })); @@ -60,15 +57,20 @@ export async function getSubaccountSummary( isolatedPositions: enrichedIsolated, }; - const nonZeroBalances = summary.balances.filter( - (b) => !b.amount.isZero(), - ); - const headers = ['Product', 'Symbol', 'Type', 'Side', 'Amount', 'Value', 'Oracle Price']; + const nonZeroBalances = summary.balances.filter((b) => !b.amount.isZero()); + const headers = [ + 'Product', + 'Symbol', + 'Type', + 'Side', + 'Amount', + 'Value', + 'Oracle Price', + ]; const rows = nonZeroBalances.map((b) => { const symbol = symbolByProductId.get(b.productId) ?? '-'; const isLong = b.amount.isPositive(); - // removeDecimals for ops like this - const absAmount = b.amount.abs().dividedBy(1e18); + const absAmount = removeDecimals(b.amount.abs()); const value = absAmount.times(b.oraclePrice); return [ String(b.productId), @@ -124,9 +126,6 @@ export async function getFeeRates( // --------------------------------------------------------------------------- const PAGE_SIZE = 500; -// NOTE: MAX_PAGES caps the fetch at 20,000 events. If the user has more history, -// it is silently truncated with no warning to the user. Consider logging a -// warning when the page limit is reached so users know the stats are incomplete. const MAX_PAGES = 40; const SECONDS_PER_DAY = 86_400; @@ -161,6 +160,7 @@ async function fetchAllEvents( const allEvents: IndexerMatchEvent[] = []; let cursor: string | undefined; + let hitPageLimit = false; for (let page = 0; page < MAX_PAGES; page++) { const response = await client.context.indexerClient.getPaginatedSubaccountMatchEvents({ @@ -180,6 +180,16 @@ async function fetchAllEvents( if (oldest <= minTimestamp) break; if (!response.meta.hasMore || !response.meta.nextCursor) break; cursor = response.meta.nextCursor; + + if (page === MAX_PAGES - 1 && response.meta.hasMore) { + hitPageLimit = true; + } + } + + if (hitPageLimit) { + console.error( + `Warning: stats are based on the most recent ${allEvents.length.toLocaleString()} events. Older history was truncated.`, + ); } return allEvents.filter((e) => Number(e.timestamp) >= minTimestamp); @@ -235,11 +245,13 @@ async function fetchSnapshotVolume( return currentVolume - historicalVolume; } -// NOTE: This rounds to 2 decimal places, which is appropriate for USD display -// but loses precision for small-cap tokens where amounts might be e.g. 0.00123. -// Stats like fees or PnL on low-value trades could display as $0.00. function round(n: number): number { - return Math.round(n * 100) / 100; + const abs = Math.abs(n); + if (abs === 0) return 0; + if (abs >= 1) return Math.round(n * 100) / 100; + const decimals = Math.min(6, Math.max(2, -Math.floor(Math.log10(abs)) + 2)); + const factor = 10 ** decimals; + return Math.round(n * factor) / factor; } function computeStats( @@ -273,28 +285,28 @@ function computeStats( const ts = Number(event.timestamp); if (ts < cutoff) continue; - // Same as on MCP, these calcs may not be accurate with `Number` - const quote = Math.abs(Number(event.quoteFilled)) / 1e18; - const fee = Number(event.totalFee) / 1e18; - const pnl = Number(event.realizedPnl ?? 0) / 1e18; + const quoteWithFee = removeDecimals(event.quoteFilled.abs()).toNumber(); + const fee = removeDecimals(event.totalFee).toNumber(); + const volume = quoteWithFee - Math.abs(fee); + const pnl = removeDecimals(event.realizedPnl ?? 0).toNumber(); const isTaker = event.isTaker ?? true; const pid = event.productId; - totalVolume += quote; + totalVolume += volume; totalFees += fee; totalPnl += pnl; totalTrades += 1; if (isTaker) { - takerVolume += quote; + takerVolume += volume; takerTrades += 1; } else { - makerVolume += quote; + makerVolume += volume; makerTrades += 1; } const m = marketAgg.get(pid) ?? { volume: 0, trades: 0, pnl: 0, fees: 0 }; - m.volume += quote; + m.volume += volume; m.trades += 1; m.pnl += pnl; m.fees += fee; @@ -302,13 +314,14 @@ function computeStats( const date = new Date(ts * 1000).toISOString().slice(0, 10); const d = dayAgg.get(date) ?? { volume: 0, trades: 0, pnl: 0 }; - d.volume += quote; + d.volume += volume; d.trades += 1; d.pnl += pnl; dayAgg.set(date, d); } - // Same comment as on MCP server, not sure why we need this, and `quote` includes fee when you probably don't want it to + // Prefer on-chain cumulative snapshot volume when available; fall back to + // the event-based sum (fee-excluded) for accounts with no snapshot data. const reportedVolume = snapshotVolume != null ? snapshotVolume : totalVolume; const byMarket: MarketStats[] = [...marketAgg.entries()] @@ -384,9 +397,7 @@ export async function getAccountStats( ).catch(() => undefined), ]); - const symbolMap = new Map( - markets.map((m) => [m.productId, m.symbol]), - ); + const symbolMap = new Map(markets.map((m) => [m.productId, m.symbol])); const data = computeStats(events, args.days, symbolMap, snapshotVolume); diff --git a/src/handlers/funds.ts b/src/handlers/funds.ts index 16f0293..15ab2ee 100644 --- a/src/handlers/funds.ts +++ b/src/handlers/funds.ts @@ -109,11 +109,6 @@ export async function transferQuote( const to = args.recipientSubaccountName ?? 'default'; try { - // NOTE: `addDecimals(args.amount)` is called with no second argument, - // so it uses the SDK's default decimal scale (likely 18). USDT0 uses 6 - // decimals, so if the default is 18 this would send 1e12x the intended - // amount. Verify what `addDecimals` defaults to, or pass the explicit - // decimals for the quote token (like `depositCollateral` does above). const data = await ctx.client.spot.transferQuote({ subaccountOwner: ctx.subaccountOwner!, subaccountName: from, diff --git a/src/handlers/market.ts b/src/handlers/market.ts index c8be8aa..2049e78 100644 --- a/src/handlers/market.ts +++ b/src/handlers/market.ts @@ -1,12 +1,10 @@ -import { removeDecimals } from '@nadohq/client'; +import { ProductEngineType, removeDecimals } from '@nadohq/client'; import type { NadoContext } from '../context.js'; -import { - renderOrderbook, - type OrderbookLevel, -} from '../output/orderbook.js'; +import { renderOrderbook, type OrderbookLevel, type OrderbookIncrements } from '../output/orderbook.js'; import { ToolExecutionError } from '../utils/errors.js'; import { fmtProductIds } from '../utils/formatting.js'; +import { resolveMarketData } from '../utils/orderBuilder.js'; import { getMarkets } from '../utils/resolveMarket.js'; import { subscribeBookDepth } from '../ws/subscribe.js'; import type { CommandResult } from './types.js'; @@ -15,9 +13,7 @@ import type { CommandResult } from './types.js'; // get_all_markets // --------------------------------------------------------------------------- -export async function getAllMarkets( - ctx: NadoContext, -): Promise { +export async function getAllMarkets(ctx: NadoContext): Promise { try { const [data, metadata] = await Promise.all([ ctx.client.market.getAllMarkets(), @@ -27,17 +23,21 @@ export async function getAllMarkets( return { data, - headers: ['ID', 'Name', 'Symbol', 'Type', 'Price Increment', 'Size Increment'], + headers: [ + 'ID', + 'Name', + 'Symbol', + 'Type', + 'Price Increment', + 'Size Increment', + ], rows: data.map((m) => { const meta = nameById.get(m.productId); return [ String(m.productId), meta?.marketName ?? '-', meta?.symbol ?? '-', - // NOTE: Magic number comparison — use `ProductEngineType.PERP` (imported - // at the top of this file) instead of `=== 1` for readability and safety - // against future enum value changes. - m.type === 1 ? 'perp' : 'spot', + m.type === ProductEngineType.PERP ? 'perp' : 'spot', m.priceIncrement.toFixed(), m.sizeIncrement.toFixed(), ]; @@ -281,6 +281,7 @@ function mergeBookLevels( existing: OrderbookLevel[], updates: OrderbookLevel[], descending: boolean, + maxLevels?: number, ): OrderbookLevel[] { const byPrice = new Map(existing.map((l) => [l.price, l])); @@ -294,6 +295,9 @@ function mergeBookLevels( const result = Array.from(byPrice.values()); result.sort((a, b) => (descending ? b.price - a.price : a.price - b.price)); + if (maxLevels != null && result.length > maxLevels) { + result.length = maxLevels; + } return result; } @@ -306,10 +310,18 @@ export async function watchOrderbook( ctx: NadoContext, args: WatchOrderbookArgs, ): Promise { - const initialData = await ctx.client.market.getMarketLiquidity({ - productId: args.productId, - depth: args.depth, - }); + const [initialData, marketData] = await Promise.all([ + ctx.client.market.getMarketLiquidity({ + productId: args.productId, + depth: args.depth, + }), + resolveMarketData(ctx.client, args.productId), + ]); + + const increments: OrderbookIncrements = { + priceIncrement: marketData.priceIncrement.toNumber(), + sizeIncrement: marketData.sizeIncrement.toNumber(), + }; let latestBids: OrderbookLevel[] = initialData.bids.map((b) => ({ price: b.price.toNumber(), @@ -319,16 +331,11 @@ export async function watchOrderbook( price: a.price.toNumber(), size: removeDecimals(a.liquidity).toNumber(), })); - // NOTE: `latestBids` and `latestAsks` are unbounded — as levels are added they - // grow without limit. For high-depth books or long-running sessions this could - // consume significant memory. Consider capping to `args.depth` levels after - // each merge so the arrays stay bounded. - function draw() { const output = renderOrderbook(args.marketName, { bids: latestBids, asks: latestAsks, - }); + }, increments); process.stdout.write('\x1B[2J\x1B[H'); process.stdout.write(output + '\n'); } @@ -346,22 +353,19 @@ export async function watchOrderbook( const askUpdates = (event.asks as [string, string][]).map(([p, l]) => parseWsLevel(p, l), ); - latestBids = mergeBookLevels(latestBids, bidUpdates, true); - latestAsks = mergeBookLevels(latestAsks, askUpdates, false); + latestBids = mergeBookLevels(latestBids, bidUpdates, true, args.depth); + latestAsks = mergeBookLevels(latestAsks, askUpdates, false, args.depth); draw(); }, ); return new Promise((resolve) => { const onExit = () => { + process.removeListener('SIGINT', onExit); + process.removeListener('SIGTERM', onExit); handle.close(); resolve(); }; - // NOTE: These signal listeners are added to the process but never removed - // after resolution. In the interactive shell, calling `market orderbook --watch` - // multiple times in one session would accumulate SIGINT/SIGTERM listeners, - // eventually triggering Node's MaxListenersExceededWarning. Consider calling - // `process.removeListener('SIGINT', onExit)` after the promise resolves. process.on('SIGINT', onExit); process.on('SIGTERM', onExit); }); diff --git a/src/handlers/trade.ts b/src/handlers/trade.ts index 39498ed..0f4b490 100644 --- a/src/handlers/trade.ts +++ b/src/handlers/trade.ts @@ -1,3 +1,4 @@ +import type { BalanceSide, BalanceWithProduct } from '@nadohq/client'; import { addDecimals, getOrderNonce, @@ -6,7 +7,6 @@ import { removeDecimals, toBigDecimal, } from '@nadohq/client'; -import type { BalanceWithProduct } from '@nadohq/client'; import BigNumber from 'bignumber.js'; import chalk from 'chalk'; @@ -50,7 +50,7 @@ function warnOnCancelFailure(label: string) { export interface PlaceOrderSingle { productId: number; - side: 'long' | 'short'; + side: BalanceSide; amount: number; price?: number; marginMode?: 'cross' | 'isolated'; @@ -75,11 +75,6 @@ export async function placeOrder( const orderList = Array.isArray(args.orders) ? args.orders : [args.orders]; try { - // NOTE: Market data (priceIncrement, sizeIncrement) is pre-fetched per unique - // product to avoid N redundant `getAllMarkets` calls inside `buildEngineOrder`. - // However `resolveMarketData` itself still issues an `getAllMarkets` RPC call - // on each invocation (no SDK-level cache). A module-level cache similar to - // the one in resolveMarket.ts would reduce round trips for batch orders. const uniqueProductIds = [...new Set(orderList.map((o) => o.productId))]; const marketDataMap = new Map( await Promise.all( @@ -184,9 +179,7 @@ export async function closePosition( ctx.client.market.getAllMarkets(), ]); - const marketByProductId = new Map( - allMarkets.map((m) => [m.productId, m]), - ); + const marketByProductId = new Map(allMarkets.map((m) => [m.productId, m])); const crossBalance = summary.balances.find( (b) => @@ -208,9 +201,7 @@ export async function closePosition( } // 3. Build close order using oracle price (like e2e cleanup) - const balance = hasCrossPosition - ? crossBalance - : isolatedPos!.baseBalance; + const balance = hasCrossPosition ? crossBalance : isolatedPos!.baseBalance; const marginMode = hasCrossPosition ? 'cross' : 'isolated'; const orderParams = buildCloseOrderFromBalance( balance as BalanceWithProduct, @@ -278,10 +269,7 @@ export async function closeAllPositions( ctx.client.market.getAllMarkets(), ]); - const matchesFilter = ( - productId: number, - isPositive: boolean, - ): boolean => { + const matchesFilter = (productId: number, isPositive: boolean): boolean => { if (args.productIds && !args.productIds.includes(productId)) return false; if (args.side === 'long' && !isPositive) return false; if (args.side === 'short' && isPositive) return false; @@ -335,9 +323,7 @@ export async function closeAllPositions( ]); // 3. Build close orders using oracle prices - const marketByProductId = new Map( - allMarkets.map((m) => [m.productId, m]), - ); + const marketByProductId = new Map(allMarkets.map((m) => [m.productId, m])); const closeOrders = [ ...crossPositions.map((b) => @@ -408,7 +394,10 @@ export async function closeAllPositions( */ function buildCloseOrderFromBalance( balance: BalanceWithProduct, - marketByProductId: Map, + marketByProductId: Map< + number, + { priceIncrement: BigNumber; sizeIncrement: BigNumber } + >, slippagePct: number, marginMode: 'cross' | 'isolated', subaccountOwner: string, @@ -606,8 +595,7 @@ export type TriggerType = export interface PlaceTriggerOrderArgs { productId: number; - // BalanceSide - side: 'long' | 'short'; + side: BalanceSide; amount: number; price?: number; triggerPrice: number; @@ -669,7 +657,7 @@ export async function placeTriggerOrder( export interface PlaceTwapOrderArgs { productId: number; - side: 'long' | 'short'; + side: BalanceSide; amount: number; intervalSeconds?: number; durationMinutes: number; @@ -718,18 +706,10 @@ export async function placeTwapOrder( ); const perOrderSigned = isLong ? perOrderAmount : perOrderAmount.negated(); - // NOTE: `totalAmountX18` is derived by multiplying the *rounded* per-order - // amount back out, so it may differ from `args.amount` due to rounding. - // The summary displays `args.amount` (the user's requested total), but the - // protocol receives `totalAmountX18` (the rounded total). For small amounts - // with many sub-orders this discrepancy could be meaningful. const totalAmountX18 = perOrderSigned.times(numOrders); - // NOTE: The TWAP long price is set to `refPrice * 1000` as a sentinel "market - // buy" value — effectively placing a limit order so far above market that it - // fills immediately at whatever the prevailing ask is. For shorts, '0' serves - // the same purpose (always below bid). This is intentional protocol-level - // behaviour but is non-obvious without a comment. + // Sentinel prices that guarantee immediate fill: longs use a price far + // above market (refPrice * 1000), shorts use 0. const orderPrice = isLong ? roundToIncrement(refPrice.times(1000), priceIncrement).toFixed() : '0'; @@ -776,10 +756,9 @@ export async function placeTwapOrder( summary: { side: args.side, productId: args.productId, - totalAmount: args.amount, - perOrderAmount: perOrderAmount - .dividedBy(toBigDecimal(10).pow(18)) - .toFixed(), + requestedAmount: args.amount, + actualAmount: removeDecimals(totalAmountX18.abs()).toFixed(), + perOrderAmount: removeDecimals(perOrderAmount).toFixed(), numOrders, intervalSeconds, totalDuration: `${args.durationMinutes} minutes`, diff --git a/src/output/orderbook.ts b/src/output/orderbook.ts index 2c65cb1..36ce065 100644 --- a/src/output/orderbook.ts +++ b/src/output/orderbook.ts @@ -12,9 +12,15 @@ interface OrderbookSnapshot { const BLOCK = '\u2588'; +export interface OrderbookIncrements { + priceIncrement: number; + sizeIncrement: number; +} + export function renderOrderbook( market: string, snapshot: OrderbookSnapshot, + increments: OrderbookIncrements, ): string { const termWidth = process.stdout.columns || 80; const now = new Date(); @@ -37,8 +43,8 @@ export function renderOrderbook( } const maxSize = Math.max(...allLevels.map((l) => l.size)); - const priceDecs = guessPriceDecimals(allLevels); - const sizeDecs = guessSizeDecimals(allLevels); + const priceDecs = decimalsFromIncrement(increments.priceIncrement); + const sizeDecs = decimalsFromIncrement(increments.sizeIncrement); const priceColW = Math.max( 6, @@ -119,23 +125,8 @@ function padLeft(str: string, width: number): string { return str.padStart(width); } -// Why do we need to guess anything if we can just get the market config via a query? -function guessPriceDecimals(levels: OrderbookLevel[]): number { - let maxDec = 1; - for (const l of levels) { - const s = l.price.toString(); - const dot = s.indexOf('.'); - if (dot >= 0) maxDec = Math.max(maxDec, s.length - dot - 1); - } - return Math.min(maxDec, 6); +function decimalsFromIncrement(increment: number): number { + if (increment <= 0 || increment >= 1) return 0; + return Math.min(6, Math.max(0, -Math.floor(Math.log10(increment)))); } -function guessSizeDecimals(levels: OrderbookLevel[]): number { - let maxDec = 2; - for (const l of levels) { - const s = l.size.toString(); - const dot = s.indexOf('.'); - if (dot >= 0) maxDec = Math.max(maxDec, s.length - dot - 1); - } - return Math.min(maxDec, 6); -} diff --git a/src/utils/orderBuilder.ts b/src/utils/orderBuilder.ts index b94c45f..9aed1f2 100644 --- a/src/utils/orderBuilder.ts +++ b/src/utils/orderBuilder.ts @@ -24,7 +24,10 @@ export function toExecutionType(tif: string): OrderExecutionType { export const DEFAULT_SLIPPAGE_PCT = 2; -export function roundToIncrement(value: BigNumber, increment: BigNumber): BigNumber { +export function roundToIncrement( + value: BigNumber, + increment: BigNumber, +): BigNumber { if (increment.isZero()) return value; return value .dividedBy(increment) @@ -48,12 +51,23 @@ export interface MarketData { sizeIncrement: BigNumber; } +let allMarketsCache: + | Awaited> + | undefined; + +/** @internal Exposed for testing only. */ +export function _resetMarketDataCache(): void { + allMarketsCache = undefined; +} + export async function resolveMarketData( client: NadoClient, productId: number, ): Promise { - const allMarkets = await client.market.getAllMarkets(); - const market = allMarkets.find((m) => m.productId === productId); + if (!allMarketsCache) { + allMarketsCache = await client.market.getAllMarkets(); + } + const market = allMarketsCache.find((m) => m.productId === productId); if (!market) { throw new Error( `Unknown product ${productId}. Use get_all_markets to find valid product IDs.`, @@ -99,35 +113,26 @@ async function resolvePrice( const slippageFrac = slippagePct / 100; if (isLong) { - // POTENTIAL BUG: Market buy slippage is applied to the *bid* price, but when - // buying you cross the *ask* (the price sellers are offering). Using - // `bid * (1 + slippage)` sets the limit price near the mid-market, which may - // sit below the ask in wide-spread markets and cause the IOC to miss entirely. - // Consider using `ask * (1 + slippage)` instead for more reliable fills. - const bidPrice = marketPrice.bid; - if (bidPrice.lte(0)) { + const askPrice = marketPrice.ask; + if (askPrice.lte(0)) { throw new Error( - `No bid price available for product ${productId}. Cannot place market buy order.`, + `No ask price available for product ${productId}. Cannot place market buy order.`, ); } return roundToIncrement( - bidPrice.times(1 + slippageFrac), + askPrice.times(1 + slippageFrac), priceIncrement, ).toFixed(); } - // POTENTIAL BUG: Market sell slippage is applied to the *ask* price, but when - // selling you cross the *bid* (the price buyers are offering). Using - // `ask * (1 - slippage)` may still sit above the bid in wide-spread markets, - // causing the IOC to miss. Consider using `bid * (1 - slippage)` instead. - const askPrice = marketPrice.ask; - if (askPrice.lte(0)) { + const bidPrice = marketPrice.bid; + if (bidPrice.lte(0)) { throw new Error( - `No ask price available for product ${productId}. Cannot place market sell order.`, + `No bid price available for product ${productId}. Cannot place market sell order.`, ); } return roundToIncrement( - askPrice.times(1 - slippageFrac), + bidPrice.times(1 - slippageFrac), priceIncrement, ).toFixed(); } @@ -144,9 +149,9 @@ function resolveIsolatedMargin( if (leverage == null) { throw new Error('leverage is required when marginMode is "isolated".'); } + const notional = new BigNumber(Math.abs(amount)).times(resolvedPrice); return { - // Number is inaccurate when working with X18. anything doing these ops should be with BigNumber - margin: addDecimals(Math.abs((amount * Number(resolvedPrice)) / leverage)), + margin: addDecimals(notional.div(leverage).toNumber()), }; } @@ -161,8 +166,7 @@ export interface BuildEngineOrderInput { reduceOnly: boolean; marginMode?: 'cross' | 'isolated'; leverage?: number; - // Why not just cache the loaded mkts? - /** Pre-resolved market data to avoid redundant getAllMarkets calls. */ + /** Pre-resolved market data to skip the module-level cache lookup. */ marketData?: MarketData; } @@ -183,8 +187,8 @@ export async function buildEngineOrder( } = input; const isLong = amount > 0; - const { priceIncrement, sizeIncrement } = marketData ?? - await resolveMarketData(client, productId); + const { priceIncrement, sizeIncrement } = + marketData ?? (await resolveMarketData(client, productId)); const { signedAmountX18 } = resolveAmount(amount, sizeIncrement); const resolvedPrice = await resolvePrice( client, @@ -213,13 +217,8 @@ export async function buildEngineOrder( order: { price: resolvedPrice, amount: signedAmountX18.toFixed(0), - // NOTE: `expiration` is set to `Date.now()` (milliseconds since epoch), but - // `calculateTwapExpiration` returns seconds. These two code paths use - // inconsistent units. Does the protocol engine accept milliseconds here, - // or should this be `Math.floor(Date.now() / 1000)`? Worth verifying against - // the SDK contract to ensure orders don't immediately expire. - - // I think this just needs a comment to explain that this is effectively no expiration + // Milliseconds — always in the past when interpreted as unix seconds, + // so the engine treats it as a non-expiring order. expiration: Date.now(), nonce: getOrderNonce(), appendix, @@ -257,8 +256,8 @@ export async function buildPriceTriggerOrder( } = input; const isLong = amount > 0; - const { priceIncrement, sizeIncrement } = marketData ?? - await resolveMarketData(client, productId); + const { priceIncrement, sizeIncrement } = + marketData ?? (await resolveMarketData(client, productId)); const { signedAmountX18 } = resolveAmount(amount, sizeIncrement); const resolvedPrice = await resolvePrice( client, @@ -290,8 +289,7 @@ export async function buildPriceTriggerOrder( order: { price: resolvedPrice, amount: signedAmountX18.toFixed(0), - // NOTE: Same milliseconds vs. seconds inconsistency as in `buildEngineOrder` - // above — see comment there. + // Non-expiring: see comment in buildEngineOrder. expiration: Date.now(), nonce: getOrderNonce(), appendix, diff --git a/src/utils/resolveMarket.ts b/src/utils/resolveMarket.ts index fe34058..90f03f8 100644 --- a/src/utils/resolveMarket.ts +++ b/src/utils/resolveMarket.ts @@ -132,7 +132,7 @@ export function resolveProduct( const found = markets.find((m) => m.productId === id); if (!found) { throw new Error( - `No market with product ID ${id}. Run "nado market markets" to list available markets.`, + `No market with product ID ${id}. Run "nado market list" to list available markets.`, ); } if (typeFilter != null && found.type !== typeFilter) { @@ -167,7 +167,7 @@ export function resolveProduct( if (candidates.length === 0) { const suffix = typeLabel ? ` ${typeLabel}` : ''; throw new Error( - `Unknown${suffix} market "${input}". Run "nado market markets" to list available markets.`, + `Unknown${suffix} market "${input}". Run "nado market list" to list available markets.`, ); } @@ -198,6 +198,59 @@ export function resolveProduct( throw new Error(`Ambiguous market "${input}". Matches: ${names}`); } +/** + * Like `resolveProduct` but returns *all* matching markets instead of picking + * one. Useful for commands like `account orders` where `BTC` should match both + * the spot and perp product. + */ +export function resolveAllProducts( + markets: Market[], + input: string, +): Market[] { + const trimmed = input.trim(); + + if (/^\d+$/.test(trimmed)) { + const id = parseInt(trimmed, 10); + const found = markets.find((m) => m.productId === id); + if (!found) { + throw new Error( + `No market with product ID ${id}. Run "nado market list" to list available markets.`, + ); + } + return [found]; + } + + const q = trimmed.toLowerCase(); + const candidates: { market: Market; priority: number }[] = []; + + for (const m of markets) { + const sym = m.symbol.toLowerCase(); + const name = m.marketName.toLowerCase(); + const alts = m.altSearchTerms.map((a) => a.toLowerCase()); + + if (sym === q || name === q) { + candidates.push({ market: m, priority: 0 }); + } else if (alts.includes(q)) { + candidates.push({ market: m, priority: 1 }); + } else if (sym.startsWith(q) || name.startsWith(q)) { + candidates.push({ market: m, priority: 2 }); + } else if (stripWrapperPrefix(sym) === q) { + candidates.push({ market: m, priority: 3 }); + } + } + + if (candidates.length === 0) { + throw new Error( + `Unknown market "${input}". Run "nado market list" to list available markets.`, + ); + } + + const bestPriority = Math.min(...candidates.map((c) => c.priority)); + return candidates + .filter((c) => c.priority === bestPriority) + .map((c) => c.market); +} + export async function getTokenDecimals( dataEnv: DataEnv, chainEnv: ChainEnv, diff --git a/src/ws/ReconnectingWebSocket.ts b/src/ws/ReconnectingWebSocket.ts new file mode 100644 index 0000000..23f815f --- /dev/null +++ b/src/ws/ReconnectingWebSocket.ts @@ -0,0 +1,106 @@ +import WebSocket from 'ws'; + +export interface ReconnectingWebSocketOptions { + /** Initial delay before the first reconnect attempt (ms). Default: 1000 */ + initialDelay?: number; + /** Maximum delay between reconnect attempts (ms). Default: 30000 */ + maxDelay?: number; + /** Multiplier applied to delay after each failed attempt. Default: 2 */ + backoffFactor?: number; +} + +export type MessageListener = (data: WebSocket.RawData) => void; +export type ErrorListener = (err: Error) => void; +export type OpenListener = () => void; + +/** + * A WebSocket wrapper with automatic reconnection and exponential backoff. + * + * The socket reconnects transparently on close/error. Listeners registered + * via `onOpen`, `onMessage`, and `onError` are re-attached to every new + * underlying connection. + */ +export class ReconnectingWebSocket { + private readonly url: string; + private readonly opts: Required; + + private ws: WebSocket | null = null; + private closed = false; + private reconnectTimer: ReturnType | null = null; + private currentDelay: number; + + private openListeners: OpenListener[] = []; + private messageListeners: MessageListener[] = []; + private errorListeners: ErrorListener[] = []; + + constructor(url: string, opts: ReconnectingWebSocketOptions = {}) { + this.url = url; + this.opts = { + initialDelay: opts.initialDelay ?? 1000, + maxDelay: opts.maxDelay ?? 30_000, + backoffFactor: opts.backoffFactor ?? 2, + }; + this.currentDelay = this.opts.initialDelay; + } + + onOpen(listener: OpenListener): this { + this.openListeners.push(listener); + return this; + } + + onMessage(listener: MessageListener): this { + this.messageListeners.push(listener); + return this; + } + + onError(listener: ErrorListener): this { + this.errorListeners.push(listener); + return this; + } + + connect(): void { + if (this.closed) return; + + this.ws = new WebSocket(this.url); + + this.ws.on('open', () => { + this.currentDelay = this.opts.initialDelay; + for (const fn of this.openListeners) fn(); + }); + + this.ws.on('message', (raw: WebSocket.RawData) => { + for (const fn of this.messageListeners) fn(raw); + }); + + this.ws.on('close', () => { + if (!this.closed) this.scheduleReconnect(); + }); + + this.ws.on('error', (err: Error) => { + for (const fn of this.errorListeners) fn(err); + this.ws?.close(); + }); + } + + send(data: string): void { + this.ws?.send(data); + } + + close(): void { + this.closed = true; + if (this.reconnectTimer) clearTimeout(this.reconnectTimer); + this.ws?.close(); + this.ws = null; + } + + private scheduleReconnect(): void { + if (this.closed) return; + this.reconnectTimer = setTimeout(() => { + this.currentDelay = Math.min( + this.currentDelay * this.opts.backoffFactor, + this.opts.maxDelay, + ); + this.connect(); + }, this.currentDelay); + } +} diff --git a/src/ws/subscribe.ts b/src/ws/subscribe.ts index e435055..de9e6c7 100644 --- a/src/ws/subscribe.ts +++ b/src/ws/subscribe.ts @@ -1,12 +1,12 @@ +import type { ChainEnv } from '@nadohq/client'; import { ENGINE_WS_SUBSCRIPTION_CLIENT_ENDPOINTS, type EngineServerSubscriptionBookDepthEvent, type EngineServerSubscriptionEvent, } from '@nadohq/client'; -import type { ChainEnv } from '@nadohq/client'; -import WebSocket from 'ws'; import type { NadoClient } from '../context.js'; +import { ReconnectingWebSocket } from './ReconnectingWebSocket.js'; export interface SubscriptionHandle { close: () => void; @@ -20,73 +20,44 @@ export function subscribeBookDepth( onError?: (err: Error) => void, ): SubscriptionHandle { const url = ENGINE_WS_SUBSCRIPTION_CLIENT_ENDPOINTS[chainEnv]; - let ws: WebSocket | null = null; - let closed = false; - let reconnectDelay = 1000; - let reconnectTimer: ReturnType | null = null; const streamParams = client.ws.subscription.buildSubscriptionParams( 'book_depth', { product_id: productId }, ); - const subscribeMsg = client.ws.subscription.buildSubscriptionMessage( - 1, - 'subscribe', - streamParams, + const subscribeMsg = JSON.stringify( + client.ws.subscription.buildSubscriptionMessage( + 1, + 'subscribe', + streamParams, + ), ); - // if we continue to expand this project, we should create a `ReconnectingWebsocket`, and perhaps a singleton connection across the entire cli (if processes can run in background) - function connect() { - if (closed) return; - - ws = new WebSocket(url); + const ws = new ReconnectingWebSocket(url); - ws.on('open', () => { - reconnectDelay = 1000; - ws?.send(JSON.stringify(subscribeMsg)); - }); + ws.onOpen(() => { + ws.send(subscribeMsg); + }); - ws.on('message', (raw: WebSocket.RawData) => { - try { - const data = JSON.parse(raw.toString()) as - | EngineServerSubscriptionEvent - | { id: number; status: string }; + ws.onMessage((raw) => { + try { + const data = JSON.parse(raw.toString()) as + | EngineServerSubscriptionEvent + | { id: number; status: string }; - if ('type' in data && data.type === 'book_depth') { - onEvent(data as EngineServerSubscriptionBookDepthEvent); - } - } catch { - // ignore malformed messages + if ('type' in data && data.type === 'book_depth') { + onEvent(data as EngineServerSubscriptionBookDepthEvent); } - }); - - ws.on('close', () => { - if (closed) return; - scheduleReconnect(); - }); - - ws.on('error', (err: Error) => { - onError?.(err); - ws?.close(); - }); - } + } catch { + // ignore malformed messages + } + }); - function scheduleReconnect() { - if (closed) return; - reconnectTimer = setTimeout(() => { - reconnectDelay = Math.min(reconnectDelay * 2, 30_000); - connect(); - }, reconnectDelay); + if (onError) { + ws.onError(onError); } - connect(); + ws.connect(); - return { - close() { - closed = true; - if (reconnectTimer) clearTimeout(reconnectTimer); - ws?.close(); - ws = null; - }, - }; + return { close: () => ws.close() }; } From 0db276fc4f0d085e890e87526fa24682d9a57493 Mon Sep 17 00:00:00 2001 From: przemekink Date: Mon, 16 Mar 2026 11:16:27 +0100 Subject: [PATCH 3/8] remove skills comment --- skills/nado/SKILL.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/skills/nado/SKILL.md b/skills/nado/SKILL.md index ad366ca..24b30f1 100644 --- a/skills/nado/SKILL.md +++ b/skills/nado/SKILL.md @@ -132,8 +132,6 @@ nado market list # List all product IDs and names ## Quick Command Reference -# is it a requirement that we need to list every command? can AI just figure it out? - ### Auth & Configuration ```bash From 27b50729463d0f8761ca09f7f5a7d733a329f323 Mon Sep 17 00:00:00 2001 From: przemekink Date: Mon, 16 Mar 2026 13:57:37 +0100 Subject: [PATCH 4/8] replace 0x with Address --- src/handlers/auth.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/handlers/auth.ts b/src/handlers/auth.ts index d9c28c4..7a6e572 100644 --- a/src/handlers/auth.ts +++ b/src/handlers/auth.ts @@ -1,3 +1,4 @@ +import { Address } from 'viem'; import type { NadoContext } from '../context.js'; import { ToolExecutionError } from '../utils/errors.js'; import { requireSigner } from '../utils/requireSigner.js'; @@ -21,7 +22,7 @@ export async function linkSigner( const data = await ctx.client.subaccount.linkSigner({ subaccountOwner: ctx.subaccountOwner!, subaccountName: ctx.subaccountName, - signer: args.signer as `0x${string}`, + signer: args.signer as Address, }); return { data }; } catch (err) { From b9e1758d35e70bfd78beac91589b6b263be17268 Mon Sep 17 00:00:00 2001 From: przemekink Date: Mon, 16 Mar 2026 16:36:42 +0100 Subject: [PATCH 5/8] improve math --- src/handlers/account.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/handlers/account.ts b/src/handlers/account.ts index 4c09a4f..44c7078 100644 --- a/src/handlers/account.ts +++ b/src/handlers/account.ts @@ -1,6 +1,7 @@ import type { NadoClient } from '@nadohq/client'; import { ProductEngineType, removeDecimals } from '@nadohq/client'; import type { IndexerMatchEvent } from '@nadohq/indexer-client'; +import BigNumber from 'bignumber.js'; import type { NadoContext } from '../context.js'; import { ToolExecutionError } from '../utils/errors.js'; @@ -200,13 +201,13 @@ function sumSnapshotVolume(snapshot: { trackedVars: { quoteVolumeCumulative: { toString(): string } }; }>; }): number { - let total = 0; + let total = new BigNumber(0); for (const balance of snapshot.balances) { - total += - Math.abs(Number(balance.trackedVars.quoteVolumeCumulative.toString())) / - 1e18; + total = total.plus( + new BigNumber(balance.trackedVars.quoteVolumeCumulative.toString()).abs(), + ); } - return total; + return removeDecimals(total).toNumber(); } async function fetchSnapshotVolume( From 1f6e26153fafc48f8e695086607035931cb9f997 Mon Sep 17 00:00:00 2001 From: przemekink Date: Tue, 17 Mar 2026 15:35:53 +0100 Subject: [PATCH 6/8] add ci tests --- .github/workflows/ci.yml | 31 ++++++++ package.json | 7 +- src/__tests__/utils/orderBuilder.test.ts | 3 +- src/commands/trade.ts | 90 ++++++++++++++++++++++++ src/handlers/trade.ts | 70 +++++++++++++++++- src/utils/orderBuilder.ts | 7 +- 6 files changed, 201 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a4bf6ed --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,31 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + types: [opened, synchronize] + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - run: bun install + + - run: bun run build + + - run: bun run typecheck + + - run: bun run lint:check + + - run: bun run test diff --git a/package.json b/package.json index 4f13b86..31b8390 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,10 @@ "type": "git", "url": "https://github.com/nadohq/nado-cli.git" }, - "homepage": "https://github.com/nadohq/nado-cli", + "homepage": "https://github.com/nadohq/nado-cli#readme", + "bugs": { + "url": "https://github.com/nadohq/nado-cli/issues" + }, "keywords": [ "nado", "crypto", @@ -34,7 +37,7 @@ "lint": "eslint --cache --fix './**/*.{ts,tsx}' && prettier --write './**/*.{ts,tsx}'", "lint:check": "eslint --cache './**/*.{ts,tsx}'", "format": "prettier --write './src/**/*.ts' --log-level warn", - "prepare": "husky", + "prepare": "husky || true", "start": "node dist/index.js", "test": "vitest run", "test:watch": "vitest", diff --git a/src/__tests__/utils/orderBuilder.test.ts b/src/__tests__/utils/orderBuilder.test.ts index f4ed1c7..499f321 100644 --- a/src/__tests__/utils/orderBuilder.test.ts +++ b/src/__tests__/utils/orderBuilder.test.ts @@ -17,8 +17,9 @@ import { vi.mock('@nadohq/client', async () => { const BN = (await import('bignumber.js')).default; + const E18 = new BN('1000000000000000000'); return { - addDecimals: (v: number) => BigInt(Math.round(v * 1e18)), + addDecimals: (v: unknown) => new BN(String(v)).times(E18), toBigDecimal: (v: bigint | number) => new BN(v.toString()), getOrderNonce: () => '0xdeadbeef', packOrderAppendix: (opts: Record) => { diff --git a/src/commands/trade.ts b/src/commands/trade.ts index f19c06e..5ee8c2b 100644 --- a/src/commands/trade.ts +++ b/src/commands/trade.ts @@ -3,6 +3,7 @@ import chalk from 'chalk'; import { Argument, Command, Option } from 'commander'; import { + cancelAndPlace, cancelOrders, cancelProductOrders, cancelTriggerOrders, @@ -246,6 +247,95 @@ export function createTradeCommand(): Command { }); }); + trade + .command('cancel-and-place') + .description('Atomically cancel orders and place a new one') + .argument('', 'Market for the new order') + .addArgument(sideArgument()) + .argument('', 'Order size in base units') + .requiredOption( + '--cancel-digests ', + 'Comma-separated order digests to cancel', + splitMarketArgs, + ) + .requiredOption( + '--cancel-market ', + 'Market the cancelled orders belong to', + ) + .option('-p, --price ', 'Limit price (omit for market order)') + .option('-m, --margin ', 'Margin mode: cross or isolated', 'cross') + .option('--leverage ', 'Leverage for isolated margin') + .option('--tif ', 'Time in force: gtc, ioc, fok, post_only') + .option('--reduce-only', 'Reduce-only order') + .option('--slippage ', 'Slippage tolerance %', '2') + .action( + async ( + marketArg: string, + side: string, + amountStr: string, + opts: { + cancelDigests: string[]; + cancelMarket: string; + price?: string; + margin: string; + leverage?: string; + tif?: string; + reduceOnly?: boolean; + slippage: string; + }, + cmd: Command, + ) => { + await runConfirmed(cmd, async (ctx) => { + const m = await resolveMarketArg(ctx, marketArg); + const cancelM = await resolveMarketArg(ctx, opts.cancelMarket); + const amount = parseFloat(amountStr); + const price = opts.price ? parseFloat(opts.price) : undefined; + const cancelProductIds = opts.cancelDigests.map( + () => cancelM.productId, + ); + + const summary = [ + '', + chalk.bold( + ` Cancel ${opts.cancelDigests.length} order(s) on ${cancelM.marketName}`, + ), + ...opts.cancelDigests.map((d) => chalk.dim(` • ${d}`)), + '', + chalk.bold(` + Place ${side} ${amount} ${m.marketName}`), + ` Type: ${price ? `limit @ $${price}` : 'market'}`, + ` Margin: ${opts.margin}`, + opts.leverage ? ` Leverage: ${opts.leverage}x` : null, + opts.tif ? ` TIF: ${opts.tif}` : null, + opts.reduceOnly ? ' Reduce: yes' : null, + !price ? ` Slippage: ${opts.slippage}%` : null, + '', + ] + .filter((line): line is string => line != null) + .join('\n'); + + return { + summary, + execute: () => + cancelAndPlace(ctx, { + cancelProductIds, + cancelDigests: opts.cancelDigests, + productId: m.productId, + side: side as 'long' | 'short', + amount, + price, + marginMode: opts.margin as 'cross' | 'isolated', + leverage: opts.leverage + ? parseFloat(opts.leverage) + : undefined, + timeInForce: opts.tif, + reduceOnly: opts.reduceOnly ?? false, + slippagePct: parseFloat(opts.slippage), + }), + }; + }); + }, + ); + trade .command('twap') .description('Place a TWAP order (split across time)') diff --git a/src/handlers/trade.ts b/src/handlers/trade.ts index 0f4b490..a61f8bb 100644 --- a/src/handlers/trade.ts +++ b/src/handlers/trade.ts @@ -129,6 +129,74 @@ export async function placeOrder( } } +// --------------------------------------------------------------------------- +// cancel_and_place +// --------------------------------------------------------------------------- + +export interface CancelAndPlaceArgs { + cancelProductIds: number[]; + cancelDigests: string[]; + productId: number; + side: BalanceSide; + amount: number; + price?: number; + marginMode?: 'cross' | 'isolated'; + leverage?: number; + timeInForce?: string; + reduceOnly?: boolean; + slippagePct?: number; +} + +export async function cancelAndPlace( + ctx: NadoContext, + args: CancelAndPlaceArgs, +): Promise { + requireSigner('cancel_and_place', ctx); + + try { + const isMarketOrder = args.price == null; + const executionType = isMarketOrder + ? 'ioc' + : toExecutionType(args.timeInForce ?? 'gtc'); + + const orderParams = await buildEngineOrder({ + client: ctx.client, + productId: args.productId, + amount: args.side === 'short' ? -args.amount : args.amount, + price: args.price, + slippagePct: args.slippagePct ?? DEFAULT_SLIPPAGE_PCT, + orderExecutionType: executionType, + reduceOnly: args.reduceOnly ?? false, + marginMode: args.marginMode ?? 'cross', + leverage: args.leverage, + }); + + const data = await ctx.client.market.cancelAndPlace({ + cancelOrders: { + subaccountOwner: (ctx as NadoContextWithSigner).subaccountOwner, + subaccountName: ctx.subaccountName, + productIds: args.cancelProductIds, + digests: args.cancelDigests, + }, + placeOrder: { + ...orderParams, + order: { + subaccountOwner: (ctx as NadoContextWithSigner).subaccountOwner, + subaccountName: ctx.subaccountName, + ...orderParams.order, + }, + }, + }); + return { data }; + } catch (err) { + throw new ToolExecutionError( + 'cancel_and_place', + 'Failed to cancel and place order. Verify the digests are valid using "nado account orders".', + err, + ); + } +} + // --------------------------------------------------------------------------- // close_position // --------------------------------------------------------------------------- @@ -701,7 +769,7 @@ export async function placeTwapOrder( } const perOrderAmount = roundToIncrement( - toBigDecimal(addDecimals(args.amount / numOrders)), + addDecimals(toBigDecimal(args.amount / numOrders)), sizeIncrement, ); diff --git a/src/utils/orderBuilder.ts b/src/utils/orderBuilder.ts index 9aed1f2..fa0bd98 100644 --- a/src/utils/orderBuilder.ts +++ b/src/utils/orderBuilder.ts @@ -90,7 +90,7 @@ function resolveAmount( ): ResolvedAmount { const isLong = amount > 0; const absAmountX18 = roundToIncrement( - toBigDecimal(addDecimals(Math.abs(amount))), + addDecimals(toBigDecimal(Math.abs(amount))), sizeIncrement, ); const signedAmountX18 = isLong ? absAmountX18 : absAmountX18.negated(); @@ -149,9 +149,10 @@ function resolveIsolatedMargin( if (leverage == null) { throw new Error('leverage is required when marginMode is "isolated".'); } - const notional = new BigNumber(Math.abs(amount)).times(resolvedPrice); return { - margin: addDecimals(notional.div(leverage).toNumber()), + margin: addDecimals( + toBigDecimal(Math.abs(amount)).times(resolvedPrice).dividedBy(leverage), + ), }; } From 12dff20ca7f3d7e199592ad8310b660c0ecffd93 Mon Sep 17 00:00:00 2001 From: przemekink Date: Tue, 17 Mar 2026 15:47:55 +0100 Subject: [PATCH 7/8] add missing imports --- src/__tests__/utils/orderBuilder.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/utils/orderBuilder.test.ts b/src/__tests__/utils/orderBuilder.test.ts index 499f321..2e1cf5a 100644 --- a/src/__tests__/utils/orderBuilder.test.ts +++ b/src/__tests__/utils/orderBuilder.test.ts @@ -1,5 +1,5 @@ import BigNumber from 'bignumber.js'; -import { describe, expect, it, vi } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { _resetMarketDataCache, From f8368f1ae156679acf35048f72787c3ef234168a Mon Sep 17 00:00:00 2001 From: przemekink Date: Tue, 17 Mar 2026 15:59:03 +0100 Subject: [PATCH 8/8] lint fix --- eslint.config.js | 1 + src/__tests__/utils/errors.test.ts | 5 +- src/__tests__/utils/formatting.test.ts | 6 +- src/__tests__/utils/orderBuilder.test.ts | 1 + src/__tests__/utils/resolveMarket.test.ts | 36 ++------ src/commands/account.ts | 103 ++++++++++++++++------ src/commands/auth.ts | 4 +- src/commands/funds.ts | 27 +++--- src/commands/helpers.ts | 11 ++- src/commands/nlp.ts | 4 +- src/commands/shell.ts | 7 +- src/commands/trade.ts | 4 +- src/config.ts | 2 +- src/handlers/auth.ts | 2 +- src/handlers/funds.ts | 4 +- src/handlers/market.ts | 18 ++-- src/handlers/nlp.ts | 8 +- src/handlers/trade.ts | 46 +++++----- src/output/orderbook.ts | 27 +++--- src/output/table.ts | 8 +- src/utils/resolveMarket.ts | 8 +- src/ws/subscribe.ts | 9 +- 22 files changed, 186 insertions(+), 155 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 92d9fa2..813f3f3 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -43,6 +43,7 @@ export default tseslint.config( 'node_modules/', 'dist/', 'tsup.config.ts', + 'vitest.config.ts', '.lintstagedrc.js', 'eslint.config.js', ], diff --git a/src/__tests__/utils/errors.test.ts b/src/__tests__/utils/errors.test.ts index c58eb45..ea7a379 100644 --- a/src/__tests__/utils/errors.test.ts +++ b/src/__tests__/utils/errors.test.ts @@ -1,9 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { - formatError, - ToolExecutionError, -} from '../../utils/errors.js'; +import { formatError, ToolExecutionError } from '../../utils/errors.js'; describe('ToolExecutionError', () => { it('includes the tool name in the message', () => { diff --git a/src/__tests__/utils/formatting.test.ts b/src/__tests__/utils/formatting.test.ts index fe926a7..0f33ffa 100644 --- a/src/__tests__/utils/formatting.test.ts +++ b/src/__tests__/utils/formatting.test.ts @@ -1,10 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { - fmtNumber, - fmtProductIds, - fmtUsd, -} from '../../utils/formatting.js'; +import { fmtNumber, fmtProductIds, fmtUsd } from '../../utils/formatting.js'; describe('fmtNumber', () => { it('formats a positive number with default 2 decimals', () => { diff --git a/src/__tests__/utils/orderBuilder.test.ts b/src/__tests__/utils/orderBuilder.test.ts index 2e1cf5a..ee0d685 100644 --- a/src/__tests__/utils/orderBuilder.test.ts +++ b/src/__tests__/utils/orderBuilder.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */ import BigNumber from 'bignumber.js'; import { beforeEach, describe, expect, it, vi } from 'vitest'; diff --git a/src/__tests__/utils/resolveMarket.test.ts b/src/__tests__/utils/resolveMarket.test.ts index f85c2a6..103682a 100644 --- a/src/__tests__/utils/resolveMarket.test.ts +++ b/src/__tests__/utils/resolveMarket.test.ts @@ -153,31 +153,19 @@ describe('resolveProduct', () => { describe('with typeFilter', () => { it('resolves kBTC to spot when filtered to SPOT', () => { - const result = resolveProduct( - MARKETS, - 'kBTC', - ProductEngineType.SPOT, - ); + const result = resolveProduct(MARKETS, 'kBTC', ProductEngineType.SPOT); expect(result.productId).toBe(1); expect(result.type).toBe(ProductEngineType.SPOT); }); it('resolves BTC to perp when filtered to PERP', () => { - const result = resolveProduct( - MARKETS, - 'BTC', - ProductEngineType.PERP, - ); + const result = resolveProduct(MARKETS, 'BTC', ProductEngineType.PERP); expect(result.productId).toBe(2); expect(result.type).toBe(ProductEngineType.PERP); }); it('resolves wETH to spot when filtered to SPOT', () => { - const result = resolveProduct( - MARKETS, - 'wETH', - ProductEngineType.SPOT, - ); + const result = resolveProduct(MARKETS, 'wETH', ProductEngineType.SPOT); expect(result.productId).toBe(3); expect(result.type).toBe(ProductEngineType.SPOT); }); @@ -204,11 +192,7 @@ describe('resolveProduct', () => { }); it('allows numeric ID when type matches', () => { - const result = resolveProduct( - MARKETS, - '3', - ProductEngineType.SPOT, - ); + const result = resolveProduct(MARKETS, '3', ProductEngineType.SPOT); expect(result.productId).toBe(3); expect(result.symbol).toBe('wETH'); }); @@ -216,21 +200,13 @@ describe('resolveProduct', () => { describe('stripped wrapper prefix', () => { it('resolves "btc" to kBTC spot via stripped prefix', () => { - const result = resolveProduct( - MARKETS, - 'btc', - ProductEngineType.SPOT, - ); + const result = resolveProduct(MARKETS, 'btc', ProductEngineType.SPOT); expect(result.productId).toBe(1); expect(result.symbol).toBe('kBTC'); }); it('resolves "eth" to wETH spot via stripped prefix', () => { - const result = resolveProduct( - MARKETS, - 'eth', - ProductEngineType.SPOT, - ); + const result = resolveProduct(MARKETS, 'eth', ProductEngineType.SPOT); expect(result.productId).toBe(3); expect(result.symbol).toBe('wETH'); }); diff --git a/src/commands/account.ts b/src/commands/account.ts index 0a96b50..45eae9b 100644 --- a/src/commands/account.ts +++ b/src/commands/account.ts @@ -1,10 +1,10 @@ import { + nowInSeconds, ProductEngineType, removeDecimals, + toBigDecimal, toPrintableObject, type BigDecimalish, - toBigDecimal, - nowInSeconds, } from '@nadohq/client'; import chalk from 'chalk'; import { Command } from 'commander'; @@ -72,7 +72,9 @@ export function createAccountCommand(): Command { const owner = requireOwner(config); const metaMarkets = await getMarkets(ctx.dataEnv, ctx.chainEnv); - const nameById = new Map(metaMarkets.map((m) => [m.productId, m.marketName])); + const nameById = new Map( + metaMarkets.map((m) => [m.productId, m.marketName]), + ); const [summary, isolated, snapshotResp] = await Promise.all([ ctx.client.subaccount.getSubaccountSummary({ @@ -119,7 +121,10 @@ export function createAccountCommand(): Command { } // Fetch market prices (bid/ask) for exit price estimation - const exitPriceByProduct = new Map(); + const exitPriceByProduct = new Map< + number, + { bid?: BigDecimalish; ask?: BigDecimalish } + >(); if (perpIds.length > 0) { const pricesResp = await ctx.client.market.getLatestMarketPrices({ productIds: perpIds, @@ -136,9 +141,11 @@ export function createAccountCommand(): Command { const crossPerps = summary.balances.filter( (b) => b.type === ProductEngineType.PERP && !b.amount.isZero(), ); - console.log(renderJson({ - data: { crossPositions: crossPerps, isolatedPositions: isolated }, - })); + console.log( + renderJson({ + data: { crossPositions: crossPerps, isolatedPositions: isolated }, + }), + ); return; } @@ -146,13 +153,29 @@ export function createAccountCommand(): Command { for (const b of summary.balances) { if (b.type !== ProductEngineType.PERP || b.amount.isZero()) continue; - rows.push(buildPositionRow(b, nameById, netEntryByProduct, exitPriceByProduct, 'cross')); + rows.push( + buildPositionRow( + b, + nameById, + netEntryByProduct, + exitPriceByProduct, + 'cross', + ), + ); } for (const iso of isolated) { const b = iso.baseBalance; if (b.amount.isZero()) continue; - rows.push(buildPositionRow(b, nameById, netEntryByProduct, exitPriceByProduct, 'isolated')); + rows.push( + buildPositionRow( + b, + nameById, + netEntryByProduct, + exitPriceByProduct, + 'isolated', + ), + ); } if (rows.length === 0) { @@ -164,7 +187,16 @@ export function createAccountCommand(): Command { console.log( renderTable({ data: null, - headers: ['Market', 'Side', 'Size', 'Notional', 'Entry', 'Mark', 'Est. PnL', 'Mode'], + headers: [ + 'Market', + 'Side', + 'Size', + 'Notional', + 'Entry', + 'Mark', + 'Est. PnL', + 'Mode', + ], rows, }), ); @@ -178,7 +210,10 @@ export function createAccountCommand(): Command { account .command('orders') .description('List open orders (all markets or filtered)') - .argument('[markets...]', 'Optional: symbols, names, or product IDs to filter') + .argument( + '[markets...]', + 'Optional: symbols, names, or product IDs to filter', + ) .action(async (marketArgs: string[], _opts: unknown, cmd: Command) => { await run(cmd, async (ctx, config) => { const owner = requireOwner(config); @@ -187,7 +222,9 @@ export function createAccountCommand(): Command { let nameById: Map; const metaMarkets = await getMarkets(ctx.dataEnv, ctx.chainEnv); - const metaNameById = new Map(metaMarkets.map((m) => [m.productId, m.marketName])); + const metaNameById = new Map( + metaMarkets.map((m) => [m.productId, m.marketName]), + ); if (marketArgs.length > 0) { const resolved = await resolveMarketArgsAll(ctx, marketArgs); @@ -204,7 +241,10 @@ export function createAccountCommand(): Command { // Some products (e.g. NLP pool token) exist on the engine but have no // orderbook. When querying all markets, batch into chunks and silently // skip products that the engine rejects. - const allProductOrders: Array<{ productId: number; orders: unknown[] }> = []; + const allProductOrders: Array<{ + productId: number; + orders: unknown[]; + }> = []; const BATCH_SIZE = 10; for (let i = 0; i < productIds.length; i += BATCH_SIZE) { @@ -228,7 +268,10 @@ export function createAccountCommand(): Command { productIds: [pid], }); const sp = toPrintableObject(single.data) as { - productOrders: Array<{ productId: number; orders: unknown[] }>; + productOrders: Array<{ + productId: number; + orders: unknown[]; + }>; }; allProductOrders.push(...(sp.productOrders ?? [])); } catch { @@ -238,7 +281,9 @@ export function createAccountCommand(): Command { } } - const result: CommandResult = { data: { productOrders: allProductOrders } }; + const result: CommandResult = { + data: { productOrders: allProductOrders }, + }; const rows: string[][] = []; for (const po of allProductOrders as Array<{ @@ -279,7 +324,15 @@ export function createAccountCommand(): Command { return result; } - result.headers = ['Market', 'Side', 'Price', 'Total', 'Unfilled', 'Placed', 'Digest']; + result.headers = [ + 'Market', + 'Side', + 'Price', + 'Total', + 'Unfilled', + 'Placed', + 'Digest', + ]; result.rows = rows; return result; }); @@ -295,14 +348,13 @@ export function createAccountCommand(): Command { splitMarketArgs, ) .action( - async ( - opts: { limit: string; productIds?: string[] }, - cmd: Command, - ) => { + async (opts: { limit: string; productIds?: string[] }, cmd: Command) => { await run(cmd, async (ctx, config) => { const owner = requireOwner(config); const productIds = opts.productIds - ? (await resolveMarketArgs(ctx, opts.productIds)).map((m) => m.productId) + ? (await resolveMarketArgs(ctx, opts.productIds)).map( + (m) => m.productId, + ) : undefined; return getHistoricalOrders(ctx, { subaccountOwner: owner, @@ -337,14 +389,13 @@ export function createAccountCommand(): Command { splitMarketArgs, ) .action( - async ( - opts: { days: string; productIds?: string[] }, - cmd: Command, - ) => { + async (opts: { days: string; productIds?: string[] }, cmd: Command) => { await run(cmd, async (ctx, config) => { const owner = requireOwner(config); const productIds = opts.productIds - ? (await resolveMarketArgs(ctx, opts.productIds)).map((m) => m.productId) + ? (await resolveMarketArgs(ctx, opts.productIds)).map( + (m) => m.productId, + ) : undefined; return getAccountStats(ctx, { subaccountOwner: owner, diff --git a/src/commands/auth.ts b/src/commands/auth.ts index 2ab01f5..2261ccf 100644 --- a/src/commands/auth.ts +++ b/src/commands/auth.ts @@ -80,7 +80,7 @@ export function createAuthCommand(): Command { `Signer address to link (use ${ZERO_ADDR} to revoke)`, ) .action(async (address: string, _opts: unknown, cmd: Command) => { - await runConfirmed(cmd, async (ctx) => { + await runConfirmed(cmd, (ctx) => { const isRevoke = address === ZERO_ADDR; const summary = [ @@ -418,7 +418,7 @@ async function setup1ct(): Promise { await client.subaccount.linkSigner({ subaccountOwner: owner, subaccountName, - signer: signerBytes32 as Address, + signer: signerBytes32, }); console.log(chalk.green(' Signer linked! ✓')); autoLinked = true; diff --git a/src/commands/funds.ts b/src/commands/funds.ts index 8a2f867..dd26db8 100644 --- a/src/commands/funds.ts +++ b/src/commands/funds.ts @@ -20,12 +20,8 @@ export function createFundsCommand(): Command { .argument('', 'Amount to deposit (e.g. 100 for 100 USDT0)') .option('--product-id ', 'Spot product ID (0 = USDT0)', '0') .action( - async ( - amountStr: string, - opts: { productId: string }, - cmd: Command, - ) => { - await runConfirmed(cmd, async (ctx) => { + async (amountStr: string, opts: { productId: string }, cmd: Command) => { + await runConfirmed(cmd, (ctx) => { const amount = parseFloat(amountStr); const productId = parseInt(opts.productId, 10); @@ -50,12 +46,8 @@ export function createFundsCommand(): Command { .argument('', 'Amount to withdraw (e.g. 100 for 100 USDT0)') .option('--product-id ', 'Spot product ID (0 = USDT0)', '0') .action( - async ( - amountStr: string, - opts: { productId: string }, - cmd: Command, - ) => { - await runConfirmed(cmd, async (ctx) => { + async (amountStr: string, opts: { productId: string }, cmd: Command) => { + await runConfirmed(cmd, (ctx) => { const amount = parseFloat(amountStr); const productId = parseInt(opts.productId, 10); @@ -112,10 +104,13 @@ export function createFundsCommand(): Command { if (others.length === 1) { to = others[0].subaccountName; } else { - to = await select('Select destination subaccount:', others.map((s) => ({ - label: s.subaccountName, - value: s.subaccountName, - }))); + to = await select( + 'Select destination subaccount:', + others.map((s) => ({ + label: s.subaccountName, + value: s.subaccountName, + })), + ); } } diff --git a/src/commands/helpers.ts b/src/commands/helpers.ts index 25b9630..6de0a2a 100644 --- a/src/commands/helpers.ts +++ b/src/commands/helpers.ts @@ -1,19 +1,19 @@ import chalk from 'chalk'; import type { Command } from 'commander'; +import { type ProductEngineType } from '@nadohq/client'; import { loadConfig, type CliConfig } from '../config.js'; import { createNadoContext, type NadoContext } from '../context.js'; import type { CommandResult } from '../handlers/types.js'; import { render } from '../output/index.js'; import { confirm } from '../utils/confirm.js'; import { formatError } from '../utils/errors.js'; -import { type ProductEngineType } from '@nadohq/client'; import { getMarkets, - type Market, resolveAllProducts, resolveProduct, + type Market, } from '../utils/resolveMarket.js'; /** @@ -47,10 +47,9 @@ export async function runConfirmed( prepare: ( ctx: NadoContext, config: CliConfig, - ) => Promise<{ - summary: string; - execute: () => Promise; - }>, + ) => + | Promise<{ summary: string; execute: () => Promise }> + | { summary: string; execute: () => Promise }, ): Promise { try { const flags = command.optsWithGlobals(); diff --git a/src/commands/nlp.ts b/src/commands/nlp.ts index 76ed4b3..7560437 100644 --- a/src/commands/nlp.ts +++ b/src/commands/nlp.ts @@ -38,7 +38,7 @@ export function createNlpCommand(): Command { .description('Deposit USDT0 into the NLP vault to mint NLP tokens') .argument('', 'Amount of USDT0 to deposit') .action(async (amountStr: string, _opts: unknown, cmd: Command) => { - await runConfirmed(cmd, async (ctx) => { + await runConfirmed(cmd, (ctx) => { const amount = parseFloat(amountStr); const summary = [ @@ -60,7 +60,7 @@ export function createNlpCommand(): Command { .description('Burn NLP tokens to withdraw USDT0 from the vault') .argument('', 'Amount of NLP tokens to burn') .action(async (amountStr: string, _opts: unknown, cmd: Command) => { - await runConfirmed(cmd, async (ctx) => { + await runConfirmed(cmd, (ctx) => { const amount = parseFloat(amountStr); const summary = [ diff --git a/src/commands/shell.ts b/src/commands/shell.ts index 672d194..b6f7a50 100644 --- a/src/commands/shell.ts +++ b/src/commands/shell.ts @@ -52,7 +52,9 @@ async function startShell(program: Command): Promise { setSharedReadline(rl); console.log( - chalk.dim('Nado interactive shell. Type "help" for commands, "exit" to quit.'), + chalk.dim( + 'Nado interactive shell. Type "help" for commands, "exit" to quit.', + ), ); rl.prompt(); @@ -152,8 +154,7 @@ function loadHistory(): string[] { function saveHistory(rl: readline.Interface): void { try { ensureConfigDir(); - const history = - (rl as unknown as { history: string[] }).history ?? []; + const history = (rl as unknown as { history: string[] }).history ?? []; const lines = history.slice(0, MAX_HISTORY).reverse(); fs.writeFileSync(HISTORY_FILE, lines.join('\n') + '\n'); } catch { diff --git a/src/commands/trade.ts b/src/commands/trade.ts index 5ee8c2b..3624266 100644 --- a/src/commands/trade.ts +++ b/src/commands/trade.ts @@ -324,9 +324,7 @@ export function createTradeCommand(): Command { amount, price, marginMode: opts.margin as 'cross' | 'isolated', - leverage: opts.leverage - ? parseFloat(opts.leverage) - : undefined, + leverage: opts.leverage ? parseFloat(opts.leverage) : undefined, timeInForce: opts.tif, reduceOnly: opts.reduceOnly ?? false, slippagePct: parseFloat(opts.slippage), diff --git a/src/config.ts b/src/config.ts index 792ddbf..9251464 100644 --- a/src/config.ts +++ b/src/config.ts @@ -2,8 +2,8 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import type { ChainEnv } from '@nadohq/client'; import * as TOML from '@iarna/toml'; +import type { ChainEnv } from '@nadohq/client'; import { ALL_DATA_ENVS, type DataEnv, getDataEnvConfig } from './dataEnv.js'; diff --git a/src/handlers/auth.ts b/src/handlers/auth.ts index 7a6e572..982da8a 100644 --- a/src/handlers/auth.ts +++ b/src/handlers/auth.ts @@ -20,7 +20,7 @@ export async function linkSigner( try { const data = await ctx.client.subaccount.linkSigner({ - subaccountOwner: ctx.subaccountOwner!, + subaccountOwner: ctx.subaccountOwner, subaccountName: ctx.subaccountName, signer: args.signer as Address, }); diff --git a/src/handlers/funds.ts b/src/handlers/funds.ts index 15ab2ee..0f1fc98 100644 --- a/src/handlers/funds.ts +++ b/src/handlers/funds.ts @@ -73,7 +73,7 @@ export async function withdrawCollateral( ); const data = await ctx.client.spot.withdraw({ - subaccountOwner: ctx.subaccountOwner!, + subaccountOwner: ctx.subaccountOwner, subaccountName: ctx.subaccountName, productId: args.productId, amount: addDecimals(args.amount, decimals), @@ -110,7 +110,7 @@ export async function transferQuote( try { const data = await ctx.client.spot.transferQuote({ - subaccountOwner: ctx.subaccountOwner!, + subaccountOwner: ctx.subaccountOwner, subaccountName: from, recipientSubaccountName: to, amount: addDecimals(args.amount), diff --git a/src/handlers/market.ts b/src/handlers/market.ts index 2049e78..788f7c9 100644 --- a/src/handlers/market.ts +++ b/src/handlers/market.ts @@ -1,7 +1,11 @@ import { ProductEngineType, removeDecimals } from '@nadohq/client'; import type { NadoContext } from '../context.js'; -import { renderOrderbook, type OrderbookLevel, type OrderbookIncrements } from '../output/orderbook.js'; +import { + renderOrderbook, + type OrderbookIncrements, + type OrderbookLevel, +} from '../output/orderbook.js'; import { ToolExecutionError } from '../utils/errors.js'; import { fmtProductIds } from '../utils/formatting.js'; import { resolveMarketData } from '../utils/orderBuilder.js'; @@ -332,10 +336,14 @@ export async function watchOrderbook( size: removeDecimals(a.liquidity).toNumber(), })); function draw() { - const output = renderOrderbook(args.marketName, { - bids: latestBids, - asks: latestAsks, - }, increments); + const output = renderOrderbook( + args.marketName, + { + bids: latestBids, + asks: latestAsks, + }, + increments, + ); process.stdout.write('\x1B[2J\x1B[H'); process.stdout.write(output + '\n'); } diff --git a/src/handlers/nlp.ts b/src/handlers/nlp.ts index 323e57f..727f61a 100644 --- a/src/handlers/nlp.ts +++ b/src/handlers/nlp.ts @@ -9,9 +9,7 @@ import type { CommandResult } from './types.js'; // get_nlp_pool_info // --------------------------------------------------------------------------- -export async function getNlpPoolInfo( - ctx: NadoContext, -): Promise { +export async function getNlpPoolInfo(ctx: NadoContext): Promise { try { const data = await ctx.client.context.engineClient.getNlpPoolInfo(); return { data }; @@ -99,7 +97,7 @@ export async function mintNlp( try { const data = await ctx.client.spot.mintNlp({ - subaccountOwner: ctx.subaccountOwner!, + subaccountOwner: ctx.subaccountOwner, subaccountName: ctx.subaccountName, quoteAmount: addDecimals(args.quoteAmount), spotLeverage: args.spotLeverage, @@ -130,7 +128,7 @@ export async function burnNlp( try { const data = await ctx.client.spot.burnNlp({ - subaccountOwner: ctx.subaccountOwner!, + subaccountOwner: ctx.subaccountOwner, subaccountName: ctx.subaccountName, nlpAmount: addDecimals(args.nlpAmount), }); diff --git a/src/handlers/trade.ts b/src/handlers/trade.ts index a61f8bb..ce7ca22 100644 --- a/src/handlers/trade.ts +++ b/src/handlers/trade.ts @@ -11,7 +11,7 @@ import BigNumber from 'bignumber.js'; import chalk from 'chalk'; -import type { NadoContext, NadoContextWithSigner } from '../context.js'; +import type { NadoContext } from '../context.js'; import { ToolExecutionError } from '../utils/errors.js'; import { fmtProductIds } from '../utils/formatting.js'; import { @@ -108,7 +108,7 @@ export async function placeOrder( return { ...orderParams, order: { - subaccountOwner: (ctx as NadoContextWithSigner).subaccountOwner, + subaccountOwner: ctx.subaccountOwner, subaccountName: ctx.subaccountName, ...orderParams.order, }, @@ -173,7 +173,7 @@ export async function cancelAndPlace( const data = await ctx.client.market.cancelAndPlace({ cancelOrders: { - subaccountOwner: (ctx as NadoContextWithSigner).subaccountOwner, + subaccountOwner: ctx.subaccountOwner, subaccountName: ctx.subaccountName, productIds: args.cancelProductIds, digests: args.cancelDigests, @@ -181,7 +181,7 @@ export async function cancelAndPlace( placeOrder: { ...orderParams, order: { - subaccountOwner: (ctx as NadoContextWithSigner).subaccountOwner, + subaccountOwner: ctx.subaccountOwner, subaccountName: ctx.subaccountName, ...orderParams.order, }, @@ -218,14 +218,14 @@ export async function closePosition( await Promise.all([ ctx.client.market .cancelProductOrders({ - subaccountOwner: ctx.subaccountOwner!, + subaccountOwner: ctx.subaccountOwner, subaccountName: ctx.subaccountName, productIds: [args.productId], }) .catch(warnOnCancelFailure('cancel open orders')), ctx.client.market .cancelTriggerProductOrders({ - subaccountOwner: ctx.subaccountOwner!, + subaccountOwner: ctx.subaccountOwner, subaccountName: ctx.subaccountName, productIds: [args.productId], }) @@ -235,12 +235,12 @@ export async function closePosition( // 2. Query positions + market data const [summary, isolatedPositions, allMarkets] = await Promise.all([ ctx.client.subaccount.getSubaccountSummary({ - subaccountOwner: ctx.subaccountOwner!, + subaccountOwner: ctx.subaccountOwner, subaccountName: ctx.subaccountName, }), ctx.client.subaccount .getIsolatedPositions({ - subaccountOwner: ctx.subaccountOwner!, + subaccountOwner: ctx.subaccountOwner, subaccountName: ctx.subaccountName, }) .catch(() => []), @@ -272,11 +272,11 @@ export async function closePosition( const balance = hasCrossPosition ? crossBalance : isolatedPos!.baseBalance; const marginMode = hasCrossPosition ? 'cross' : 'isolated'; const orderParams = buildCloseOrderFromBalance( - balance as BalanceWithProduct, + balance, marketByProductId, slippagePct, marginMode, - ctx.subaccountOwner!, + ctx.subaccountOwner, ctx.subaccountName, ); @@ -325,12 +325,12 @@ export async function closeAllPositions( // 1. Query positions + market data const [summary, isolatedPositions, allMarkets] = await Promise.all([ ctx.client.subaccount.getSubaccountSummary({ - subaccountOwner: ctx.subaccountOwner!, + subaccountOwner: ctx.subaccountOwner, subaccountName: ctx.subaccountName, }), ctx.client.subaccount .getIsolatedPositions({ - subaccountOwner: ctx.subaccountOwner!, + subaccountOwner: ctx.subaccountOwner, subaccountName: ctx.subaccountName, }) .catch(() => []), @@ -376,14 +376,14 @@ export async function closeAllPositions( await Promise.all([ ctx.client.market .cancelProductOrders({ - subaccountOwner: ctx.subaccountOwner!, + subaccountOwner: ctx.subaccountOwner, subaccountName: ctx.subaccountName, productIds: affectedProductIds, }) .catch(warnOnCancelFailure('cancel open orders')), ctx.client.market .cancelTriggerProductOrders({ - subaccountOwner: ctx.subaccountOwner!, + subaccountOwner: ctx.subaccountOwner, subaccountName: ctx.subaccountName, productIds: affectedProductIds, }) @@ -396,11 +396,11 @@ export async function closeAllPositions( const closeOrders = [ ...crossPositions.map((b) => buildCloseOrderFromBalance( - b as BalanceWithProduct, + b, marketByProductId, slippagePct, 'cross', - ctx.subaccountOwner!, + ctx.subaccountOwner, ctx.subaccountName, ), ), @@ -410,7 +410,7 @@ export async function closeAllPositions( marketByProductId, slippagePct, 'isolated', - ctx.subaccountOwner!, + ctx.subaccountOwner, ctx.subaccountName, ), ), @@ -542,7 +542,7 @@ export async function cancelOrders( try { const data = await ctx.client.market.cancelOrders({ - subaccountOwner: ctx.subaccountOwner!, + subaccountOwner: ctx.subaccountOwner, subaccountName: ctx.subaccountName, productIds: args.productIds, digests: args.digests, @@ -573,7 +573,7 @@ export async function cancelProductOrders( try { const data = await ctx.client.market.cancelProductOrders({ - subaccountOwner: ctx.subaccountOwner!, + subaccountOwner: ctx.subaccountOwner, subaccountName: ctx.subaccountName, productIds: args.productIds, }); @@ -604,7 +604,7 @@ export async function cancelTriggerOrders( try { const data = await ctx.client.market.cancelTriggerOrders({ - subaccountOwner: ctx.subaccountOwner!, + subaccountOwner: ctx.subaccountOwner, subaccountName: ctx.subaccountName, productIds: args.productIds, digests: args.digests, @@ -635,7 +635,7 @@ export async function cancelTriggerProductOrders( try { const data = await ctx.client.market.cancelTriggerProductOrders({ - subaccountOwner: ctx.subaccountOwner!, + subaccountOwner: ctx.subaccountOwner, subaccountName: ctx.subaccountName, productIds: args.productIds, }); @@ -696,7 +696,7 @@ export async function placeTriggerOrder( const data = await ctx.client.market.placeTriggerOrder({ productId: orderParams.productId, order: { - subaccountOwner: ctx.subaccountOwner!, + subaccountOwner: ctx.subaccountOwner, subaccountName: ctx.subaccountName, ...orderParams.order, }, @@ -795,7 +795,7 @@ export async function placeTwapOrder( }); const order = { - subaccountOwner: ctx.subaccountOwner!, + subaccountOwner: ctx.subaccountOwner, subaccountName: ctx.subaccountName, price: orderPrice, amount: totalAmountX18.toFixed(0), diff --git a/src/output/orderbook.ts b/src/output/orderbook.ts index 36ce065..84e9648 100644 --- a/src/output/orderbook.ts +++ b/src/output/orderbook.ts @@ -28,11 +28,12 @@ export function renderOrderbook( const title = `${market} Order Book (watching)`; const updated = `Last updated: ${timestamp}`; - const headerLine = title + ' '.repeat(Math.max(1, termWidth - title.length - updated.length)) + chalk.dim(updated); + const headerLine = + title + + ' '.repeat(Math.max(1, termWidth - title.length - updated.length)) + + chalk.dim(updated); - const asks = [...snapshot.asks] - .sort((a, b) => b.price - a.price) - .slice(-15); + const asks = [...snapshot.asks].sort((a, b) => b.price - a.price).slice(-15); const bids = [...snapshot.bids] .sort((a, b) => b.price - a.price) .slice(0, 15); @@ -71,7 +72,10 @@ export function renderOrderbook( const lines: string[] = [headerLine, '', colHeader]; for (const level of asks) { - const barLen = maxSize > 0 ? Math.max(1, Math.round((level.size / maxSize) * barMaxW)) : 0; + const barLen = + maxSize > 0 + ? Math.max(1, Math.round((level.size / maxSize) * barMaxW)) + : 0; const bar = chalk.redBright(BLOCK.repeat(barLen)); lines.push( chalk.red(pad(fmtPrice(level.price, priceDecs), priceColW)) + @@ -82,9 +86,10 @@ export function renderOrderbook( ); } - const spread = asks.length > 0 && bids.length > 0 - ? Math.abs(asks[asks.length - 1].price - bids[0].price) - : 0; + const spread = + asks.length > 0 && bids.length > 0 + ? Math.abs(asks[asks.length - 1].price - bids[0].price) + : 0; const spreadStr = `spread: ${fmtPrice(spread, priceDecs)}`; const spreadPad = Math.max(0, Math.floor((termWidth - spreadStr.length) / 2)); lines.push(''); @@ -92,7 +97,10 @@ export function renderOrderbook( lines.push(''); for (const level of bids) { - const barLen = maxSize > 0 ? Math.max(1, Math.round((level.size / maxSize) * barMaxW)) : 0; + const barLen = + maxSize > 0 + ? Math.max(1, Math.round((level.size / maxSize) * barMaxW)) + : 0; const bar = chalk.green(BLOCK.repeat(barLen)); lines.push( chalk.greenBright(pad(fmtPrice(level.price, priceDecs), priceColW)) + @@ -129,4 +137,3 @@ function decimalsFromIncrement(increment: number): number { if (increment <= 0 || increment >= 1) return 0; return Math.min(6, Math.max(0, -Math.floor(Math.log10(increment)))); } - diff --git a/src/output/table.ts b/src/output/table.ts index 213a68b..41eaaee 100644 --- a/src/output/table.ts +++ b/src/output/table.ts @@ -71,9 +71,7 @@ function renderObjectAsTable(data: Record): string { if (Array.isArray(value)) { if (value.length === 0) { table.push({ [chalk.cyan(key)]: chalk.dim('[]') }); - } else if ( - value.every((v) => typeof v !== 'object' || v === null) - ) { + } else if (value.every((v) => typeof v !== 'object' || v === null)) { table.push({ [chalk.cyan(key)]: value.map((v) => fmtCell(v)).join(', '), }); @@ -128,5 +126,7 @@ function fmtCompact(value: unknown): string { function fmtCell(value: unknown): string { if (value === null || value === undefined) return chalk.dim('-'); if (typeof value === 'number') return value.toLocaleString('en-US'); - return String(value); + if (typeof value === 'string') return value; + if (typeof value === 'object') return JSON.stringify(value); + return String(value as boolean | bigint | symbol); } diff --git a/src/utils/resolveMarket.ts b/src/utils/resolveMarket.ts index 90f03f8..6dd65d6 100644 --- a/src/utils/resolveMarket.ts +++ b/src/utils/resolveMarket.ts @@ -143,7 +143,8 @@ export function resolveProduct( return found; } - const filtered = typeFilter != null ? markets.filter((m) => m.type === typeFilter) : markets; + const filtered = + typeFilter != null ? markets.filter((m) => m.type === typeFilter) : markets; const q = trimmed.toLowerCase(); const candidates: { market: Market; priority: number }[] = []; @@ -203,10 +204,7 @@ export function resolveProduct( * one. Useful for commands like `account orders` where `BTC` should match both * the spot and perp product. */ -export function resolveAllProducts( - markets: Market[], - input: string, -): Market[] { +export function resolveAllProducts(markets: Market[], input: string): Market[] { const trimmed = input.trim(); if (/^\d+$/.test(trimmed)) { diff --git a/src/ws/subscribe.ts b/src/ws/subscribe.ts index de9e6c7..13639c6 100644 --- a/src/ws/subscribe.ts +++ b/src/ws/subscribe.ts @@ -41,12 +41,17 @@ export function subscribeBookDepth( ws.onMessage((raw) => { try { - const data = JSON.parse(raw.toString()) as + const text = Buffer.isBuffer(raw) + ? raw.toString('utf-8') + : Array.isArray(raw) + ? Buffer.concat(raw).toString('utf-8') + : Buffer.from(raw).toString('utf-8'); + const data = JSON.parse(text) as | EngineServerSubscriptionEvent | { id: number; status: string }; if ('type' in data && data.type === 'book_depth') { - onEvent(data as EngineServerSubscriptionBookDepthEvent); + onEvent(data); } } catch { // ignore malformed messages