diff --git a/.env.example b/.env.example index 8e2201924..a93f7d867 100644 --- a/.env.example +++ b/.env.example @@ -39,6 +39,16 @@ GENVMROOT="/genvm" GENVM_LLM_DEBUG="0" GENVM_WEB_DEBUG="0" +######################################## +# Studio Fee Accounting +# Set all three price values to 0 to run Studio in gasless mode. +GENLAYER_STUDIO_GEN_PER_TIME_UNIT='1000000000000000' # 0.001 GEN per time unit +GENLAYER_STUDIO_STORAGE_UNIT_PRICE='1' +GENLAYER_STUDIO_RECEIPT_GAS_PRICE='1' +GENLAYER_STUDIO_FIXED_PROPOSE_RECEIPT_GAS='210000' +GENLAYER_STUDIO_FIXED_MESSAGE_REVEAL_GAS='100000' +GENLAYER_STUDIO_RECEIPT_WRAPPER_BYTES='1024' + # Ollama Server Configuration OLAMAPORT='11434' diff --git a/.github/workflows/unit-tests-pr.yml b/.github/workflows/unit-tests-pr.yml index 5b4ab9613..cafbed6c4 100644 --- a/.github/workflows/unit-tests-pr.yml +++ b/.github/workflows/unit-tests-pr.yml @@ -21,6 +21,24 @@ jobs: secrets: codecov_token: ${{ secrets.CODECOV_TOKEN }} + explorer-unit-tests: + if: (github.actor != 'dependabot[bot]' && github.actor != 'renovate[bot]') + name: Explorer Unit Tests + runs-on: ubuntu-latest + defaults: + run: + working-directory: explorer + steps: + - uses: actions/checkout@v6 + - name: Use Node.js + uses: actions/setup-node@v6 + with: + node-version: 24 + - run: npm ci --ignore-scripts + - run: npm run test:fee-accounting + - run: npm run lint + - run: npm run build + backend-unit-tests: if: (github.actor != 'dependabot[bot]' && github.actor != 'renovate[bot]') runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index f3eaffcd4..cd04cf23f 100644 --- a/.gitignore +++ b/.gitignore @@ -173,3 +173,7 @@ config-overrides/ frontend/genlayer-js-0.28.2.tgz frontend/.cache-synpress/ frontend/test-results/ + +# Local tool caches +.vite/ +.claude/scheduled_tasks.lock diff --git a/backend/consensus/base.py b/backend/consensus/base.py index f6dad1fa1..d8d20494e 100644 --- a/backend/consensus/base.py +++ b/backend/consensus/base.py @@ -9,7 +9,7 @@ import os import asyncio -from typing import Callable, List, Iterable, Literal +from typing import Any, Callable, List, Iterable, Literal import time from abc import ABC, abstractmethod import random @@ -29,6 +29,7 @@ TransactionsProcessor, TransactionStatus, ) +from backend.database_handler.models import Transactions from backend.database_handler.accounts_manager import AccountsManager from backend.database_handler.types import ConsensusData from backend.domain.types import ( @@ -52,6 +53,18 @@ EventType, EventScope, ) +from backend.protocol_rpc.fees import ( + FEE_ACCOUNTING_KEY, + FeeValidationError, + StudioFeePolicy, + consume_message_fees, + create_child_fee_accounting, + derive_external_message_call_key, + fill_message_fee_payload_from_allocation, + record_external_message_execution_fees, + record_reveal_message_fees, + unwind_reveal_message_fees, +) from backend.rollup.consensus_service import ConsensusService import backend.validators as validators @@ -1475,6 +1488,380 @@ async def handle( """ +def _external_message_value_total( + pending_transactions: Iterable[PendingTransaction], +) -> int: + return sum( + int(pending_transaction.value or 0) + for pending_transaction in pending_transactions + if pending_transaction.is_eth_send and int(pending_transaction.value or 0) > 0 + ) + + +def _external_message_value_for_phase( + pending_transactions: Iterable[PendingTransaction], + on: Literal["accepted", "finalized"], +) -> int: + return sum( + int(pending_transaction.value or 0) + for pending_transaction in pending_transactions + if pending_transaction.is_eth_send + and pending_transaction.on == on + and int(pending_transaction.value or 0) > 0 + ) + + +def _apply_external_message_freeze_check( + context: TransactionContext, + leader_receipt: Receipt, +) -> None: + if leader_receipt.execution_result != ExecutionResultStatus.SUCCESS: + return + + declared_value = _external_message_value_total(leader_receipt.pending_transactions) + if declared_value <= 0: + return + + other_reserved = _external_message_pending_freeze_total(context) + balance = context.accounts_manager.get_account_balance( + context.transaction.to_address + ) + available = max(balance - other_reserved, 0) + if declared_value <= available: + return + + error_message = ( + "ExternalMessageFreezeExceeded: " + f"declaredValue={declared_value}, availableLimit={available}" + ) + leader_receipt.execution_result = ExecutionResultStatus.ERROR + leader_receipt.result = bytes([ResultCode.VM_ERROR]) + error_message.encode("utf-8") + leader_receipt.contract_state = {} + leader_receipt.contract_state_hash = None + leader_receipt.pending_transactions = [] + leader_receipt.genvm_result = { + **(leader_receipt.genvm_result or {}), + "error_code": "EXTERNAL_MESSAGE_FREEZE_EXCEEDED", + "error_description": error_message, + "external_message_freeze": { + "declaredValue": declared_value, + "availableLimit": available, + "balance": balance, + "reservedExternal": other_reserved, + }, + } + + +def _internal_message_value_for_phase( + pending_transactions: Iterable[PendingTransaction], + on: Literal["accepted", "finalized"], +) -> int: + return sum( + int(pending_transaction.value or 0) + for pending_transaction in pending_transactions + if not pending_transaction.is_eth_send + and pending_transaction.on == on + and int(pending_transaction.value or 0) > 0 + ) + + +def _remaining_external_freeze_after_phase( + context: TransactionContext, + pending_transactions: Iterable[PendingTransaction], + on: Literal["accepted", "finalized"], +) -> int: + pending_freeze = _external_message_pending_freeze_total(context) + if on == "finalized": + return pending_freeze + + return pending_freeze + _external_message_value_for_phase( + pending_transactions, "finalized" + ) + + +def _external_message_pending_freeze_total(context: TransactionContext) -> int: + contract_address = context.transaction.to_address + if not contract_address or not hasattr(context.transactions_processor, "session"): + return 0 + + current_created_at = ( + context.transactions_processor.session.query(Transactions.created_at) + .filter(Transactions.hash == context.transaction.hash) + .scalar() + ) + filters = [ + Transactions.to_address == contract_address, + Transactions.status == TransactionStatus.ACCEPTED, + Transactions.hash != context.transaction.hash, + Transactions.consensus_data.isnot(None), + ] + if current_created_at is not None: + filters.append(Transactions.created_at < current_created_at) + + rows = ( + context.transactions_processor.session.query( + Transactions.hash, + Transactions.consensus_data, + ) + .filter(*filters) + .all() + ) + + total = 0 + for row in rows: + for receipt in _leader_receipts_from_consensus_data(row.consensus_data): + if ( + _receipt_execution_result(receipt) + != ExecutionResultStatus.SUCCESS.value + ): + continue + total += _external_message_value_for_phase_from_raw( + _receipt_pending_transactions(receipt), + "finalized", + ) + return total + + +def _leader_receipts_from_consensus_data(consensus_data: Any) -> list[Any]: + if isinstance(consensus_data, ConsensusData): + leader_receipt = consensus_data.leader_receipt + if isinstance(leader_receipt, list): + return leader_receipt[:1] + if leader_receipt: + return [leader_receipt] + return [] + + if not isinstance(consensus_data, dict): + return [] + + leader_receipt = consensus_data.get("leader_receipt") + if isinstance(leader_receipt, list): + return leader_receipt[:1] + if isinstance(leader_receipt, dict): + return [leader_receipt] + return [] + + +def _receipt_execution_result(receipt: Any) -> str | None: + if isinstance(receipt, Receipt): + return receipt.execution_result.value + if isinstance(receipt, dict): + return receipt.get("execution_result") + return None + + +def _receipt_pending_transactions(receipt: Any) -> Iterable[Any]: + if isinstance(receipt, Receipt): + return receipt.pending_transactions + if isinstance(receipt, dict): + return receipt.get("pending_transactions") or [] + return [] + + +def _external_message_value_for_phase_from_raw( + pending_transactions: Iterable[Any], + on: Literal["accepted", "finalized"], +) -> int: + return sum( + _pending_transaction_external_value(pending_transaction, on) + for pending_transaction in pending_transactions + ) + + +def _pending_transaction_external_value( + pending_transaction: Any, + on: Literal["accepted", "finalized"], +) -> int: + if isinstance(pending_transaction, PendingTransaction): + if not pending_transaction.is_eth_send or pending_transaction.on != on: + return 0 + return int(pending_transaction.value or 0) + + if not isinstance(pending_transaction, dict): + return 0 + + is_external = bool( + pending_transaction.get("is_eth_send") + or pending_transaction.get("isEthSend") + or pending_transaction.get("messageType") in {0, "0", "External", "external"} + ) + if not is_external: + return 0 + + pending_on = pending_transaction.get("on") + if pending_on is None and "onAcceptance" in pending_transaction: + pending_on = ( + "accepted" if pending_transaction.get("onAcceptance") else "finalized" + ) + if pending_on != on: + return 0 + + return int(pending_transaction.get("value", 0) or 0) + + +def _pending_transaction_with_value( + pending_transaction: PendingTransaction, + value: int, +) -> PendingTransaction: + adjusted = deepcopy(pending_transaction) + adjusted.value = value + return adjusted + + +def _debit_external_message_value_for_phase( + context: TransactionContext, + pending_transactions: list[PendingTransaction], + on: Literal["accepted", "finalized"], +) -> bool: + external_value = _external_message_value_for_phase(pending_transactions, on) + if external_value <= 0: + return True + + debited = context.accounts_manager.debit_account_balance( + context.transaction.to_address, external_value + ) + if not debited: + _log_message_value_debit_failure( + context, + on, + external_value, + "external", + "Skipping value-bearing external child emission.", + ) + return debited + + +def _debit_internal_message_value_for_phase( + context: TransactionContext, + pending_transactions: list[PendingTransaction], + on: Literal["accepted", "finalized"], +) -> bool: + internal_value = _internal_message_value_for_phase(pending_transactions, on) + if internal_value <= 0: + return True + + internal_cap = _internal_message_value_cap(context, pending_transactions, on) + if internal_value > internal_cap: + _log_internal_message_value_cap_failure( + context, + on, + internal_value, + internal_cap, + pending_transactions, + ) + return False + + debited = context.accounts_manager.debit_account_balance( + context.transaction.to_address, internal_value + ) + if not debited: + _log_message_value_debit_failure( + context, + on, + internal_value, + "internal", + "Emitting internal children with value=0.", + ) + return debited + + +def _internal_message_value_cap( + context: TransactionContext, + pending_transactions: list[PendingTransaction], + on: Literal["accepted", "finalized"], +) -> int: + frozen_after_phase = _remaining_external_freeze_after_phase( + context, pending_transactions, on + ) + balance_after_external = context.accounts_manager.get_account_balance( + context.transaction.to_address + ) + return max(balance_after_external - frozen_after_phase, 0) + + +def _log_internal_message_value_cap_failure( + context: TransactionContext, + on: Literal["accepted", "finalized"], + amount: int, + available: int, + pending_transactions: list[PendingTransaction], +) -> None: + from loguru import logger + + reserved_external = _remaining_external_freeze_after_phase( + context, pending_transactions, on + ) + logger.error( + f"Contract internal message value is not backed for {context.transaction.to_address}, " + f"phase={on}, amount={amount}, available={available}, " + f"reserved_external={reserved_external}, tx={context.transaction.hash}. " + f"Emitting internal children with value=0." + ) + + +def _log_message_value_debit_failure( + context: TransactionContext, + on: Literal["accepted", "finalized"], + amount: int, + message_kind: str, + consequence: str, +) -> None: + from loguru import logger + + logger.error( + f"Contract {message_kind} message debit failed for {context.transaction.to_address}, " + f"phase={on}, amount={amount}, tx={context.transaction.hash}. {consequence}" + ) + + +def _adjust_unbacked_message_values( + pending_transactions: list[PendingTransaction], + on: Literal["accepted", "finalized"], + *, + external_value_backed: bool, + internal_value_backed: bool, +) -> list[PendingTransaction]: + adjusted_pending_transactions = [] + for pending_transaction in pending_transactions: + value = int(pending_transaction.value or 0) + if pending_transaction.on == on and value > 0: + if pending_transaction.is_eth_send and not external_value_backed: + continue + if not pending_transaction.is_eth_send and not internal_value_backed: + adjusted_pending_transactions.append( + _pending_transaction_with_value(pending_transaction, 0) + ) + continue + + adjusted_pending_transactions.append(pending_transaction) + + return adjusted_pending_transactions + + +def _apply_message_value_withdrawals_for_phase( + context: TransactionContext, + pending_transactions: Iterable[PendingTransaction], + on: Literal["accepted", "finalized"], +) -> list[PendingTransaction]: + pending_list = list(pending_transactions) + external_value_backed = _debit_external_message_value_for_phase( + context, pending_list, on + ) + internal_value_backed = _debit_internal_message_value_for_phase( + context, pending_list, on + ) + + if external_value_backed and internal_value_backed: + return pending_list + + return _adjust_unbacked_message_values( + pending_list, + on, + external_value_backed=external_value_backed, + internal_value_backed=internal_value_backed, + ) + + class PendingState(TransactionState): """ Class representing the pending state of a transaction. @@ -1651,7 +2038,7 @@ async def handle(self, context): # Credit target contract on activation (value from transaction) # Placed AFTER validator check — if no validators, tx gets canceled # and refund_tx_value must be able to refund (requires value_credited=false) - tx_value = context.transaction.value or 0 + tx_value = int(context.transaction.value or 0) if tx_value > 0: credited = context.accounts_manager.credit_tx_value_once( context.transaction.hash, @@ -2404,6 +2791,8 @@ class AcceptedState(TransactionState): async def handle(self, context): leader_receipt = context.consensus_data.leader_receipt[0] + _apply_external_message_freeze_check(context, leader_receipt) + _sync_reveal_message_fee_accounting(context, leader_receipt) accepted_contract_state = leader_receipt.contract_state execution_success = ( leader_receipt.execution_result == ExecutionResultStatus.SUCCESS @@ -2450,39 +2839,13 @@ async def handle(self, context): # Impure: triggered transaction processing (needs DB reads for nonce/accounts) # Cumulative: child emission happens on every acceptance round (including appeal re-acceptance) if execution_success: - # Balance debit for on_accepted messages BEFORE child emission - total_msg_debit = sum( - pt.value - for pt in leader_receipt.pending_transactions - if pt.on == "accepted" and pt.value > 0 - ) - debit_ok = True - if total_msg_debit > 0: - debit_ok = context.accounts_manager.debit_account_balance( - context.transaction.to_address, total_msg_debit - ) - if not debit_ok: - from loguru import logger - - logger.error( - f"Contract balance debit failed for {context.transaction.to_address}, " - f"amount={total_msg_debit}, tx={context.transaction.hash}. " - f"Skipping value-bearing child emission." - ) - - # Emit child messages — filter out value-bearing children if debit failed - if debit_ok: - pending_to_emit = leader_receipt.pending_transactions - else: - pending_to_emit = [ - pt - for pt in leader_receipt.pending_transactions - if pt.on != "accepted" or pt.value <= 0 - ] - internal_messages_data, insert_transactions_data = _get_messages_data( context, - pending_to_emit, + _apply_message_value_withdrawals_for_phase( + context, + leader_receipt.pending_transactions, + "accepted", + ), "accepted", ) @@ -2649,38 +3012,13 @@ async def handle(self, context): finalized_state=accepted_state, ) - # Balance debit BEFORE child emission - total_finalized_debit = sum( - pt.value - for pt in leader_receipt.pending_transactions - if pt.on == "finalized" and pt.value > 0 - ) - finalize_debit_ok = True - if total_finalized_debit > 0: - finalize_debit_ok = context.accounts_manager.debit_account_balance( - context.transaction.to_address, total_finalized_debit - ) - if not finalize_debit_ok: - from loguru import logger - - logger.error( - f"Contract finalization debit failed for {context.transaction.to_address}, " - f"amount={total_finalized_debit}, tx={context.transaction.hash}" - ) - - # Filter out value-bearing children if debit failed - if finalize_debit_ok: - pending_to_finalize = leader_receipt.pending_transactions - else: - pending_to_finalize = [ - pt - for pt in leader_receipt.pending_transactions - if pt.on != "finalized" or pt.value <= 0 - ] - internal_messages_data, insert_transactions_data = _get_messages_data( context, - pending_to_finalize, + _apply_message_value_withdrawals_for_phase( + context, + leader_receipt.pending_transactions, + "finalized", + ), "finalized", ) @@ -2697,6 +3035,18 @@ async def handle(self, context): await executor.execute(post_effects) + refund_recipient = ( + context.transaction.origin_address or context.transaction.from_address + ) + if refund_recipient: + context.accounts_manager.settle_tx_fee_accounting_once( + context.transaction.hash, + refund_recipient, + receipt=leader_receipt, + reason="finalized", + ) + context.accounts_manager.session.commit() + def _get_messages_data( context: TransactionContext, @@ -2705,53 +3055,31 @@ def _get_messages_data( ): insert_transactions_data = [] internal_messages_data = [] + message_fee_payloads = [] + parent_fee_accounting = (context.transaction.data or {}).get(FEE_ACCOUNTING_KEY) + reveal_recorded = bool( + parent_fee_accounting + and parent_fee_accounting.get("message_fees_recorded_at_reveal") + ) base_nonce = context.transactions_processor.get_transaction_count( context.transaction.to_address ) - nonce_offset = 0 - for pending_transaction in filter(lambda t: t.on == on, pending_transactions): + for nonce_offset, pending_transaction in enumerate( + _pending_transactions_for_phase(pending_transactions, on) + ): nonce = base_nonce + nonce_offset - nonce_offset += 1 - data: dict - transaction_type: TransactionType - if pending_transaction.is_eth_send: - transaction_type = TransactionType.SEND - data = {} - elif pending_transaction.is_deploy(): - transaction_type = TransactionType.DEPLOY_CONTRACT - new_contract_address: str - if pending_transaction.salt_nonce == 0: - # NOTE: this address is random, which doesn't 100% align with consensus spec - new_contract_address = ( - context.accounts_manager.create_new_account().address - ) - else: - from eth_utils.crypto import keccak - from backend.node.types import Address - from backend.node.base import get_simulator_chain_id - - arr = bytearray() - arr.append(1) - arr.extend(Address(context.transaction.to_address).as_bytes) - arr.extend( - pending_transaction.salt_nonce.to_bytes(32, "big", signed=False) - ) - arr.extend(get_simulator_chain_id().to_bytes(32, "big", signed=False)) - new_contract_address = Address(keccak(arr)[:20]).as_hex - context.accounts_manager.create_new_account_with_address( - new_contract_address - ) - pending_transaction.address = new_contract_address - data = { - "contract_address": new_contract_address, - "contract_code": pending_transaction.code, - "calldata": pending_transaction.calldata, - } - else: - transaction_type = TransactionType.RUN_CONTRACT - data = { - "calldata": pending_transaction.calldata, - } + transaction_type, data = _child_transaction_payload( + context, pending_transaction + ) + + _append_message_fee_payload( + context, + pending_transaction, + parent_fee_accounting, + message_fee_payloads, + data, + on, + ) insert_transactions_data.append( [ @@ -2763,28 +3091,371 @@ def _get_messages_data( ] ) - serializable_data = data.copy() - if "contract_code" in serializable_data: - serializable_data["contract_code"] = serializable_data[ - "contract_code" - ].decode() - if "calldata" in serializable_data: - # Encode binary calldata as base64 instead of trying to decode as UTF-8 - serializable_data["calldata"] = base64.b64encode( - serializable_data["calldata"] - ).decode("utf-8") - internal_messages_data.append( - { - "sender": context.transaction.to_address, - "recipient": pending_transaction.address, - "data": json.dumps(serializable_data).encode(), - } + _internal_message_event_data(context, pending_transaction, data) ) + _record_parent_message_fee_consumption( + context, + parent_fee_accounting, + message_fee_payloads, + reveal_recorded, + ) + return internal_messages_data, insert_transactions_data +def _pending_transactions_for_phase( + pending_transactions: Iterable[PendingTransaction], + on: Literal["accepted", "finalized"], +) -> Iterable[PendingTransaction]: + return ( + pending_transaction + for pending_transaction in pending_transactions + if pending_transaction.on == on + ) + + +def _child_transaction_payload( + context: TransactionContext, + pending_transaction: PendingTransaction, +) -> tuple[TransactionType, dict]: + if pending_transaction.is_eth_send: + return TransactionType.SEND, {} + if pending_transaction.is_deploy(): + return _deploy_child_transaction_payload(context, pending_transaction) + return TransactionType.RUN_CONTRACT, {"calldata": pending_transaction.calldata} + + +def _deploy_child_transaction_payload( + context: TransactionContext, + pending_transaction: PendingTransaction, +) -> tuple[TransactionType, dict]: + new_contract_address = _child_contract_address(context, pending_transaction) + pending_transaction.address = new_contract_address + return ( + TransactionType.DEPLOY_CONTRACT, + { + "contract_address": new_contract_address, + "contract_code": pending_transaction.code, + "calldata": pending_transaction.calldata, + }, + ) + + +def _child_contract_address( + context: TransactionContext, + pending_transaction: PendingTransaction, +) -> str: + if pending_transaction.salt_nonce == 0: + # NOTE: this address is random, which doesn't 100% align with consensus spec + return context.accounts_manager.create_new_account().address + + from eth_utils.crypto import keccak + from backend.node.types import Address + from backend.node.base import get_simulator_chain_id + + arr = bytearray() + arr.append(1) + arr.extend(Address(context.transaction.to_address).as_bytes) + arr.extend(pending_transaction.salt_nonce.to_bytes(32, "big", signed=False)) + arr.extend(get_simulator_chain_id().to_bytes(32, "big", signed=False)) + new_contract_address = Address(keccak(arr)[:20]).as_hex + context.accounts_manager.create_new_account_with_address(new_contract_address) + return new_contract_address + + +def _append_message_fee_payload( + context: TransactionContext, + pending_transaction: PendingTransaction, + parent_fee_accounting: dict[str, Any] | None, + message_fee_payloads: list[dict[str, Any]], + data: dict, + on: Literal["accepted", "finalized"], +) -> None: + if not parent_fee_accounting: + return + + message_payload = _parent_message_fee_payload( + parent_fee_accounting, + pending_transaction, + on, + ) + message_fee_payloads.append(message_payload) + if pending_transaction.is_eth_send: + return + + _attach_child_fee_accounting( + context, + parent_fee_accounting, + message_payload, + pending_transaction, + data, + ) + + +def _parent_message_fee_payload( + parent_fee_accounting: dict[str, Any], + pending_transaction: PendingTransaction, + on: Literal["accepted", "finalized"], +) -> dict[str, Any]: + payload = _pending_transaction_fee_payload(pending_transaction, on) + if pending_transaction.is_eth_send: + return payload + + try: + return fill_message_fee_payload_from_allocation(parent_fee_accounting, payload) + except FeeValidationError as exc: + raise RuntimeError(str(exc)) from exc + + +def _attach_child_fee_accounting( + context: TransactionContext, + parent_fee_accounting: dict[str, Any], + message_payload: dict[str, Any], + pending_transaction: PendingTransaction, + data: dict, +) -> None: + if int(message_payload.get("declaredBudget", 0) or 0) <= 0: + return + + try: + child_fees, child_fee_accounting = create_child_fee_accounting( + message=message_payload, + parent_fees_distribution=parent_fee_accounting.get("fees_distribution"), + message_allocations=message_payload.get("allocationSubtree") or [], + sender=context.transaction.origin_address + or context.transaction.from_address, + policy=StudioFeePolicy.from_env(), + ) + except FeeValidationError as exc: + raise RuntimeError(str(exc)) from exc + + data.update( + { + "fee_value": int(message_payload["declaredBudget"]), + "user_value": pending_transaction.value, + "fees_distribution": child_fees, + "message_allocations_count": len( + child_fee_accounting.get("message_allocations") or [] + ), + FEE_ACCOUNTING_KEY: child_fee_accounting, + } + ) + + +def _internal_message_event_data( + context: TransactionContext, + pending_transaction: PendingTransaction, + data: dict, +) -> dict[str, Any]: + return { + "sender": context.transaction.to_address, + "recipient": pending_transaction.address, + "data": json.dumps(_serializable_message_data(data)).encode(), + } + + +def _serializable_message_data(data: dict) -> dict: + serializable_data = data.copy() + if "contract_code" in serializable_data: + serializable_data["contract_code"] = serializable_data["contract_code"].decode() + if "calldata" in serializable_data: + serializable_data["calldata"] = base64.b64encode( + serializable_data["calldata"] + ).decode("utf-8") + return serializable_data + + +def _record_parent_message_fee_consumption( + context: TransactionContext, + parent_fee_accounting: dict[str, Any] | None, + message_fee_payloads: list[dict[str, Any]], + reveal_recorded: bool, +) -> None: + if not parent_fee_accounting or not message_fee_payloads: + return + + updated_accounting = _consume_parent_message_fee_payloads( + parent_fee_accounting, + message_fee_payloads, + reveal_recorded, + ) + context.transaction.data = dict(context.transaction.data or {}) + context.transaction.data[FEE_ACCOUNTING_KEY] = updated_accounting + context.transactions_processor.update_transaction_fee_accounting( + context.transaction.hash, updated_accounting + ) + + +def _consume_parent_message_fee_payloads( + parent_fee_accounting: dict[str, Any], + message_fee_payloads: list[dict[str, Any]], + reveal_recorded: bool, +) -> dict[str, Any]: + try: + if reveal_recorded: + return record_external_message_execution_fees( + parent_fee_accounting, + message_fee_payloads, + ) + return consume_message_fees( + parent_fee_accounting, + message_fee_payloads, + ) + except FeeValidationError as exc: + raise RuntimeError(str(exc)) from exc + + +def _sync_reveal_message_fee_accounting( + context: TransactionContext, + leader_receipt: Receipt, +) -> None: + if ( + leader_receipt.execution_result != ExecutionResultStatus.SUCCESS + or not leader_receipt.pending_transactions + ): + _unwind_discarded_reveal_message_fee_accounting(context) + return + + parent_fee_accounting = (context.transaction.data or {}).get(FEE_ACCOUNTING_KEY) + if not parent_fee_accounting: + return + + message_fee_payloads = _reveal_message_fee_payloads( + parent_fee_accounting, + leader_receipt.pending_transactions, + ) + if not message_fee_payloads: + return + + try: + updated_accounting = record_reveal_message_fees( + parent_fee_accounting, + message_fee_payloads, + ) + except FeeValidationError as exc: + raise RuntimeError(str(exc)) from exc + + context.transaction.data = dict(context.transaction.data or {}) + context.transaction.data[FEE_ACCOUNTING_KEY] = updated_accounting + context.transactions_processor.update_transaction_fee_accounting( + context.transaction.hash, + updated_accounting, + ) + + +def _reveal_message_fee_payloads( + parent_fee_accounting: dict[str, Any], + pending_transactions: Iterable[Any], +) -> list[dict[str, Any]]: + message_fee_payloads = [] + for raw_pending_transaction in pending_transactions: + pending_transaction = _coerce_pending_transaction(raw_pending_transaction) + message_payload = _pending_transaction_fee_payload( + pending_transaction, + pending_transaction.on, + ) + if not pending_transaction.is_eth_send: + message_payload = fill_message_fee_payload_from_allocation( + parent_fee_accounting, + message_payload, + ) + message_fee_payloads.append(message_payload) + return message_fee_payloads + + +def _unwind_discarded_reveal_message_fee_accounting( + context: TransactionContext, +) -> None: + parent_fee_accounting = (context.transaction.data or {}).get(FEE_ACCOUNTING_KEY) + if not parent_fee_accounting: + return + + prior_receipts = _leader_receipts_from_consensus_data( + context.transaction.consensus_data + ) + if not prior_receipts: + prior_receipts = _leader_receipts_from_consensus_history( + context.transaction.consensus_history + ) + if not prior_receipts: + return + + message_fee_payloads = _reveal_message_fee_payloads( + parent_fee_accounting, + _receipt_pending_transactions(prior_receipts[0]), + ) + if not message_fee_payloads: + return + + updated_accounting = unwind_reveal_message_fees( + parent_fee_accounting, + message_fee_payloads, + acceptance_dispatched=context.transaction.status == TransactionStatus.ACCEPTED, + ) + updated_accounting["message_fees_recorded_at_reveal"] = True + context.transaction.data = dict(context.transaction.data or {}) + context.transaction.data[FEE_ACCOUNTING_KEY] = updated_accounting + context.transactions_processor.update_transaction_fee_accounting( + context.transaction.hash, + updated_accounting, + ) + + +def _coerce_pending_transaction(raw: Any) -> PendingTransaction: + if isinstance(raw, PendingTransaction): + return raw + if isinstance(raw, dict): + return PendingTransaction.from_dict(raw) + raise TypeError(f"Unsupported pending transaction type: {type(raw).__name__}") + + +def _leader_receipts_from_consensus_history(consensus_history: Any) -> list[Any]: + if not isinstance(consensus_history, dict): + return [] + + consensus_results = consensus_history.get("consensus_results") + if not isinstance(consensus_results, list): + return [] + + for consensus_round in reversed(consensus_results): + if not isinstance(consensus_round, dict): + continue + leader_result = consensus_round.get("leader_result") + if isinstance(leader_result, list): + return leader_result[:1] + if isinstance(leader_result, dict): + return [leader_result] + return [] + + +def _pending_transaction_fee_payload( + pending_transaction: PendingTransaction, + on: Literal["accepted", "finalized"], +) -> dict[str, Any]: + message_type = 0 if pending_transaction.is_eth_send else 1 + call_key = pending_transaction.call_key + if message_type == 0: + call_key = derive_external_message_call_key( + call_key, + pending_transaction.calldata, + ) + return { + "messageType": message_type, + "recipient": pending_transaction.address, + "value": pending_transaction.value, + "data": pending_transaction.calldata, + "onAcceptance": on == "accepted", + "saltNonce": pending_transaction.salt_nonce, + "feeParams": pending_transaction.fee_params, + "declaredBudget": pending_transaction.declared_budget, + "allocationSubtree": pending_transaction.allocation_subtree, + "callKey": call_key, + "gasUsed": pending_transaction.gas_used, + } + + def _emit_messages( context: TransactionContext, insert_transactions_data: list, diff --git a/backend/consensus/worker.py b/backend/consensus/worker.py index 1e6808508..067f20c96 100644 --- a/backend/consensus/worker.py +++ b/backend/consensus/worker.py @@ -11,6 +11,7 @@ from backend.database_handler.models import Transactions, TransactionStatus from backend.database_handler.transactions_processor import TransactionsProcessor +from backend.database_handler.accounts_manager import AccountsManager from backend.database_handler.errors import ContractNotFoundError from backend.domain.types import Transaction from backend.node.genvm.error_codes import GenVMInternalError @@ -1297,6 +1298,12 @@ async def _handle_no_validators_retry( from backend.database_handler.accounts_manager import AccountsManager AccountsManager(session).refund_tx_value(tx_hash, tx.from_address) + if tx.from_address: + from backend.database_handler.accounts_manager import AccountsManager + + AccountsManager(session).cancel_tx_fee_accounting_once( + tx_hash, tx.from_address, "no_validators_available" + ) session.commit() # Clean up retry tracking @@ -1362,6 +1369,14 @@ async def _handle_generic_error_retry(self, tx_hash: str, error: Exception): AccountsManager(cancel_session).refund_tx_value( tx_hash, tx.from_address ) + if tx.from_address: + from backend.database_handler.accounts_manager import ( + AccountsManager, + ) + + AccountsManager(cancel_session).cancel_tx_fee_accounting_once( + tx_hash, tx.from_address, "max_generic_retries_exceeded" + ) cancel_session.commit() # Send WebSocket notification @@ -1698,6 +1713,14 @@ async def process_finalization(self, finalization_data: dict, session: Session): TransactionStatus.FINALIZED, self.msg_handler, ) + tx = error_session.query(Transactions).filter_by(hash=tx_hash).one() + refund_recipient = tx.origin_address or tx.from_address + if refund_recipient: + AccountsManager(error_session).settle_tx_fee_accounting_once( + tx_hash, + refund_recipient, + reason="finalized_contract_not_found", + ) error_session.commit() logger.info( @@ -1802,6 +1825,14 @@ async def process_appeal(self, appeal_data: dict, session: Session): TransactionStatus.FINALIZED, self.msg_handler, ) + tx = error_session.query(Transactions).filter_by(hash=tx_hash).one() + refund_recipient = tx.origin_address or tx.from_address + if refund_recipient: + AccountsManager(error_session).settle_tx_fee_accounting_once( + tx_hash, + refund_recipient, + reason="finalized_contract_not_found_during_appeal", + ) error_session.commit() logger.info( diff --git a/backend/database_handler/accounts_manager.py b/backend/database_handler/accounts_manager.py index 32149d893..338663ab7 100644 --- a/backend/database_handler/accounts_manager.py +++ b/backend/database_handler/accounts_manager.py @@ -3,8 +3,13 @@ from eth_account import Account from eth_utils import is_address, to_checksum_address -from .models import CurrentState +from .models import CurrentState, Transactions from backend.database_handler.errors import AccountNotFoundError +from backend.protocol_rpc.fees import ( + FEE_ACCOUNTING_KEY, + cancel_fee_accounting, + settle_fee_accounting, +) from sqlalchemy.orm import Session from sqlalchemy import text @@ -177,3 +182,64 @@ def refund_tx_value(self, tx_hash: str, sender_address: str) -> bool: return False # target already received funds, can't refund self.credit_account_balance(sender_address, row.value) return True + + def cancel_tx_fee_accounting_once( + self, tx_hash: str, sender_address: str, reason: str = "canceled" + ) -> int: + transaction = ( + self.session.query(Transactions).filter_by(hash=tx_hash).one_or_none() + ) + if transaction is None: + return 0 + if not isinstance(transaction.data, dict): + return 0 + data = dict(transaction.data) + accounting = data.get(FEE_ACCOUNTING_KEY) + if not accounting: + return 0 + updated, refund = cancel_fee_accounting(accounting, reason=reason) + data[FEE_ACCOUNTING_KEY] = updated + transaction.data = data + if refund > 0: + self.credit_account_balance(sender_address, refund) + return refund + + def settle_tx_fee_accounting_once( + self, + tx_hash: str, + sender_address: str, + receipt=None, + reason: str = "finalized", + ) -> int: + transaction = ( + self.session.query(Transactions).filter_by(hash=tx_hash).one_or_none() + ) + if transaction is None: + return 0 + if not isinstance(transaction.data, dict): + return 0 + data = dict(transaction.data) + accounting = data.get(FEE_ACCOUNTING_KEY) + if not accounting: + return 0 + updated, refund = settle_fee_accounting( + accounting, + receipt=receipt, + reason=reason, + actual_final_round=_infer_final_round(transaction.consensus_history), + num_of_validators=transaction.num_of_initial_validators, + ) + data[FEE_ACCOUNTING_KEY] = updated + transaction.data = data + if refund > 0: + self.credit_account_balance(sender_address, refund) + return refund + + +def _infer_final_round(consensus_history: dict | None) -> int: + if not isinstance(consensus_history, dict): + return 0 + rounds = consensus_history.get("consensus_results") + if not isinstance(rounds, list) or len(rounds) == 0: + return 0 + return max(0, len(rounds) - 1) diff --git a/backend/database_handler/transactions_processor.py b/backend/database_handler/transactions_processor.py index e58db974c..40e1931d9 100644 --- a/backend/database_handler/transactions_processor.py +++ b/backend/database_handler/transactions_processor.py @@ -19,6 +19,8 @@ from backend.consensus.utils import determine_consensus_from_votes from backend.rollup.web3_pool import Web3ConnectionPool +MAX_JSON_SAFE_INTEGER = (2**53) - 1 + class TransactionAddressFilter(Enum): ALL = "all" @@ -72,6 +74,21 @@ def __init__( # Use singleton Web3 connection pool self.web3 = Web3ConnectionPool.get() + @staticmethod + def _json_safe_numbers(value): + if isinstance(value, bool) or value is None or isinstance(value, str): + return value + if isinstance(value, int): + return str(value) if abs(value) > MAX_JSON_SAFE_INTEGER else value + if isinstance(value, list): + return [TransactionsProcessor._json_safe_numbers(item) for item in value] + if isinstance(value, dict): + return { + key: TransactionsProcessor._json_safe_numbers(item) + for key, item in value.items() + } + return value + @staticmethod def _parse_transaction_data(transaction_data: Transactions) -> dict: if transaction_data.consensus_data: @@ -90,12 +107,14 @@ def _parse_transaction_data(transaction_data: Transactions) -> dict: "hash": transaction_data.hash, "from_address": transaction_data.from_address, "to_address": transaction_data.to_address, - "data": transaction_data.data, - "value": transaction_data.value, + "data": TransactionsProcessor._json_safe_numbers(transaction_data.data), + "value": TransactionsProcessor._json_safe_numbers(transaction_data.value), "type": transaction_data.type, "status": transaction_data.status.value, "result": TransactionsProcessor._decode_base64_data(result), - "consensus_data": transaction_data.consensus_data, + "consensus_data": TransactionsProcessor._json_safe_numbers( + transaction_data.consensus_data + ), "gaslimit": transaction_data.nonce, "nonce": transaction_data.nonce, "r": transaction_data.r, @@ -900,6 +919,40 @@ def set_transaction_result( self.session.commit() + def update_transaction_data(self, transaction_hash: str, data: dict | None): + result = self.session.execute( + text( + "UPDATE transactions SET data = CAST(:data AS jsonb) WHERE hash = :hash" + ), + { + "hash": transaction_hash, + "data": json.dumps(data) if data is not None else None, + }, + ) + if result.rowcount == 0: + print( + f"[TRANSACTIONS_PROCESSOR]: Transaction {transaction_hash} not found, skipping data update" + ) + return + self.session.commit() + + def update_transaction_fee_accounting( + self, transaction_hash: str, fee_accounting: dict + ): + transaction = ( + self.session.query(Transactions) + .filter_by(hash=transaction_hash) + .one_or_none() + ) + if transaction is None: + print( + f"[TRANSACTIONS_PROCESSOR]: Transaction {transaction_hash} not found, skipping fee accounting update" + ) + return + data = dict(transaction.data or {}) + data["fee_accounting"] = fee_accounting + self.update_transaction_data(transaction_hash, data) + def get_transaction_count(self, address: str) -> int: # Normalize address to checksum format try: diff --git a/backend/database_handler/validators_registry.py b/backend/database_handler/validators_registry.py index aea670f1f..f16aa05e6 100644 --- a/backend/database_handler/validators_registry.py +++ b/backend/database_handler/validators_registry.py @@ -102,17 +102,21 @@ async def update_validator( validator.plugin_config = new_validator.llmprovider.plugin_config self.session.flush() # Ensure the validator update is persisted - return to_dict(validator, False) + result = to_dict(validator, False) + self.session.commit() + return result async def delete_validator(self, validator_address): validator = self._get_validator_or_fail(validator_address) self.session.delete(validator) self.session.flush() # Ensure the validator deletion is persisted + self.session.commit() async def delete_all_validators(self): self.session.query(Validators).delete(synchronize_session=False) self.session.flush() # Ensure all validator deletions are persisted + self.session.commit() async def batch_create_validators(self, validators: list[Validator]) -> list[dict]: """Create multiple validators in a single batch without triggering restarts per-validator.""" diff --git a/backend/domain/types.py b/backend/domain/types.py index 3fd56c88d..383261b73 100644 --- a/backend/domain/types.py +++ b/backend/domain/types.py @@ -164,6 +164,12 @@ class TransactionExecutionMode(Enum): NORMAL = "NORMAL" +def _int_from_serialized(value, default: int | None = 0) -> int | None: + if value is None or value == "": + return default + return int(value) + + @dataclass class Transaction: hash: str @@ -259,7 +265,7 @@ def from_dict(cls, input: dict) -> "Transaction": data=input.get("data"), consensus_data=ConsensusData.from_dict(input.get("consensus_data")), nonce=input.get("nonce"), - value=input.get("value"), + value=_int_from_serialized(input.get("value"), None), gaslimit=input.get("gaslimit"), r=input.get("r"), s=input.get("s"), diff --git a/backend/node/base.py b/backend/node/base.py index 20de9f5e5..79cf66e27 100644 --- a/backend/node/base.py +++ b/backend/node/base.py @@ -15,6 +15,12 @@ from backend.domain.types import Validator, Transaction, TransactionType from backend.protocol_rpc.message_handler.types import LogEvent, EventType, EventScope +from backend.protocol_rpc.fees import ( + FEE_ACCOUNTING_KEY, + FeeValidationError, + genvm_fee_context, + genvm_message_fee_allocation, +) import backend.node.genvm.base as genvmbase import backend.node.genvm.origin.calldata as calldata from backend.database_handler.contract_snapshot import ContractSnapshot @@ -626,6 +632,7 @@ async def exec_transaction(self, transaction: Transaction) -> Receipt: assert transaction.data is not None transaction_data = transaction.data + fee_accounting = transaction_data.get(FEE_ACCOUNTING_KEY) assert transaction.from_address is not None # Override transaction timestamp @@ -650,6 +657,7 @@ async def exec_transaction(self, transaction: Transaction) -> Receipt: transaction_created_at, value=transaction.value or 0, origin_address=transaction.origin_address, + fee_accounting=fee_accounting, ) self.timing_callback("DEPLOY_END") @@ -667,6 +675,7 @@ async def exec_transaction(self, transaction: Transaction) -> Receipt: transaction_created_at, value=transaction.value or 0, origin_address=transaction.origin_address, + fee_accounting=fee_accounting, ) self.timing_callback("RUN_END") @@ -797,6 +806,7 @@ async def deploy_contract( transaction_created_at: str | None = None, value: int = 0, origin_address: str | None = None, + fee_accounting: dict | None = None, ) -> Receipt: assert self.contract_snapshot is not None @@ -814,6 +824,7 @@ async def deploy_contract( code=code_to_deploy, value=value, origin_address=origin_address, + fee_accounting=fee_accounting, ) async def run_contract( @@ -824,6 +835,7 @@ async def run_contract( transaction_created_at: str | None = None, value: int = 0, origin_address: str | None = None, + fee_accounting: dict | None = None, ) -> Receipt: return await self._run_genvm( from_address, @@ -834,6 +846,7 @@ async def run_contract( transaction_datetime=self._date_from_str(transaction_created_at), value=value, origin_address=origin_address, + fee_accounting=fee_accounting, ) async def get_contract_data( @@ -971,6 +984,7 @@ async def _run_genvm( code: bytes | None = None, value: int = 0, origin_address: str | None = None, + fee_accounting: dict | None = None, ) -> Receipt: self.timing_callback("GENVM_PREPARATION_START") @@ -1020,7 +1034,6 @@ async def _run_genvm( host_data["node_address"] = self.address logger = self.logger.with_keys({"tx_id": host_data["tx_id"]}) - message = { "is_init": is_init, "contract_address": contract_address, @@ -1038,6 +1051,13 @@ async def _run_genvm( start_time = time.time() try: + bucket_totals, gas_data = genvm_fee_context( + fee_accounting, + ) + message_fee_allocation = genvm_message_fee_allocation( + fee_accounting, + address_factory=Address, + ) result = await genvmbase.run_genvm_host( functools.partial( genvmbase.Host, @@ -1054,8 +1074,36 @@ async def _run_genvm( manager_uri=self.manager.url, timeout=timeout, code=code, + fee_context=genvmbase.GenVMFeeContext( + bucket_totals=bucket_totals, + gas_data=gas_data, + message_fee_allocation=message_fee_allocation, + ), logger=logger, ) + except FeeValidationError as e: + result = genvmbase.ExecutionResult( + result=genvmbase.ExecutionError( + message=str(e), + kind=public_abi.ResultCode.USER_ERROR, + error_code=e.__class__.__name__, + raw_error={ + "fatal": False, + "causes": [str(e)], + "ctx": {"source": "studio_fee_accounting"}, + }, + description=str(e), + ), + eq_outputs={}, + pending_transactions=[], + stdout="", + stderr=str(e), + genvm_log=[], + state=snapshot_view, + processing_time=int((time.time() - start_time) * 1000), + nondet_disagree=None, + execution_stats=None, + ) except genvmbase.GenVMInternalError as e: e.is_leader = self.validator_mode == ExecutionMode.LEADER raise @@ -1082,6 +1130,17 @@ async def _run_genvm( if isinstance(result.result, genvmbase.ExecutionReturn) else ExecutionResultStatus.ERROR ) + data_fees_consumed = None + if ( + result.data_fee_bucket_totals is not None + and result.data_fees_remaining is not None + ): + data_fees_consumed = [ + max(0, int(total) - int(remaining)) + for total, remaining in zip( + result.data_fee_bucket_totals, result.data_fees_remaining + ) + ] result = Receipt( result=genvmbase.encode_result_to_bytes(result.result), @@ -1121,6 +1180,9 @@ async def _run_genvm( if isinstance(result.result, genvmbase.ExecutionError) else None ), + "data_fee_bucket_totals": result.data_fee_bucket_totals, + "data_fees_remaining": result.data_fees_remaining, + "data_fees_consumed": data_fees_consumed, }, processing_time=result.processing_time, nondet_disagree=result.nondet_disagree, diff --git a/backend/node/genvm/base.py b/backend/node/genvm/base.py index a5927e409..1511b879a 100644 --- a/backend/node/genvm/base.py +++ b/backend/node/genvm/base.py @@ -10,6 +10,7 @@ "ExecutionResult", "apply_storage_changes", "GenVMInternalError", + "GenVMFeeContext", "Context", "set_genvm_callbacks", ) @@ -49,6 +50,24 @@ GenVMInternalError, ) +GENVM_GASLESS_GAS_DATA: dict[str, str] = { + "storageUnitPrice": "0", + "receiptGasPerByte": "0", + "gasPerChangedSlot": "0", + "intrinsicGas": "0", + "bootloaderOverhead": "0", + "fixedProposeReceiptGas": "0", + "fixedMessageRevealGas": "0", + "genPerTimeUnit": "0", +} + + +@dataclass(frozen=True) +class GenVMFeeContext: + bucket_totals: list[int] | None = None + gas_data: dict[str, str] | None = None + message_fee_allocation: list[dict] | None = None + @dataclass class ExecutionError: @@ -234,6 +253,46 @@ class ExecutionResult: processing_time: int nondet_disagree: int | None execution_stats: dict | None = None + data_fee_bucket_totals: list[int] | None = None + data_fees_remaining: list[int] | None = None + + +def _emission_value(emission: dict, name: str): + snake = "".join(f"_{char.lower()}" if char.isupper() else char for char in name) + return emission.get(name, emission.get(snake)) + + +def _emission_bytes(emission: dict, name: str) -> bytes: + value = _emission_value(emission, name) + if value is None: + return b"" + if isinstance(value, bytes): + return value + if isinstance(value, str): + raw = value.removeprefix("0x") + try: + return bytes.fromhex(raw) + except ValueError: + return base64.b64decode(value) + return bytes(value) + + +def _emission_int(emission: dict, name: str) -> int: + return int(_emission_value(emission, name) or 0) + + +def _emission_hex(emission: dict, name: str) -> str: + value = _emission_value(emission, name) + if value is None: + return "0x" + ("0" * 64) + if isinstance(value, bytes): + return "0x" + value.hex().rjust(64, "0")[-64:] + return "0x" + str(value).removeprefix("0x").lower().rjust(64, "0")[-64:] + + +def _emission_list(emission: dict, name: str) -> list: + value = _emission_value(emission, name) + return value if isinstance(value, list) else [] class Host(genvmhost.IHost): @@ -355,6 +414,12 @@ def provide_result( salt_nonce=0, value=emission["value"], on=emission["on"], + fee_params=_emission_bytes(emission, "feeParams"), + declared_budget=_emission_int(emission, "declaredBudget"), + call_key=_emission_hex(emission, "callKey"), + allocation_subtree=_emission_list( + emission, "allocationSubtree" + ), ) ) case "DeployContract": @@ -366,6 +431,12 @@ def provide_result( salt_nonce=emission["salt_nonce"], value=emission["value"], on=emission["on"], + fee_params=_emission_bytes(emission, "feeParams"), + declared_budget=_emission_int(emission, "declaredBudget"), + call_key=_emission_hex(emission, "callKey"), + allocation_subtree=_emission_list( + emission, "allocationSubtree" + ), ) ) case "EthSend": @@ -378,6 +449,13 @@ def provide_result( value=emission["value"], on="finalized", is_eth_send=True, + fee_params=_emission_bytes(emission, "feeParams"), + declared_budget=_emission_int(emission, "declaredBudget"), + call_key=_emission_hex(emission, "callKey"), + allocation_subtree=_emission_list( + emission, "allocationSubtree" + ), + gas_used=_emission_int(emission, "gasUsed"), ) ) @@ -395,6 +473,7 @@ def provide_result( processing_time=0, nondet_disagree=self._nondet_disagreement, execution_stats=ctx.stats, + data_fees_remaining=res.data_fees_remaining, ) async def loop_enter(self, cancellation) -> socket.socket: @@ -499,6 +578,7 @@ def _create_timeout_result( state=state_proxy, processing_time=processing_time, nondet_disagree=None, + data_fees_remaining=[], ) @@ -527,10 +607,22 @@ async def run_genvm_host( extra_args: list[str] = [], permissions: str = "rwscn", code: bytes | None = None, + fee_context: GenVMFeeContext | None = None, ) -> ExecutionResult: if logger is None: logger = genvm_logger.NoLogger() ctx = Context(logger=logger) + fee_context = fee_context or GenVMFeeContext() + effective_bucket_totals = fee_context.bucket_totals or [ + 10_000_000, + 10_000_000, + 10_000_000, + ] + effective_gas_data = ( + dict(fee_context.gas_data) + if fee_context.gas_data + else dict(GENVM_GASLESS_GAS_DATA) + ) tmpdir = Path(tempfile.mkdtemp()) try: base_delay = 5 # seconds @@ -602,6 +694,9 @@ async def run_genvm_host( host=f"unix://{sock_path}", extra_args=extra_args, code=code, + bucket_totals=effective_bucket_totals, + gas_data=effective_gas_data, + message_fee_allocation=fee_context.message_fee_allocation or [], calldata=fresh_args.get( "calldata_bytes", host_args.get("calldata_bytes", b"") ), @@ -613,6 +708,7 @@ async def run_genvm_host( fresh_args.get("state_proxy", host_args.get("state_proxy")), ctx, ) + execution_result.data_fee_bucket_totals = effective_bucket_totals execution_result.processing_time = math.ceil( (time.time() - start_time) * 1000 diff --git a/backend/node/genvm/origin/base_host.py b/backend/node/genvm/origin/base_host.py index f86158aba..32c427bb2 100644 --- a/backend/node/genvm/origin/base_host.py +++ b/backend/node/genvm/origin/base_host.py @@ -25,6 +25,18 @@ ACCOUNT_ADDR_SIZE = 20 SLOT_ID_SIZE = 32 +DEFAULT_GAS_DATA: dict[str, str] = { + "storageUnitPrice": "1", + "receiptGasPerByte": "1", + "gasPerChangedSlot": "1", + "intrinsicGas": "0", + "bootloaderOverhead": "0", + "fixedProposeReceiptGas": "0", + "fixedMessageRevealGas": "0", + "genPerTimeUnit": "0", +} +DEFAULT_INITIAL_TIME_UNITS_ALLOCATION = 10 * 60 + from .logger import Logger @@ -102,11 +114,31 @@ class ResultFingerprint(typing.TypedDict): module_instances: dict[str, typing.Any] +class MessageFeeParams(typing.TypedDict): + leader_timeunits_allocation: int + validator_timeunits_allocation: int + execution_budget_per_round: int + rotations: list[int] + + +class MessageFeeAllocationNode(typing.TypedDict): + message_type: typing.Literal["InternalAccepted", "InternalFinalized", "External"] + parent_index: int | None + recipient: Address | None + call_key: bytes | None + budget: int + fee_params: MessageFeeParams + + class EthSendInner(typing.TypedDict): type: typing.Literal["EthSend"] address: Address calldata: bytes value: int + feeParams: typing.NotRequired[bytes] + declaredBudget: typing.NotRequired[int] + callKey: typing.NotRequired[bytes] + allocationSubtree: typing.NotRequired[list[dict]] class PostMessageInner(typing.TypedDict): @@ -115,6 +147,10 @@ class PostMessageInner(typing.TypedDict): calldata: gvm_calldata.Decoded value: int on: typing.Literal["finalized", "accepted"] + feeParams: typing.NotRequired[bytes] + declaredBudget: typing.NotRequired[int] + callKey: typing.NotRequired[bytes] + allocationSubtree: typing.NotRequired[list[dict]] class DeployContractInner(typing.TypedDict): @@ -124,6 +160,10 @@ class DeployContractInner(typing.TypedDict): value: int on: typing.Literal["finalized", "accepted"] salt_nonce: int + feeParams: typing.NotRequired[bytes] + declaredBudget: typing.NotRequired[int] + callKey: typing.NotRequired[bytes] + allocationSubtree: typing.NotRequired[list[dict]] class EmitEventInner(typing.TypedDict): @@ -375,6 +415,7 @@ class RunHostAndProgramRes: result_storage_changes: list[tuple[bytes, bytes]] result_emissions: list[ResultEmission] result_nondet_results: list[bytes] + data_fees_remaining: list[int] vm_error_description: str | None = None @@ -429,17 +470,20 @@ async def run_genvm( capture_output: bool = True, message: Message, host_data: str = "", + gas_data: dict[str, str] | None = None, host: str, extra_args: list[str] = [], - data_fees_limit: int = 10_000_000, - storage_page_cost: int = 1, - receipt_word_cost: int = 1, + bucket_totals: list[int] | None = None, code: bytes | None = None, calldata: bytes, leader_nondet_results: list[bytes] | None = None, + message_fee_allocation: list[MessageFeeAllocationNode] | None = None, request_extra: dict[str, gvm_calldata.Encodable] = {}, ) -> RunHostAndProgramRes: logger = ctx.logger + effective_bucket_totals = bucket_totals or [10_000_000, 10_000_000, 10_000_000] + effective_gas_data = DEFAULT_GAS_DATA if gas_data is None else gas_data + effective_message_fee_allocation = message_fee_allocation or [] perf_timeline: dict[str, typing.Any] = { "run_started_s": time.perf_counter(), @@ -475,9 +519,10 @@ async def wrap_proc_body(attempt: int): "code": code, "calldata": calldata, "leader_nondet_results": leader_nondet_results, - "data_fees_limit": data_fees_limit, - "storage_page_cost": storage_page_cost, - "receipt_word_cost": receipt_word_cost, + "bucket_totals": effective_bucket_totals, + "gas_data": effective_gas_data, + "message_fee_allocation": effective_message_fee_allocation, + "initial_time_units_allocation": DEFAULT_INITIAL_TIME_UNITS_ALLOCATION, **request_extra, } ), @@ -791,6 +836,7 @@ async def prob_died(): result_storage_changes = decoded.get("storage_changes", []) result_emissions = decoded.get("emissions", []) nondet_results = decoded.get("nondet_results", []) + data_fees_remaining = decoded.get("data_fees_remaining", []) else: execution_hash = b"" result_kind = public_abi.ResultCode.INTERNAL_ERROR @@ -799,10 +845,11 @@ async def prob_died(): result_storage_changes = [] result_emissions = [] nondet_results = [] + data_fees_remaining = [] if timeout_fired.is_set() and result_kind != public_abi.ResultCode.RETURN: result_kind = public_abi.ResultCode.VM_ERROR - result_data = public_abi.VmError.TIMEOUT.value + result_data = str(public_abi.VmError.TIMEOUT) vm_error_description: str | None = None if result_kind == public_abi.ResultCode.VM_ERROR and isinstance( @@ -832,6 +879,7 @@ async def prob_died(): result_storage_changes=result_storage_changes, result_emissions=result_emissions, result_nondet_results=nondet_results, + data_fees_remaining=data_fees_remaining, vm_error_description=vm_error_description, execution_time=time.time() - started_at[0], ) diff --git a/backend/node/genvm/origin/host_fns.py b/backend/node/genvm/origin/host_fns.py index cd7e5dd84..281d17dd2 100644 --- a/backend/node/genvm/origin/host_fns.py +++ b/backend/node/genvm/origin/host_fns.py @@ -1,18 +1,18 @@ # This file is auto-generated. Do not edit! from enum import IntEnum +import typing class Methods(IntEnum): STORAGE_READ = 0 - STORAGE_WRITE = 1 - CONSUME_FUEL = 2 - ETH_CALL = 3 - GET_BALANCE = 4 - REMAINING_FUEL_AS_GEN = 5 - NOTIFY_NONDET_DISAGREEMENT = 6 - CONSUME_RESULT = 7 - NOTIFY_FINISHED = 8 + CONSUME_FUEL = 1 + ETH_CALL = 2 + GET_BALANCE = 3 + REMAINING_FUEL_AS_GEN = 4 + NOTIFY_NONDET_DISAGREEMENT = 5 + CONSUME_RESULT = 6 + NOTIFY_FINISHED = 7 class Errors(IntEnum): @@ -20,3 +20,9 @@ class Errors(IntEnum): ABSENT = 1 FORBIDDEN = 2 OUT_OF_STORAGE_GAS = 3 + + +CURRENT_MAJOR: typing.Final[int] = 0 + + +CURRENT_MAJOR_STR: typing.Final[str] = "v0.0.0" diff --git a/backend/node/types.py b/backend/node/types.py index d49c83ca1..414e40d79 100644 --- a/backend/node/types.py +++ b/backend/node/types.py @@ -155,6 +155,12 @@ def from_string(cls, value: str) -> "ExecutionResultStatus": raise ValueError(f"Invalid execution result status value: {value}") +def _int_from_serialized(value, default: int = 0) -> int: + if value is None or value == "": + return default + return int(value) + + @dataclass class PendingTransaction: address: str # Address of the contract to call @@ -166,6 +172,11 @@ class PendingTransaction: is_eth_send: bool = ( False # True for EthSend (simple value transfer, no contract call) ) + fee_params: bytes = b"" + declared_budget: int = 0 + call_key: str = "0x" + ("0" * 64) + allocation_subtree: list[dict] = field(default_factory=list) + gas_used: int = 0 def is_deploy(self) -> bool: return self.code is not None @@ -177,6 +188,11 @@ def to_dict(self): "is_eth_send": True, "on": self.on, "value": self.value, + "fee_params": str(base64.b64encode(self.fee_params), encoding="ascii"), + "declared_budget": self.declared_budget, + "call_key": self.call_key, + "allocation_subtree": self.allocation_subtree, + "gas_used": self.gas_used, } elif self.code is None: return { @@ -184,6 +200,11 @@ def to_dict(self): "calldata": str(base64.b64encode(self.calldata), encoding="ascii"), "on": self.on, "value": self.value, + "fee_params": str(base64.b64encode(self.fee_params), encoding="ascii"), + "declared_budget": self.declared_budget, + "call_key": self.call_key, + "allocation_subtree": self.allocation_subtree, + "gas_used": self.gas_used, } else: return { @@ -192,6 +213,11 @@ def to_dict(self): "salt_nonce": self.salt_nonce, "on": self.on, "value": self.value, + "fee_params": str(base64.b64encode(self.fee_params), encoding="ascii"), + "declared_budget": self.declared_budget, + "call_key": self.call_key, + "allocation_subtree": self.allocation_subtree, + "gas_used": self.gas_used, } @classmethod @@ -202,27 +228,42 @@ def from_dict(cls, input: dict) -> "PendingTransaction": calldata=b"", code=None, salt_nonce=0, - value=input.get("value", 0), + value=_int_from_serialized(input.get("value"), 0), on=input.get("on", "finalized"), is_eth_send=True, + fee_params=base64.b64decode(input.get("fee_params", "")), + declared_budget=_int_from_serialized(input.get("declared_budget"), 0), + call_key=input.get("call_key", "0x" + ("0" * 64)), + allocation_subtree=input.get("allocation_subtree", []), + gas_used=_int_from_serialized(input.get("gas_used"), 0), ) elif "code" in input: return cls( address="0x", calldata=base64.b64decode(input["calldata"]), code=base64.b64decode(input["code"]), - salt_nonce=input.get("salt_nonce", 0), - value=input.get("value", 0), + salt_nonce=_int_from_serialized(input.get("salt_nonce"), 0), + value=_int_from_serialized(input.get("value"), 0), on=input.get("on", "finalized"), + fee_params=base64.b64decode(input.get("fee_params", "")), + declared_budget=_int_from_serialized(input.get("declared_budget"), 0), + call_key=input.get("call_key", "0x" + ("0" * 64)), + allocation_subtree=input.get("allocation_subtree", []), + gas_used=_int_from_serialized(input.get("gas_used"), 0), ) else: return cls( address=input["address"], calldata=base64.b64decode(input["calldata"]), - value=input.get("value", 0), + value=_int_from_serialized(input.get("value"), 0), code=None, salt_nonce=0, on=input.get("on", "finalized"), + fee_params=base64.b64decode(input.get("fee_params", "")), + declared_budget=_int_from_serialized(input.get("declared_budget"), 0), + call_key=input.get("call_key", "0x" + ("0" * 64)), + allocation_subtree=input.get("allocation_subtree", []), + gas_used=_int_from_serialized(input.get("gas_used"), 0), ) diff --git a/backend/protocol_rpc/endpoints.py b/backend/protocol_rpc/endpoints.py index 493554822..1e9cb4d52 100644 --- a/backend/protocol_rpc/endpoints.py +++ b/backend/protocol_rpc/endpoints.py @@ -39,6 +39,18 @@ ) from backend.protocol_rpc.transactions_parser import TransactionParser +from backend.protocol_rpc.fees import ( + FEE_ACCOUNTING_KEY, + FeeValidationError, + StudioFeePolicy, + apply_fee_top_up, + create_fee_accounting, + record_appeal_bond, + record_execution_fee_consumption, + required_fee_deposit, + studio_fee_config, + validate_transaction_fee_deposit, +) from backend.errors.errors import InvalidAddressError, InvalidTransactionError from backend.database_handler.errors import ContractNotFoundError @@ -49,6 +61,7 @@ logger = logging.getLogger(__name__) +TRANSACTION_NOT_FOUND_MESSAGE = "Transaction not found" from backend.node.base import Node, get_simulator_chain_id from backend.node.types import ExecutionMode, ExecutionResultStatus from backend.consensus.base import ConsensusAlgorithm @@ -59,7 +72,11 @@ import os import secrets as secrets_module from backend.protocol_rpc.message_handler.types import LogEvent, EventType, EventScope -from backend.protocol_rpc.types import DecodedsubmitAppealDataArgs +from backend.protocol_rpc.types import ( + DecodedRollupTransaction, + DecodedTopUpFeesDataArgs, + DecodedsubmitAppealDataArgs, +) from backend.database_handler.snapshot_manager import SnapshotManager from backend.node.base import Manager as GenVMManager import asyncio @@ -69,7 +86,6 @@ # Workers use asyncio.Semaphore(8) in consensus/base.py; keep the RPC path # bounded too. _GENVM_CONCURRENCY = int(os.environ.get("GENVM_MAX_CONCURRENT", "8")) -_genvm_semaphore = asyncio.Semaphore(_GENVM_CONCURRENCY) _genvm_admission_semaphore = asyncio.Semaphore(_GENVM_CONCURRENCY) # --------------------------------------------------------------------------- @@ -230,6 +246,10 @@ def _enforce_pending_queue_caps( ) +def get_studio_fee_config() -> dict[str, Any]: + return studio_fee_config(StudioFeePolicy.from_env()) + + ####### ADMIN ACCESS CONTROL ####### def require_admin_access(func): """ @@ -873,7 +893,7 @@ def cancel_transaction( ) if not transaction: raise NotFoundError( - message="Transaction not found", + message=TRANSACTION_NOT_FOUND_MESSAGE, data={"transaction_hash": transaction_hash}, ) @@ -948,6 +968,11 @@ def cancel_transaction( AccountsManager(session).refund_tx_value( transaction_hash, transaction.from_address ) + if transaction.from_address: + AccountsManager(session).cancel_tx_fee_accounting_once( + transaction_hash, transaction.from_address, "canceled" + ) + session.commit() # Notify frontend via WebSocket msg_handler.send_transaction_status_update(transaction_hash, "CANCELED") @@ -1232,6 +1257,72 @@ async def sim_call( return receipt.to_dict() +async def sim_estimate_transaction_fees( + session: Session, + accounts_manager: AccountsManager, + msg_handler: IMessageHandler, + transactions_parser: TransactionParser, + validators_manager: validators.Manager, + genvm_manager: GenVMManager, + params: dict, +) -> dict: + estimate_params = _with_default_simulation_fees(params) + receipt = await sim_call( + session=session, + accounts_manager=accounts_manager, + msg_handler=msg_handler, + transactions_parser=transactions_parser, + validators_manager=validators_manager, + genvm_manager=genvm_manager, + params=estimate_params, + ) + genvm_result = receipt.get("genvm_result") or {} + fee_accounting = ( + genvm_result.get(FEE_ACCOUNTING_KEY) if isinstance(genvm_result, dict) else {} + ) or {} + return { + "scenario": _first_present(params, "scenario", "scenarioName") or "default", + "receipt": receipt, + "feeAccounting": fee_accounting, + "feeReport": fee_accounting.get("execution_fee_report") or {}, + "recommendedPreset": fee_accounting.get("recommended_fee_preset") or {}, + } + + +def _with_default_simulation_fees(params: dict) -> dict: + if not isinstance(params, dict): + return params + fees = params.get("fees") if isinstance(params.get("fees"), dict) else {} + has_fee_params = any( + key in params + for key in ( + "fees_distribution", + "feesDistribution", + "message_allocations", + "messageAllocations", + "fee_value", + "feeValue", + ) + ) or any( + key in fees + for key in ( + "distribution", + "fees_distribution", + "feesDistribution", + "message_allocations", + "messageAllocations", + "fee_value", + "feeValue", + ) + ) + if has_fee_params: + return params + + updated = dict(params) + updated["fees"] = studio_fee_config(StudioFeePolicy.from_env())["defaultFees"] + return updated + + async def _gen_call_with_validator( session: Session, accounts_manager: AccountsManager, @@ -1247,6 +1338,11 @@ async def _gen_call_with_validator( from_address = params["from"] origin_address = params.get("origin_address") call_value = int(params.get("value", "0x0"), 16) if params.get("value") else 0 + simulation_fee_accounting = _simulation_fee_accounting( + params, + sender=from_address, + user_value=call_value, + ) transaction_hash_variant = ( params["transaction_hash_variant"] if "transaction_hash_variant" in params @@ -1301,90 +1397,88 @@ async def _gen_call_with_validator( sim_config is not None and sim_config.genvm_datetime is not None ) - if _genvm_semaphore.locked(): - _rate_limit_logger.warning( - f"GenVM at capacity ({_GENVM_CONCURRENCY} concurrent) — rejecting gen_call to {to_address}" - ) - raise JSONRPCError( - code=-32006, - message=f"Server busy: all {_GENVM_CONCURRENCY} execution slots occupied, retry later", - data={"retry_after_seconds": 2}, + try: + if type == "read": + # Pre-parse timestamp override and map errors + txn_dt = None + if sim_config and override_transaction_datetime: + try: + txn_dt = sim_config.genvm_datetime_as_datetime + except ValueError as e: + raise JSONRPCError( + code=-32602, + message=f"Invalid sim_config.genvm_datetime: {sim_config.genvm_datetime}", + data={}, + ) from e + decoded_data = transactions_parser.decode_method_call_data(data) + receipt = await node.get_contract_data( + from_address=from_address, + calldata=decoded_data.calldata, + state_status=state_status, + transaction_datetime=txn_dt, + origin_address=origin_address, + ) + elif type == "write": + txn_created_at = None + if sim_config and override_transaction_datetime: + try: + _ = sim_config.genvm_datetime_as_datetime # validation only + txn_created_at = sim_config.genvm_datetime + except ValueError as e: + raise JSONRPCError( + code=-32602, + message=f"Invalid sim_config.genvm_datetime: {sim_config.genvm_datetime}", + data={}, + ) from e + decoded_data = transactions_parser.decode_method_send_data(data) + receipt = await node.run_contract( + from_address=from_address, + calldata=decoded_data.calldata, + transaction_created_at=txn_created_at, + value=call_value, + origin_address=origin_address, + fee_accounting=simulation_fee_accounting, + ) + elif type == "deploy": + txn_created_at = None + if sim_config and override_transaction_datetime: + try: + _ = sim_config.genvm_datetime_as_datetime # validation only + txn_created_at = sim_config.genvm_datetime + except ValueError as e: + raise JSONRPCError( + code=-32602, + message=f"Invalid sim_config.genvm_datetime: {sim_config.genvm_datetime}", + data={}, + ) from e + decoded_data = transactions_parser.decode_deployment_data(data) + receipt = await node.deploy_contract( + from_address=from_address, + code_to_deploy=decoded_data.contract_code, + calldata=decoded_data.calldata, + transaction_created_at=txn_created_at, + value=call_value, + origin_address=origin_address, + fee_accounting=simulation_fee_accounting, + ) + else: + raise JSONRPCError( + code=-32602, + message=f"Invalid type '{type}': must be 'read', 'write', or 'deploy'", + ) + except ContractNotFoundError as e: + raise NotFoundError( + message=f"Contract {e.address} not found", + data={"contract_address": e.address}, + ) from e + + if simulation_fee_accounting is not None: + receipt.genvm_result = dict(receipt.genvm_result or {}) + receipt.genvm_result["fee_accounting"] = record_execution_fee_consumption( + simulation_fee_accounting, + receipt, ) - async with _genvm_semaphore: - try: - if type == "read": - # Pre-parse timestamp override and map errors - txn_dt = None - if sim_config and override_transaction_datetime: - try: - txn_dt = sim_config.genvm_datetime_as_datetime - except ValueError as e: - raise JSONRPCError( - code=-32602, - message=f"Invalid sim_config.genvm_datetime: {sim_config.genvm_datetime}", - data={}, - ) from e - decoded_data = transactions_parser.decode_method_call_data(data) - receipt = await node.get_contract_data( - from_address=from_address, - calldata=decoded_data.calldata, - state_status=state_status, - transaction_datetime=txn_dt, - origin_address=origin_address, - ) - elif type == "write": - txn_created_at = None - if sim_config and override_transaction_datetime: - try: - _ = sim_config.genvm_datetime_as_datetime # validation only - txn_created_at = sim_config.genvm_datetime - except ValueError as e: - raise JSONRPCError( - code=-32602, - message=f"Invalid sim_config.genvm_datetime: {sim_config.genvm_datetime}", - data={}, - ) from e - decoded_data = transactions_parser.decode_method_send_data(data) - receipt = await node.run_contract( - from_address=from_address, - calldata=decoded_data.calldata, - transaction_created_at=txn_created_at, - value=call_value, - origin_address=origin_address, - ) - elif type == "deploy": - txn_created_at = None - if sim_config and override_transaction_datetime: - try: - _ = sim_config.genvm_datetime_as_datetime # validation only - txn_created_at = sim_config.genvm_datetime - except ValueError as e: - raise JSONRPCError( - code=-32602, - message=f"Invalid sim_config.genvm_datetime: {sim_config.genvm_datetime}", - data={}, - ) from e - decoded_data = transactions_parser.decode_deployment_data(data) - receipt = await node.deploy_contract( - from_address=from_address, - code_to_deploy=decoded_data.contract_code, - calldata=decoded_data.calldata, - transaction_created_at=txn_created_at, - value=call_value, - origin_address=origin_address, - ) - else: - raise JSONRPCError( - code=-32602, - message=f"Invalid type '{type}': must be 'read', 'write', or 'deploy'", - ) - except ContractNotFoundError as e: - raise NotFoundError( - message=f"Contract {e.address} not found", - data={"contract_address": e.address}, - ) from e - # Return the result of the write method if receipt.execution_result != ExecutionResultStatus.SUCCESS: raise JSONRPCError( @@ -1546,6 +1640,265 @@ async def eth_call( return eth_utils.hexadecimal.encode_hex(receipt.result[1:]) +def _fee_metadata(decoded_rollup_transaction: DecodedRollupTransaction) -> dict: + if ( + decoded_rollup_transaction.data is None + or isinstance(decoded_rollup_transaction.data, DecodedsubmitAppealDataArgs) + or isinstance(decoded_rollup_transaction.data, DecodedTopUpFeesDataArgs) + or not hasattr(decoded_rollup_transaction.data, "args") + or decoded_rollup_transaction.data.args is None + ): + return {} + + args = decoded_rollup_transaction.data.args + if args.fees_distribution is None and decoded_rollup_transaction.fee_value == 0: + return {} + + metadata = { + "fee_value": decoded_rollup_transaction.fee_value, + "user_value": args.user_value, + "valid_until": args.valid_until, + "salt_nonce": args.salt_nonce, + "fees_distribution": args.fees_distribution, + "message_allocations_count": args.message_allocations_count, + } + metadata[FEE_ACCOUNTING_KEY] = create_fee_accounting( + fees_distribution=args.fees_distribution, + message_allocations=args.message_allocations, + num_of_validators=args.num_of_initial_validators, + submitted_value=decoded_rollup_transaction.total_spend, + user_value=int(args.user_value or 0), + sender=decoded_rollup_transaction.from_address, + policy=StudioFeePolicy.from_env(), + ) + return metadata + + +def _validate_fee_envelope( + decoded_rollup_transaction: DecodedRollupTransaction, +) -> None: + if ( + decoded_rollup_transaction.data is None + or isinstance(decoded_rollup_transaction.data, DecodedsubmitAppealDataArgs) + or isinstance(decoded_rollup_transaction.data, DecodedTopUpFeesDataArgs) + or not hasattr(decoded_rollup_transaction.data, "args") + or decoded_rollup_transaction.data.args is None + ): + return + + args = decoded_rollup_transaction.data.args + if args.fees_distribution is None: + return + + try: + validate_transaction_fee_deposit( + fees_distribution=args.fees_distribution, + message_allocations=args.message_allocations, + num_of_validators=args.num_of_initial_validators, + submitted_value=decoded_rollup_transaction.total_spend, + user_value=int(args.user_value or 0), + policy=StudioFeePolicy.from_env(), + ) + except FeeValidationError as exc: + raise InvalidTransactionError(str(exc)) from exc + + +def _sandbox_debit_sender( + accounts_manager: AccountsManager, from_address: str, amount: int +) -> None: + if amount <= 0: + return + sender_balance = accounts_manager.get_account_balance(from_address) + if sender_balance < amount: + accounts_manager.credit_account_balance(from_address, amount - sender_balance) + accounts_manager.debit_account_balance(from_address, amount) + + +def _handle_top_up_fees( + *, + accounts_manager: AccountsManager, + transactions_processor: TransactionsProcessor, + decoded_rollup_transaction: DecodedRollupTransaction, +) -> str: + assert isinstance(decoded_rollup_transaction.data, DecodedTopUpFeesDataArgs) + tx_id = _tx_id_to_hex(decoded_rollup_transaction.data.tx_id) + tx = transactions_processor.get_transaction_by_hash(tx_id) + if tx is None: + raise NotFoundError(message=TRANSACTION_NOT_FOUND_MESSAGE, data={"hash": tx_id}) + + status = tx.get("status") + if status in { + TransactionStatus.ACCEPTED.value, + TransactionStatus.UNDETERMINED.value, + TransactionStatus.FINALIZED.value, + TransactionStatus.CANCELED.value, + }: + raise InvalidTransactionError("InvalidTransactionStatus") + + fee_accounting = (tx.get("data") or {}).get(FEE_ACCOUNTING_KEY) + if fee_accounting is None: + raise InvalidTransactionError("FeeAccountingMissing") + + try: + updated = apply_fee_top_up( + fee_accounting, + fees_distribution=decoded_rollup_transaction.data.fees_distribution, + amount=decoded_rollup_transaction.total_spend, + sender=decoded_rollup_transaction.from_address, + num_of_validators=int(tx.get("num_of_initial_validators") or 5), + policy=StudioFeePolicy.from_env(), + ) + except FeeValidationError as exc: + raise InvalidTransactionError(str(exc)) from exc + + _sandbox_debit_sender( + accounts_manager, + decoded_rollup_transaction.from_address, + decoded_rollup_transaction.total_spend, + ) + transactions_processor.update_transaction_fee_accounting(tx_id, updated) + return tx_id + + +def _handle_appeal_or_top_up_and_submit( + *, + accounts_manager: AccountsManager, + transactions_processor: TransactionsProcessor, + msg_handler: IMessageHandler, + decoded_rollup_transaction: DecodedRollupTransaction, +) -> str: + assert isinstance(decoded_rollup_transaction.data, DecodedsubmitAppealDataArgs) + tx_id = _tx_id_to_hex(decoded_rollup_transaction.data.tx_id) + tx = transactions_processor.get_transaction_by_hash(tx_id) + if tx is None: + raise NotFoundError(message=TRANSACTION_NOT_FOUND_MESSAGE, data={"hash": tx_id}) + + fee_accounting = (tx.get("data") or {}).get(FEE_ACCOUNTING_KEY) + if fee_accounting is not None and decoded_rollup_transaction.total_spend > 0: + try: + updated = record_appeal_bond( + fee_accounting, + amount=decoded_rollup_transaction.total_spend, + appealer=decoded_rollup_transaction.from_address, + current_round=_current_fee_round(tx.get("consensus_history")), + status=str(tx.get("status") or ""), + fees_distribution=decoded_rollup_transaction.data.fees_distribution, + top_up_and_submit=decoded_rollup_transaction.data.top_up_and_submit, + ) + except FeeValidationError as exc: + raise InvalidTransactionError(str(exc)) from exc + _sandbox_debit_sender( + accounts_manager, + decoded_rollup_transaction.from_address, + decoded_rollup_transaction.total_spend, + ) + transactions_processor.update_transaction_fee_accounting(tx_id, updated) + + transactions_processor.set_transaction_appeal(tx_id, True) + msg_handler.send_message( + log_event=LogEvent( + "transaction_appeal_updated", + EventType.INFO, + EventScope.CONSENSUS, + "Set transaction appealed", + { + "hash": tx_id, + }, + ), + log_to_terminal=False, + ) + return tx_id + + +def _tx_id_to_hex(tx_id: str | bytes) -> str: + return "0x" + tx_id.hex() if isinstance(tx_id, bytes) else tx_id + + +def _current_fee_round(consensus_history: dict | None) -> int: + if not isinstance(consensus_history, dict): + return 0 + rounds = consensus_history.get("consensus_results") + if not isinstance(rounds, list) or len(rounds) == 0: + return 0 + return max(0, len(rounds) - 1) + + +def _simulation_fee_accounting( + params: dict, + *, + sender: str, + user_value: int, +) -> dict | None: + fees = params.get("fees") if isinstance(params.get("fees"), dict) else {} + fees_distribution = _first_present( + params, + "fees_distribution", + "feesDistribution", + ) or _first_present(fees, "distribution", "fees_distribution", "feesDistribution") + message_allocations = _first_present( + params, + "message_allocations", + "messageAllocations", + ) + if message_allocations is None: + message_allocations = _first_present( + fees, + "message_allocations", + "messageAllocations", + ) + raw_fee_value = _first_present(params, "fee_value", "feeValue") + if raw_fee_value is None: + raw_fee_value = _first_present(fees, "fee_value", "feeValue") + + if fees_distribution is None and not message_allocations and raw_fee_value is None: + return None + + fees_distribution = fees_distribution or {} + message_allocations = message_allocations or [] + num_of_initial_validators = _int_param( + _first_present(params, "num_of_initial_validators", "numOfInitialValidators"), + 5, + ) + policy = StudioFeePolicy.from_env() + fee_value = _int_param(raw_fee_value, None) + if fee_value is None: + fee_value = required_fee_deposit( + fees_distribution, + num_of_initial_validators, + policy, + ) + + try: + return create_fee_accounting( + fees_distribution=fees_distribution, + message_allocations=message_allocations, + num_of_validators=num_of_initial_validators, + submitted_value=int(user_value) + int(fee_value), + user_value=int(user_value), + sender=sender, + policy=policy, + ) + except FeeValidationError as exc: + raise JSONRPCError(code=-32602, message=str(exc), data={}) from exc + + +def _first_present(source: dict | None, *keys: str): + if not isinstance(source, dict): + return None + for key in keys: + if key in source: + return source[key] + return None + + +def _int_param(value: Any, default: int | None = None) -> int | None: + if value is None: + return default + if isinstance(value, str): + return int(value, 16) if value.startswith("0x") else int(value) + return int(value) + + def send_raw_transaction( session: Session, msg_handler: IMessageHandler, @@ -1570,6 +1923,7 @@ def send_raw_transaction( from_address = decoded_rollup_transaction.from_address value = decoded_rollup_transaction.value + total_spend = getattr(decoded_rollup_transaction, "total_spend", value) if not accounts_manager.is_valid_address(from_address): raise InvalidAddressError( @@ -1587,29 +1941,27 @@ def send_raw_transaction( raise InvalidTransactionError("Transaction signature verification failed") if isinstance(decoded_rollup_transaction.data, DecodedsubmitAppealDataArgs): - tx_id = decoded_rollup_transaction.data.tx_id - tx_id_hex = "0x" + tx_id.hex() if isinstance(tx_id, bytes) else tx_id - transactions_processor.set_transaction_appeal(tx_id_hex, True) - msg_handler.send_message( - log_event=LogEvent( - "transaction_appeal_updated", - EventType.INFO, - EventScope.CONSENSUS, - "Set transaction appealed", - { - "hash": tx_id_hex, - }, - ), - log_to_terminal=False, + return _handle_appeal_or_top_up_and_submit( + accounts_manager=accounts_manager, + transactions_processor=transactions_processor, + msg_handler=msg_handler, + decoded_rollup_transaction=decoded_rollup_transaction, + ) + elif isinstance(decoded_rollup_transaction.data, DecodedTopUpFeesDataArgs): + return _handle_top_up_fees( + accounts_manager=accounts_manager, + transactions_processor=transactions_processor, + decoded_rollup_transaction=decoded_rollup_transaction, ) - return tx_id_hex else: + _validate_fee_envelope(decoded_rollup_transaction) transaction_hash = consensus_service.generate_transaction_hash( signed_rollup_transaction ) to_address = decoded_rollup_transaction.to_address nonce = decoded_rollup_transaction.nonce value = decoded_rollup_transaction.value + total_spend = getattr(decoded_rollup_transaction, "total_spend", value) genlayer_transaction = transactions_parser.get_genlayer_transaction( decoded_rollup_transaction ) @@ -1659,6 +2011,8 @@ def send_raw_transaction( "contract_code": genlayer_transaction.data.contract_code, "calldata": genlayer_transaction.data.calldata, } + if fee_metadata := _fee_metadata(decoded_rollup_transaction): + transaction_data.update(fee_metadata) to_address = new_contract_address elif genlayer_transaction.type == TransactionType.RUN_CONTRACT: # Contract Call @@ -1675,6 +2029,8 @@ def send_raw_transaction( ) transaction_data = {"calldata": genlayer_transaction.data.calldata} + if fee_metadata := _fee_metadata(decoded_rollup_transaction): + transaction_data.update(fee_metadata) # Check for duplicate before debit+insert to avoid TOCTOU races is_duplicate = transactions_processor.get_transaction_by_hash(transaction_hash) @@ -1699,16 +2055,12 @@ def send_raw_transaction( # Debit sender BEFORE insert. Mint on demand if insufficient (Studio sandbox). # Skip for SEND (execute_transfer handles it) and duplicates. if ( - value > 0 + total_spend > 0 and from_address and genlayer_transaction.type != TransactionType.SEND and is_duplicate is None ): - sender_balance = accounts_manager.get_account_balance(from_address) - if sender_balance < value: - shortfall = value - sender_balance - accounts_manager.credit_account_balance(from_address, shortfall) - accounts_manager.debit_account_balance(from_address, value) + _sandbox_debit_sender(accounts_manager, from_address, total_spend) # Insert transaction into the database transactions_processor.insert_transaction( diff --git a/backend/protocol_rpc/fastapi_endpoint_generator.py b/backend/protocol_rpc/fastapi_endpoint_generator.py index d1dbdd888..5d74ab3e5 100644 --- a/backend/protocol_rpc/fastapi_endpoint_generator.py +++ b/backend/protocol_rpc/fastapi_endpoint_generator.py @@ -489,6 +489,7 @@ def register(func, method_name=None): partial(endpoints.get_finality_window_time, consensus), "sim_getFinalityWindowTime", ) + register(endpoints.get_studio_fee_config, "sim_getFeeConfig") register( partial(endpoints.get_contract, accounts_manager), "sim_getConsensusContract" ) @@ -546,6 +547,18 @@ def register(func, method_name=None): ), "sim_call", ) + register( + partial( + endpoints.sim_estimate_transaction_fees, + request_session, + accounts_manager, + msg_handler, + transactions_parser, + validators_manager, + genvm_manager, + ), + "sim_estimateTransactionFees", + ) # Ethereum-compatible endpoints register(partial(endpoints.get_balance, accounts_manager), "eth_getBalance") diff --git a/backend/protocol_rpc/fees.py b/backend/protocol_rpc/fees.py new file mode 100644 index 000000000..3357500c6 --- /dev/null +++ b/backend/protocol_rpc/fees.py @@ -0,0 +1,3298 @@ +from __future__ import annotations + +import base64 +import copy +import os +from dataclasses import dataclass, fields +from typing import Any, Callable + +import rlp +from eth_abi import decode, encode + + +VALIDATORS_PER_ROUND = ( + 5, + 7, + 11, + 13, + 23, + 25, + 47, + 49, + 95, + 97, + 191, + 193, + 383, + 385, + 767, + 769, + 1535, + 1537, +) + +MIN_RECEIPT_BYTES = 512 +PROPOSE_RECEIPT_SLOTS = 7 +MESSAGE_REVEAL_LENGTH_SLOTS = 32 +NONDET_OUTPUT_LENGTH_BYTES = 32 +NODE_ROOT_SENTINEL = (1 << 256) - 1 +CALL_KEY_WILDCARD = "0x" + ("0" * 64) +MESSAGE_TYPE_EXTERNAL = 0 +MESSAGE_TYPE_INTERNAL = 1 +FEE_ACCOUNTING_KEY = "fee_accounting" + +INTERNAL_MESSAGE_FEE_PARAMS_ABI_TYPE = "(uint256,uint256,uint256,uint256,uint256[])" +EXTERNAL_MESSAGE_FEE_PARAMS_ABI_TYPE = "(uint256,uint256)" +MESSAGE_ALLOCATION_NODE_ABI_TYPE = ( + "(uint8,bool,uint256,address,bytes32,uint256,bytes)[]" +) +SUBMITTED_MESSAGE_ABI_TYPE = ( + "(uint8,address,uint256,bytes,bool,uint256,bytes,uint256,bytes,bytes32)[]" +) + +WEI_PER_GEN = 10**18 +DEFAULT_GEN_PER_TIME_UNIT = WEI_PER_GEN // 1_000 +DEFAULT_STORAGE_UNIT_PRICE = 1 +DEFAULT_RECEIPT_GAS_PRICE = 1 +DEFAULT_TRANSACTION_EXECUTION_BUDGET_PER_ROUND = 500_000 +DEFAULT_LEADER_TIMEUNITS_ALLOCATION = 100 +DEFAULT_VALIDATOR_TIMEUNITS_ALLOCATION = 200 +DEFAULT_PRICE_CAP_HEADROOM_BPS = 12_000 +GENVM_UNMETERED_DATA_FEE_BUCKET = (1 << 256) - 1 + + +class FeeValidationError(ValueError): + pass + + +class InvalidNumOfValidators(FeeValidationError): + pass + + +class InvalidAppealRounds(FeeValidationError): + pass + + +class InsufficientFees(FeeValidationError): + pass + + +class BudgetTooLow(FeeValidationError): + pass + + +class MaxPriceExceeded(FeeValidationError): + pass + + +class MessageAllocationsNotEqualBudget(FeeValidationError): + pass + + +class AllocationTreeMalformed(FeeValidationError): + pass + + +class AllocationLifecycleBudgetInsufficient(FeeValidationError): + pass + + +class AllocationTreeBudgetInconsistent(FeeValidationError): + pass + + +class AllocationSubtreeMismatch(FeeValidationError): + pass + + +class AllocationDuplicateKey(FeeValidationError): + pass + + +class AllocationTreeTooDeep(FeeValidationError): + pass + + +class ExternalAllocationInvalid(FeeValidationError): + pass + + +class InvalidFeeParams(FeeValidationError): + pass + + +class Mode1MessageFeesRequireGenVMPerEmissionSupport(FeeValidationError): + """GenVM must expose per-emission feeParams/declaredBudget before Mode 1 is safe.""" + + +class InvalidAppealBond(FeeValidationError): + pass + + +class MessageDeclaredBudgetInsufficient(FeeValidationError): + pass + + +class MessageFeesReportMismatch(FeeValidationError): + pass + + +class MessageBudgetExceeded(FeeValidationError): + pass + + +def _with_cap_headroom( + value: int, headroom_bps: int = DEFAULT_PRICE_CAP_HEADROOM_BPS +) -> int: + if value <= 0: + return 0 + return (value * headroom_bps + 9_999) // 10_000 + + +def _with_padding(value: int, padding_bps: int) -> int: + if value <= 0: + return 0 + return (value * int(padding_bps) + 9_999) // 10_000 + + +class MessageNoMatchingAllocation(FeeValidationError): + pass + + +class MessageEmissionPhaseMismatch(FeeValidationError): + pass + + +class MessageFeeParamsMismatch(FeeValidationError): + pass + + +class TooManyMessages(FeeValidationError): + pass + + +@dataclass(frozen=True) +class StudioFeePolicy: + gen_per_time_unit: int = 0 + storage_unit_price: int = 0 + receipt_gas_price: int = 0 + intrinsic_gas: int = 21_000 + bootloader_overhead: int = 60_000 + gas_per_changed_slot: int = 1_000 + calldata_gas_per_byte: int = 16 + fixed_propose_receipt_gas: int = 210_000 + fixed_message_reveal_gas: int = 100_000 + receipt_wrapper_bytes: int = 1_024 + extra_exec_gas: int = 210_000 + max_allocation_tree_depth: int = 5 + max_messages_per_tx: int = 0 + + @classmethod + def from_env(cls) -> "StudioFeePolicy": + return cls( + gen_per_time_unit=_env_int( + "GENLAYER_STUDIO_GEN_PER_TIME_UNIT", DEFAULT_GEN_PER_TIME_UNIT + ), + storage_unit_price=_env_int( + "GENLAYER_STUDIO_STORAGE_UNIT_PRICE", DEFAULT_STORAGE_UNIT_PRICE + ), + receipt_gas_price=_env_int( + "GENLAYER_STUDIO_RECEIPT_GAS_PRICE", DEFAULT_RECEIPT_GAS_PRICE + ), + intrinsic_gas=_env_int("GENLAYER_STUDIO_INTRINSIC_GAS", 21_000), + bootloader_overhead=_env_int("GENLAYER_STUDIO_BOOTLOADER_OVERHEAD", 60_000), + gas_per_changed_slot=_env_int( + "GENLAYER_STUDIO_GAS_PER_CHANGED_SLOT", 1_000 + ), + calldata_gas_per_byte=_env_int("GENLAYER_STUDIO_CALLDATA_GAS_PER_BYTE", 16), + fixed_propose_receipt_gas=_env_int( + "GENLAYER_STUDIO_FIXED_PROPOSE_RECEIPT_GAS", 210_000 + ), + fixed_message_reveal_gas=_env_int( + "GENLAYER_STUDIO_FIXED_MESSAGE_REVEAL_GAS", 100_000 + ), + receipt_wrapper_bytes=_env_int( + "GENLAYER_STUDIO_RECEIPT_WRAPPER_BYTES", 1_024 + ), + extra_exec_gas=_env_int("GENLAYER_STUDIO_EXTRA_EXEC_GAS", 210_000), + max_allocation_tree_depth=_env_int( + "GENLAYER_STUDIO_MAX_ALLOCATION_TREE_DEPTH", 5 + ), + max_messages_per_tx=_env_int("GENLAYER_STUDIO_MAX_MESSAGES_PER_TX", 0), + ) + + def estimate_propose_receipt_bytes(self, eq_outputs_length: int) -> int: + return self.receipt_wrapper_bytes + max(0, int(eq_outputs_length)) + + def estimate_propose_receipt_gas(self, receipt_bytes: int) -> int: + return ( + self.fixed_propose_receipt_gas + + self.intrinsic_gas + + self.bootloader_overhead + + (max(0, int(receipt_bytes)) * self.calldata_gas_per_byte) + + (PROPOSE_RECEIPT_SLOTS * self.gas_per_changed_slot) + ) + + def estimate_message_reveal_gas( + self, + message_bytes: int, + message_count: int, + ) -> int: + return ( + self.fixed_message_reveal_gas + + self.intrinsic_gas + + self.bootloader_overhead + + (max(0, int(message_bytes)) * self.calldata_gas_per_byte) + + ( + (MESSAGE_REVEAL_LENGTH_SLOTS + max(0, int(message_count))) + * self.gas_per_changed_slot + ) + ) + + def estimate_consensus_message_reveal_gas( + self, + message_bytes: int, + message_count: int, + ) -> int: + return self.estimate_receipt_gas( + measured_exec_gas=0, + calldata_length=message_bytes, + slots_changed=message_count, + ) + + def estimate_receipt_gas( + self, + measured_exec_gas: int = 0, + calldata_length: int = MIN_RECEIPT_BYTES, + slots_changed: int = 7, + ) -> int: + measured = max(0, int(measured_exec_gas)) + if measured > 0: + measured += self.extra_exec_gas + return ( + measured + + self.intrinsic_gas + + self.bootloader_overhead + + (max(0, int(calldata_length)) * self.calldata_gas_per_byte) + + (max(0, int(slots_changed)) * self.gas_per_changed_slot) + ) + + def estimate_nondet_output_start_gas(self) -> int: + return NONDET_OUTPUT_LENGTH_BYTES * self.calldata_gas_per_byte + + def message_fee_params_budget_floor(self) -> int: + return self.minimum_execution_budget_per_round() + + def minimum_execution_budget_per_round(self) -> int: + if self.receipt_gas_price <= 0: + return 0 + fixed_bucket_gas = self.estimate_receipt_gas( + measured_exec_gas=0, + calldata_length=MIN_RECEIPT_BYTES, + slots_changed=PROPOSE_RECEIPT_SLOTS, + ) + return fixed_bucket_gas * self.receipt_gas_price + + def fee_accounting_enabled(self) -> bool: + return ( + self.gen_per_time_unit > 0 + or self.storage_unit_price > 0 + or self.receipt_gas_price > 0 + ) + + def to_snapshot(self) -> dict[str, int]: + return {field.name: int(getattr(self, field.name)) for field in fields(self)} + + @classmethod + def from_snapshot(cls, snapshot: dict[str, Any]) -> "StudioFeePolicy": + return cls(**{field.name: int(snapshot[field.name]) for field in fields(cls)}) + + +def _accounting_policy( + accounting: dict[str, Any] | None, + override: StudioFeePolicy | None = None, +) -> StudioFeePolicy: + if override is not None: + return override + snapshot = (accounting or {}).get("policy_snapshot") + if isinstance(snapshot, dict): + try: + return StudioFeePolicy.from_snapshot(snapshot) + except (KeyError, TypeError, ValueError): + pass + return StudioFeePolicy() + + +def _env_int(name: str, default: int) -> int: + raw = os.getenv(name) + if raw is None or raw == "": + return default + try: + return int(raw) + except ValueError as exc: + raise ValueError(f"{name} must be an integer, got {raw!r}") from exc + + +def _int_field(fees_distribution: dict[str, Any], field: str) -> int: + return int(fees_distribution.get(field, 0) or 0) + + +def normalize_fees_distribution( + fees_distribution: dict[str, Any], +) -> dict[str, int | list[int]]: + return { + "leaderTimeunitsAllocation": _int_field( + fees_distribution, "leaderTimeunitsAllocation" + ), + "validatorTimeunitsAllocation": _int_field( + fees_distribution, "validatorTimeunitsAllocation" + ), + "appealRounds": _int_field(fees_distribution, "appealRounds"), + "executionBudgetPerRound": _int_field( + fees_distribution, "executionBudgetPerRound" + ), + "executionConsumed": _int_field(fees_distribution, "executionConsumed"), + "totalMessageFees": _int_field(fees_distribution, "totalMessageFees"), + "rotations": [ + int(rotation) for rotation in fees_distribution.get("rotations", []) + ], + "maxPriceGenPerTimeUnit": _int_field( + fees_distribution, "maxPriceGenPerTimeUnit" + ), + "storageFeeMaxGasPrice": _int_field(fees_distribution, "storageFeeMaxGasPrice"), + "receiptFeeMaxGasPrice": _int_field(fees_distribution, "receiptFeeMaxGasPrice"), + } + + +def get_leader_rounds(fees_distribution: dict[str, Any]) -> int: + fees = normalize_fees_distribution(fees_distribution) + return sum(rotation + 1 for rotation in fees["rotations"]) + int( + fees["appealRounds"] + ) + + +def get_leader_rounds_through_round( + fees_distribution: dict[str, Any], final_round: int +) -> int: + fees = normalize_fees_distribution(fees_distribution) + rotations = fees["rotations"] + if not isinstance(rotations, list) or len(rotations) == 0: + raise InvalidAppealRounds("InvalidAppealRounds") + + final_round = max(0, int(final_round)) + total = int(rotations[0]) + 1 + rotations_index = 1 + for offset in range(1, min(final_round, int(fees["appealRounds"]) * 2) + 1): + if offset % 2 == 1: + total += 1 + elif rotations_index < len(rotations): + total += int(rotations[rotations_index]) + 1 + rotations_index += 1 + return total + + +def calculate_time_unit_fees_through_round( + fees_distribution: dict[str, Any], + num_of_validators: int, + final_round: int, + policy: StudioFeePolicy | None = None, +) -> int: + fees = normalize_fees_distribution(fees_distribution) + policy = policy or StudioFeePolicy() + validator_index = _validator_index(num_of_validators) + rotations = fees["rotations"] + if not isinstance(rotations, list) or len(rotations) == 0: + raise InvalidAppealRounds("InvalidAppealRounds") + + capped_final_round = min(max(0, int(final_round)), int(fees["appealRounds"]) * 2) + if validator_index + capped_final_round >= len(VALIDATORS_PER_ROUND): + raise InvalidNumOfValidators("InvalidNumOfValidators") + + leader_timeunits = int(fees["leaderTimeunitsAllocation"]) + validator_timeunits = int(fees["validatorTimeunitsAllocation"]) + total = _calculate_fee_for_round( + VALIDATORS_PER_ROUND[validator_index], + int(rotations[0]) + 1, + leader_timeunits, + validator_timeunits, + ) + rotations_index = 1 + for offset in range(1, capped_final_round + 1): + if offset % 2 == 0 and rotations_index < len(rotations): + rotations_this_round = int(rotations[rotations_index]) + 1 + rotations_index += 1 + else: + rotations_this_round = 1 + total += _calculate_fee_for_round( + VALIDATORS_PER_ROUND[validator_index + offset], + rotations_this_round, + leader_timeunits, + validator_timeunits, + ) + + max_price = int(fees["maxPriceGenPerTimeUnit"]) + if policy.gen_per_time_unit > 0: + if max_price > 0 and policy.gen_per_time_unit > max_price: + raise MaxPriceExceeded("MaxPriceExceeded") + total *= policy.gen_per_time_unit + return total + + +def calculate_round_fees( + fees_distribution: dict[str, Any], + num_of_validators: int, + round: int = 0, + policy: StudioFeePolicy | None = None, +) -> int: + fees = normalize_fees_distribution(fees_distribution) + policy = policy or StudioFeePolicy() + + if round == 0: + total = _calculate_initial_round_total(fees, num_of_validators) + else: + total = _calculate_appeal_round_total(fees, round) + + total = _apply_time_unit_price(total, int(fees["maxPriceGenPerTimeUnit"]), policy) + _enforce_gas_price_cap( + policy.storage_unit_price, int(fees["storageFeeMaxGasPrice"]) + ) + _enforce_gas_price_cap(policy.receipt_gas_price, int(fees["receiptFeeMaxGasPrice"])) + + if round == 0: + total += int(fees["executionBudgetPerRound"]) * get_leader_rounds(fees) + + return total + + +def required_fee_deposit( + fees_distribution: dict[str, Any], + num_of_validators: int, + policy: StudioFeePolicy | None = None, +) -> int: + fees = normalize_fees_distribution(fees_distribution) + return calculate_round_fees(fees, num_of_validators, 0, policy) + int( + fees["totalMessageFees"] + ) + + +def default_transaction_fees_for_policy( + policy: StudioFeePolicy | None = None, +) -> tuple[dict[str, int | list[int]], int]: + policy = policy or StudioFeePolicy() + execution_budget_per_round = ( + max( + DEFAULT_TRANSACTION_EXECUTION_BUDGET_PER_ROUND, + policy.message_fee_params_budget_floor(), + ) + if policy.storage_unit_price > 0 or policy.receipt_gas_price > 0 + else 0 + ) + distribution = _serializable_fees_distribution( + { + "leaderTimeunitsAllocation": ( + DEFAULT_LEADER_TIMEUNITS_ALLOCATION + if policy.gen_per_time_unit > 0 + else 0 + ), + "validatorTimeunitsAllocation": ( + DEFAULT_VALIDATOR_TIMEUNITS_ALLOCATION + if policy.gen_per_time_unit > 0 + else 0 + ), + "appealRounds": 0, + "executionBudgetPerRound": execution_budget_per_round, + "executionConsumed": 0, + "totalMessageFees": 0, + "rotations": [0], + "maxPriceGenPerTimeUnit": _with_cap_headroom(policy.gen_per_time_unit), + "storageFeeMaxGasPrice": _with_cap_headroom(policy.storage_unit_price), + "receiptFeeMaxGasPrice": _with_cap_headroom(policy.receipt_gas_price), + } + ) + fee_value = ( + required_fee_deposit(distribution, VALIDATORS_PER_ROUND[0], policy) + if policy.fee_accounting_enabled() + else 0 + ) + return distribution, fee_value + + +def studio_fee_config(policy: StudioFeePolicy | None = None) -> dict[str, Any]: + policy = policy or StudioFeePolicy.from_env() + distribution, fee_value = default_transaction_fees_for_policy(policy) + return { + "enabled": policy.fee_accounting_enabled(), + "policy": { + "genPerTimeUnit": str(policy.gen_per_time_unit), + "storageUnitPrice": str(policy.storage_unit_price), + "receiptGasPrice": str(policy.receipt_gas_price), + "intrinsicGas": str(policy.intrinsic_gas), + "bootloaderOverhead": str(policy.bootloader_overhead), + "gasPerChangedSlot": str(policy.gas_per_changed_slot), + "calldataGasPerByte": str(policy.calldata_gas_per_byte), + "fixedProposeReceiptGas": str(policy.fixed_propose_receipt_gas), + "fixedMessageRevealGas": str(policy.fixed_message_reveal_gas), + "receiptWrapperBytes": str(policy.receipt_wrapper_bytes), + "extraExecGas": str(policy.extra_exec_gas), + "messageFeeParamsBudgetFloor": str( + policy.message_fee_params_budget_floor() + ), + "maxAllocationTreeDepth": str(policy.max_allocation_tree_depth), + "maxMessagesPerTx": str(policy.max_messages_per_tx), + }, + "capabilities": { + "messageFees": { + "mode1": { + "accounting": True, + "genvmExecution": False, + }, + "mode2": { + "accounting": True, + "genvmExecution": True, + }, + "externalFinalization": { + "accounting": True, + "genvmExecution": True, + }, + } + }, + "defaultFees": { + "distribution": { + key: ( + [str(item) for item in value] + if isinstance(value, list) + else str(value) + ) + for key, value in distribution.items() + }, + "feeValue": str(fee_value), + }, + } + + +def validate_transaction_fee_deposit( + *, + fees_distribution: dict[str, Any], + message_allocations: list[dict[str, Any]] | None = None, + num_of_validators: int, + submitted_value: int, + user_value: int, + policy: StudioFeePolicy | None = None, +) -> int: + policy = policy or StudioFeePolicy() + fees = normalize_fees_distribution(fees_distribution) + execution_budget_per_round = int(fees["executionBudgetPerRound"]) + if ( + execution_budget_per_round > 0 + and execution_budget_per_round < policy.message_fee_params_budget_floor() + ): + raise BudgetTooLow("BudgetTooLow") + + if submitted_value < user_value: + raise InsufficientFees("InsufficientFees") + + required_fee_value = required_fee_deposit(fees, num_of_validators, policy) + paid_fee_value = submitted_value - user_value + if paid_fee_value < required_fee_value: + raise InsufficientFees("InsufficientFees") + + validate_message_allocations( + message_allocations or [], + total_message_fees=int(fees["totalMessageFees"]), + policy=policy, + ) + + return required_fee_value + + +def create_fee_accounting( + *, + fees_distribution: dict[str, Any], + message_allocations: list[dict[str, Any]] | None = None, + num_of_validators: int, + submitted_value: int, + user_value: int, + sender: str | None = None, + policy: StudioFeePolicy | None = None, +) -> dict[str, Any]: + policy = policy or StudioFeePolicy() + required = validate_transaction_fee_deposit( + fees_distribution=fees_distribution, + message_allocations=message_allocations or [], + num_of_validators=num_of_validators, + submitted_value=submitted_value, + user_value=user_value, + policy=policy, + ) + fee_value = max(0, int(submitted_value) - int(user_value)) + return _new_fee_accounting( + fees_distribution=fees_distribution, + message_allocations=message_allocations or [], + num_of_validators=num_of_validators, + fee_value=fee_value, + required_fee_value=required, + user_value=user_value, + sender=sender, + source="submission", + policy=policy, + ) + + +def create_child_fee_accounting( + *, + message: dict[str, Any], + parent_fees_distribution: dict[str, Any] | None, + message_allocations: list[dict[str, Any]] | None = None, + sender: str | None = None, + policy: StudioFeePolicy | None = None, +) -> tuple[dict[str, Any], dict[str, Any]]: + policy = policy or StudioFeePolicy() + declared_budget = int(message.get("declaredBudget", 0) or 0) + if declared_budget <= 0: + raise MessageDeclaredBudgetInsufficient("MessageDeclaredBudgetInsufficient") + + fee_params = decode_internal_message_fee_params(message.get("feeParams", b"")) + capless_child_fees = _fees_distribution_from_internal_params( + fee_params, + total_message_fees=0, + parent_fees_distribution=normalize_fees_distribution({}), + ) + try: + child_primary = validate_transaction_fee_deposit( + fees_distribution=capless_child_fees, + message_allocations=[], + num_of_validators=VALIDATORS_PER_ROUND[0], + submitted_value=declared_budget, + user_value=0, + policy=policy, + ) + except InsufficientFees as exc: + raise MessageDeclaredBudgetInsufficient( + "MessageDeclaredBudgetInsufficient" + ) from exc + if declared_budget < child_primary: + raise MessageDeclaredBudgetInsufficient("MessageDeclaredBudgetInsufficient") + + parent_fees = ( + normalize_fees_distribution(parent_fees_distribution) + if parent_fees_distribution + else normalize_fees_distribution({}) + ) + child_fees = _fees_distribution_from_internal_params( + fee_params, + total_message_fees=declared_budget - child_primary, + parent_fees_distribution=parent_fees, + ) + child_message_allocations = _child_allocations_from_message_subtree( + message, + message_allocations or [], + ) + validate_message_allocations( + child_message_allocations, + total_message_fees=int(child_fees["totalMessageFees"]), + policy=policy, + ) + user_value = int(message.get("value", 0) or 0) + accounting = _new_fee_accounting( + fees_distribution=child_fees, + message_allocations=child_message_allocations, + num_of_validators=VALIDATORS_PER_ROUND[0], + fee_value=declared_budget, + required_fee_value=declared_budget, + user_value=user_value, + sender=sender, + source="internal_message", + policy=policy, + ) + return child_fees, accounting + + +def genvm_fee_context( + accounting: dict[str, Any] | None, + policy: StudioFeePolicy | None = None, +) -> tuple[list[int] | None, dict[str, str] | None]: + if not accounting: + return None, None + + policy = _accounting_policy(accounting, policy) + fees = normalize_fees_distribution(accounting.get("fees_distribution") or {}) + bucket_total = int(fees["executionBudgetPerRound"]) + + gas_data = { + "storageUnitPrice": str(policy.storage_unit_price), + "receiptGasPerByte": str( + policy.receipt_gas_price * policy.calldata_gas_per_byte + ), + "gasPerChangedSlot": str( + policy.receipt_gas_price * policy.gas_per_changed_slot + ), + "intrinsicGas": str(policy.receipt_gas_price * policy.intrinsic_gas), + "bootloaderOverhead": str( + policy.receipt_gas_price * policy.bootloader_overhead + ), + "fixedProposeReceiptGas": str( + policy.receipt_gas_price * policy.fixed_propose_receipt_gas + ), + "fixedMessageRevealGas": str( + policy.receipt_gas_price * policy.fixed_message_reveal_gas + ), + "genPerTimeUnit": str(policy.gen_per_time_unit), + } + message_bucket_total = int(accounting.get("message_fee_budget", 0) or 0) + if bucket_total > 0 or message_bucket_total > 0: + data_bucket_total = ( + bucket_total if bucket_total > 0 else GENVM_UNMETERED_DATA_FEE_BUCKET + ) + bucket_totals = [data_bucket_total, data_bucket_total, message_bucket_total] + else: + bucket_totals = None + return bucket_totals, gas_data + + +def genvm_message_fee_allocation( + accounting: dict[str, Any] | None, + *, + address_factory: Callable[[str], Any] | None = None, +) -> list[dict[str, Any]]: + if not accounting: + return _genvm_unmetered_message_fee_allocation() + + if not accounting.get("message_allocations"): + if int(accounting.get("message_fee_budget", 0) or 0) > 0: + raise Mode1MessageFeesRequireGenVMPerEmissionSupport( + "Mode1MessageFeesRequireGenVMPerEmissionSupport: GenVM v0.3.x " + "message emissions do not carry per-emission feeParams/" + "declaredBudget without a message allocation tree" + ) + return [] + + nodes: list[dict[str, Any]] = [] + for raw_node in accounting.get("message_allocations") or []: + node = _serializable_message_allocation(raw_node) + if int(node["parentIndex"]) != NODE_ROOT_SENTINEL: + continue + nodes.append(_genvm_message_allocation_node(node, address_factory)) + if nodes: + nodes.append(_genvm_external_legacy_fallback_message_fee_allocation()) + return nodes + + +def apply_fee_top_up( + accounting: dict[str, Any], + *, + fees_distribution: dict[str, Any], + amount: int, + sender: str | None = None, + num_of_validators: int = VALIDATORS_PER_ROUND[0], + perform_fee_checks: bool = True, + policy: StudioFeePolicy | None = None, +) -> dict[str, Any]: + policy = _accounting_policy(accounting, policy) + amount = int(amount) + incoming = normalize_fees_distribution(fees_distribution) + incoming_message_fees = int(incoming["totalMessageFees"]) + if incoming_message_fees > amount: + raise InsufficientFees("InsufficientFees") + + primary_amount = amount - incoming_message_fees + if perform_fee_checks: + required_primary = calculate_round_fees(incoming, num_of_validators, 0, policy) + if required_primary > primary_amount: + raise InsufficientFees("InsufficientFeesForRound") + + updated = copy.deepcopy(accounting) + merged = merge_fees_distribution(updated.get("fees_distribution") or {}, incoming) + if ( + int(merged["executionBudgetPerRound"]) > 0 + and int(merged["executionBudgetPerRound"]) + < policy.message_fee_params_budget_floor() + ): + raise BudgetTooLow("BudgetTooLow") + + updated["fees_distribution"] = merged + updated["paid_fee_value"] = int(updated.get("paid_fee_value", 0)) + amount + updated["primary_fee_budget"] = ( + int(updated.get("primary_fee_budget", 0)) + primary_amount + ) + updated["message_fee_budget"] = ( + int(updated.get("message_fee_budget", 0)) + incoming_message_fees + ) + updated["execution_budget_total"] = int(merged["executionBudgetPerRound"]) * ( + get_leader_rounds(merged) + ) + updated.setdefault("top_ups", []).append( + { + "sender": sender, + "amount": amount, + "primaryAmount": primary_amount, + "messageFees": incoming_message_fees, + "feesDistribution": _serializable_fees_distribution(incoming), + } + ) + _refresh_message_fee_accounting_report_if_present(updated, policy) + return updated + + +def record_appeal_bond( + accounting: dict[str, Any], + *, + amount: int, + appealer: str | None, + current_round: int = 0, + status: str | None = None, + round: int | None = None, + fees_distribution: dict[str, Any] | None = None, + top_up_and_submit: bool = False, + policy: StudioFeePolicy | None = None, +) -> dict[str, Any]: + updated = copy.deepcopy(accounting) + policy = _accounting_policy(updated, policy) + amount = int(amount) + + min_required = 0 + if status is not None: + min_required = calculate_min_appeal_bond( + updated.get("fees_distribution") or {}, + current_round=current_round, + status=status, + policy=policy, + ) + if amount < min_required: + raise InvalidAppealBond("InvalidAppealBond") + + if top_up_and_submit: + updated["primary_fee_budget"] = ( + int(updated.get("primary_fee_budget", 0)) + amount + ) + updated["paid_fee_value"] = int(updated.get("paid_fee_value", 0)) + amount + merged = normalize_fees_distribution(updated.get("fees_distribution") or {}) + merged["appealRounds"] = int(merged["appealRounds"]) + 1 + updated["fees_distribution"] = merged + updated["execution_budget_total"] = int( + merged["executionBudgetPerRound"] + ) * get_leader_rounds(merged) + + updated["appeal_bonds_total"] = int(updated.get("appeal_bonds_total", 0)) + amount + updated.setdefault("appeal_bonds", []).append( + { + "appealer": appealer, + "amount": amount, + "round": current_round if round is None else round, + "status": status, + "minimumRequired": min_required, + "topUpAndSubmit": bool(top_up_and_submit), + "feesDistributionIgnored": fees_distribution is not None + and top_up_and_submit, + } + ) + _refresh_message_fee_accounting_report_if_present(updated, policy) + return updated + + +def calculate_min_appeal_bond( + fees_distribution: dict[str, Any], + *, + current_round: int, + status: str, + policy: StudioFeePolicy | None = None, +) -> int: + policy = policy or StudioFeePolicy() + fees = normalize_fees_distribution(fees_distribution) + current_round = max(0, int(current_round)) + status_value = str(status).upper() + if status_value in {"LEADER_TIMEOUT", "UNDETERMINED"}: + target_round = current_round + 2 + if target_round >= len(VALIDATORS_PER_ROUND): + raise InvalidNumOfValidators("InvalidNumOfValidators") + rotations = ( + int(fees["rotations"][target_round - 1]) + if target_round - 1 < len(fees["rotations"]) + else 0 + ) + total = _calculate_fee_for_round( + VALIDATORS_PER_ROUND[target_round], + rotations, + int(fees["leaderTimeunitsAllocation"]), + int(fees["validatorTimeunitsAllocation"]), + ) + return ( + total * policy.gen_per_time_unit if policy.gen_per_time_unit > 0 else total + ) + + if status_value in {"VALIDATORS_TIMEOUT", "ACCEPTED"}: + target_round = current_round + 1 + if target_round >= len(VALIDATORS_PER_ROUND): + raise InvalidNumOfValidators("InvalidNumOfValidators") + total = VALIDATORS_PER_ROUND[target_round] * int( + fees["validatorTimeunitsAllocation"] + ) + return ( + total * policy.gen_per_time_unit if policy.gen_per_time_unit > 0 else total + ) + + return 0 + + +def fill_message_fee_payload_from_allocation( + accounting: dict[str, Any], + message: dict[str, Any], +) -> dict[str, Any]: + if int(message.get("messageType", MESSAGE_TYPE_INTERNAL)) != MESSAGE_TYPE_INTERNAL: + return copy.deepcopy(message) + + allocations = accounting.get("message_allocations") or [] + if not allocations: + return copy.deepcopy(message) + + resolved = _resolve_allocation(allocations, message) + if resolved is None: + raise MessageNoMatchingAllocation("MessageNoMatchingAllocation") + + index, allocation = resolved + if bool(allocation["onAcceptance"]) != bool(message.get("onAcceptance", False)): + raise MessageEmissionPhaseMismatch("MessageEmissionPhaseMismatch") + + updated = copy.deepcopy(message) + if int(updated.get("declaredBudget", 0) or 0) == 0: + updated["declaredBudget"] = int(allocation["budget"]) + if not _message_has_fee_params(updated): + updated["feeParams"] = allocation["feeParams"] + updated["callKey"] = _normalize_call_key( + updated.get("callKey", allocation["callKey"]) + ) + expected_subtree = _allocation_subtree(allocations, index) + if not updated.get("allocationSubtree"): + updated["allocationSubtree"] = expected_subtree + elif ( + _canonical_allocation_subtree(updated["allocationSubtree"]) != expected_subtree + ): + raise AllocationSubtreeMismatch("AllocationSubtreeMismatch") + updated["messageFeeMode"] = "mode2" + return updated + + +def consume_message_fees( + accounting: dict[str, Any], + messages: list[dict[str, Any]], + *, + reported_total: int | None = None, + policy: StudioFeePolicy | None = None, + reimburse_external: bool = True, +) -> dict[str, Any]: + policy = _accounting_policy(accounting, policy) + if policy.max_messages_per_tx > 0 and len(messages) > policy.max_messages_per_tx: + raise TooManyMessages("TooManyMessages") + + updated = copy.deepcopy(accounting) + recalculated_total = 0 + external_reimbursement_total = 0 + + for message in messages: + message_type = _message_type_value(message) + if message_type == MESSAGE_TYPE_EXTERNAL: + external_reimbursement_total += _consume_external_message_fee( + updated, + message, + policy, + reimburse_external, + ) + continue + + if message_type == MESSAGE_TYPE_INTERNAL: + recalculated_total += _consume_internal_message_fee( + updated, + message, + policy, + ) + + if reported_total is not None and int(reported_total) < recalculated_total: + raise MessageFeesReportMismatch("MessageFeesReportMismatch") + + attempted = ( + int(updated.get("message_fee_consumed", 0)) + + recalculated_total + + external_reimbursement_total + ) + message_budget = int(updated.get("message_fee_budget", 0)) + if attempted > message_budget: + raise MessageBudgetExceeded("MessageBudgetExceeded") + + updated["message_fee_consumed"] = attempted + updated.setdefault("message_consumption_events", []).append( + { + "consumed": recalculated_total + external_reimbursement_total, + "internalConsumed": recalculated_total, + "externalReimbursed": external_reimbursement_total, + "remaining": message_budget - attempted, + } + ) + _refresh_message_fee_accounting_report_if_present(updated, policy) + return updated + + +def _message_type_value(message: dict[str, Any]) -> int: + return int(message.get("messageType", MESSAGE_TYPE_INTERNAL)) + + +def _consume_external_message_fee( + accounting: dict[str, Any], + message: dict[str, Any], + policy: StudioFeePolicy, + reimburse_external: bool, +) -> int: + if int(message.get("declaredBudget", 0) or 0) != 0: + raise MessageDeclaredBudgetInsufficient("MessageDeclaredBudgetInsufficient") + return _reserve_external_execution( + accounting, + message, + policy, + reimburse=reimburse_external, + ) + + +def _consume_internal_message_fee( + accounting: dict[str, Any], + message: dict[str, Any], + policy: StudioFeePolicy, +) -> int: + declared_budget = int(message.get("declaredBudget", 0) or 0) + fee_params = decode_internal_message_fee_params(message.get("feeParams", b"")) + _validate_internal_execution_budget_floor(fee_params, policy) + + min_required = min_message_primary_fees(fee_params, policy) + if declared_budget < min_required: + raise MessageDeclaredBudgetInsufficient("MessageDeclaredBudgetInsufficient") + + _consume_against_allocation(accounting, message, declared_budget) + return declared_budget + + +def _validate_internal_execution_budget_floor( + fee_params: dict[str, Any], + policy: StudioFeePolicy, +) -> None: + execution_budget_per_round = int(fee_params["executionBudgetPerRound"]) + if ( + execution_budget_per_round > 0 + and execution_budget_per_round < policy.message_fee_params_budget_floor() + ): + raise BudgetTooLow("BudgetTooLow") + + +def record_reveal_message_fees( + accounting: dict[str, Any], + messages: list[dict[str, Any]], + *, + reported_total: int | None = None, + policy: StudioFeePolicy | None = None, +) -> dict[str, Any]: + updated = consume_message_fees( + accounting, + messages, + reported_total=reported_total, + policy=policy, + reimburse_external=False, + ) + updated["message_fees_recorded_at_reveal"] = True + return updated + + +def record_external_message_execution_fees( + accounting: dict[str, Any], + messages: list[dict[str, Any]], + *, + policy: StudioFeePolicy | None = None, +) -> dict[str, Any]: + updated = copy.deepcopy(accounting) + policy = _accounting_policy(updated, policy) + reimbursement_total = 0 + remainder_total = 0 + updated_any = False + + for message in messages: + if ( + int(message.get("messageType", MESSAGE_TYPE_INTERNAL)) + != MESSAGE_TYPE_EXTERNAL + ): + continue + + event_index = _find_unexecuted_external_message_event(updated, message) + if event_index is None: + continue + + event = updated.setdefault("external_message_events", [])[event_index] + reservation = int(event.get("reservation", 0) or 0) + gas_limit = int(event.get("gasLimit", 0) or 0) + locked_price = int(event.get("lockedGasPrice", 0) or 0) + gas_used = int(message.get("gasUsed", 0) or 0) + effective_gas = min(gas_limit, gas_used) + reimbursement = min(reservation, effective_gas * locked_price) + remainder = reservation - reimbursement + + attempted = ( + int(updated.get("message_fee_consumed", 0)) + + reimbursement_total + + reimbursement + ) + message_budget = int(updated.get("message_fee_budget", 0)) + if attempted > message_budget: + raise MessageBudgetExceeded("MessageBudgetExceeded") + + event["gasUsed"] = gas_used + event["reimbursement"] = reimbursement + event["remainder"] = remainder + event["executionRecorded"] = True + reimbursement_total += reimbursement + remainder_total += remainder + updated_any = True + + if updated_any: + updated["message_fee_consumed"] = ( + int(updated.get("message_fee_consumed", 0)) + reimbursement_total + ) + updated["external_message_fee_reimbursed"] = ( + int(updated.get("external_message_fee_reimbursed", 0)) + reimbursement_total + ) + updated["external_message_fee_remainder"] = ( + int(updated.get("external_message_fee_remainder", 0)) + remainder_total + ) + updated.setdefault("message_consumption_events", []).append( + { + "consumed": reimbursement_total, + "internalConsumed": 0, + "externalReimbursed": reimbursement_total, + "remaining": max( + 0, + int(updated.get("message_fee_budget", 0)) + - int(updated.get("message_fee_consumed", 0)), + ), + } + ) + _refresh_message_fee_accounting_report_if_present(updated, policy) + + return updated + + +def refund_failed_external_message_fee( + accounting: dict[str, Any], + message: dict[str, Any], +) -> dict[str, Any]: + if int(message.get("messageType", MESSAGE_TYPE_INTERNAL)) != MESSAGE_TYPE_EXTERNAL: + return copy.deepcopy(accounting) + + updated = copy.deepcopy(accounting) + event_index = _find_unrefunded_external_message_event(updated, message) + if event_index is None: + return updated + + event = updated.setdefault("external_message_events", [])[event_index] + reservation = int(event.get("reservation", 0) or 0) + reimbursement = int(event.get("reimbursement", 0) or 0) + remainder = int(event.get("remainder", 0) or 0) + + # Execution-level failures still spent gas. Consensus reimburses the + # executor and leaves the external execution reservation consumed; only the + # external message value leg is refunded outside this fee-accounting helper. + event["failureRefunded"] = True + updated.setdefault("external_message_refund_events", []).append( + { + "recipient": event.get("recipient"), + "callKey": event.get("callKey"), + "allocationIndex": int(event.get("allocationIndex", 0) or 0), + "reservation": reservation, + "reimbursement": reimbursement, + "remainder": remainder, + "feeRefunded": 0, + } + ) + _refresh_message_fee_accounting_report_if_present(updated) + return updated + + +def unwind_reveal_message_fees( + accounting: dict[str, Any], + messages: list[dict[str, Any]], + *, + acceptance_dispatched: bool = False, +) -> dict[str, Any]: + updated = copy.deepcopy(accounting) + internal_refund = 0 + external_unreserved = 0 + external_reimbursement_rolled_back = 0 + external_remainder_rolled_back = 0 + + for message in messages: + if ( + int(message.get("messageType", MESSAGE_TYPE_INTERNAL)) + == MESSAGE_TYPE_EXTERNAL + ): + ( + reservation, + reimbursement, + remainder, + ) = _unreserve_external_message_fee(updated, message) + external_unreserved += reservation + external_reimbursement_rolled_back += reimbursement + external_remainder_rolled_back += remainder + continue + + if ( + int(message.get("messageType", MESSAGE_TYPE_INTERNAL)) + != MESSAGE_TYPE_INTERNAL + ): + continue + if acceptance_dispatched and bool(message.get("onAcceptance", False)): + continue + + declared_budget = int(message.get("declaredBudget", 0) or 0) + if declared_budget <= 0: + continue + internal_refund += declared_budget + _decrement_allocation_consumed(updated, message, declared_budget) + + if internal_refund > 0: + updated["message_fee_consumed"] = max( + 0, + int(updated.get("message_fee_consumed", 0)) - internal_refund, + ) + + if ( + internal_refund > 0 + or external_unreserved > 0 + or external_reimbursement_rolled_back > 0 + ): + updated.setdefault("message_fee_unwind_events", []).append( + { + "acceptanceDispatched": bool(acceptance_dispatched), + "internalRefunded": internal_refund, + "externalUnreserved": external_unreserved, + "externalReimbursementRolledBack": (external_reimbursement_rolled_back), + "externalRemainderRolledBack": external_remainder_rolled_back, + "remaining": max( + 0, + int(updated.get("message_fee_budget", 0)) + - int(updated.get("message_fee_consumed", 0)) + - int(updated.get("message_fee_refunded", 0)), + ), + } + ) + + # A re-reveal replaces or discards the previous message set. Keep the + # aggregate unwind event, but reopen receipt-based message consumption. + updated.pop("message_fees_recorded_from_receipt", None) + updated["message_consumption_events"] = [] + _refresh_message_fee_accounting_report_if_present(updated) + return updated + + +def record_execution_fee_consumption( + accounting: dict[str, Any], + receipt: Any | None, + policy: StudioFeePolicy | None = None, +) -> dict[str, Any]: + updated = copy.deepcopy(accounting) + policy = _accounting_policy(updated, policy) + message_payloads = _receipt_message_fee_payloads(updated, receipt) + reported_message_fees_total = _receipt_reported_message_fees_total(receipt) + if ( + message_payloads + and _receipt_messages_require_fee_validation(updated, message_payloads) + and not updated.get("message_fees_recorded_from_receipt") + and not updated.get("message_consumption_events") + ): + updated = consume_message_fees( + updated, + message_payloads, + reported_total=reported_message_fees_total, + policy=policy, + ) + updated["message_fees_recorded_from_receipt"] = True + if reported_message_fees_total is not None: + updated["reported_message_fees_total"] = reported_message_fees_total + + fee_report = _receipt_fee_report(receipt, policy, message_payloads) + if fee_report is not None: + updated["execution_fee_report"] = fee_report + _attach_message_fee_accounting_report(updated) + _attach_recommended_fee_preset(updated, policy) + consumed = _receipt_data_fees_consumed(receipt) + if consumed is None: + return updated + updated["genvm_fee_consumed_buckets"] = consumed + bucket_report = _genvm_fee_bucket_report( + consumed, + execution_budget_per_round=_execution_budget_per_round(updated), + ) + execution_consumed = _chargeable_execution_fee_buckets( + consumed, + fee_report, + policy, + receipt, + ) + execution_bucket_report = _genvm_fee_bucket_report( + execution_consumed, + execution_budget_per_round=_execution_budget_per_round(updated), + ) + updated["execution_fee_consumed"] = sum(execution_consumed) + updated["execution_fee_consumed_buckets"] = execution_consumed + updated["genvm_fee_bucket_report"] = bucket_report + execution_metering_report = _execution_metering_report( + chargeable_bucket_report=execution_bucket_report, + genvm_bucket_report=bucket_report, + ) + updated["execution_fee_report"] = { + **(updated.get("execution_fee_report") or {}), + "genvmBuckets": bucket_report, + "chargeableExecution": execution_bucket_report, + "executionMetering": execution_metering_report, + } + budget_exhaustion_reason = _receipt_budget_exhaustion_reason( + receipt, + execution_bucket_report, + ) + if budget_exhaustion_reason is not None: + updated["execution_fee_report"][ + "budgetExhaustionReason" + ] = budget_exhaustion_reason + if len(consumed) > 2: + updated["genvm_message_fee_consumed"] = int(consumed[2]) + _attach_message_fee_accounting_report(updated) + _attach_recommended_fee_preset(updated, policy) + return updated + + +def settle_fee_accounting( + accounting: dict[str, Any], + *, + receipt: Any | None = None, + reason: str = "finalized", + actual_final_round: int | None = None, + num_of_validators: int | None = None, + policy: StudioFeePolicy | None = None, +) -> tuple[dict[str, Any], int]: + policy = _accounting_policy(accounting, policy) + updated = record_execution_fee_consumption(accounting, receipt, policy) + if updated.get("status") in {"settled", "canceled"}: + return updated, 0 + + primary_budget = int(updated.get("primary_fee_budget", 0)) + execution_budget = int(updated.get("execution_budget_total", 0)) + primary_required = int(updated.get("primary_fee_required", 0)) + fees_distribution = updated.get("fees_distribution") or {} + if actual_final_round is not None: + validators = int( + num_of_validators or updated.get("num_of_initial_validators") or 0 + ) + time_unit_budget = calculate_time_unit_fees_through_round( + fees_distribution, + validators, + actual_final_round, + policy, + ) + execution_budget = int( + normalize_fees_distribution(fees_distribution)["executionBudgetPerRound"] + ) * get_leader_rounds_through_round(fees_distribution, actual_final_round) + updated["actual_final_round"] = int(actual_final_round) + else: + time_unit_budget = max(0, primary_required - execution_budget) + execution_spent = min( + int(updated.get("execution_fee_consumed", 0)), execution_budget + ) + primary_spent = min(primary_budget, time_unit_budget + execution_spent) + primary_refund = max( + 0, primary_budget - primary_spent - int(updated.get("primary_fee_refunded", 0)) + ) + + message_refund = max( + 0, + int(updated.get("message_fee_budget", 0)) + - int(updated.get("message_fee_consumed", 0)) + - int(updated.get("message_fee_refunded", 0)), + ) + refund = primary_refund + message_refund + + updated["status"] = "settled" + updated["settlement_reason"] = reason + updated["primary_fee_spent"] = primary_spent + updated["primary_fee_refunded"] = ( + int(updated.get("primary_fee_refunded", 0)) + primary_refund + ) + updated["message_fee_refunded"] = ( + int(updated.get("message_fee_refunded", 0)) + message_refund + ) + updated["total_refunded"] = int(updated.get("total_refunded", 0)) + refund + updated.setdefault("refunds", []).append( + { + "reason": reason, + "primary": primary_refund, + "message": message_refund, + "amount": refund, + } + ) + _refresh_message_fee_accounting_report_if_present(updated, policy) + return updated, refund + + +def cancel_fee_accounting( + accounting: dict[str, Any], + *, + reason: str = "canceled", +) -> tuple[dict[str, Any], int]: + updated = copy.deepcopy(accounting) + if updated.get("status") in {"settled", "canceled"}: + return updated, 0 + + primary_refund = max( + 0, + int(updated.get("primary_fee_budget", 0)) + - int(updated.get("primary_fee_spent", 0)) + - int(updated.get("primary_fee_refunded", 0)), + ) + message_refund = max( + 0, + int(updated.get("message_fee_budget", 0)) + - int(updated.get("message_fee_consumed", 0)) + - int(updated.get("message_fee_refunded", 0)), + ) + refund = primary_refund + message_refund + updated["status"] = "canceled" + updated["settlement_reason"] = reason + updated["primary_fee_refunded"] = ( + int(updated.get("primary_fee_refunded", 0)) + primary_refund + ) + updated["message_fee_refunded"] = ( + int(updated.get("message_fee_refunded", 0)) + message_refund + ) + updated["total_refunded"] = int(updated.get("total_refunded", 0)) + refund + updated.setdefault("refunds", []).append( + { + "reason": reason, + "primary": primary_refund, + "message": message_refund, + "amount": refund, + } + ) + _refresh_message_fee_accounting_report_if_present(updated) + return updated, refund + + +def merge_fees_distribution( + current: dict[str, Any], incoming: dict[str, Any] +) -> dict[str, Any]: + current_fees = normalize_fees_distribution(current) + incoming_fees = normalize_fees_distribution(incoming) + is_initial = len(current_fees["rotations"]) == 0 + merged = dict(current_fees) + if is_initial: + merged["leaderTimeunitsAllocation"] = incoming_fees["leaderTimeunitsAllocation"] + merged["validatorTimeunitsAllocation"] = incoming_fees[ + "validatorTimeunitsAllocation" + ] + merged["appealRounds"] = incoming_fees["appealRounds"] + + merged["executionBudgetPerRound"] = int(merged["executionBudgetPerRound"]) + int( + incoming_fees["executionBudgetPerRound"] + ) + merged["totalMessageFees"] = int(merged["totalMessageFees"]) + int( + incoming_fees["totalMessageFees"] + ) + merged["rotations"] = list(merged["rotations"]) + list(incoming_fees["rotations"]) + for cap in ( + "maxPriceGenPerTimeUnit", + "storageFeeMaxGasPrice", + "receiptFeeMaxGasPrice", + ): + incoming_cap = int(incoming_fees[cap]) + if incoming_cap > 0 and ( + is_initial or (int(merged[cap]) > 0 and incoming_cap > int(merged[cap])) + ): + merged[cap] = incoming_cap + return _serializable_fees_distribution(merged) + + +def validate_message_allocations( + message_allocations: list[dict[str, Any]], + *, + total_message_fees: int, + policy: StudioFeePolicy | None = None, +) -> None: + if not message_allocations: + return + + policy = policy or StudioFeePolicy() + root_sum = 0 + root_keys: set[tuple[int, str, str]] = set() + external_keys: set[tuple[str, str]] = set() + min_required_by_index: dict[int, int] = {} + + for index, raw_node in enumerate(message_allocations): + root_sum += _validate_message_allocation_node( + index, + raw_node, + message_allocations, + root_keys, + external_keys, + min_required_by_index, + policy, + ) + + if root_sum != total_message_fees: + raise MessageAllocationsNotEqualBudget("MessageAllocationsNotEqualBudget") + + _validate_child_budget_consistency(message_allocations, min_required_by_index) + _validate_allocation_tree_depth(message_allocations, policy) + _validate_sibling_duplicates(message_allocations) + + +def _validate_message_allocation_node( + index: int, + raw_node: dict[str, Any], + message_allocations: list[dict[str, Any]], + root_keys: set[tuple[int, str, str]], + external_keys: set[tuple[str, str]], + min_required_by_index: dict[int, int], + policy: StudioFeePolicy, +) -> int: + node = _normalize_message_allocation(raw_node) + _validate_allocation_parent(index, node, message_allocations) + + if int(node["messageType"]) == MESSAGE_TYPE_EXTERNAL: + _validate_external_allocation(node, external_keys) + return int(node["budget"]) + + if int(node["messageType"]) != MESSAGE_TYPE_INTERNAL: + raise AllocationTreeMalformed("AllocationTreeMalformed") + + min_required = _validate_internal_allocation_budget(node, policy) + min_required_by_index[index] = min_required + return _root_allocation_budget(node, root_keys) + + +def _validate_allocation_parent( + index: int, + node: dict[str, Any], + message_allocations: list[dict[str, Any]], +) -> None: + parent_index = int(node["parentIndex"]) + if parent_index == NODE_ROOT_SENTINEL: + return + if parent_index >= index: + raise AllocationTreeMalformed("AllocationTreeMalformed") + + parent_node = _normalize_message_allocation(message_allocations[parent_index]) + if int(parent_node["messageType"]) == MESSAGE_TYPE_EXTERNAL: + raise AllocationTreeMalformed("AllocationTreeMalformed") + + +def _validate_internal_allocation_budget( + node: dict[str, Any], + policy: StudioFeePolicy, +) -> int: + internal_fee_params = decode_internal_message_fee_params(node["feeParams"]) + min_required = _internal_allocation_min_required(node, internal_fee_params, policy) + if int(node["budget"]) < min_required: + raise AllocationLifecycleBudgetInsufficient( + "AllocationLifecycleBudgetInsufficient" + ) + + _validate_internal_execution_budget_floor(internal_fee_params, policy) + return min_required + + +def _internal_allocation_min_required( + node: dict[str, Any], + internal_fee_params: dict[str, Any], + policy: StudioFeePolicy, +) -> int: + min_primary = min_message_primary_fees(internal_fee_params, policy) + lifecycle_multiplier = ( + int(internal_fee_params["appealRounds"]) + 1 + if bool(node["onAcceptance"]) + else 1 + ) + return min_primary * lifecycle_multiplier + + +def _root_allocation_budget( + node: dict[str, Any], + root_keys: set[tuple[int, str, str]], +) -> int: + if int(node["parentIndex"]) != NODE_ROOT_SENTINEL: + return 0 + + key = _allocation_key(node) + if key in root_keys: + raise AllocationDuplicateKey("AllocationDuplicateKey") + root_keys.add(key) + return int(node["budget"]) + + +def _validate_child_budget_consistency( + message_allocations: list[dict[str, Any]], + min_required_by_index: dict[int, int], +) -> None: + for index, raw_node in enumerate(message_allocations): + node = _normalize_message_allocation(raw_node) + if int(node["messageType"]) == MESSAGE_TYPE_EXTERNAL: + continue + child_sum = _child_allocation_budget_sum(message_allocations, index) + if int(node["budget"]) < min_required_by_index[index] + child_sum: + raise AllocationTreeBudgetInconsistent("AllocationTreeBudgetInconsistent") + + +def _child_allocation_budget_sum( + message_allocations: list[dict[str, Any]], + parent_index: int, +) -> int: + child_sum = 0 + for raw_child in message_allocations[parent_index + 1 :]: + child = _normalize_message_allocation(raw_child) + if int(child["parentIndex"]) == parent_index: + child_sum += int(child["budget"]) + return child_sum + + +def decode_internal_message_fee_params(fee_params: bytes | str) -> dict[str, Any]: + raw_fee_params = _fee_params_bytes(fee_params) + try: + decoded = decode([INTERNAL_MESSAGE_FEE_PARAMS_ABI_TYPE], raw_fee_params)[0] + except Exception as exc: + raise InvalidFeeParams("InvalidFeeParams") from exc + return { + "leaderTimeunitsAllocation": int(decoded[0]), + "validatorTimeunitsAllocation": int(decoded[1]), + "appealRounds": int(decoded[2]), + "executionBudgetPerRound": int(decoded[3]), + "rotations": [int(rotation) for rotation in decoded[4]], + } + + +def decode_external_message_fee_params(fee_params: bytes | str) -> dict[str, int]: + raw_fee_params = _fee_params_bytes(fee_params) + try: + decoded = decode([EXTERNAL_MESSAGE_FEE_PARAMS_ABI_TYPE], raw_fee_params)[0] + except Exception as exc: + raise InvalidFeeParams("InvalidFeeParams") from exc + return { + "gasLimit": int(decoded[0]), + "maxGasPrice": int(decoded[1]), + } + + +def min_message_primary_fees( + internal_fee_params: dict[str, Any], + policy: StudioFeePolicy | None = None, +) -> int: + return calculate_round_fees( + { + "leaderTimeunitsAllocation": int( + internal_fee_params["leaderTimeunitsAllocation"] + ), + "validatorTimeunitsAllocation": int( + internal_fee_params["validatorTimeunitsAllocation"] + ), + "appealRounds": int(internal_fee_params["appealRounds"]), + "executionBudgetPerRound": int( + internal_fee_params["executionBudgetPerRound"] + ), + "executionConsumed": 0, + "totalMessageFees": 0, + "rotations": internal_fee_params["rotations"], + "maxPriceGenPerTimeUnit": 0, + "storageFeeMaxGasPrice": 0, + "receiptFeeMaxGasPrice": 0, + }, + VALIDATORS_PER_ROUND[0], + 0, + policy, + ) + + +def _calculate_initial_round_total( + fees: dict[str, int | list[int]], + num_of_validators: int, +) -> int: + validator_index = _validator_index(num_of_validators) + if int(fees["appealRounds"]) != len(fees["rotations"]) - 1: + raise InvalidAppealRounds("InvalidAppealRounds") + return _calculate_fees(fees, validator_index) + + +def _calculate_appeal_round_total( + fees: dict[str, int | list[int]], + round: int, +) -> int: + if round >= len(VALIDATORS_PER_ROUND): + raise InvalidNumOfValidators("InvalidNumOfValidators") + + rotations = ( + int(fees["rotations"][round - 1]) if round - 1 < len(fees["rotations"]) else 0 + ) + return _calculate_fee_for_round( + VALIDATORS_PER_ROUND[round], + rotations, + int(fees["leaderTimeunitsAllocation"]), + int(fees["validatorTimeunitsAllocation"]), + ) + + +def _apply_time_unit_price( + total: int, + max_price: int, + policy: StudioFeePolicy, +) -> int: + if policy.gen_per_time_unit <= 0: + return total + if max_price > 0 and policy.gen_per_time_unit > max_price: + raise MaxPriceExceeded("MaxPriceExceeded") + return total * policy.gen_per_time_unit + + +def _enforce_gas_price_cap(actual_price: int, max_price: int) -> None: + if max_price > 0 and actual_price > max_price: + raise MaxPriceExceeded("MaxPriceExceeded") + + +def _validator_index(num_of_validators: int) -> int: + if num_of_validators > VALIDATORS_PER_ROUND[-1]: + raise InvalidNumOfValidators("InvalidNumOfValidators") + for index, validators in enumerate(VALIDATORS_PER_ROUND): + if validators >= num_of_validators: + if validators != num_of_validators: + raise InvalidNumOfValidators("InvalidNumOfValidators") + return index + raise InvalidNumOfValidators("InvalidNumOfValidators") + + +def _calculate_fees( + fees_distribution: dict[str, int | list[int]], validator_index: int +) -> int: + rotations = fees_distribution["rotations"] + if not isinstance(rotations, list) or len(rotations) == 0: + raise InvalidAppealRounds("InvalidAppealRounds") + + leader_timeunits = int(fees_distribution["leaderTimeunitsAllocation"]) + validator_timeunits = int(fees_distribution["validatorTimeunitsAllocation"]) + calculated_fees = _calculate_fee_for_round( + VALIDATORS_PER_ROUND[validator_index], + int(rotations[0]) + 1, + leader_timeunits, + validator_timeunits, + ) + + rotations_index = 1 + rotations_this_round = 1 + appeal_rounds = int(fees_distribution["appealRounds"]) + if validator_index + (appeal_rounds * 2) >= len(VALIDATORS_PER_ROUND): + raise InvalidNumOfValidators("InvalidNumOfValidators") + for offset in range(1, (appeal_rounds * 2) + 1): + round_validators = VALIDATORS_PER_ROUND[validator_index + offset] + if offset % 2 == 0 and rotations_index < len(rotations): + rotations_this_round = int(rotations[rotations_index]) + 1 + rotations_index += 1 + elif offset % 2 == 1: + rotations_this_round = 1 + + calculated_fees += _calculate_fee_for_round( + round_validators, + rotations_this_round, + leader_timeunits, + validator_timeunits, + ) + + return calculated_fees + + +def _calculate_fee_for_round( + num_of_validators: int, + rotations: int, + leader_timeunits_allocation: int, + validator_timeunits_allocation: int, +) -> int: + return rotations * ( + leader_timeunits_allocation + + (num_of_validators * validator_timeunits_allocation) + ) + + +def _normalize_message_allocation(node: dict[str, Any]) -> dict[str, Any]: + return { + "messageType": int(node.get("messageType", 0)), + "onAcceptance": bool(node.get("onAcceptance", False)), + "parentIndex": int(node.get("parentIndex", 0)), + "recipient": str(node.get("recipient", "")).lower(), + "callKey": _normalize_call_key(node.get("callKey", CALL_KEY_WILDCARD)), + "budget": int(node.get("budget", 0)), + "feeParams": node.get("feeParams", b""), + } + + +def _validate_external_allocation( + node: dict[str, Any], + external_keys: set[tuple[str, str]], +) -> None: + if int(node["parentIndex"]) != NODE_ROOT_SENTINEL: + raise AllocationTreeMalformed("AllocationTreeMalformed") + + external_fee_params = decode_external_message_fee_params(node["feeParams"]) + gas_limit = int(external_fee_params["gasLimit"]) + max_gas_price = int(external_fee_params["maxGasPrice"]) + if gas_limit == 0 or max_gas_price == 0: + raise ExternalAllocationInvalid("ExternalAllocationInvalid") + + per_call = gas_limit * max_gas_price + budget = int(node["budget"]) + if budget == 0 or budget % per_call != 0: + raise ExternalAllocationInvalid("ExternalAllocationInvalid") + + external_key = (str(node["recipient"]).lower(), str(node["callKey"]).lower()) + if external_key in external_keys: + raise ExternalAllocationInvalid("ExternalAllocationInvalid") + external_keys.add(external_key) + + +def _validate_allocation_tree_depth( + message_allocations: list[dict[str, Any]], + policy: StudioFeePolicy, +) -> None: + depth: list[int] = [] + cap = policy.max_allocation_tree_depth or 5 + for index, raw_node in enumerate(message_allocations): + node = _normalize_message_allocation(raw_node) + if int(node["messageType"]) == MESSAGE_TYPE_EXTERNAL: + depth.append(1) + continue + parent_index = int(node["parentIndex"]) + current_depth = ( + 1 if parent_index == NODE_ROOT_SENTINEL else depth[parent_index] + 1 + ) + if current_depth > cap: + raise AllocationTreeTooDeep("AllocationTreeTooDeep") + depth.append(current_depth) + + +def _validate_sibling_duplicates(message_allocations: list[dict[str, Any]]) -> None: + sibling_keys: set[tuple[int, int, str, str]] = set() + for raw_node in message_allocations: + node = _normalize_message_allocation(raw_node) + parent_index = int(node["parentIndex"]) + if parent_index == NODE_ROOT_SENTINEL: + continue + key = (parent_index, *_allocation_key(node)) + if key in sibling_keys: + raise AllocationDuplicateKey("AllocationDuplicateKey") + sibling_keys.add(key) + + +def _allocation_key(node: dict[str, Any]) -> tuple[int, str, str]: + return ( + int(node["messageType"]), + str(node["recipient"]).lower(), + str(node["callKey"]).lower(), + ) + + +def _fee_params_bytes(fee_params: bytes | str) -> bytes: + if isinstance(fee_params, str): + return bytes.fromhex(fee_params.removeprefix("0x")) + return bytes(fee_params) + + +def _new_fee_accounting( + *, + fees_distribution: dict[str, Any], + message_allocations: list[dict[str, Any]], + num_of_validators: int, + fee_value: int, + required_fee_value: int, + user_value: int, + sender: str | None, + source: str, + policy: StudioFeePolicy, +) -> dict[str, Any]: + fees = _serializable_fees_distribution(fees_distribution) + total_message_fees = int(fees["totalMessageFees"]) + execution_budget_total = int(fees["executionBudgetPerRound"]) * get_leader_rounds( + fees + ) + primary_required = max(0, int(required_fee_value) - total_message_fees) + return { + "version": 1, + "source": source, + "status": "active", + "policy_snapshot": policy.to_snapshot(), + "sender": sender, + "user_value": int(user_value), + "num_of_initial_validators": int(num_of_validators), + "paid_fee_value": int(fee_value), + "required_fee_value": int(required_fee_value), + "primary_fee_required": primary_required, + "primary_fee_budget": max(0, int(fee_value) - total_message_fees), + "primary_fee_spent": 0, + "primary_fee_refunded": 0, + "execution_budget_total": execution_budget_total, + "execution_fee_consumed": 0, + "execution_fee_consumed_buckets": [], + "genvm_fee_consumed_buckets": [], + "genvm_message_fee_consumed": 0, + "execution_fee_report": {}, + "message_fee_budget": total_message_fees, + "message_fee_consumed": 0, + "message_fee_refunded": 0, + "external_message_fee_reserved": 0, + "external_message_fee_reimbursed": 0, + "external_message_fee_remainder": 0, + "external_message_events": [], + "appeal_bonds": [], + "appeal_bonds_total": 0, + "total_refunded": 0, + "refunds": [], + "top_ups": [ + { + "sender": sender, + "amount": int(fee_value), + "primaryAmount": max(0, int(fee_value) - total_message_fees), + "messageFees": total_message_fees, + "feesDistribution": fees, + } + ], + "fees_distribution": fees, + "message_allocations": [ + _serializable_message_allocation(allocation) + for allocation in message_allocations + ], + "allocation_consumed": {}, + "message_consumption_events": [], + } + + +def _serializable_fees_distribution( + fees_distribution: dict[str, Any], +) -> dict[str, int | list[int]]: + return normalize_fees_distribution(fees_distribution) + + +def _serializable_message_allocation(node: dict[str, Any]) -> dict[str, Any]: + normalized = _normalize_message_allocation(node) + return { + "messageType": int(normalized["messageType"]), + "onAcceptance": bool(normalized["onAcceptance"]), + "parentIndex": int(normalized["parentIndex"]), + "recipient": str(normalized["recipient"]).lower(), + "callKey": _normalize_call_key(normalized["callKey"]), + "budget": int(normalized["budget"]), + "feeParams": _fee_params_hex(normalized["feeParams"]), + } + + +def _fees_distribution_from_internal_params( + fee_params: dict[str, Any], + *, + total_message_fees: int, + parent_fees_distribution: dict[str, Any], +) -> dict[str, Any]: + return { + "leaderTimeunitsAllocation": int(fee_params["leaderTimeunitsAllocation"]), + "validatorTimeunitsAllocation": int(fee_params["validatorTimeunitsAllocation"]), + "appealRounds": int(fee_params["appealRounds"]), + "executionBudgetPerRound": int(fee_params["executionBudgetPerRound"]), + "executionConsumed": 0, + "totalMessageFees": int(total_message_fees), + "rotations": [int(rotation) for rotation in fee_params["rotations"]], + "maxPriceGenPerTimeUnit": int( + parent_fees_distribution.get("maxPriceGenPerTimeUnit", 0) + ), + "storageFeeMaxGasPrice": int( + parent_fees_distribution.get("storageFeeMaxGasPrice", 0) + ), + "receiptFeeMaxGasPrice": int( + parent_fees_distribution.get("receiptFeeMaxGasPrice", 0) + ), + } + + +def _genvm_message_fee_params(node: dict[str, Any]) -> dict[str, Any]: + if int(node["messageType"]) == MESSAGE_TYPE_EXTERNAL: + return { + "leader_timeunits_allocation": 0, + "validator_timeunits_allocation": 0, + "execution_budget_per_round": 0, + "rotations": [0], + } + + decoded = decode_internal_message_fee_params(node["feeParams"]) + return { + "leader_timeunits_allocation": int(decoded["leaderTimeunitsAllocation"]), + "validator_timeunits_allocation": int(decoded["validatorTimeunitsAllocation"]), + "execution_budget_per_round": int(decoded["executionBudgetPerRound"]), + "rotations": [int(rotation) for rotation in decoded["rotations"]], + } + + +def _genvm_message_allocation_node( + node: dict[str, Any], + address_factory: Callable[[str], Any] | None, +) -> dict[str, Any]: + return { + "message_type": _genvm_message_type_name(node), + "parent_index": _genvm_parent_index(node), + "recipient": _genvm_recipient(node, address_factory), + "call_key": _genvm_call_key(node), + "budget": int(node["budget"]), + "fee_params": _genvm_message_fee_params(node), + } + + +def _genvm_message_type_name(node: dict[str, Any]) -> str: + if int(node["messageType"]) == MESSAGE_TYPE_EXTERNAL: + return "External" + return "InternalAccepted" if bool(node["onAcceptance"]) else "InternalFinalized" + + +def _genvm_parent_index(node: dict[str, Any]) -> int | None: + parent_index = int(node["parentIndex"]) + return None if parent_index == NODE_ROOT_SENTINEL else parent_index + + +def _genvm_recipient( + node: dict[str, Any], + address_factory: Callable[[str], Any] | None, +) -> Any | None: + recipient = str(node["recipient"]).lower() + if recipient == "": + return None + return address_factory(recipient) if address_factory else recipient + + +def _genvm_call_key(node: dict[str, Any]) -> bytes | None: + call_key = _normalize_call_key(node["callKey"]) + if call_key == CALL_KEY_WILDCARD: + return None + return bytes.fromhex(call_key.removeprefix("0x")) + + +def _genvm_unmetered_message_fee_allocation() -> list[dict[str, Any]]: + fee_params = { + "leader_timeunits_allocation": 5, + "validator_timeunits_allocation": 5, + "execution_budget_per_round": 2**10, + "rotations": [4, 4, 4, 4, 4], + } + budget = 2**200 + return [ + { + "message_type": "External", + "parent_index": None, + "recipient": None, + "call_key": None, + "budget": budget, + "fee_params": fee_params, + }, + { + "message_type": "InternalFinalized", + "parent_index": None, + "recipient": None, + "call_key": None, + "budget": budget, + "fee_params": fee_params, + }, + { + "message_type": "InternalAccepted", + "parent_index": None, + "recipient": None, + "call_key": None, + "budget": budget, + "fee_params": fee_params, + }, + ] + + +def _genvm_external_legacy_fallback_message_fee_allocation() -> dict[str, Any]: + return { + "message_type": "External", + "parent_index": None, + "recipient": None, + "call_key": None, + "budget": 2**200, + "fee_params": { + "leader_timeunits_allocation": 0, + "validator_timeunits_allocation": 0, + "execution_budget_per_round": 0, + "rotations": [0], + }, + } + + +def _allocation_subtree( + message_allocations: list[dict[str, Any]], + root_index: int, +) -> list[dict[str, Any]]: + root = copy.deepcopy( + _serializable_message_allocation(message_allocations[root_index]) + ) + root["parentIndex"] = NODE_ROOT_SENTINEL + old_to_new: dict[int, int] = {root_index: 0} + subtree: list[dict[str, Any]] = [root] + for index, raw_node in enumerate(message_allocations): + if index == root_index: + continue + node = _serializable_message_allocation(raw_node) + parent_index = int(node["parentIndex"]) + if parent_index not in old_to_new: + continue + + old_to_new[index] = len(subtree) + copied = copy.deepcopy(node) + copied["parentIndex"] = old_to_new[parent_index] + subtree.append(copied) + return subtree + + +def _child_allocations_from_message_subtree( + message: dict[str, Any], + allocation_subtree: list[dict[str, Any]], +) -> list[dict[str, Any]]: + if not allocation_subtree: + return [] + + root = _serializable_message_allocation(allocation_subtree[0]) + if not _is_matched_root_allocation(message, root): + return [ + _serializable_message_allocation(allocation) + for allocation in allocation_subtree + ] + + child_allocations: list[dict[str, Any]] = [] + for raw_node in allocation_subtree[1:]: + node = _serializable_message_allocation(raw_node) + copied = copy.deepcopy(node) + parent_index = int(copied["parentIndex"]) + copied["parentIndex"] = ( + NODE_ROOT_SENTINEL if parent_index == 0 else parent_index - 1 + ) + child_allocations.append(copied) + return child_allocations + + +def _canonical_allocation_subtree( + allocation_subtree: list[dict[str, Any]], +) -> list[dict[str, Any]]: + canonical = [] + for allocation in allocation_subtree: + node = _submitted_allocation_node(allocation) + canonical.append( + { + "messageType": int(node[0]), + "onAcceptance": bool(node[1]), + "parentIndex": int(node[2]), + "recipient": str(node[3]).lower(), + "callKey": "0x" + bytes(node[4]).hex(), + "budget": int(node[5]), + "feeParams": "0x" + bytes(node[6]).hex(), + } + ) + return canonical + + +def _is_matched_root_allocation( + message: dict[str, Any], + allocation: dict[str, Any], +) -> bool: + if int(allocation["parentIndex"]) != NODE_ROOT_SENTINEL: + return False + if int(allocation["messageType"]) != int( + message.get("messageType", MESSAGE_TYPE_INTERNAL) + ): + return False + if bool(allocation["onAcceptance"]) != bool(message.get("onAcceptance", False)): + return False + if ( + str(allocation["recipient"]).lower() + != str(message.get("recipient", "")).lower() + ): + return False + if _normalize_call_key(allocation["callKey"]) != _normalize_call_key( + message.get("callKey", CALL_KEY_WILDCARD) + ): + return False + if _fee_params_hex(allocation["feeParams"]) != _fee_params_hex( + message.get("feeParams", b"") + ): + return False + return True + + +def _consume_against_allocation( + accounting: dict[str, Any], + message: dict[str, Any], + declared_budget: int, +) -> None: + allocations = accounting.get("message_allocations") or [] + if not allocations: + return + + resolved = _resolve_allocation(allocations, message) + if resolved is None: + raise MessageNoMatchingAllocation("MessageNoMatchingAllocation") + + index, allocation = resolved + if bool(allocation["onAcceptance"]) != bool(message.get("onAcceptance", False)): + raise MessageEmissionPhaseMismatch("MessageEmissionPhaseMismatch") + + if _fee_params_hex(allocation["feeParams"]) != _fee_params_hex( + message.get("feeParams", b"") + ): + raise MessageFeeParamsMismatch("MessageFeeParamsMismatch") + + key = str(index) + consumed = int(accounting.setdefault("allocation_consumed", {}).get(key, 0)) + attempted = consumed + declared_budget + if attempted > int(allocation["budget"]): + raise MessageBudgetExceeded("MessageBudgetExceeded") + accounting["allocation_consumed"][key] = attempted + + +def _reserve_external_execution( + accounting: dict[str, Any], + message: dict[str, Any], + policy: StudioFeePolicy, + *, + reimburse: bool = True, +) -> int: + if bool(message.get("onAcceptance", False)): + return 0 + + allocations = accounting.get("message_allocations") or [] + if not allocations: + return 0 + + resolved = _resolve_allocation(allocations, message) + if resolved is None: + return 0 + + index, allocation = resolved + if int(allocation["messageType"]) != MESSAGE_TYPE_EXTERNAL: + return 0 + + external_fee_params = decode_external_message_fee_params(allocation["feeParams"]) + gas_limit = int(external_fee_params["gasLimit"]) + max_gas_price = int(external_fee_params["maxGasPrice"]) + locked_price = ( + min(policy.receipt_gas_price, max_gas_price) + if policy.receipt_gas_price > 0 + else 0 + ) + reservation = gas_limit * locked_price + key = str(index) + consumed = int(accounting.setdefault("allocation_consumed", {}).get(key, 0)) + attempted = consumed + reservation + if attempted > int(allocation["budget"]): + raise MessageBudgetExceeded("MessageBudgetExceeded") + accounting["allocation_consumed"][key] = attempted + + gas_used = int(message.get("gasUsed", 0) or 0) + reimbursement = min(reservation, gas_used * locked_price) + remainder = reservation - reimbursement + accounting["external_message_fee_reserved"] = ( + int(accounting.get("external_message_fee_reserved", 0)) + reservation + ) + if reimburse: + accounting["external_message_fee_reimbursed"] = ( + int(accounting.get("external_message_fee_reimbursed", 0)) + reimbursement + ) + accounting["external_message_fee_remainder"] = ( + int(accounting.get("external_message_fee_remainder", 0)) + remainder + ) + accounting.setdefault("external_message_events", []).append( + { + "recipient": str(message.get("recipient", "")).lower(), + "callKey": _normalize_call_key(message.get("callKey", CALL_KEY_WILDCARD)), + "allocationIndex": index, + "gasLimit": gas_limit, + "lockedGasPrice": locked_price, + "reservation": reservation, + "gasUsed": gas_used if reimburse else 0, + "reimbursement": reimbursement if reimburse else 0, + "remainder": remainder if reimburse else 0, + "executionRecorded": bool(reimburse), + } + ) + return reimbursement if reimburse else 0 + + +def _find_unrefunded_external_message_event( + accounting: dict[str, Any], + message: dict[str, Any], +) -> int | None: + recipient = str(message.get("recipient", "")).lower() + call_key = _normalize_call_key(message.get("callKey", CALL_KEY_WILDCARD)) + for index, event in enumerate(accounting.get("external_message_events") or []): + if ( + event.get("failureRefunded") + or event.get("refunded") + or event.get("unreserved") + ): + continue + if str(event.get("recipient", "")).lower() != recipient: + continue + if _normalize_call_key(event.get("callKey", CALL_KEY_WILDCARD)) != call_key: + continue + return index + return None + + +def _find_unexecuted_external_message_event( + accounting: dict[str, Any], + message: dict[str, Any], +) -> int | None: + recipient = str(message.get("recipient", "")).lower() + call_key = _normalize_call_key(message.get("callKey", CALL_KEY_WILDCARD)) + for index, event in enumerate(accounting.get("external_message_events") or []): + if event.get("executionRecorded") or event.get("unreserved"): + continue + if str(event.get("recipient", "")).lower() != recipient: + continue + if _normalize_call_key(event.get("callKey", CALL_KEY_WILDCARD)) != call_key: + continue + return index + return None + + +def _unreserve_external_message_fee( + accounting: dict[str, Any], + message: dict[str, Any], +) -> tuple[int, int, int]: + event_index = _find_unrefunded_external_message_event(accounting, message) + if event_index is None: + return 0, 0, 0 + + event = accounting.setdefault("external_message_events", [])[event_index] + reservation = int(event.get("reservation", 0) or 0) + reimbursement = int(event.get("reimbursement", 0) or 0) + remainder = int(event.get("remainder", 0) or 0) + allocation_index = str(event.get("allocationIndex")) + + allocation_consumed = accounting.setdefault("allocation_consumed", {}) + consumed = int(allocation_consumed.get(allocation_index, 0) or 0) + allocation_consumed[allocation_index] = max(0, consumed - reservation) + accounting["message_fee_consumed"] = max( + 0, + int(accounting.get("message_fee_consumed", 0)) - reimbursement, + ) + accounting["external_message_fee_reserved"] = max( + 0, + int(accounting.get("external_message_fee_reserved", 0)) - reservation, + ) + accounting["external_message_fee_reimbursed"] = max( + 0, + int(accounting.get("external_message_fee_reimbursed", 0)) - reimbursement, + ) + accounting["external_message_fee_remainder"] = max( + 0, + int(accounting.get("external_message_fee_remainder", 0)) - remainder, + ) + event["unreserved"] = True + return reservation, reimbursement, remainder + + +def _decrement_allocation_consumed( + accounting: dict[str, Any], + message: dict[str, Any], + amount: int, +) -> None: + resolved = _resolve_allocation(accounting.get("message_allocations") or [], message) + if resolved is None: + return + index, _ = resolved + allocation_consumed = accounting.setdefault("allocation_consumed", {}) + key = str(index) + consumed = int(allocation_consumed.get(key, 0) or 0) + allocation_consumed[key] = max(0, consumed - int(amount)) + + +def _resolve_allocation( + allocations: list[dict[str, Any]], + message: dict[str, Any], +) -> tuple[int, dict[str, Any]] | None: + message_type = int(message.get("messageType", MESSAGE_TYPE_INTERNAL)) + recipient = str(message.get("recipient", "")).lower() + call_key = _normalize_call_key(message.get("callKey", CALL_KEY_WILDCARD)) + + for wanted_call_key in (call_key, CALL_KEY_WILDCARD): + for index, raw_allocation in enumerate(allocations): + allocation = _serializable_message_allocation(raw_allocation) + if int(allocation["parentIndex"]) != NODE_ROOT_SENTINEL: + continue + if int(allocation["messageType"]) != message_type: + continue + if str(allocation["recipient"]).lower() != recipient: + continue + if _normalize_call_key(allocation["callKey"]) == wanted_call_key: + return index, allocation + return None + + +def _receipt_message_fee_payloads( + accounting: dict[str, Any], + receipt: Any | None, +) -> list[dict[str, Any]]: + if receipt is None: + return [] + if not _receipt_execution_allows_messages(receipt): + return [] + + payloads: list[dict[str, Any]] = [] + for raw in _receipt_pending_transactions(receipt): + message = _receipt_pending_transaction_fee_payload(raw) + if int(message["messageType"]) == MESSAGE_TYPE_INTERNAL and accounting.get( + "message_allocations" + ): + message = fill_message_fee_payload_from_allocation(accounting, message) + payloads.append(message) + return payloads + + +def _receipt_execution_allows_messages(receipt: Any) -> bool: + status = _receipt_value(receipt, "execution_result") + if status is None: + status = _receipt_value(receipt, "executionResult") + if hasattr(status, "value"): + status = status.value + if _receipt_budget_exhaustion_reason(receipt) in { + "ExecutionBudgetExceeded", + "MessageBudgetExceeded", + }: + return False + if status is None: + return True + return str(status).replace("_", "").upper() in { + "SUCCESS", + "FINISHEDWITHRETURN", + "RETURN", + } + + +def _receipt_messages_require_fee_validation( + accounting: dict[str, Any], + messages: list[dict[str, Any]], +) -> bool: + if int(accounting.get("message_fee_budget", 0) or 0) > 0: + return True + if accounting.get("message_allocations"): + return True + return any(_message_has_fee_fields(message) for message in messages) + + +def _message_has_fee_fields(message: dict[str, Any]) -> bool: + if int(message.get("declaredBudget", 0) or 0) > 0: + return True + return _message_has_fee_params(message) + + +def _message_has_fee_params(message: dict[str, Any]) -> bool: + fee_params = message.get("feeParams", b"") + if isinstance(fee_params, str): + return fee_params not in {"", "0x"} + return bool(fee_params) + + +def _receipt_pending_transaction_fee_payload(raw: Any) -> dict[str, Any]: + message = _pending_transaction_dict(raw) + message_type = _message_type(message) + data = _bytes_field( + _message_field(message, "calldata", "data", b"") + or _message_field(message, "data", "calldata", b"") + ) + call_key = _message_field( + message, + "call_key", + "callKey", + CALL_KEY_WILDCARD, + ) + if message_type == MESSAGE_TYPE_EXTERNAL: + call_key = derive_external_message_call_key(call_key, data) + fee_params = b"" + else: + fee_params = _bytes_field( + _message_field(message, "fee_params", "feeParams", b"") + ) + return { + "messageType": message_type, + "recipient": _abi_address( + _message_field(message, "address", "recipient") + or _message_field(message, "recipient", "address") + ), + "value": int(message.get("value", 0) or 0), + "data": data, + "onAcceptance": _message_on_acceptance(message), + "saltNonce": int(_message_field(message, "salt_nonce", "saltNonce", 0) or 0), + "feeParams": fee_params, + "declaredBudget": int( + _message_field( + message, + "declared_budget", + "declaredBudget", + 0, + ) + or 0 + ), + "allocationSubtree": _message_field( + message, + "allocation_subtree", + "allocationSubtree", + [], + ), + "callKey": call_key, + "gasUsed": int(_message_field(message, "gas_used", "gasUsed", 0) or 0), + } + + +def _execution_fee_buckets(consumed: list[int]) -> list[int]: + if len(consumed) <= 2: + return consumed + return consumed[:2] + + +def _chargeable_execution_fee_buckets( + consumed: list[int], + fee_report: dict[str, Any] | None, + policy: StudioFeePolicy, + receipt: Any | None = None, +) -> list[int]: + storage_fee = _chargeable_storage_fee(receipt, consumed) + if policy.receipt_gas_price <= 0 or not isinstance(fee_report, dict): + return [ + _bucket_value(consumed, 0), + storage_fee, + ] + + return [ + _receipt_report_chargeable_fee(fee_report), + storage_fee, + ] + + +def _chargeable_storage_fee(receipt: Any | None, consumed: list[int]) -> int: + if receipt is not None and not _receipt_execution_allows_messages(receipt): + return 0 + return _bucket_value(consumed, 1) + + +def _receipt_report_chargeable_fee(fee_report: dict[str, Any]) -> int: + proposal = fee_report.get("proposalReceipt") + proposal_fee = int(proposal.get("fee", 0) or 0) if isinstance(proposal, dict) else 0 + message_reveal = fee_report.get("messageReveal") + message_fee = ( + int(message_reveal.get("consensusAdditionalFee", 0) or 0) + if isinstance(message_reveal, dict) + else 0 + ) + return max(0, proposal_fee + message_fee) + + +def _bucket_value(consumed: list[int], index: int) -> int: + return int(consumed[index]) if len(consumed) > index else 0 + + +def _execution_budget_per_round(accounting: dict[str, Any]) -> int: + try: + fees = normalize_fees_distribution(accounting.get("fees_distribution") or {}) + except FeeValidationError: + return 0 + return int(fees["executionBudgetPerRound"]) + + +def _genvm_fee_bucket_report( + consumed: list[int], + *, + execution_budget_per_round: int = 0, +) -> dict[str, Any]: + receipt_and_nondet_output = _bucket_value(consumed, 0) + storage = _bucket_value(consumed, 1) + message = _bucket_value(consumed, 2) + total_execution = receipt_and_nondet_output + storage + buckets = [ + { + "index": 0, + "name": "receiptAndNondetOutput", + "consumed": receipt_and_nondet_output, + }, + {"index": 1, "name": "storage", "consumed": storage}, + ] + if len(consumed) > 2: + buckets.append({"index": 2, "name": "message", "consumed": message}) + report = { + "receiptAndNondetOutput": receipt_and_nondet_output, + "storage": storage, + "message": message, + "totalExecution": total_execution, + "totalWithMessage": sum(int(value) for value in consumed), + "buckets": buckets, + } + overrun = max(0, total_execution - execution_budget_per_round) + report.update( + { + "executionBudgetPerRound": execution_budget_per_round, + "executionBudgetRemaining": max( + 0, execution_budget_per_round - total_execution + ), + "executionBudgetOverrun": overrun, + "executionBudgetExceeded": overrun > 0, + } + ) + return report + + +def _execution_metering_report( + *, + chargeable_bucket_report: dict[str, Any], + genvm_bucket_report: dict[str, Any], +) -> dict[str, int]: + chargeable = int(chargeable_bucket_report.get("totalExecution", 0) or 0) + genvm_reported = int(genvm_bucket_report.get("totalExecution", 0) or 0) + return { + "chargeableExecutionFee": chargeable, + "genvmReportedExecution": genvm_reported, + "genvmDeltaFromChargeable": genvm_reported - chargeable, + } + + +def _receipt_budget_exhaustion_reason( + receipt: Any | None, + bucket_report: dict[str, Any] | None = None, +) -> str | None: + genvm_result = _receipt_genvm_result(receipt) + if isinstance(genvm_result, dict): + for key in ("budgetExhaustionReason", "budget_exhaustion_reason"): + reason = genvm_result.get(key) + if reason not in (None, "", "None"): + return str(reason) + + error_code = genvm_result.get("error_code") or genvm_result.get("errorCode") + if error_code in {"ExecutionBudgetExceeded", "MessageBudgetExceeded"}: + return str(error_code) + + if bucket_report and bucket_report.get("executionBudgetExceeded"): + return "ExecutionBudgetExceeded" + + return None + + +def _message_fee_accounting_report(accounting: dict[str, Any]) -> dict[str, int]: + budget = int(accounting.get("message_fee_budget", 0) or 0) + total_consumed = int(accounting.get("message_fee_consumed", 0) or 0) + external_reserved = int(accounting.get("external_message_fee_reserved", 0) or 0) + external_reimbursed = int(accounting.get("external_message_fee_reimbursed", 0) or 0) + external_remainder = int(accounting.get("external_message_fee_remainder", 0) or 0) + declared_consumed = max(0, total_consumed - external_reimbursed) + declared_refunded = int(accounting.get("message_fee_refunded", 0) or 0) + genvm_metered_consumed = int(accounting.get("genvm_message_fee_consumed", 0) or 0) + report = { + "budget": budget, + "declaredConsumed": declared_consumed, + "genvmMeteredConsumed": genvm_metered_consumed, + "declaredRefunded": declared_refunded, + "remaining": max(0, budget - total_consumed - declared_refunded), + "meteringDelta": declared_consumed - genvm_metered_consumed, + } + if external_reserved or external_reimbursed or external_remainder: + report["externalReserved"] = external_reserved + report["externalReimbursed"] = external_reimbursed + report["externalRemainder"] = external_remainder + report["totalConsumed"] = total_consumed + if accounting.get("reported_message_fees_total") is not None: + report["reportedTotal"] = int(accounting["reported_message_fees_total"]) + return report + + +def _attach_message_fee_accounting_report(accounting: dict[str, Any]) -> None: + report = dict(accounting.get("execution_fee_report") or {}) + report["messageFees"] = _message_fee_accounting_report(accounting) + accounting["execution_fee_report"] = report + + +def _attach_recommended_fee_preset( + accounting: dict[str, Any], + policy: StudioFeePolicy, +) -> None: + accounting["recommended_fee_preset"] = recommended_fee_preset(accounting, policy) + + +def recommended_fee_preset( + accounting: dict[str, Any], + policy: StudioFeePolicy | None = None, + *, + padding_bps: int = DEFAULT_PRICE_CAP_HEADROOM_BPS, +) -> dict[str, Any]: + policy = _accounting_policy(accounting, policy) + fees = normalize_fees_distribution(accounting.get("fees_distribution") or {}) + report = accounting.get("execution_fee_report") or {} + message_report = ( + report.get("messageFees") if isinstance(report.get("messageFees"), dict) else {} + ) + message_allocations = list(accounting.get("message_allocations") or []) + num_validators = int( + accounting.get("num_of_initial_validators") or VALIDATORS_PER_ROUND[0] + ) + + observed_execution = _observed_chargeable_execution_fee(accounting, report) + recommended_execution = int(fees["executionBudgetPerRound"]) + if observed_execution > 0: + recommended_execution = max( + _with_padding(observed_execution, padding_bps), + policy.message_fee_params_budget_floor(), + ) + + declared_message = _int_report_field(message_report, "declaredConsumed") + external_reserved = int(accounting.get("external_message_fee_reserved", 0) or 0) + observed_message_budget = declared_message + external_reserved + recommended_message_budget = int(fees["totalMessageFees"]) + message_budget_mode = "current" + if message_allocations: + message_budget_mode = "allocation-preserved" + elif observed_message_budget > 0: + recommended_message_budget = _with_padding(observed_message_budget, padding_bps) + message_budget_mode = "observed" + + distribution = _serializable_fees_distribution( + { + **fees, + "rotations": _preset_rotations(fees), + "executionBudgetPerRound": recommended_execution, + "totalMessageFees": recommended_message_budget, + } + ) + fee_value = required_fee_deposit( + distribution, + num_validators, + policy, + ) + + return { + "source": "simulation", + "paddingBps": int(padding_bps), + "numOfInitialValidators": num_validators, + "distribution": distribution, + "feeValue": fee_value, + "messageAllocations": message_allocations, + "messageBudgetMode": message_budget_mode, + "observed": { + "executionFee": observed_execution, + "messageFeeBudget": observed_message_budget, + "declaredMessageFees": declared_message, + "externalMessageReserved": external_reserved, + "totalEstimatedFee": _int_report_field(report, "totalEstimatedFee"), + "totalStudioMeteredFee": _int_report_field(report, "totalStudioMeteredFee"), + }, + } + + +def _preset_rotations(fees: dict[str, Any]) -> list[int]: + appeal_rounds = int(fees.get("appealRounds", 0) or 0) + expected = appeal_rounds + 1 + rotations = [int(rotation) for rotation in fees.get("rotations", [])] + if len(rotations) >= expected: + return rotations[:expected] + return rotations + ([0] * (expected - len(rotations))) + + +def _observed_chargeable_execution_fee( + accounting: dict[str, Any], + report: dict[str, Any], +) -> int: + consumed = int(accounting.get("execution_fee_consumed", 0) or 0) + if consumed > 0: + return consumed + + chargeable = report.get("chargeableExecution") + if isinstance(chargeable, dict): + total = int(chargeable.get("totalExecution", 0) or 0) + if total > 0: + return total + + return _int_report_field(report, "totalEstimatedFee") + + +def _int_report_field(report: dict[str, Any], key: str) -> int: + try: + return int(report.get(key, 0) or 0) + except (TypeError, ValueError): + return 0 + + +def _refresh_message_fee_accounting_report_if_present( + accounting: dict[str, Any], + policy: StudioFeePolicy | None = None, +) -> None: + if accounting.get("execution_fee_report"): + policy = _accounting_policy(accounting, policy) + _attach_message_fee_accounting_report(accounting) + _attach_recommended_fee_preset(accounting, policy) + + +def _receipt_data_fees_consumed(receipt: Any | None) -> list[int] | None: + if receipt is None: + return None + genvm_result = ( + getattr(receipt, "genvm_result", None) + if not isinstance(receipt, dict) + else receipt.get("genvm_result") + ) + if not isinstance(genvm_result, dict): + return None + consumed = genvm_result.get("data_fees_consumed") + if consumed is not None: + return [int(value) for value in consumed] + totals = genvm_result.get("data_fee_bucket_totals") + remaining = genvm_result.get("data_fees_remaining") + if totals is None or remaining is None: + return None + return [max(0, int(total) - int(rest)) for total, rest in zip(totals, remaining)] + + +def _receipt_reported_message_fees_total(receipt: Any | None) -> int | None: + if receipt is None: + return None + for source in (receipt, _receipt_genvm_result(receipt) or {}): + for key in ( + "reported_message_fees_total", + "reportedMessageFeesTotal", + "message_fees_consumed", + "messageFeesConsumed", + ): + value = _receipt_value(source, key) + if value is not None: + return int(value) + return None + + +def _receipt_fee_report( + receipt: Any | None, + policy: StudioFeePolicy, + message_payloads: list[dict[str, Any]] | None = None, +) -> dict[str, Any] | None: + if receipt is None: + return None + + eq_outputs_length = _receipt_eq_blocks_outputs_length(receipt) + receipt_bytes = policy.estimate_propose_receipt_bytes(eq_outputs_length) + proposal_gas = policy.estimate_propose_receipt_gas(receipt_bytes) + proposal_fee = proposal_gas * policy.receipt_gas_price + report: dict[str, Any] = { + "receiptGasPrice": policy.receipt_gas_price, + "proposalReceipt": { + "eqBlocksOutputsLength": eq_outputs_length, + "receiptBytes": receipt_bytes, + "estimatedGas": proposal_gas, + "fee": proposal_fee, + }, + "totalEstimatedFee": proposal_fee, + "totalStudioMeteredFee": proposal_fee, + } + + submitted_messages, message_reports = _receipt_submitted_messages_and_reports( + receipt, + message_payloads, + ) + if submitted_messages: + message_bytes = len(encode([SUBMITTED_MESSAGE_ABI_TYPE], [submitted_messages])) + message_gas = policy.estimate_message_reveal_gas( + message_bytes, + len(submitted_messages), + ) + consensus_message_gas = policy.estimate_consensus_message_reveal_gas( + message_bytes, + len(submitted_messages), + ) + message_fee = message_gas * policy.receipt_gas_price + consensus_message_fee = consensus_message_gas * policy.receipt_gas_price + report["messageReveal"] = { + "messageBytes": message_bytes, + "messageCount": len(submitted_messages), + "estimatedGas": message_gas, + "fee": message_fee, + "consensusAdditionalGas": consensus_message_gas, + "consensusAdditionalFee": consensus_message_fee, + "studioFixedOverheadGas": max(0, message_gas - consensus_message_gas), + "studioFixedOverheadFee": max(0, message_fee - consensus_message_fee), + "messages": message_reports, + } + report["totalEstimatedFee"] += consensus_message_fee + report["totalStudioMeteredFee"] += message_fee + + return report + + +def _receipt_eq_blocks_outputs_length(receipt: Any) -> int: + genvm_result = _receipt_genvm_result(receipt) + if isinstance(genvm_result, dict): + explicit = genvm_result.get("eq_blocks_outputs_length") or genvm_result.get( + "eqBlocksOutputsLength" + ) + if explicit is not None: + return max(0, int(explicit)) + + explicit_outputs = _receipt_value(receipt, "eq_blocks_outputs") + if isinstance(explicit_outputs, str) and explicit_outputs.startswith("0x"): + return len(bytes.fromhex(explicit_outputs.removeprefix("0x"))) + + return len(_encode_eq_blocks_outputs(_receipt_eq_outputs(receipt))) + + +def _receipt_submitted_messages(receipt: Any) -> list[tuple[Any, ...]]: + submitted, _ = _receipt_submitted_messages_and_reports(receipt) + return submitted + + +def _receipt_submitted_messages_and_reports( + receipt: Any, + message_payloads: list[dict[str, Any]] | None = None, +) -> tuple[list[tuple[Any, ...]], list[dict[str, Any]]]: + submitted = [] + reports = [] + raw_messages = ( + message_payloads + if message_payloads is not None + else [ + _pending_transaction_dict(raw) + for raw in _receipt_pending_transactions(receipt) + ] + ) + for message in raw_messages: + message_type = _message_type(message) + recipient = _abi_address( + _message_field(message, "address", "recipient") + or _message_field(message, "recipient", "address") + ) + value = int(message.get("value", 0) or 0) + data = _bytes_field( + _message_field(message, "calldata", "data", b"") + or _message_field(message, "data", "calldata", b"") + ) + on_acceptance = _message_on_acceptance(message) + salt_nonce = int(_message_field(message, "salt_nonce", "saltNonce", 0) or 0) + fee_params = _bytes_field( + _message_field(message, "fee_params", "feeParams", b"") + ) + if message_type == MESSAGE_TYPE_EXTERNAL: + fee_params = b"" + declared_budget = int( + _message_field( + message, + "declared_budget", + "declaredBudget", + 0, + ) + or 0 + ) + allocation_subtree = _allocation_subtree_bytes( + _message_field( + message, + "allocation_subtree", + "allocationSubtree", + ) + ) + call_key_value = _message_field( + message, + "call_key", + "callKey", + CALL_KEY_WILDCARD, + ) + if message_type == MESSAGE_TYPE_EXTERNAL: + call_key_value = derive_external_message_call_key(call_key_value, data) + call_key = _bytes32_field(call_key_value) + submitted.append( + ( + message_type, + recipient, + value, + data, + on_acceptance, + salt_nonce, + fee_params, + declared_budget, + allocation_subtree, + call_key, + ) + ) + reports.append( + { + "messageFeeMode": _message_fee_mode( + message_type, + allocation_subtree, + message.get("messageFeeMode"), + ), + "messageType": ( + "External" if message_type == MESSAGE_TYPE_EXTERNAL else "Internal" + ), + "recipient": recipient, + "value": value, + "dataBytes": len(data), + "onAcceptance": on_acceptance, + "saltNonce": salt_nonce, + "feeParams": _fee_params_hex(fee_params), + "feeParamsDecoded": _message_fee_params_for_report( + message_type, + fee_params, + ), + "feeParamsBytes": len(fee_params), + "declaredBudget": declared_budget, + "allocationSubtree": "0x" + allocation_subtree.hex(), + "allocationSubtreeBytes": len(allocation_subtree), + "callKey": "0x" + call_key.hex(), + } + ) + return submitted, reports + + +def _message_fee_mode( + message_type: int, + allocation_subtree: bytes, + explicit: Any = None, +) -> str: + if explicit in {"mode1", "mode2", "external"}: + return str(explicit) + if message_type == MESSAGE_TYPE_EXTERNAL: + return "external" + return "mode2" if allocation_subtree else "mode1" + + +def _message_fee_params_for_report( + message_type: int, + fee_params: bytes, +) -> dict[str, Any] | None: + if not fee_params: + return None + try: + if message_type == MESSAGE_TYPE_EXTERNAL: + return decode_external_message_fee_params(fee_params) + return decode_internal_message_fee_params(fee_params) + except FeeValidationError: + return None + + +def _receipt_pending_transactions(receipt: Any) -> list[Any]: + pending = _receipt_value(receipt, "pending_transactions", []) + return pending if isinstance(pending, list) else list(pending or []) + + +def _pending_transaction_dict(pending_transaction: Any) -> dict[str, Any]: + if isinstance(pending_transaction, dict): + return pending_transaction + if hasattr(pending_transaction, "to_dict"): + return pending_transaction.to_dict() + return { + "address": getattr(pending_transaction, "address", ""), + "calldata": getattr( + pending_transaction, + "calldata", + getattr(pending_transaction, "data", b""), + ), + "code": getattr(pending_transaction, "code", b""), + "salt_nonce": getattr(pending_transaction, "salt_nonce", 0), + "on": getattr(pending_transaction, "on", "finalized"), + "value": getattr(pending_transaction, "value", 0), + "is_eth_send": getattr( + pending_transaction, + "is_eth_send", + getattr(pending_transaction, "isEthSend", False), + ), + "fee_params": getattr(pending_transaction, "fee_params", b""), + "declared_budget": getattr(pending_transaction, "declared_budget", 0), + "call_key": getattr(pending_transaction, "call_key", CALL_KEY_WILDCARD), + "allocation_subtree": getattr(pending_transaction, "allocation_subtree", []), + } + + +def _message_field( + message: dict[str, Any], + snake_key: str, + camel_key: str, + default: Any = None, +) -> Any: + if snake_key in message: + return message[snake_key] + return message.get(camel_key, default) + + +def _message_type(message: dict[str, Any]) -> int: + explicit = _message_field(message, "message_type", "messageType") + if explicit is not None: + if isinstance(explicit, str) and not explicit.isdigit(): + return ( + MESSAGE_TYPE_EXTERNAL + if explicit.lower() == "external" + else MESSAGE_TYPE_INTERNAL + ) + return int(explicit) + is_eth_send = bool(_message_field(message, "is_eth_send", "isEthSend", False)) + return MESSAGE_TYPE_EXTERNAL if is_eth_send else MESSAGE_TYPE_INTERNAL + + +def _message_on_acceptance(message: dict[str, Any]) -> bool: + explicit = _message_field(message, "on_acceptance", "onAcceptance") + if explicit is not None: + return bool(explicit) + phase = str(message.get("on", "finalized")).lower() + return phase == "accepted" or phase == "acceptance" + + +def _receipt_eq_outputs(receipt: Any) -> list[bytes]: + eq_outputs = _receipt_value(receipt, "eq_outputs") + if eq_outputs is None: + eq_outputs = _receipt_value(receipt, "eqOutputs") + if isinstance(eq_outputs, dict): + + def sort_key(item: tuple[Any, Any]) -> int: + try: + return int(item[0]) + except (TypeError, ValueError): + return 0 + + return [ + _eq_output_bytes(value) + for _, value in sorted(eq_outputs.items(), key=sort_key) + ] + if isinstance(eq_outputs, list): + return [_eq_output_bytes(value) for value in eq_outputs] + return [] + + +def _eq_output_bytes(value: Any) -> bytes: + if isinstance(value, dict): + value = value.get("data", value.get("output", value.get("value", b""))) + return _bytes_field(value) + + +def _encode_eq_blocks_outputs(eq_outputs: list[bytes]) -> bytes: + return rlp.encode([*eq_outputs, b"padded"]) + + +def _receipt_genvm_result(receipt: Any) -> dict[str, Any] | None: + genvm_result = _receipt_value(receipt, "genvm_result") + return genvm_result if isinstance(genvm_result, dict) else None + + +def _receipt_value(receipt: Any, key: str, default: Any = None) -> Any: + if isinstance(receipt, dict): + return receipt.get(key, default) + return getattr(receipt, key, default) + + +def _abi_address(value: Any) -> str: + raw = str(value or "").lower() + if raw.startswith("0x"): + raw = raw[2:] + if len(raw) == 40: + try: + bytes.fromhex(raw) + return "0x" + raw + except ValueError: + pass + return "0x" + ("0" * 40) + + +def _allocation_subtree_bytes(value: Any) -> bytes: + if value is None or value == []: + return b"" + if isinstance(value, list): + nodes = [_submitted_allocation_node(node) for node in value] + return encode([MESSAGE_ALLOCATION_NODE_ABI_TYPE], [nodes]) + if isinstance(value, dict): + return encode( + [MESSAGE_ALLOCATION_NODE_ABI_TYPE], + [[_submitted_allocation_node(value)]], + ) + return _bytes_field(value) + + +def _submitted_allocation_node(node: dict[str, Any]) -> tuple[Any, ...]: + return ( + int(node.get("messageType", node.get("message_type", MESSAGE_TYPE_INTERNAL))), + bool(node.get("onAcceptance", node.get("on_acceptance", False))), + int(node.get("parentIndex", node.get("parent_index", NODE_ROOT_SENTINEL))), + _abi_address(node.get("recipient")), + _bytes32_field(node.get("callKey", node.get("call_key", CALL_KEY_WILDCARD))), + int(node.get("budget", 0) or 0), + _bytes_field(node.get("feeParams", node.get("fee_params", b""))), + ) + + +def _bytes32_field(value: Any) -> bytes: + if isinstance(value, bytes): + return value.rjust(32, b"\x00")[-32:] + raw = str(value or "").removeprefix("0x").lower() + try: + return bytes.fromhex(raw.rjust(64, "0")[-64:]) + except ValueError: + return bytes(32) + + +def _bytes_field(value: Any) -> bytes: + if value is None: + return b"" + if isinstance(value, bytes): + return value + if isinstance(value, bytearray): + return bytes(value) + if isinstance(value, str): + return _bytes_from_string_field(value) + return bytes(value) + + +def _bytes_from_string_field(value: str) -> bytes: + raw = value.removeprefix("0x") + if value.startswith("0x"): + return _bytes_from_hex_field(raw) + if raw == "": + return b"" + return _bytes_from_encoded_text(raw) + + +def _bytes_from_encoded_text(raw: str) -> bytes: + try: + return base64.b64decode(raw, validate=True) + except Exception: + return _bytes_from_hex_or_utf8(raw) + + +def _bytes_from_hex_or_utf8(raw: str) -> bytes: + try: + return bytes.fromhex(raw) + except ValueError: + return raw.encode("utf-8") + + +def _bytes_from_hex_field(raw: str) -> bytes: + try: + return bytes.fromhex(raw) + except ValueError: + return b"" + + +def _fee_params_hex(fee_params: bytes | str) -> str: + if isinstance(fee_params, str): + return "0x" + fee_params.removeprefix("0x").lower() + return "0x" + bytes(fee_params).hex() + + +def _normalize_call_key(call_key: bytes | str) -> str: + if isinstance(call_key, bytes): + raw = call_key.hex() + else: + raw = str(call_key).removeprefix("0x").lower() + return "0x" + raw.rjust(64, "0")[-64:] + + +def derive_external_message_call_key( + call_key: bytes | str | None, calldata: Any +) -> str: + normalized = _normalize_call_key(call_key or CALL_KEY_WILDCARD) + if normalized != CALL_KEY_WILDCARD: + return normalized + + raw_calldata = _bytes_field(calldata) + if len(raw_calldata) < 4: + return CALL_KEY_WILDCARD + + return "0x" + raw_calldata[:4].hex().ljust(64, "0") diff --git a/backend/protocol_rpc/rpc_methods.py b/backend/protocol_rpc/rpc_methods.py index 1c8785850..d3a2c31bb 100644 --- a/backend/protocol_rpc/rpc_methods.py +++ b/backend/protocol_rpc/rpc_methods.py @@ -169,6 +169,7 @@ async def update_validator( stake: int | None = None, provider: str | None = None, model: str | None = None, + config: dict | None = None, plugin: str | None = None, plugin_config: dict | None = None, session: Session = Depends(get_db_session), @@ -181,6 +182,7 @@ async def update_validator( stake=stake, provider=provider, model=model, + config=config, plugin=plugin, plugin_config=plugin_config, ) @@ -291,6 +293,11 @@ def get_finality_window_time( return impl.get_finality_window_time(consensus) +@rpc.method("sim_getFeeConfig", log_policy=LogPolicy.debug()) +def get_fee_config() -> dict: + return impl.get_studio_fee_config() + + @rpc.method("sim_getConsensusContract", log_policy=LogPolicy.debug()) def get_consensus_contract( contract_name: str, @@ -424,6 +431,27 @@ async def sim_call( ) +@rpc.method("sim_estimateTransactionFees") +async def sim_estimate_transaction_fees( + params: dict, + session: Session = Depends(get_db_session), + accounts_manager: AccountsManager = Depends(get_accounts_manager), + msg_handler=Depends(get_message_handler), + transactions_parser=Depends(get_transactions_parser), + validators_manager=Depends(get_validators_manager), + genvm_manager=Depends(get_genvm_manager), +) -> dict: + return await impl.sim_estimate_transaction_fees( + session=session, + accounts_manager=accounts_manager, + msg_handler=msg_handler, + transactions_parser=transactions_parser, + validators_manager=validators_manager, + genvm_manager=genvm_manager, + params=params, + ) + + # --------------------------------------------------------------------------- # Ethereum-compatible endpoints # --------------------------------------------------------------------------- diff --git a/backend/protocol_rpc/transactions_parser.py b/backend/protocol_rpc/transactions_parser.py index 3502d8d6d..1a0f85126 100644 --- a/backend/protocol_rpc/transactions_parser.py +++ b/backend/protocol_rpc/transactions_parser.py @@ -22,9 +22,181 @@ DecodedGenlayerTransaction, DecodedGenlayerTransactionData, DecodedsubmitAppealDataArgs, + DecodedTopUpFeesDataArgs, ZERO_ADDRESS, ) +FEE_AWARE_ADD_TRANSACTION_ABI = { + "inputs": [ + { + "components": [ + {"internalType": "address", "name": "sender", "type": "address"}, + {"internalType": "address", "name": "recipient", "type": "address"}, + { + "internalType": "uint256", + "name": "numOfInitialValidators", + "type": "uint256", + }, + {"internalType": "uint256", "name": "maxRotations", "type": "uint256"}, + {"internalType": "uint256", "name": "validUntil", "type": "uint256"}, + {"internalType": "uint256", "name": "saltNonce", "type": "uint256"}, + {"internalType": "uint256", "name": "userValue", "type": "uint256"}, + { + "components": [ + { + "internalType": "uint256", + "name": "leaderTimeunitsAllocation", + "type": "uint256", + }, + { + "internalType": "uint256", + "name": "validatorTimeunitsAllocation", + "type": "uint256", + }, + { + "internalType": "uint256", + "name": "appealRounds", + "type": "uint256", + }, + { + "internalType": "uint256", + "name": "executionBudgetPerRound", + "type": "uint256", + }, + { + "internalType": "uint256", + "name": "executionConsumed", + "type": "uint256", + }, + { + "internalType": "uint256", + "name": "totalMessageFees", + "type": "uint256", + }, + { + "internalType": "uint256[]", + "name": "rotations", + "type": "uint256[]", + }, + { + "internalType": "uint256", + "name": "maxPriceGenPerTimeUnit", + "type": "uint256", + }, + { + "internalType": "uint256", + "name": "storageFeeMaxGasPrice", + "type": "uint256", + }, + { + "internalType": "uint256", + "name": "receiptFeeMaxGasPrice", + "type": "uint256", + }, + ], + "internalType": "struct IFeeManager.FeesDistribution", + "name": "feesDistribution", + "type": "tuple", + }, + {"internalType": "bytes", "name": "txCalldata", "type": "bytes"}, + { + "components": [ + { + "internalType": "enum IMessages.MessageType", + "name": "messageType", + "type": "uint8", + }, + { + "internalType": "bool", + "name": "onAcceptance", + "type": "bool", + }, + { + "internalType": "uint256", + "name": "parentIndex", + "type": "uint256", + }, + { + "internalType": "address", + "name": "recipient", + "type": "address", + }, + { + "internalType": "bytes32", + "name": "callKey", + "type": "bytes32", + }, + { + "internalType": "uint256", + "name": "budget", + "type": "uint256", + }, + {"internalType": "bytes", "name": "feeParams", "type": "bytes"}, + ], + "internalType": "struct IMessages.MessageFeeAllocationNode[]", + "name": "messageAllocations", + "type": "tuple[]", + }, + ], + "internalType": "struct IConsensusMainWithFees.AddTransactionParams", + "name": "_params", + "type": "tuple", + } + ], + "name": "addTransaction", + "outputs": [], + "stateMutability": "payable", + "type": "function", +} + +FEE_AWARE_DEPLOY_SALTED_ABI = { + **FEE_AWARE_ADD_TRANSACTION_ABI, + "name": "deploySalted", +} + +FEE_AWARE_TOP_UP_FEES_ABI = { + "inputs": [ + {"internalType": "bytes32", "name": "_txId", "type": "bytes32"}, + FEE_AWARE_ADD_TRANSACTION_ABI["inputs"][0]["components"][7] + | {"name": "_feesDistribution"}, + ], + "name": "topUpFees", + "outputs": [], + "stateMutability": "payable", + "type": "function", +} + +FEE_AWARE_TOP_UP_AND_SUBMIT_APPEAL_ABI = { + **FEE_AWARE_TOP_UP_FEES_ABI, + "name": "topUpAndSubmitAppeal", +} + +FEES_DISTRIBUTION_FIELDS = [ + "leaderTimeunitsAllocation", + "validatorTimeunitsAllocation", + "appealRounds", + "executionBudgetPerRound", + "executionConsumed", + "totalMessageFees", + "rotations", + "maxPriceGenPerTimeUnit", + "storageFeeMaxGasPrice", + "receiptFeeMaxGasPrice", +] + +ADD_TRANSACTION_PARAMS_FIELDS = [ + "sender", + "recipient", + "numOfInitialValidators", + "maxRotations", + "validUntil", + "saltNonce", + "userValue", + "feesDistribution", + "txCalldata", + "messageAllocations", +] + class Boolean: """A sedes for booleans @@ -164,6 +336,8 @@ def _to_int(value: bytes) -> int: to_address = None nonce = signed_transaction_as_dict["nonce"] value = signed_transaction_as_dict["value"] + submitted_value = int(value) + fee_value = 0 # Some decoders return `data`, others return `input` input_raw = ( signed_transaction_as_dict.get("data") @@ -192,7 +366,7 @@ def _to_int(value: bytes) -> int: for abi_entry in contract_abi: if abi_entry["type"] == "function": # Calculate function selector from ABI - function_signature = f"{abi_entry['name']}({','.join([input['type'] for input in abi_entry['inputs']])})" + function_signature = f"{abi_entry['name']}({','.join([self._canonical_abi_type(input) for input in abi_entry['inputs']])})" calculated_selector = self.web3.keccak(text=function_signature)[ :4 ].hex() @@ -200,7 +374,8 @@ def _to_int(value: bytes) -> int: if calculated_selector == function_selector: # Decode parameters using the input types from ABI input_types = [ - input["type"] for input in abi_entry["inputs"] + self._canonical_abi_type(input) + for input in abi_entry["inputs"] ] decoded_params = self.web3.codec.decode( input_types, bytes.fromhex(parameters) @@ -219,27 +394,42 @@ def _to_int(value: bytes) -> int: ), } # Convert the decoded data into proper dataclasses - if decoded_data["function"] == "addTransaction": + if decoded_data["function"] in { + "addTransaction", + "deploySalted", + }: params = decoded_data["params"] - decoded_data = DecodedRollupTransactionData( - function_name=decoded_data["function"], - args=DecodedRollupTransactionDataArgs( - sender=to_checksum_address(params["_sender"]), - recipient=to_checksum_address( - params["_recipient"] - ), - num_of_initial_validators=params[ - "_numOfInitialValidators" - ], - max_rotations=params["_maxRotations"], - data=params["_txData"], - ), + decoded_data, value, fee_value = ( + self._decode_add_transaction_data( + decoded_data["function"], params, value + ) ) elif decoded_data["function"] == "submitAppeal": params = decoded_data["params"] decoded_data = DecodedsubmitAppealDataArgs( tx_id=params["_txId"], ) + elif decoded_data["function"] == "topUpFees": + params = decoded_data["params"] + decoded_data = DecodedTopUpFeesDataArgs( + tx_id=params["_txId"], + fees_distribution=self._fees_distribution_to_dict( + params["_feesDistribution"] + ), + ) + fee_value = int(value) + value = 0 + elif decoded_data["function"] == "topUpAndSubmitAppeal": + params = decoded_data["params"] + decoded_data = DecodedsubmitAppealDataArgs( + tx_id=params["_txId"], + fees_distribution=self._fees_distribution_to_dict( + params["_feesDistribution"] + ), + top_up_and_submit=True, + ) + fee_value = int(value) + value = 0 return DecodedRollupTransaction( from_address=sender, @@ -248,6 +438,8 @@ def _to_int(value: bytes) -> int: type=signed_transaction_as_dict.get("type", 0), nonce=nonce, value=value, + fee_value=fee_value, + submitted_value=submitted_value, ) except Exception as e: @@ -484,7 +676,97 @@ def _vrs_from(self, signed_transaction) -> tuple: def _get_contract_abi(self) -> list: # Get contract ABI from consensus service contract_data = self.consensus_service.load_contract("ConsensusMain") - return contract_data["abi"] if contract_data else [] + contract_abi = list(contract_data["abi"]) if contract_data else [] + contract_abi.extend( + [ + FEE_AWARE_ADD_TRANSACTION_ABI, + FEE_AWARE_DEPLOY_SALTED_ABI, + FEE_AWARE_TOP_UP_FEES_ABI, + FEE_AWARE_TOP_UP_AND_SUBMIT_APPEAL_ABI, + ] + ) + return contract_abi + + def _canonical_abi_type(self, abi_input: dict) -> str: + input_type = abi_input["type"] + if not input_type.startswith("tuple"): + return input_type + + suffix = input_type[5:] + component_types = ",".join( + self._canonical_abi_type(component) + for component in abi_input.get("components", []) + ) + return f"({component_types}){suffix}" + + def _decode_add_transaction_data( + self, function_name: str, params: dict, msg_value: int + ) -> tuple[DecodedRollupTransactionData, int, int]: + if "_params" in params: + add_params = dict(zip(ADD_TRANSACTION_PARAMS_FIELDS, params["_params"])) + user_value = int(add_params["userValue"]) + fee_value = max(0, int(msg_value) - user_value) + return ( + DecodedRollupTransactionData( + function_name=function_name, + args=DecodedRollupTransactionDataArgs( + sender=to_checksum_address(add_params["sender"]), + recipient=to_checksum_address(add_params["recipient"]), + num_of_initial_validators=int( + add_params["numOfInitialValidators"] + ), + max_rotations=int(add_params["maxRotations"]), + data=add_params["txCalldata"], + valid_until=int(add_params["validUntil"]), + salt_nonce=int(add_params["saltNonce"]), + user_value=user_value, + fees_distribution=self._fees_distribution_to_dict( + add_params["feesDistribution"] + ), + message_allocations=[ + self._message_allocation_to_dict(allocation) + for allocation in add_params["messageAllocations"] + ], + message_allocations_count=len(add_params["messageAllocations"]), + ), + ), + user_value, + fee_value, + ) + + return ( + DecodedRollupTransactionData( + function_name=function_name, + args=DecodedRollupTransactionDataArgs( + sender=to_checksum_address(params["_sender"]), + recipient=to_checksum_address(params["_recipient"]), + num_of_initial_validators=int(params["_numOfInitialValidators"]), + max_rotations=int(params["_maxRotations"]), + data=params["_txData"], + ), + ), + int(msg_value), + 0, + ) + + def _fees_distribution_to_dict(self, fees_distribution: tuple) -> dict: + result = dict(zip(FEES_DISTRIBUTION_FIELDS, fees_distribution)) + result["rotations"] = [int(rotation) for rotation in result["rotations"]] + for key, value in result.items(): + if key != "rotations": + result[key] = int(value) + return result + + def _message_allocation_to_dict(self, message_allocation: tuple) -> dict: + return { + "messageType": int(message_allocation[0]), + "onAcceptance": bool(message_allocation[1]), + "parentIndex": int(message_allocation[2]), + "recipient": to_checksum_address(message_allocation[3]), + "callKey": eth_utils.to_hex(message_allocation[4]), + "budget": int(message_allocation[5]), + "feeParams": bytes(message_allocation[6]), + } class DeploymentContractTransactionPayload(rlp.Serializable): diff --git a/backend/protocol_rpc/types.py b/backend/protocol_rpc/types.py index d90781081..05325c2c1 100644 --- a/backend/protocol_rpc/types.py +++ b/backend/protocol_rpc/types.py @@ -30,6 +30,14 @@ def to_json(self) -> dict[str]: @dataclass class DecodedsubmitAppealDataArgs: tx_id: str + fees_distribution: dict | None = None + top_up_and_submit: bool = False + + +@dataclass +class DecodedTopUpFeesDataArgs: + tx_id: str + fees_distribution: dict @dataclass @@ -39,6 +47,12 @@ class DecodedRollupTransactionDataArgs: num_of_initial_validators: int max_rotations: int data: str + valid_until: int | None = None + salt_nonce: int = 0 + user_value: int | None = None + fees_distribution: dict | None = None + message_allocations: list[dict] = field(default_factory=list) + message_allocations_count: int = 0 @dataclass @@ -51,10 +65,23 @@ class DecodedRollupTransactionData: class DecodedRollupTransaction: from_address: str to_address: str - data: DecodedRollupTransactionData | DecodedsubmitAppealDataArgs + data: ( + DecodedRollupTransactionData + | DecodedsubmitAppealDataArgs + | DecodedTopUpFeesDataArgs + | None + ) type: str nonce: int value: int + fee_value: int = 0 + submitted_value: int | None = None + + @property + def total_spend(self) -> int: + if self.submitted_value is not None: + return self.submitted_value + return self.value + self.fee_value @dataclass diff --git a/docker-compose.yml b/docker-compose.yml index 3e84651eb..7552fcafb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -125,18 +125,25 @@ services: - RATE_LIMIT_ANON_PER_MINUTE=${RATE_LIMIT_ANON_PER_MINUTE:-30} - RATE_LIMIT_ANON_PER_HOUR=${RATE_LIMIT_ANON_PER_HOUR:-500} - RATE_LIMIT_ANON_PER_DAY=${RATE_LIMIT_ANON_PER_DAY:-5000} + - GENLAYER_STUDIO_GEN_PER_TIME_UNIT=${GENLAYER_STUDIO_GEN_PER_TIME_UNIT:-1000000000000000} + - GENLAYER_STUDIO_STORAGE_UNIT_PRICE=${GENLAYER_STUDIO_STORAGE_UNIT_PRICE:-1} + - GENLAYER_STUDIO_RECEIPT_GAS_PRICE=${GENLAYER_STUDIO_RECEIPT_GAS_PRICE:-1} + - GENLAYER_STUDIO_FIXED_PROPOSE_RECEIPT_GAS=${GENLAYER_STUDIO_FIXED_PROPOSE_RECEIPT_GAS:-210000} + - GENLAYER_STUDIO_FIXED_MESSAGE_REVEAL_GAS=${GENLAYER_STUDIO_FIXED_MESSAGE_REVEAL_GAS:-100000} + - GENLAYER_STUDIO_RECEIPT_WRAPPER_BYTES=${GENLAYER_STUDIO_RECEIPT_WRAPPER_BYTES:-1024} # Per-contract / per-sender PENDING tx caps (admission control on # eth_sendRawTransaction). Empty/unset = no cap. Set in shared # deployments to keep one heavy user from filling the queue. - MAX_PENDING_PER_CONTRACT_DEFAULT=${MAX_PENDING_PER_CONTRACT_DEFAULT:-} - MAX_PENDING_PER_SENDER_DEFAULT=${MAX_PENDING_PER_SENDER_DEFAULT:-} ports: - - "${RPCPORT}:${RPCPORT}" + - "${RPCHOSTPORT:-4000}:${RPCPORT:-4000}" expose: - "${RPCPORT}" volumes: - ./.env:/app/.env - ./backend:/app/backend + - ${GENVM_CACHE_DIR:-genvm_cache}:/genvm-cache # - hardhat_artifacts:/app/hardhat/artifacts # - hardhat_deployments:/app/hardhat/deployments depends_on: @@ -246,7 +253,7 @@ services: image: postgres:16-alpine command: sh -c "if [ \"$REMOTE_DATABASE\" = \"true\" ]; then echo 'Postgres disabled in hosted environment' && exec tail -f /dev/null; else exec docker-entrypoint.sh postgres; fi" ports: - - "${DBPORT}:5432" + - "${DBHOSTPORT:-5432}:5432" environment: - POSTGRES_USER=${DBUSER} - POSTGRES_PASSWORD=${DBPASSWORD} @@ -301,9 +308,16 @@ services: - WEBDRIVERHOST=${WEBDRIVERHOST} - WEBDRIVERPORT=${WEBDRIVERPORT} - REDIS_URL=${REDIS_URL:-redis://redis:6379/0} + - GENLAYER_STUDIO_GEN_PER_TIME_UNIT=${GENLAYER_STUDIO_GEN_PER_TIME_UNIT:-1000000000000000} + - GENLAYER_STUDIO_STORAGE_UNIT_PRICE=${GENLAYER_STUDIO_STORAGE_UNIT_PRICE:-1} + - GENLAYER_STUDIO_RECEIPT_GAS_PRICE=${GENLAYER_STUDIO_RECEIPT_GAS_PRICE:-1} + - GENLAYER_STUDIO_FIXED_PROPOSE_RECEIPT_GAS=${GENLAYER_STUDIO_FIXED_PROPOSE_RECEIPT_GAS:-210000} + - GENLAYER_STUDIO_FIXED_MESSAGE_REVEAL_GAS=${GENLAYER_STUDIO_FIXED_MESSAGE_REVEAL_GAS:-100000} + - GENLAYER_STUDIO_RECEIPT_WRAPPER_BYTES=${GENLAYER_STUDIO_RECEIPT_WRAPPER_BYTES:-1024} volumes: - ./.env:/app/.env - ./backend:/app/backend + - ${GENVM_CACHE_DIR:-genvm_cache}:/genvm-cache depends_on: database-migration: condition: service_completed_successfully @@ -375,7 +389,7 @@ services: redis: image: redis:8-alpine ports: - - "6379:6379" + - "${REDISPORT:-6379}:6379" volumes: - redis_data:/data healthcheck: @@ -422,5 +436,6 @@ volumes: # hardhat_deployments: ignition_deployments: # hardhat_snapshots: + genvm_cache: postgres_data: redis_data: diff --git a/docker/Dockerfile.backend b/docker/Dockerfile.backend index 83bfe0c45..a36d44412 100644 --- a/docker/Dockerfile.backend +++ b/docker/Dockerfile.backend @@ -3,7 +3,7 @@ FROM ubuntu:24.04 AS base ARG TARGETPLATFORM ARG TARGETARCH -ARG GENVM_TAG=v0.2.16 +ARG GENVM_TAG=v0.3.0-rc1 ENV GENVM_TAG=$GENVM_TAG @@ -46,32 +46,32 @@ ENV HUGGINGFACE_HUB_CACHE /home/backend-user/.cache/huggingface ENV RUST_BACKTRACE=1 -ADD \ - https://github.com/genlayerlabs/genvm/releases/download/$GENVM_TAG/genvm-linux-amd64.tar.xz \ - /genvm/genvm-linux-amd64.tar.xz - -ADD \ - https://github.com/genlayerlabs/genvm/releases/download/$GENVM_TAG/genvm-linux-arm64.tar.xz \ - /genvm/genvm-linux-arm64.tar.xz - -ADD \ - https://github.com/genlayerlabs/genvm/releases/download/$GENVM_TAG/genvm-universal.tar.xz \ - /genvm/genvm-universal.tar.xz - -# Extract and prepare GenVM binaries +# Download and extract GenVM binaries. Keep downloads in the extraction layer so +# architecture tarballs do not remain in lower image layers. RUN cd /genvm \ - && if [[ "$TARGETPLATFORM" == "linux/amd64" ]] ; \ - then \ - tar -xf genvm-linux-amd64.tar.xz ; \ + && if [[ "$TARGETPLATFORM" == "linux/amd64" ]] || [[ -z "$TARGETPLATFORM" ]]; then \ + ARCH_FILE="genvm-linux-amd64.tar.xz" ; \ elif [[ "$TARGETPLATFORM" == "linux/arm64" ]] ; \ then \ - tar -xf genvm-linux-arm64.tar.xz ; \ + ARCH_FILE="genvm-linux-arm64.tar.xz" ; \ else \ echo "Sorry, $TARGETPLATFORM is not supported yet" ; exit 1 ; \ fi \ - && tar -xf genvm-universal.tar.xz \ + && curl -L --proto '=https' --proto-redir '=https' \ + --fail --retry 3 --retry-delay 2 \ + --connect-timeout 10 --max-time 300 \ + --progress-bar \ + -o "$ARCH_FILE" \ + "https://github.com/genlayerlabs/genvm/releases/download/$GENVM_TAG/$ARCH_FILE" \ + && curl -L --proto '=https' --proto-redir '=https' \ + --fail --retry 3 --retry-delay 2 \ + --connect-timeout 10 --max-time 300 \ + --progress-bar \ + -o "genvm-runners-all.tar.xz" \ + "https://github.com/genlayerlabs/genvm/releases/download/$GENVM_TAG/genvm-runners-all.tar.xz" \ + && tar -xf "$ARCH_FILE" \ + && tar -xf genvm-runners-all.tar.xz \ && rm *.tar.xz \ - && ls -R . \ && chown -R backend-user:backend-group /genvm \ && su - backend-user -c "/genvm/bin/post-install.py --precompile false" \ && find /genvm -name genvm.yaml -exec sed -i 's|cache_dir:.*|cache_dir: /genvm-cache/|' {} + \ diff --git a/docker/Dockerfile.consensus-worker b/docker/Dockerfile.consensus-worker index 337630548..0e812e38b 100644 --- a/docker/Dockerfile.consensus-worker +++ b/docker/Dockerfile.consensus-worker @@ -4,7 +4,7 @@ FROM ubuntu:24.04 AS base ARG TARGETPLATFORM ARG TARGETARCH -ARG GENVM_TAG=v0.2.16 +ARG GENVM_TAG=v0.3.0-rc1 ENV GENVM_TAG=$GENVM_TAG ARG path=/app @@ -66,13 +66,13 @@ RUN cd /genvm \ -o "$ARCH_FILE" \ "https://github.com/genlayerlabs/genvm/releases/download/$GENVM_TAG/$ARCH_FILE" \ && echo "✓ Downloaded $ARCH_FILE" \ - && echo "Downloading genvm-universal.tar.xz..." \ + && echo "Downloading genvm-runners-all.tar.xz..." \ && curl -L --fail --retry 3 --retry-delay 2 \ --connect-timeout 10 --max-time 300 \ --progress-bar \ - -o "genvm-universal.tar.xz" \ - "https://github.com/genlayerlabs/genvm/releases/download/$GENVM_TAG/genvm-universal.tar.xz" \ - && echo "✓ Downloaded genvm-universal.tar.xz" \ + -o "genvm-runners-all.tar.xz" \ + "https://github.com/genlayerlabs/genvm/releases/download/$GENVM_TAG/genvm-runners-all.tar.xz" \ + && echo "✓ Downloaded genvm-runners-all.tar.xz" \ && echo "Verifying downloads..." \ && ls -lah *.tar.xz \ && for f in *.tar.xz; do \ @@ -82,7 +82,7 @@ RUN cd /genvm \ done \ && echo "Extracting archives..." \ && tar -xf "$ARCH_FILE" \ - && tar -xf "genvm-universal.tar.xz" \ + && tar -xf "genvm-runners-all.tar.xz" \ && rm *.tar.xz \ && echo "Configuring GenVM manager threads..." \ && CONFIG_FILE="/genvm/config/genvm-manager.yaml" \ diff --git a/docker/entrypoint-backend.sh b/docker/entrypoint-backend.sh index eeefb54e8..1cd457791 100644 --- a/docker/entrypoint-backend.sh +++ b/docker/entrypoint-backend.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e -CACHE_MARKER="/genvm-cache/pc/.precompiled" +CACHE_MARKER="/genvm-cache/pc/.precompiled-${GENVM_TAG:-unknown}-$(uname -m)" if [ -f "$CACHE_MARKER" ]; then echo "GenVM already precompiled for this host, skipping." diff --git a/docker/entrypoint-consensus-worker.sh b/docker/entrypoint-consensus-worker.sh index fb261836f..bbcc61a25 100755 --- a/docker/entrypoint-consensus-worker.sh +++ b/docker/entrypoint-consensus-worker.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e -CACHE_MARKER="/genvm-cache/pc/.precompiled" +CACHE_MARKER="/genvm-cache/pc/.precompiled-${GENVM_TAG:-unknown}-$(uname -m)" if [ -f "$CACHE_MARKER" ]; then echo "GenVM ${GENVM_TAG} already precompiled for this host, skipping." diff --git a/explorer/eslint.config.mjs b/explorer/eslint.config.mjs index 05e726d1b..56b53789e 100644 --- a/explorer/eslint.config.mjs +++ b/explorer/eslint.config.mjs @@ -5,6 +5,11 @@ import nextTs from "eslint-config-next/typescript"; const eslintConfig = defineConfig([ ...nextVitals, ...nextTs, + { + rules: { + "react-hooks/set-state-in-effect": "off", + }, + }, // Override default ignores of eslint-config-next. globalIgnores([ // Default ignores of eslint-config-next: diff --git a/explorer/package-lock.json b/explorer/package-lock.json new file mode 100644 index 000000000..78edc7738 --- /dev/null +++ b/explorer/package-lock.json @@ -0,0 +1,8667 @@ +{ + "name": "studio-explorer", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "studio-explorer", + "version": "0.1.0", + "dependencies": { + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-tooltip": "^1.2.8", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "date-fns": "^4.1.0", + "genlayer-js": "^0.20.3", + "lucide-react": "^0.575.0", + "next": "16.1.6", + "next-themes": "^0.4.6", + "react": "19.2.4", + "react-day-picker": "^9.14.0", + "react-dom": "19.2.4", + "shiki": "^4.0.2", + "tailwind-merge": "^3.5.0" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^24.0.0", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9.39.4", + "eslint-config-next": "16.1.6", + "tailwindcss": "^4", + "typescript": "^5" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "license": "MIT" + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@date-fns/tz": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.4.1.tgz", + "integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==", + "license": "MIT" + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "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", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "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", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "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-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "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", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "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", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "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/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "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", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "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/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "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", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@next/env": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz", + "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.6.tgz", + "integrity": "sha512-/Qq3PTagA6+nYVfryAtQ7/9FEr/6YVyvOtl6rZnGsbReGLf0jZU6gkpr1FuChAQpvV46a78p4cmHOVP8mbfSMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz", + "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz", + "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz", + "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz", + "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz", + "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz", + "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz", + "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz", + "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "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", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", + "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", + "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", + "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "license": "MIT" + }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "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", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@shikijs/core": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.0.2.tgz", + "integrity": "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==", + "license": "MIT", + "dependencies": { + "@shikijs/primitive": "4.0.2", + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.0.2.tgz", + "integrity": "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.0.2.tgz", + "integrity": "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/langs": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.0.2.tgz", + "integrity": "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/primitive": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.0.2.tgz", + "integrity": "sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/themes": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.0.2.tgz", + "integrity": "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/types": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.0.2.tgz", + "integrity": "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tabby_ai/hijri-converter": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@tabby_ai/hijri-converter/-/hijri-converter-1.0.5.tgz", + "integrity": "sha512-r5bClKrcIusDoo049dSL8CawnHR6mRdDwhlQuIgZRNty68q0x8k3Lf1BtPAMxRf/GgnHBnIO4ujd3+GQdLWzxQ==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", + "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.2" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz", + "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-x64": "4.2.2", + "@tailwindcss/oxide-freebsd-x64": "4.2.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-x64-musl": "4.2.2", + "@tailwindcss/oxide-wasm32-wasi": "4.2.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz", + "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz", + "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz", + "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz", + "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz", + "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz", + "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz", + "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz", + "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz", + "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz", + "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.8.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.8.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "dev": true, + "inBundle": 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/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "inBundle": true, + "license": "0BSD", + "optional": true + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz", + "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz", + "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.2.tgz", + "integrity": "sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.2.2", + "@tailwindcss/oxide": "4.2.2", + "postcss": "^8.5.6", + "tailwindcss": "4.2.2" + } + }, + "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/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/node": { + "version": "24.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", + "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "devOptional": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.2.tgz", + "integrity": "sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/type-utils": "8.58.2", + "@typescript-eslint/utils": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.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.58.2", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.2.tgz", + "integrity": "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", + "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.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.2.tgz", + "integrity": "sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.58.2", + "@typescript-eslint/types": "^8.58.2", + "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.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.2.tgz", + "integrity": "sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2" + }, + "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.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.2.tgz", + "integrity": "sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==", + "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.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.2.tgz", + "integrity": "sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/utils": "8.58.2", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.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.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.2.tgz", + "integrity": "sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==", + "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.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.2.tgz", + "integrity": "sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.58.2", + "@typescript-eslint/tsconfig-utils": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.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.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.2.tgz", + "integrity": "sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2" + }, + "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.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.2.tgz", + "integrity": "sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.2", + "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/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/abitype": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", + "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", + "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", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "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-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.3.tgz", + "integrity": "sha512-zBQouZixDTbo3jMGqHKyePxYxr1e5W8UdTmBQ7sNtaA9M2bE32daxxPLS/jojhKOHxQ7LWwPjfiwf/fhaJWzlg==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.20.tgz", + "integrity": "sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001788", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz", + "integrity": "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/date-fns-jalali": { + "version": "4.1.0-0", + "resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz", + "integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "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/electron-to-chromium": { + "version": "1.5.340", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.340.tgz", + "integrity": "sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.2.tgz", + "integrity": "sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "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/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "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-next": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.1.6.tgz", + "integrity": "sha512-vKq40io2B0XtkkNDYyleATwblNt8xuh3FWp8SpSz3pt7P01OkBFlKsJZ2mWt5WsCySlDQLckb1zMY9yE9Qy0LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "16.1.6", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^7.0.0", + "globals": "16.4.0", + "typescript-eslint": "^8.46.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-config-next/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-config-next/node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-config-next/node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-config-next/node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-config-next/node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-config-next/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "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": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "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/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "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/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "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/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/genlayer-js": { + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/genlayer-js/-/genlayer-js-0.20.3.tgz", + "integrity": "sha512-uekZauVA4FJ0aChrQJZABY5IbFYne5idTTtNUwUNGYH/Zeuimgk3AR+eRP/8P2tZR0T8wyLQScSAxMY60znFUg==", + "license": "MIT", + "dependencies": { + "eslint-plugin-import": "^2.30.0", + "typescript-parsec": "^0.3.4", + "viem": "^2.29.0" + } + }, + "node_modules/genlayer-js/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/genlayer-js/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/genlayer-js/node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/genlayer-js/node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/genlayer-js/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "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-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "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.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "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/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "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", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "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", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "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/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "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", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.575.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.575.0.tgz", + "integrity": "sha512-VuXgKZrk0uiDlWjGGXmKV6MSk9Yy4l10qgVvzGn2AWBx1Ylt0iBexKOAoA6I7JO3m+M9oeovJd3yYENfkUbOeg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "license": "MIT" + }, + "node_modules/next": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz", + "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==", + "license": "MIT", + "dependencies": { + "@next/env": "16.1.6", + "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.8.3", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.1.6", + "@next/swc-darwin-x64": "16.1.6", + "@next/swc-linux-arm64-gnu": "16.1.6", + "@next/swc-linux-arm64-musl": "16.1.6", + "@next/swc-linux-x64-gnu": "16.1.6", + "@next/swc-linux-x64-musl": "16.1.6", + "@next/swc-win32-arm64-msvc": "16.1.6", + "@next/swc-win32-x64-msvc": "16.1.6", + "sharp": "^0.34.4" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "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", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-releases": { + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/oniguruma-parser": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", + "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz", + "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.2", + "regex": "^6.1.0", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "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/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ox": { + "version": "0.14.17", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.17.tgz", + "integrity": "sha512-jOzNb2Wlfzsr8z/GoCtd1bf6OSRuWuysvbhnHGD+7fV1WRbcBR6B0RYoe3xWnUedF7zp4l5APmS7CzAhUok/lA==", + "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/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "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", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "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", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", + "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", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-day-picker": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.14.0.tgz", + "integrity": "sha512-tBaoDWjPwe0M5pGrum4H0SR6Lyk+BO9oHnp9JbKpGKW2mlraNPgP9BMfsg5pWpwrssARmeqk7YBl2oXutZTaHA==", + "license": "MIT", + "dependencies": { + "@date-fns/tz": "^1.4.1", + "@tabby_ai/hijri-converter": "1.0.5", + "date-fns": "^4.1.0", + "date-fns-jalali": "4.1.0-0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/gpbl" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shiki": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.0.2.tgz", + "integrity": "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "4.0.2", + "@shikijs/engine-javascript": "4.0.2", + "@shikijs/engine-oniguruma": "4.0.2", + "@shikijs/langs": "4.0.2", + "@shikijs/themes": "4.0.2", + "@shikijs/types": "4.0.2", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwind-merge": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", + "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", + "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", + "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "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==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "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.58.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.2.tgz", + "integrity": "sha512-V8iSng9mRbdZjl54VJ9NKr6ZB+dW0J3TzRXRGcSbLIej9jV86ZRtlYeTKDR/QLxXykocJ5icNzbsl2+5TzIvcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.58.2", + "@typescript-eslint/parser": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/utils": "8.58.2" + }, + "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.1.0" + } + }, + "node_modules/typescript-parsec": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/typescript-parsec/-/typescript-parsec-0.3.4.tgz", + "integrity": "sha512-6RD4xOxp26BTZLopNbqT2iErqNhQZZWb5m5F07/UwGhldGvOAKOl41pZ3fxsFp04bNL+PbgMjNfb6IvJAC/uYQ==", + "license": "MIT" + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/viem": { + "version": "2.48.1", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.48.1.tgz", + "integrity": "sha512-GJC3gKV1Hngeo1IB9YanJKHH2pcmoqDymyPxddmzDtG8boXA7eFw8qdnn1PSaToJ93f3LpOZPlLLJ9beAF/Lzg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "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.17", + "ws": "8.18.3" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "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/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "devOptional": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/explorer/package.json b/explorer/package.json index 9e1687da6..9353ff02d 100644 --- a/explorer/package.json +++ b/explorer/package.json @@ -6,7 +6,8 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint" + "lint": "eslint", + "test:fee-accounting": "node scripts/test-fee-accounting.mjs" }, "dependencies": { "@radix-ui/react-collapsible": "^1.1.12", @@ -36,7 +37,7 @@ "@types/node": "^24.0.0", "@types/react": "^19", "@types/react-dom": "^19", - "eslint": "^10.0.0", + "eslint": "^9.39.4", "eslint-config-next": "16.1.6", "tailwindcss": "^4", "typescript": "^5" diff --git a/explorer/scripts/test-fee-accounting.mjs b/explorer/scripts/test-fee-accounting.mjs new file mode 100644 index 000000000..0ba5681e7 --- /dev/null +++ b/explorer/scripts/test-fee-accounting.mjs @@ -0,0 +1,211 @@ +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import Module from 'node:module'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import ts from 'typescript'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const sourcePath = path.resolve(__dirname, '../src/lib/feeAccounting.ts'); +const source = fs.readFileSync(sourcePath, 'utf8'); +const compiled = ts.transpileModule(source, { + compilerOptions: { + esModuleInterop: true, + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES2022, + }, + fileName: sourcePath, +}); + +const testModule = new Module(sourcePath); +testModule.filename = sourcePath; +testModule.paths = Module._nodeModulePaths(path.dirname(sourcePath)); +testModule._compile(compiled.outputText, sourcePath); + +const { + feeBucketRows, + feeDistributionRows, + feeMetricRows, + feeRecommendedObservedRows, + feeRecommendedPresetRows, + formatFeeAmount, + formatFeeParamsDecoded, + formatInteger, + getStudioFeeAccounting, + toBigIntAmount, +} = testModule.exports; + +function rowMap(rows) { + return Object.fromEntries(rows.map((row) => [row.label, row.value])); +} + +const WEI_PER_GEN = '1000000000000000000'; +const accounting = { + status: 'active', + paid_fee_value: '120000000000000000', + required_fee_value: '110000000000000000', + primary_fee_budget: '100000000000000000', + primary_fee_spent: '90000000000000000', + primary_fee_refunded: '10000000000000000', + execution_budget_total: '100000000000000000', + execution_fee_consumed: '90000000000000000', + genvm_message_fee_consumed: '1234', + message_fee_budget: '55000000000000000', + message_fee_consumed: '55000000000000000', + message_fee_refunded: '0', + external_message_fee_reserved: '700', + external_message_fee_reimbursed: '420', + external_message_fee_remainder: '280', + appeal_bonds_total: '1400000000000000000', + total_refunded: '10000000000000000', + fees_distribution: { + leaderTimeunitsAllocation: '100', + validatorTimeunitsAllocation: '200', + appealRounds: '1', + executionBudgetPerRound: '50000000000000000', + executionConsumed: '90000000000000000', + totalMessageFees: '55000000000000000', + rotations: ['0', '2'], + maxPriceGenPerTimeUnit: '1000000000000000', + storageFeeMaxGasPrice: '1', + receiptFeeMaxGasPrice: '1', + }, + recommended_fee_preset: { + feeValue: '132000000000000000', + paddingBps: '12000', + numOfInitialValidators: '5', + messageBudgetMode: 'allocation-preserved', + messageAllocations: [{ messageType: 1, budget: '55000000000000000' }], + distribution: { + leaderTimeunitsAllocation: '120', + validatorTimeunitsAllocation: '240', + appealRounds: '2', + executionBudgetPerRound: '60000000000000000', + executionConsumed: '0', + totalMessageFees: '55000000000000000', + rotations: ['0', '1', '1'], + maxPriceGenPerTimeUnit: '1000000000000000', + storageFeeMaxGasPrice: '1', + receiptFeeMaxGasPrice: '1', + }, + observed: { + executionFee: '90000000000000000', + messageFeeBudget: '55000000000000000', + declaredMessageFees: '55000000000000000', + externalMessageReserved: '700', + totalEstimatedFee: '145000000000000000', + totalStudioMeteredFee: '145000000000000000', + }, + }, +}; + +assert.equal(toBigIntAmount('42'), 42n); +assert.equal(toBigIntAmount(42.9), 42n); +assert.equal(toBigIntAmount('not-a-number'), null); +assert.equal(formatInteger('1000000'), '1,000,000'); +assert.equal(formatFeeAmount('999'), '999 wei'); +assert.equal( + formatFeeAmount('1000000000000000'), + '0.001 GEN (1,000,000,000,000,000 wei)', +); +assert.equal( + formatFeeAmount(WEI_PER_GEN), + '1 GEN (1,000,000,000,000,000,000 wei)', +); +assert.equal(formatFeeParamsDecoded(null), '-'); +assert.equal(formatFeeParamsDecoded({}), '-'); +assert.equal( + formatFeeParamsDecoded({ + leaderTimeunitsAllocation: 5, + validatorTimeunitsAllocation: 10, + appealRounds: 0, + executionBudgetPerRound: 0, + rotations: [0, 1], + }), + 'Leader 5, Validator 10, Appeals 0, Exec budget 0 wei, Rotations 0 / 1', +); +assert.equal( + formatFeeParamsDecoded({ + gasLimit: '21000', + maxGasPrice: '1000000000000000', + }), + 'Gas limit 21,000, Max gas price 0.001 GEN (1,000,000,000,000,000 wei)', +); +assert.equal(formatFeeParamsDecoded({ zeta: 'x', alpha: 3 }), 'alpha 3, zeta x'); + +assert.deepEqual( + getStudioFeeAccounting({ + data: { fee_accounting: accounting }, + consensus_data: { fee_accounting: { status: 'ignored' } }, + }), + accounting, +); +assert.deepEqual( + getStudioFeeAccounting({ + data: {}, + consensus_data: { fee_accounting: accounting }, + }), + accounting, +); +assert.deepEqual( + getStudioFeeAccounting({ + data: {}, + consensus_data: { + leader_receipt: [{ genvm_result: { fee_accounting: accounting } }], + }, + }), + accounting, +); +assert.equal(getStudioFeeAccounting({ data: {}, consensus_data: {} }), null); + +const metrics = rowMap(feeMetricRows(accounting)); +assert.equal(metrics['Paid fee'], '0.120 GEN (120,000,000,000,000,000 wei)'); +assert.equal(metrics['Message budget'], '0.055 GEN (55,000,000,000,000,000 wei)'); +assert.equal(metrics['GenVM message meter'], '1,234 wei'); +assert.equal(metrics['External reimbursed'], '420 wei'); +assert.equal(metrics['Appeal bonds'], '1.400 GEN (1,400,000,000,000,000,000 wei)'); + +const distribution = rowMap(feeDistributionRows(accounting)); +assert.equal(distribution['Leader time units'], '100'); +assert.equal(distribution.Rotations, '0 / 2'); +assert.equal( + distribution['Execution budget per round'], + '0.050 GEN (50,000,000,000,000,000 wei)', +); +assert.equal(distribution['Max price per time unit'], '0.001 GEN (1,000,000,000,000,000 wei)'); + +const recommended = rowMap(feeRecommendedPresetRows(accounting)); +assert.equal(recommended['Fee value'], '0.132 GEN (132,000,000,000,000,000 wei)'); +assert.equal(recommended.Padding, '12,000 bps'); +assert.equal(recommended.Validators, '5'); +assert.equal(recommended['Message budget mode'], 'allocation-preserved'); +assert.equal(recommended['Message allocations'], '1'); + +const observed = rowMap(feeRecommendedObservedRows(accounting)); +assert.equal(observed['Execution fee'], '0.090 GEN (90,000,000,000,000,000 wei)'); +assert.equal(observed['External reserved'], '700 wei'); +assert.equal(observed['Studio metered fee'], '0.145 GEN (145,000,000,000,000,000 wei)'); + +const zeroBudgetBuckets = rowMap( + feeBucketRows({ + receiptAndNondetOutput: '1', + storage: '0', + message: '0', + totalExecution: '1', + totalWithMessage: '1', + executionBudgetPerRound: '0', + executionBudgetRemaining: '0', + executionBudgetOverrun: '1', + executionBudgetExceeded: true, + }), +); +assert.equal(zeroBudgetBuckets['Receipt/nondet used'], '1 wei'); +assert.equal(zeroBudgetBuckets['Execution budget'], '0 wei'); +assert.equal(zeroBudgetBuckets['Budget remaining'], '0 wei'); +assert.equal(zeroBudgetBuckets['Budget overrun'], '1 wei'); +assert.equal(zeroBudgetBuckets['Budget exceeded'], 'true'); +assert.equal(zeroBudgetBuckets['Message meter'], '0 wei'); + +console.log('feeAccounting helper tests passed'); diff --git a/explorer/src/app/address/[addr]/AddressContent.tsx b/explorer/src/app/address/[addr]/AddressContent.tsx index d10a49ea1..06f05c311 100644 --- a/explorer/src/app/address/[addr]/AddressContent.tsx +++ b/explorer/src/app/address/[addr]/AddressContent.tsx @@ -7,8 +7,6 @@ import { Transaction, Validator, CurrentState } from '@/lib/types'; import { AddressTransactionTable } from '@/components/AddressTransactionTable'; import { CopyButton } from '@/components/CopyButton'; import { AddressDisplay } from '@/components/AddressDisplay'; -import { CodeBlock } from '@/components/CodeBlock'; -import { JsonViewer } from '@/components/JsonViewer'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'; import { Button } from '@/components/ui/button'; @@ -302,4 +300,3 @@ function AddressHeader({ title, address, backHref, icon, iconBg }: { ); } - diff --git a/explorer/src/app/address/[addr]/page.tsx b/explorer/src/app/address/[addr]/page.tsx index e1b71547c..b4200f1e7 100644 --- a/explorer/src/app/address/[addr]/page.tsx +++ b/explorer/src/app/address/[addr]/page.tsx @@ -7,13 +7,18 @@ import { ArrowLeft } from 'lucide-react'; export default async function AddressPage({ params }: { params: Promise<{ addr: string }> }) { const { addr } = await params; + let data: AddressInfo | null = null; + let error: unknown = null; try { - const data = await fetchBackend( + data = await fetchBackend( `/address/${encodeURIComponent(addr)}`, ); - return ; } catch (err) { + error = err; + } + + if (error || !data) { return (
); } + + return ; } diff --git a/explorer/src/app/contracts/page.tsx b/explorer/src/app/contracts/page.tsx index 44cf6f6fd..00002e4aa 100644 --- a/explorer/src/app/contracts/page.tsx +++ b/explorer/src/app/contracts/page.tsx @@ -68,7 +68,7 @@ function StateContent() { } }; - const SortIcon = ({ column }: { column: string }) => { + const renderSortIcon = (column: string) => { if (sortBy !== column) return ; return sortOrder === 'asc' ? @@ -103,17 +103,17 @@ function StateContent() { Balance diff --git a/explorer/src/app/providers/page.tsx b/explorer/src/app/providers/page.tsx index dbc3a7555..61dd4d405 100644 --- a/explorer/src/app/providers/page.tsx +++ b/explorer/src/app/providers/page.tsx @@ -4,19 +4,27 @@ import { ProvidersContent } from './ProvidersContent'; import { Card, CardContent } from '@/components/ui/card'; export default async function ProvidersPage() { + let data: { providers: LLMProvider[] } | null = null; + let error: unknown = null; + try { - const data = await fetchBackend<{ providers: LLMProvider[] }>('/providers'); - return ; + data = await fetchBackend<{ providers: LLMProvider[] }>('/providers'); } catch (err) { + error = err; + } + + if (error || !data) { return (

Error loading providers

- {err instanceof Error ? err.message : 'Unknown error'} + {error instanceof Error ? error.message : 'Unknown error'}

); } + + return ; } diff --git a/explorer/src/app/tx/[hash]/components/OverviewTab.tsx b/explorer/src/app/tx/[hash]/components/OverviewTab.tsx index dc9a467ab..f33b77500 100644 --- a/explorer/src/app/tx/[hash]/components/OverviewTab.tsx +++ b/explorer/src/app/tx/[hash]/components/OverviewTab.tsx @@ -9,12 +9,16 @@ import { ConsensusJourney } from '@/components/ConsensusJourney'; import { InfoRow } from '@/components/InfoRow'; import { Badge } from '@/components/ui/badge'; import { JsonViewer } from '@/components/JsonViewer'; -import { getExecutionResult, getConsensusRoundResult } from '@/lib/transactionUtils'; +import { + getExecutionResult, + getConsensusRoundResult, +} from '@/lib/transactionUtils'; import { ConsensusResultBadge } from '@/components/ConsensusResultBadge'; import { resultStatusLabel, type DecodedResult } from '@/lib/resultDecoder'; import { InputDataPanel } from '@/components/InputDataPanel'; import { DataDecodePanel } from '@/components/DataDecodePanel'; import { formatGenValue } from '@/lib/formatters'; +import { FeeAccountingPanel } from '@/components/FeeAccountingPanel'; interface OverviewTabProps { transaction: Transaction; @@ -99,14 +103,15 @@ export function OverviewTab({ transaction: tx }: OverviewTabProps) { const decodedResult = execResult?.decodedResult; const eqOutputs = execResult?.eqOutputs; - const dataObj = - tx.data && typeof tx.data === 'object' ? (tx.data as Record) : null; + const dataObj = tx.data && typeof tx.data === 'object' ? tx.data : null; const calldataB64 = (tx.type === 1 || tx.type === 2) && dataObj - ? (dataObj.calldata as string | undefined) + ? stringField(dataObj, 'calldata') : undefined; const contractCodeB64 = - tx.type === 1 && dataObj ? (dataObj.contract_code as string | undefined) : undefined; + tx.type === 1 && dataObj + ? stringField(dataObj, 'contract_code') + : undefined; return (
@@ -117,7 +122,10 @@ export function OverviewTab({ transaction: tx }: OverviewTabProps) { label="From" value={ tx.from_address ? ( - + {tx.from_address} ) : ( @@ -131,7 +139,10 @@ export function OverviewTab({ transaction: tx }: OverviewTabProps) { label="To" value={ tx.to_address ? ( - + {tx.to_address} ) : ( @@ -142,8 +153,14 @@ export function OverviewTab({ transaction: tx }: OverviewTabProps) { copyText={tx.to_address || undefined} /> - - + + Leader Only + + Leader Only + ) : tx.execution_mode === 'LEADER_SELF_VALIDATOR' ? ( - Leader + Self Validator + + Leader + Self Validator + ) : ( - Normal + + Normal + ) } /> - - + + : '-'} + value={ + consensusRound ? ( + + ) : ( + '-' + ) + } /> {tx.worker_id && } + + {contractCodeB64 && dataObj && (
-

Input Data

+

+ Input Data +

{/* Deploy: show both constructor calldata and contract source */}
@@ -178,7 +217,9 @@ export function OverviewTab({ transaction: tx }: OverviewTabProps) { {calldataB64 && !contractCodeB64 && (
-

Input Data

+

+ Input Data +

)} @@ -187,16 +228,22 @@ export function OverviewTab({ transaction: tx }: OverviewTabProps) { {(executionResult || genvmResult || decodedResult) && ( <>
-

GenVM Execution

+

+ GenVM Execution +

{executionResult && ( SUCCESS + + SUCCESS + ) : ( - {executionResult} + + {executionResult} + ) } /> @@ -213,7 +260,11 @@ export function OverviewTab({ transaction: tx }: OverviewTabProps) { {genvmResult?.stdout !== undefined && ( (empty)} + value={ + genvmResult.stdout || ( + (empty) + ) + } copyable={!!genvmResult.stdout} copyText={genvmResult.stdout} /> @@ -247,19 +298,24 @@ export function OverviewTab({ transaction: tx }: OverviewTabProps) { {Object.entries(eqOutputs).map(([key, decoded]) => (
- {key} + + {key} +
{decoded.payload != null && (
{typeof decoded.payload === 'object' && decoded.payload !== null && - 'readable' in (decoded.payload as Record) ? ( + 'readable' in + (decoded.payload as Record) ? ( {(decoded.payload as { readable: string }).readable} ) : typeof decoded.payload === 'string' ? ( - {decoded.payload} + + {decoded.payload} + ) : ( )} @@ -278,3 +334,11 @@ export function OverviewTab({ transaction: tx }: OverviewTabProps) {
); } + +function stringField( + record: Record, + key: string, +): string | undefined { + const value = record[key]; + return typeof value === 'string' ? value : undefined; +} diff --git a/explorer/src/app/validators/page.tsx b/explorer/src/app/validators/page.tsx index a797d4f09..bd333a58b 100644 --- a/explorer/src/app/validators/page.tsx +++ b/explorer/src/app/validators/page.tsx @@ -4,19 +4,27 @@ import { ValidatorsContent } from './ValidatorsContent'; import { Card, CardContent } from '@/components/ui/card'; export default async function ValidatorsPage() { + let data: { validators: Validator[] } | null = null; + let error: unknown = null; + try { - const data = await fetchBackend<{ validators: Validator[] }>('/validators'); - return ; + data = await fetchBackend<{ validators: Validator[] }>('/validators'); } catch (err) { + error = err; + } + + if (error || !data) { return (

Error loading validators

- {err instanceof Error ? err.message : 'Unknown error'} + {error instanceof Error ? error.message : 'Unknown error'}

); } + + return ; } diff --git a/explorer/src/components/FeeAccountingPanel.tsx b/explorer/src/components/FeeAccountingPanel.tsx new file mode 100644 index 000000000..a3994cd2b --- /dev/null +++ b/explorer/src/components/FeeAccountingPanel.tsx @@ -0,0 +1,372 @@ +'use client'; + +import type { Transaction } from '@/lib/types'; +import { + feeBucketRows, + feeDistributionRows, + feeMetricRows, + feeRecommendedObservedRows, + feeRecommendedPresetRows, + formatFeeAmount, + formatFeeParamsDecoded, + formatInteger, + getStudioFeeAccounting, +} from '@/lib/feeAccounting'; +import { truncateAddress, truncateHash } from '@/lib/formatters'; + +interface FeeAccountingPanelProps { + readonly transaction: Transaction; +} + +export function FeeAccountingPanel({ transaction }: Readonly) { + const accounting = getStudioFeeAccounting(transaction); + if (!accounting) return null; + + const report = accounting.execution_fee_report; + const messages = report?.messageReveal?.messages ?? []; + const genvmBuckets = report?.genvmBuckets ?? accounting.genvm_fee_bucket_report; + const messageFees = report?.messageFees; + const executionMetering = report?.executionMetering; + const metricRows = feeMetricRows(accounting); + const distributionRows = feeDistributionRows(accounting); + const recommendedRows = feeRecommendedPresetRows(accounting); + const observedRows = feeRecommendedObservedRows(accounting); + const chargeableBucketRows = feeBucketRows(report?.chargeableExecution); + const genvmBucketRows = feeBucketRows(genvmBuckets); + + return ( +
+

Fees

+ +
+ {metricRows.map((row) => ( +
+
{row.label}
+
+ {row.value} +
+
+ ))} +
+ + {distributionRows.length > 0 && ( +
+ {distributionRows.map((row) => ( +
+
+ {row.label} +
+
+ {row.value} +
+
+ ))} +
+ )} + + {recommendedRows.length > 0 && ( +
+
+
+ Recommended Preset +
+ {recommendedRows.map((row) => ( + + ))} +
+ + {observedRows.length > 0 && ( +
+
+ Observed Usage +
+ {observedRows.map((row) => ( + + ))} +
+ )} +
+ )} + + {report && ( +
+ {report.proposalReceipt && ( +
+
+ Proposal Receipt +
+ + + +
+ )} + + {report.messageReveal && ( +
+
+ Message Reveal +
+ + + + + + + + +
+ )} + +
+
+ Execution Report +
+ + + {report.totalStudioMeteredFee !== undefined && ( + + )} + {report.budgetExhaustionReason && ( + + )} + {messageFees && ( + <> + + + {messageFees.genvmMeteredConsumed !== undefined && ( + + )} + {messageFees.externalReserved !== undefined && ( + + )} + {messageFees.externalReimbursed !== undefined && ( + + )} + {messageFees.externalRemainder !== undefined && ( + + )} + {messageFees.totalConsumed !== undefined && ( + + )} + {messageFees.reportedTotal !== undefined && ( + + )} + + + + + )} + {executionMetering && ( + <> + + + + + )} + {chargeableBucketRows.length > 0 && ( + <> + Chargeable Buckets + {chargeableBucketRows.map((row) => ( + + ))} + + )} + {genvmBucketRows.length > 0 && ( + <> + GenVM Raw Buckets + {genvmBucketRows.map((row) => ( + + ))} + + )} +
+
+ )} + + {messages.length > 0 && ( +
+ + + + + + + + + + + + + + + + + {messages.map((message, index) => ( + + + + + + + + + + + + + ))} + +
TypeModeRecipientValueDataFee ParamsDeclared BudgetAllocationOnCall Key
{message.messageType}{message.messageFeeMode ?? '-'} + {truncateAddress(message.recipient)} + + {formatFeeAmount(message.value)} + + {formatInteger(message.dataBytes)} B + + {formatInteger(message.feeParamsBytes)} B + {message.feeParams && message.feeParams !== '0x' && ( + + {truncateHash(message.feeParams)} + + )} + {formatFeeParamsDecoded(message.feeParamsDecoded) !== '-' && ( + + {formatFeeParamsDecoded(message.feeParamsDecoded)} + + )} + + {formatFeeAmount(message.declaredBudget)} + + {formatInteger(message.allocationSubtreeBytes)} B + {message.allocationSubtree && + message.allocationSubtree !== '0x' && ( + + {truncateHash(message.allocationSubtree)} + + )} + + {message.onAcceptance ? 'accepted' : 'finalized'} + + {truncateHash(message.callKey)} +
+
+ )} +
+ ); +} + +function ReportRow({ + label, + value, +}: Readonly<{ label: string; value: string }>) { + return ( +
+ {label} + {value} +
+ ); +} + +function SectionLabel({ children }: Readonly<{ children: string }>) { + return ( +
+ {children} +
+ ); +} diff --git a/explorer/src/components/GlobalSearch.tsx b/explorer/src/components/GlobalSearch.tsx index 65cbd55c6..43e5839ad 100644 --- a/explorer/src/components/GlobalSearch.tsx +++ b/explorer/src/components/GlobalSearch.tsx @@ -7,7 +7,7 @@ import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog'; import { StatusBadge } from '@/components/StatusBadge'; import { Badge } from '@/components/ui/badge'; import { truncateHash, truncateAddress } from '@/lib/formatters'; -import type { Transaction, CurrentState, Validator, TransactionStatus } from '@/lib/types'; +import type { Transaction, CurrentState, Validator } from '@/lib/types'; interface SearchResults { transactions: Transaction[]; diff --git a/explorer/src/lib/feeAccounting.ts b/explorer/src/lib/feeAccounting.ts new file mode 100644 index 000000000..d3153be7a --- /dev/null +++ b/explorer/src/lib/feeAccounting.ts @@ -0,0 +1,283 @@ +import type { StudioFeeAccounting, StudioGenvmFeeBucketReport, Transaction } from './types'; + +export type FeeAccountingRow = { + label: string; + value: string; +}; + +const amountDistributionLabels = new Set([ + 'Execution budget per round', + 'Message fee budget', + 'Max price per time unit', + 'Storage gas price', + 'Receipt gas price', +]); + +const recommendedPresetFeeLabels = new Set([ + 'Fee value', + 'Execution budget per round', + 'Message fee budget', + 'Max price per time unit', + 'Storage gas price', + 'Receipt gas price', +]); + +const feeParamsDecodedLabels: Record = { + leaderTimeunitsAllocation: 'Leader', + validatorTimeunitsAllocation: 'Validator', + appealRounds: 'Appeals', + executionBudgetPerRound: 'Exec budget', + rotations: 'Rotations', + gasLimit: 'Gas limit', + maxGasPrice: 'Max gas price', +}; + +const feeParamsDecodedOrder = Object.keys(feeParamsDecodedLabels); +const feeParamsDecodedFeeKeys = new Set(['executionBudgetPerRound', 'maxGasPrice']); +const feeParamsDecodedIntegerKeys = new Set([ + 'leaderTimeunitsAllocation', + 'validatorTimeunitsAllocation', + 'appealRounds', + 'gasLimit', + 'rotations', +]); + +function asRecord(value: unknown): Record | null { + return value && typeof value === 'object' ? (value as Record) : null; +} + +function isNonEmptyRecord(value: unknown): value is Record { + const record = asRecord(value); + return Boolean(record && Object.keys(record).length > 0); +} + +export function getStudioFeeAccounting(tx: Transaction): StudioFeeAccounting | null { + const data = asRecord(tx.data); + const consensusData = asRecord(tx.consensus_data); + const leaderReceipts = consensusData?.leader_receipt; + const leaderReceipt = Array.isArray(leaderReceipts) ? asRecord(leaderReceipts[0]) : null; + const genvmResult = asRecord(leaderReceipt?.genvm_result); + const candidates = [ + data?.fee_accounting, + consensusData?.fee_accounting, + genvmResult?.fee_accounting, + ]; + const found = candidates.find(isNonEmptyRecord); + return found ?? null; +} + +export function toBigIntAmount(value: unknown): bigint | null { + if (value === null || value === undefined || value === '') return null; + if (typeof value === 'bigint') return value; + if (typeof value === 'number') { + return Number.isFinite(value) ? BigInt(Math.trunc(value)) : null; + } + if (typeof value === 'string') { + try { + return BigInt(value.trim()); + } catch { + return null; + } + } + return null; +} + +export function formatInteger(value: unknown): string { + const amount = toBigIntAmount(value); + return amount === null ? '-' : amount.toLocaleString(); +} + +function formatGenFromWei(wei: bigint): string { + const zero = BigInt(0); + const weiPerGen = BigInt('1000000000000000000'); + const negative = wei < zero; + const absWei = negative ? -wei : wei; + const whole = absWei / weiPerGen; + const remainder = absWei % weiPerGen; + const sign = negative ? '-' : ''; + + if (remainder === zero) return `${sign}${whole.toLocaleString()}`; + + const fraction = remainder.toString().padStart(18, '0'); + const trimmed = trimTrailingZeroes(fraction); + const decimals = Math.min(6, Math.max(3, trimmed.length)); + return `${sign}${whole.toLocaleString()}.${fraction.slice(0, decimals)}`; +} + +function trimTrailingZeroes(value: string): string { + let end = value.length; + while (end > 0 && value.charCodeAt(end - 1) === 48) { + end -= 1; + } + return value.slice(0, end); +} + +export function formatFeeAmount(value: unknown): string { + const amount = toBigIntAmount(value); + if (amount === null) return '-'; + + const raw = `${amount.toLocaleString()} wei`; + const zero = BigInt(0); + const absAmount = amount < zero ? -amount : amount; + if (absAmount < BigInt('1000000000000')) return raw; + + return `${formatGenFromWei(amount)} GEN (${raw})`; +} + +function formatFeeParamsDecodedValue(key: string, value: unknown): string { + if (Array.isArray(value)) { + return value + .map((item) => formatFeeParamsDecodedValue(key, item)) + .join(' / '); + } + + if (feeParamsDecodedFeeKeys.has(key)) return formatFeeAmount(value); + if (feeParamsDecodedIntegerKeys.has(key)) return formatInteger(value); + return String(value); +} + +export function formatFeeParamsDecoded(value: unknown): string { + const record = asRecord(value); + if (!record || Object.keys(record).length === 0) return '-'; + + const orderedKeys = [ + ...feeParamsDecodedOrder.filter((key) => key in record), + ...Object.keys(record) + .filter((key) => !(key in feeParamsDecodedLabels)) + .sort((left, right) => left.localeCompare(right)), + ]; + + const rows = orderedKeys + .filter((key) => record[key] !== undefined && record[key] !== null) + .map((key) => { + const label = feeParamsDecodedLabels[key] ?? key; + return `${label} ${formatFeeParamsDecodedValue(key, record[key])}`; + }); + + return rows.length > 0 ? rows.join(', ') : '-'; +} + +export function feeMetricRows(accounting: StudioFeeAccounting): FeeAccountingRow[] { + return [ + ['Paid fee', accounting.paid_fee_value], + ['Required fee', accounting.required_fee_value], + ['Primary budget', accounting.primary_fee_budget], + ['Primary spent', accounting.primary_fee_spent], + ['Primary refunded', accounting.primary_fee_refunded], + ['Execution budget', accounting.execution_budget_total], + ['Execution consumed', accounting.execution_fee_consumed], + ['GenVM message meter', accounting.genvm_message_fee_consumed], + ['Message budget', accounting.message_fee_budget], + ['Declared message spent', accounting.message_fee_consumed], + ['Declared message refunded', accounting.message_fee_refunded], + ['External reserved', accounting.external_message_fee_reserved], + ['External reimbursed', accounting.external_message_fee_reimbursed], + ['External remainder', accounting.external_message_fee_remainder], + ['Appeal bonds', accounting.appeal_bonds_total], + ['Total refunded', accounting.total_refunded], + ] + .filter(([, value]) => value !== undefined && value !== null) + .map(([label, value]) => ({ + label: String(label), + value: formatFeeAmount(value), + })); +} + +export function feeDistributionRows(accounting: StudioFeeAccounting): FeeAccountingRow[] { + const distribution = accounting.fees_distribution; + if (!distribution) return []; + return [ + ['Leader time units', distribution.leaderTimeunitsAllocation], + ['Validator time units', distribution.validatorTimeunitsAllocation], + ['Appeal rounds', distribution.appealRounds], + ['Rotations', distribution.rotations?.join(' / ')], + ['Execution budget per round', distribution.executionBudgetPerRound], + ['Message fee budget', distribution.totalMessageFees], + ['Max price per time unit', distribution.maxPriceGenPerTimeUnit], + ['Storage gas price', distribution.storageFeeMaxGasPrice], + ['Receipt gas price', distribution.receiptFeeMaxGasPrice], + ] + .filter(([, value]) => value !== undefined && value !== null) + .map(([label, value]) => ({ + label: String(label), + value: amountDistributionLabels.has(String(label)) + ? formatFeeAmount(value) + : String(value), + })); +} + +export function feeRecommendedPresetRows( + accounting: StudioFeeAccounting, +): FeeAccountingRow[] { + const preset = accounting.recommended_fee_preset; + const distribution = preset?.distribution; + if (!preset || !distribution) return []; + + return [ + ['Fee value', preset.feeValue], + ['Padding', preset.paddingBps ? `${formatInteger(preset.paddingBps)} bps` : null], + ['Validators', preset.numOfInitialValidators], + ['Leader time units', distribution.leaderTimeunitsAllocation], + ['Validator time units', distribution.validatorTimeunitsAllocation], + ['Appeal rounds', distribution.appealRounds], + ['Rotations', distribution.rotations?.join(' / ')], + ['Execution budget per round', distribution.executionBudgetPerRound], + ['Message fee budget', distribution.totalMessageFees], + ['Max price per time unit', distribution.maxPriceGenPerTimeUnit], + ['Storage gas price', distribution.storageFeeMaxGasPrice], + ['Receipt gas price', distribution.receiptFeeMaxGasPrice], + ['Message budget mode', preset.messageBudgetMode], + ['Message allocations', preset.messageAllocations?.length], + ] + .filter(([, value]) => value !== undefined && value !== null) + .map(([label, value]) => ({ + label: String(label), + value: recommendedPresetFeeLabels.has(String(label)) + ? formatFeeAmount(value) + : String(value), + })); +} + +export function feeRecommendedObservedRows( + accounting: StudioFeeAccounting, +): FeeAccountingRow[] { + const observed = accounting.recommended_fee_preset?.observed; + if (!observed) return []; + + return [ + ['Execution fee', observed.executionFee], + ['Message fee budget', observed.messageFeeBudget], + ['Declared message fees', observed.declaredMessageFees], + ['External reserved', observed.externalMessageReserved], + ['Estimated fee', observed.totalEstimatedFee], + ['Studio metered fee', observed.totalStudioMeteredFee], + ] + .filter(([, value]) => value !== undefined && value !== null) + .map(([label, value]) => ({ + label: String(label), + value: formatFeeAmount(value), + })); +} + +export function feeBucketRows( + bucketReport: StudioGenvmFeeBucketReport | null | undefined, +): FeeAccountingRow[] { + if (!bucketReport) return []; + + return [ + ['Receipt/nondet used', bucketReport.receiptAndNondetOutput, 'fee'], + ['Storage used', bucketReport.storage, 'fee'], + ['Total execution', bucketReport.totalExecution, 'fee'], + ['Execution budget', bucketReport.executionBudgetPerRound, 'fee'], + ['Budget remaining', bucketReport.executionBudgetRemaining, 'fee'], + ['Budget overrun', bucketReport.executionBudgetOverrun, 'fee'], + ['Budget exceeded', bucketReport.executionBudgetExceeded, 'boolean'], + ['Message meter', bucketReport.message, 'fee'], + ['Total with message', bucketReport.totalWithMessage, 'fee'], + ] + .filter(([, value]) => value !== undefined && value !== null) + .map(([label, value, kind]) => ({ + label: String(label), + value: kind === 'boolean' ? String(value) : formatFeeAmount(value), + })); +} diff --git a/explorer/src/lib/types.ts b/explorer/src/lib/types.ts index 01f7e5600..a5d4c9e18 100644 --- a/explorer/src/lib/types.ts +++ b/explorer/src/lib/types.ts @@ -52,6 +52,155 @@ export interface Transaction { worker_id: string | null; } +export interface StudioFeesDistribution { + leaderTimeunitsAllocation?: string | number; + validatorTimeunitsAllocation?: string | number; + appealRounds?: string | number; + executionBudgetPerRound?: string | number; + executionConsumed?: string | number; + totalMessageFees?: string | number; + rotations?: Array; + maxPriceGenPerTimeUnit?: string | number; + storageFeeMaxGasPrice?: string | number; + receiptFeeMaxGasPrice?: string | number; +} + +export interface StudioExecutionFeeReportMessage { + messageFeeMode?: 'mode1' | 'mode2' | 'external'; + messageType: string; + recipient: string; + value: string | number; + dataBytes: string | number; + onAcceptance: boolean; + saltNonce: string | number; + feeParams?: string; + feeParamsDecoded?: Record> | null; + feeParamsBytes: string | number; + declaredBudget: string | number; + allocationSubtree?: string; + allocationSubtreeBytes: string | number; + callKey: string; +} + +export interface StudioGenvmFeeBucket { + index?: string | number; + name?: string; + consumed?: string | number; +} + +export interface StudioGenvmFeeBucketReport { + receiptAndNondetOutput?: string | number; + storage?: string | number; + message?: string | number; + totalExecution?: string | number; + totalWithMessage?: string | number; + executionBudgetPerRound?: string | number; + executionBudgetRemaining?: string | number; + executionBudgetOverrun?: string | number; + executionBudgetExceeded?: boolean; + buckets?: StudioGenvmFeeBucket[]; +} + +export interface StudioExecutionFeeReport { + receiptGasPrice?: string | number; + budgetExhaustionReason?: string | null; + proposalReceipt?: { + eqBlocksOutputsLength?: string | number; + receiptBytes?: string | number; + estimatedGas?: string | number; + fee?: string | number; + }; + messageReveal?: { + messageBytes?: string | number; + messageCount?: string | number; + estimatedGas?: string | number; + fee?: string | number; + consensusAdditionalGas?: string | number; + consensusAdditionalFee?: string | number; + studioFixedOverheadGas?: string | number; + studioFixedOverheadFee?: string | number; + messages?: StudioExecutionFeeReportMessage[]; + }; + genvmBuckets?: StudioGenvmFeeBucketReport; + chargeableExecution?: StudioGenvmFeeBucketReport; + executionMetering?: { + chargeableExecutionFee?: string | number; + genvmReportedExecution?: string | number; + genvmDeltaFromChargeable?: string | number; + }; + messageFees?: { + budget?: string | number; + declaredConsumed?: string | number; + genvmMeteredConsumed?: string | number; + externalReserved?: string | number; + externalReimbursed?: string | number; + externalRemainder?: string | number; + totalConsumed?: string | number; + declaredRefunded?: string | number; + remaining?: string | number; + meteringDelta?: string | number; + reportedTotal?: string | number; + }; + totalEstimatedFee?: string | number; + totalStudioMeteredFee?: string | number; +} + +export interface StudioRecommendedFeePreset { + source?: string; + paddingBps?: string | number; + numOfInitialValidators?: string | number; + distribution?: StudioFeesDistribution; + feeValue?: string | number; + messageAllocations?: unknown[]; + messageBudgetMode?: + | 'current' + | 'observed' + | 'allocation-preserved' + | (string & {}); + observed?: { + executionFee?: string | number; + messageFeeBudget?: string | number; + declaredMessageFees?: string | number; + externalMessageReserved?: string | number; + totalEstimatedFee?: string | number; + totalStudioMeteredFee?: string | number; + }; +} + +export interface StudioFeeAccounting { + version?: string | number; + source?: string; + status?: string; + paid_fee_value?: string | number; + required_fee_value?: string | number; + primary_fee_required?: string | number; + primary_fee_budget?: string | number; + primary_fee_spent?: string | number; + primary_fee_refunded?: string | number; + execution_budget_total?: string | number; + execution_fee_consumed?: string | number; + execution_fee_consumed_buckets?: Array; + genvm_fee_consumed_buckets?: Array; + genvm_fee_bucket_report?: StudioGenvmFeeBucketReport; + genvm_message_fee_consumed?: string | number; + execution_fee_report?: StudioExecutionFeeReport; + recommended_fee_preset?: StudioRecommendedFeePreset; + message_fee_budget?: string | number; + message_fee_consumed?: string | number; + message_fee_refunded?: string | number; + external_message_fee_reserved?: string | number; + external_message_fee_reimbursed?: string | number; + external_message_fee_remainder?: string | number; + appeal_bonds_total?: string | number; + total_refunded?: string | number; + fees_distribution?: StudioFeesDistribution; + message_allocations?: unknown[]; + allocation_consumed?: Record; + message_consumption_events?: unknown[]; + refunds?: unknown[]; + top_ups?: unknown[]; +} + export interface ConsensusHistoryEntry { // Legacy format leader?: ValidatorVote; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f387ec1e8..91d0cc7e7 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,7 +24,7 @@ "cross-env": "^10.0.0", "dexie": "^4.0.4", "floating-vue": "^5.2.2", - "genlayer-js": "^1.1.1", + "genlayer-js": "^1.1.8", "hash-sum": "^2.0.0", "jump.js": "^1.0.2", "lodash-es": "^4.17.21", @@ -211,6 +211,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -680,7 +681,6 @@ "integrity": "sha512-B3e0XiZWHXgCPLRXk0dDGA2WN8eFk/MDprqRX1Xl4PPx1LAdzynGcGUg6rnidMrIQ/GSL+oelWDHdGbWtCOOoA==", "license": "Apache-2.0", "optional": true, - "peer": true, "dependencies": { "@coinbase/cdp-sdk": "^1.0.0", "brotli-wasm": "^3.0.0", @@ -699,7 +699,6 @@ "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "license": "MIT", "optional": true, - "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -719,7 +718,6 @@ ], "license": "MIT", "optional": true, - "peer": true, "dependencies": { "@adraffy/ens-normalize": "^1.10.1", "@noble/curves": "^1.6.0", @@ -877,6 +875,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" }, @@ -917,6 +916,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" } @@ -927,6 +927,7 @@ "integrity": "sha512-wxr+2gpjKRZ1eVBLhQYJxImDsRukk0DvCsEElkTMyybP+7SamWRs48o3DYE6VLEgQJFZgOoUec3t5FM5s1J1ww==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "bs58": "^5.0.0" } @@ -954,6 +955,7 @@ "integrity": "sha512-NXUmQV1f7PQ5/M4gEDKZmjEwSD//MNMXloKRc7X08DV2mLkuKUMjdFS7Klby3sLPqfBomRIy6Tk3kvbRXCaV/A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -1520,7 +1522,6 @@ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "license": "MIT", - "peer": true, "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", @@ -1544,7 +1545,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1560,15 +1560,13 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1579,7 +1577,6 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "license": "Apache-2.0", - "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1592,7 +1589,6 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", @@ -1609,15 +1605,13 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -1630,7 +1624,6 @@ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "license": "MIT", - "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -2772,6 +2765,7 @@ "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.21.1.tgz", "integrity": "sha512-QORZRjcuTKgo++XP1Pc2c2gqwRydkaExrIRfRI9vFsPA3AzuHVn5Gfmbv1ic8y34e78mr5DMBvJlelUaeOuajg==", "license": "MIT", + "peer": true, "dependencies": { "c12": "^3.3.3", "consola": "^3.4.2", @@ -2875,6 +2869,7 @@ "integrity": "sha512-54w1xCWfXuax7dz4W2M9uw0gDyh+ti/0K/MxcCUxChFh37kkdxPdfZDw5QBbuPUJHr1CiHJ1hXgSs+GgeQc5Zw==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright": "1.48.2" }, @@ -4006,48 +4001,6 @@ } } }, - "node_modules/@solana/kit": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/kit/-/kit-5.5.1.tgz", - "integrity": "sha512-irKUGiV2yRoyf+4eGQ/ZeCRxa43yjFEL1DUI5B0DkcfZw3cr0VJtVJnrG8OtVF01vT0OUfYOcUn6zJW5TROHvQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "@solana/accounts": "5.5.1", - "@solana/addresses": "5.5.1", - "@solana/codecs": "5.5.1", - "@solana/errors": "5.5.1", - "@solana/functional": "5.5.1", - "@solana/instruction-plans": "5.5.1", - "@solana/instructions": "5.5.1", - "@solana/keys": "5.5.1", - "@solana/offchain-messages": "5.5.1", - "@solana/plugin-core": "5.5.1", - "@solana/programs": "5.5.1", - "@solana/rpc": "5.5.1", - "@solana/rpc-api": "5.5.1", - "@solana/rpc-parsed-types": "5.5.1", - "@solana/rpc-spec-types": "5.5.1", - "@solana/rpc-subscriptions": "5.5.1", - "@solana/rpc-types": "5.5.1", - "@solana/signers": "5.5.1", - "@solana/sysvars": "5.5.1", - "@solana/transaction-confirmation": "5.5.1", - "@solana/transaction-messages": "5.5.1", - "@solana/transactions": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/@solana/nominal-types": { "version": "5.5.1", "resolved": "https://registry.npmjs.org/@solana/nominal-types/-/nominal-types-5.5.1.tgz", @@ -4927,6 +4880,7 @@ "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -5868,17 +5822,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/@types/react": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", - "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "csstype": "^3.2.2" - } - }, "node_modules/@types/selenium-webdriver": { "version": "4.35.5", "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-4.35.5.tgz", @@ -6042,6 +5985,7 @@ "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", @@ -6573,6 +6517,7 @@ "integrity": "sha512-CGJ25bc8fRi8Lod/3GHSvXRKi7nBo3kxh0ApW4yCjmrWmRmlT53B5E08XRSZRliygG0aVNxLrBEqPYdz/KcCtQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/utils": "4.0.18", "fflate": "^0.8.2", @@ -7106,6 +7051,7 @@ "resolved": "https://registry.npmjs.org/@wagmi/core/-/core-3.4.0.tgz", "integrity": "sha512-EU5gDsUp5t7+cuLv12/L8hfyWfCIKsBNiiBqpOqxZJxvAcAiQk4xFe2jMgaQPqApc3Omvxrk032M8AQ4N0cQeg==", "license": "MIT", + "peer": true, "dependencies": { "eventemitter3": "5.0.1", "mipd": "0.0.7", @@ -7324,21 +7270,6 @@ "ws": "^7.5.1" } }, - "node_modules/@walletconnect/jsonrpc-ws-connection/node_modules/utf-8-validate": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", - "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, "node_modules/@walletconnect/jsonrpc-ws-connection/node_modules/ws": { "version": "7.5.10", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", @@ -7669,6 +7600,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -8118,6 +8050,7 @@ "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", @@ -8355,7 +8288,6 @@ "integrity": "sha512-U3K72/JAi3jITpdhZBqzSUq+DUY697tLxOuFXB+FpAE/Ug+5C3VZrv4uA674EUZHxNAuQ9wETXNqQkxZD6oL4A==", "license": "Apache-2.0", "optional": true, - "peer": true, "engines": { "node": ">=v18.0.0" } @@ -8525,6 +8457,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -8598,21 +8531,6 @@ "node": ">=0.2.0" } }, - "node_modules/bufferutil": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", - "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, "node_modules/builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", @@ -8789,7 +8707,6 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -10237,6 +10154,7 @@ "devOptional": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -10299,6 +10217,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.2.tgz", "integrity": "sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==", "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -10355,6 +10274,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -10471,6 +10391,7 @@ "integrity": "sha512-f1J/tcbnrpgC8suPN5AtdJ5MQjuXbSU9pGRSSYAuF3SHoiYCOdEX6O22pLaRyLHXvDcOe+O5ENgc1owQ587agA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "natural-compare": "^1.4.0", @@ -10654,6 +10575,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@ethersproject/abi": "5.8.0", "@ethersproject/abstract-provider": "5.8.0", @@ -11157,9 +11079,9 @@ } }, "node_modules/genlayer-js": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/genlayer-js/-/genlayer-js-1.1.1.tgz", - "integrity": "sha512-DNKfr/E0eDigBHZ6dUx3ViCm67UJzsA9qTEmsBBPP12EnNwslo8xBobjKG2SLEni0kWVDRo8aJGeg2TBgeUy7w==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/genlayer-js/-/genlayer-js-1.1.8.tgz", + "integrity": "sha512-qlqh8oqR9Ad7FVbIdqIrHfsMPLLJ24ZRHUZ2LGMpw6DX5ySjrEWdV1X93bVIHO44cu9CLGdx8m2ubkPv78/RLg==", "license": "MIT", "dependencies": { "eslint-plugin-import": "^2.30.0", @@ -11172,7 +11094,6 @@ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", @@ -11187,7 +11108,6 @@ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@eslint/core": "^0.17.0" }, @@ -11200,7 +11120,6 @@ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.15" }, @@ -11213,7 +11132,6 @@ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "license": "Apache-2.0", - "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -11223,7 +11141,6 @@ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" @@ -11237,7 +11154,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -11254,7 +11170,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -11286,7 +11201,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -11405,7 +11319,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -11422,7 +11335,6 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "license": "Apache-2.0", - "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -11435,7 +11347,6 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", @@ -11452,8 +11363,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/genlayer-js/node_modules/minimatch": { "version": "3.1.5", @@ -11677,7 +11587,6 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -12012,21 +11921,6 @@ } } }, - "node_modules/html-encoding-sniffer/node_modules/@noble/hashes": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", - "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -12158,7 +12052,6 @@ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "license": "MIT", - "peer": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -12940,21 +12833,6 @@ "license": "MIT", "optional": true }, - "node_modules/jayson/node_modules/utf-8-validate": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", - "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, "node_modules/jayson/node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -12992,6 +12870,7 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", + "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -13152,21 +13031,6 @@ } } }, - "node_modules/jsdom/node_modules/@noble/hashes": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", - "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/jsdom/node_modules/entities": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", @@ -13491,8 +13355,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.sortby": { "version": "4.7.0", @@ -13600,6 +13463,7 @@ "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", @@ -14148,7 +14012,6 @@ "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "license": "MIT", "optional": true, - "peer": true, "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", @@ -14678,7 +14541,6 @@ ], "license": "MIT", "optional": true, - "peer": true, "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", @@ -14704,7 +14566,6 @@ "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", "license": "MIT", "optional": true, - "peer": true, "dependencies": { "@noble/hashes": "1.8.0" }, @@ -14721,7 +14582,6 @@ "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "license": "MIT", "optional": true, - "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -14735,7 +14595,6 @@ "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", "license": "MIT", "optional": true, - "peer": true, "funding": { "url": "https://github.com/sponsors/wevm" }, @@ -14810,7 +14669,6 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "license": "MIT", - "peer": true, "dependencies": { "callsites": "^3.0.0" }, @@ -15232,6 +15090,7 @@ "integrity": "sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "playwright-core": "cli.js" }, @@ -15291,6 +15150,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -15469,6 +15329,7 @@ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -16169,7 +16030,6 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -16892,7 +16752,8 @@ "version": "1.15.7", "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.7.tgz", "integrity": "sha512-Kk8wLQPlS+yi1ZEf48a4+fzHa4yxjC30M/Sr2AnQu+f/MPwvvX9XjZ6OWejiz8crBsLwSq8GHqaxaET7u6ux0A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/source-map": { "version": "0.6.1", @@ -17454,6 +17315,7 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", "license": "MIT", + "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -17687,6 +17549,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -18551,6 +18414,7 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -18664,6 +18528,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -19108,6 +18973,7 @@ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", "license": "MIT", + "peer": true, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } @@ -19119,7 +18985,6 @@ "hasInstallScript": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -19216,6 +19081,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", @@ -19340,6 +19206,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -19619,6 +19486,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -19632,6 +19500,7 @@ "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", @@ -19736,6 +19605,7 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.29.tgz", "integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==", "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.29", "@vue/compiler-sfc": "3.5.29", @@ -19981,6 +19851,7 @@ "resolved": "https://registry.npmjs.org/wagmi/-/wagmi-3.5.0.tgz", "integrity": "sha512-39uiY6Vkc28NiAHrxJzVTodoRgSVGG97EewwUxRf+jcFMTe8toAnaM8pJZA3Zw/6snMg4tSgWLJAtMnOacLe7w==", "license": "MIT", + "peer": true, "dependencies": { "@wagmi/connectors": "7.2.1", "@wagmi/core": "3.4.0", @@ -20080,21 +19951,6 @@ } } }, - "node_modules/whatwg-url/node_modules/@noble/hashes": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", - "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -20350,6 +20206,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -20550,16 +20407,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "optional": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/zustand": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz", @@ -20589,6 +20436,161 @@ "optional": true } } + }, + "node_modules/@solana/kit": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/@solana/kit/-/kit-5.5.1.tgz", + "integrity": "sha512-irKUGiV2yRoyf+4eGQ/ZeCRxa43yjFEL1DUI5B0DkcfZw3cr0VJtVJnrG8OtVF01vT0OUfYOcUn6zJW5TROHvQ==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@solana/accounts": "5.5.1", + "@solana/addresses": "5.5.1", + "@solana/codecs": "5.5.1", + "@solana/errors": "5.5.1", + "@solana/functional": "5.5.1", + "@solana/instruction-plans": "5.5.1", + "@solana/instructions": "5.5.1", + "@solana/keys": "5.5.1", + "@solana/offchain-messages": "5.5.1", + "@solana/plugin-core": "5.5.1", + "@solana/programs": "5.5.1", + "@solana/rpc": "5.5.1", + "@solana/rpc-api": "5.5.1", + "@solana/rpc-parsed-types": "5.5.1", + "@solana/rpc-spec-types": "5.5.1", + "@solana/rpc-subscriptions": "5.5.1", + "@solana/rpc-types": "5.5.1", + "@solana/signers": "5.5.1", + "@solana/sysvars": "5.5.1", + "@solana/transaction-confirmation": "5.5.1", + "@solana/transaction-messages": "5.5.1", + "@solana/transactions": "5.5.1" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@types/react": { + "version": "19.2.16", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.16.tgz", + "integrity": "sha512-esJiCAnl0kfpNdE69f3So4WJUXy95dLZydX0KwK46riIHDzHM7O9Vtf9xCHW0PXIqvgqNrswl522kA/5yx+F4w==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "optional": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/html-encoding-sniffer/node_modules/@noble/hashes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/jsdom/node_modules/@noble/hashes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/whatwg-url/node_modules/@noble/hashes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/bufferutil": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", + "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/@walletconnect/jsonrpc-ws-connection/node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/jayson/node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } } } } diff --git a/frontend/package.json b/frontend/package.json index 59cddd934..e114bc534 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -39,7 +39,7 @@ "cross-env": "^10.0.0", "dexie": "^4.0.4", "floating-vue": "^5.2.2", - "genlayer-js": "^1.1.1", + "genlayer-js": "^1.1.8", "hash-sum": "^2.0.0", "jump.js": "^1.0.2", "lodash-es": "^4.17.21", diff --git a/frontend/src/components/Simulator/ContractMethodItem.vue b/frontend/src/components/Simulator/ContractMethodItem.vue index 09d536d8b..969dc6e47 100644 --- a/frontend/src/components/Simulator/ContractMethodItem.vue +++ b/frontend/src/components/Simulator/ContractMethodItem.vue @@ -2,17 +2,27 @@ import type { ContractMethod } from 'genlayer-js/types'; import { abi } from 'genlayer-js'; import { TransactionHashVariant } from 'genlayer-js/types'; -import { ref } from 'vue'; +import { computed, ref } from 'vue'; import { Collapse } from 'vue-collapsed'; import { notify } from '@kyvg/vue3-notification'; import { ChevronDownIcon } from '@heroicons/vue/16/solid'; import { useEventTracking, useContractQueries } from '@/hooks'; import { unfoldArgsData, type ArgData } from './ContractParams'; import ContractParams from './ContractParams.vue'; -import type { ExecutionMode, ReadStateMode } from '@/types'; +import type { + ExecutionMode, + ReadStateMode, + StudioExecutionFeeReportMessage, + StudioFeeEstimateResult, +} from '@/types'; -const { callWriteMethod, callReadMethod, simulateWriteMethod, contract } = - useContractQueries(); +const { + callWriteMethod, + callReadMethod, + simulateWriteMethod, + estimateWriteMethodFees, + contract, +} = useContractQueries(); const { trackEvent } = useEventTracking(); const props = defineProps<{ @@ -27,12 +37,27 @@ const props = defineProps<{ const isExpanded = ref(false); const isCalling = ref(false); +const isEstimatingFees = ref(false); const responseMessage = ref(''); const responseMessageAccepted = ref(''); const responseMessageFinalized = ref(''); +const feeEstimateMessage = ref(''); +const feeEstimateResult = ref(null); const calldataArguments = ref({ args: [], kwargs: {} }); const payableValue = ref(''); +const WEI_PER_GEN = BigInt('1000000000000000000'); + +type FeeEstimateRow = { + label: string; + value: string; +}; + +function payableValueWei(): bigint | undefined { + return props.method.payable && payableValue.value + ? BigInt(payableValue.value) * WEI_PER_GEN + : undefined; +} const formatResponseIfNeeded = (response: string): string => { if (!response) { @@ -62,6 +87,309 @@ const formatResponseIfNeeded = (response: string): string => { return response; }; +const formatIntegerLike = ( + value: string | number | bigint | boolean | null | undefined, +): string => { + if (value === undefined || value === null) { + return ''; + } + if (typeof value === 'boolean') { + return value ? 'true' : 'false'; + } + const raw = String(value); + return /^-?\d+$/.test(raw) ? BigInt(raw).toLocaleString('en-US') : raw; +}; + +const formatFeeAmount = ( + value: string | number | bigint | null | undefined, +): string => { + const formatted = formatIntegerLike(value); + return formatted ? `${formatted} wei` : ''; +}; + +const formatPaddingBps = ( + value: string | number | bigint | null | undefined, +): string => { + if (value === undefined || value === null) { + return ''; + } + return `${formatIntegerLike(value)} bps`; +}; + +const formatRotations = (rotations: unknown): string => { + if (!Array.isArray(rotations)) { + return ''; + } + return rotations.map((rotation) => formatIntegerLike(rotation)).join(', '); +}; + +const feeParamsDecodedLabels: Record = { + leaderTimeunitsAllocation: 'Leader', + validatorTimeunitsAllocation: 'Validator', + appealRounds: 'Appeals', + executionBudgetPerRound: 'Exec budget', + rotations: 'Rotations', + gasLimit: 'Gas limit', + maxGasPrice: 'Max gas price', +}; + +const feeParamsDecodedOrder = Object.keys(feeParamsDecodedLabels); +const feeParamsDecodedFeeKeys = new Set([ + 'executionBudgetPerRound', + 'maxGasPrice', +]); +const feeParamsDecodedIntegerKeys = new Set([ + 'leaderTimeunitsAllocation', + 'validatorTimeunitsAllocation', + 'appealRounds', + 'gasLimit', + 'rotations', +]); + +const formatFeeParamsDecodedValue = (key: string, value: unknown): string => { + if (Array.isArray(value)) { + return value + .map((item) => formatFeeParamsDecodedValue(key, item)) + .join(' / '); + } + + if (feeParamsDecodedFeeKeys.has(key)) { + return formatFeeAmount(value as string | number | bigint); + } + if (feeParamsDecodedIntegerKeys.has(key)) { + return formatIntegerLike(value as string | number | bigint); + } + return String(value); +}; + +const formatFeeParamsDecoded = (value: unknown): string => { + if (!value || typeof value !== 'object') { + return ''; + } + + const record = value as Record; + const keys = Object.keys(record); + if (keys.length === 0) { + return ''; + } + + const orderedKeys = [ + ...feeParamsDecodedOrder.filter((key) => key in record), + ...keys + .filter((key) => !(key in feeParamsDecodedLabels)) + .sort((left, right) => left.localeCompare(right)), + ]; + return orderedKeys + .filter((key) => record[key] !== undefined && record[key] !== null) + .map((key) => { + const label = feeParamsDecodedLabels[key] ?? key; + return `${label} ${formatFeeParamsDecodedValue(key, record[key])}`; + }) + .join(', '); +}; + +const shortHex = (value: string | undefined, start = 8, end = 6): string => { + if (!value) { + return ''; + } + if (value.length <= start + end) { + return value; + } + return `${value.slice(0, start)}...${value.slice(-end)}`; +}; + +const addFeeEstimateRow = ( + rows: FeeEstimateRow[], + label: string, + value: string, +) => { + if (value !== '') { + rows.push({ label, value }); + } +}; + +const feeEstimateRows = computed(() => { + const result = feeEstimateResult.value; + if (!result) { + return []; + } + + const preset = result.recommendedPreset; + const distribution = preset?.distribution; + const observed = preset?.observed; + const report = result.feeReport; + const messageFees = report?.messageFees; + const metering = report?.executionMetering; + const chargeable = report?.chargeableExecution; + const proposalReceipt = report?.proposalReceipt; + const messageReveal = report?.messageReveal; + const rows: FeeEstimateRow[] = []; + + addFeeEstimateRow(rows, 'Scenario', result.scenario ?? ''); + addFeeEstimateRow( + rows, + 'Recommended fee value', + formatFeeAmount(preset?.feeValue), + ); + addFeeEstimateRow( + rows, + 'Execution budget / round', + formatFeeAmount(distribution?.executionBudgetPerRound), + ); + addFeeEstimateRow( + rows, + 'Leader time units', + formatIntegerLike(distribution?.leaderTimeunitsAllocation), + ); + addFeeEstimateRow( + rows, + 'Validator time units', + formatIntegerLike(distribution?.validatorTimeunitsAllocation), + ); + addFeeEstimateRow( + rows, + 'Message fee budget', + formatFeeAmount(distribution?.totalMessageFees), + ); + addFeeEstimateRow( + rows, + 'Appeal rounds', + formatIntegerLike(distribution?.appealRounds), + ); + addFeeEstimateRow( + rows, + 'Rotations', + formatRotations(distribution?.rotations), + ); + addFeeEstimateRow( + rows, + 'Max GEN / time unit', + formatFeeAmount(distribution?.maxPriceGenPerTimeUnit), + ); + addFeeEstimateRow( + rows, + 'Storage gas price', + formatFeeAmount(distribution?.storageFeeMaxGasPrice), + ); + addFeeEstimateRow( + rows, + 'Receipt gas price', + formatFeeAmount(distribution?.receiptFeeMaxGasPrice), + ); + addFeeEstimateRow( + rows, + 'Proposal receipt bytes', + formatIntegerLike(proposalReceipt?.receiptBytes), + ); + addFeeEstimateRow( + rows, + 'Proposal receipt gas', + formatIntegerLike(proposalReceipt?.estimatedGas), + ); + addFeeEstimateRow( + rows, + 'Message count', + formatIntegerLike(messageReveal?.messageCount), + ); + addFeeEstimateRow( + rows, + 'Message bytes', + formatIntegerLike(messageReveal?.messageBytes), + ); + addFeeEstimateRow( + rows, + 'Message reveal gas', + formatIntegerLike(messageReveal?.estimatedGas), + ); + addFeeEstimateRow(rows, 'Padding', formatPaddingBps(preset?.paddingBps)); + addFeeEstimateRow( + rows, + 'Message budget mode', + preset?.messageBudgetMode ?? '', + ); + addFeeEstimateRow( + rows, + 'Observed execution', + formatFeeAmount(observed?.executionFee), + ); + addFeeEstimateRow( + rows, + 'Observed message budget', + formatFeeAmount(observed?.messageFeeBudget), + ); + addFeeEstimateRow( + rows, + 'Observed external reserve', + formatFeeAmount(observed?.externalMessageReserved), + ); + addFeeEstimateRow( + rows, + 'Total estimated fee', + formatFeeAmount(report?.totalEstimatedFee), + ); + addFeeEstimateRow( + rows, + 'Chargeable execution', + formatFeeAmount(metering?.chargeableExecutionFee), + ); + addFeeEstimateRow( + rows, + 'Chargeable storage', + formatFeeAmount(chargeable?.storage), + ); + addFeeEstimateRow( + rows, + 'Chargeable receipt/non-det', + formatFeeAmount(chargeable?.receiptAndNondetOutput), + ); + addFeeEstimateRow( + rows, + 'Chargeable message', + formatFeeAmount(chargeable?.message), + ); + addFeeEstimateRow( + rows, + 'GenVM raw execution', + formatFeeAmount(metering?.genvmReportedExecution), + ); + addFeeEstimateRow( + rows, + 'Message fees spent', + formatFeeAmount(messageFees?.declaredConsumed), + ); + addFeeEstimateRow( + rows, + 'GenVM metered message', + formatFeeAmount(messageFees?.genvmMeteredConsumed), + ); + addFeeEstimateRow( + rows, + 'External message reserved', + formatFeeAmount(messageFees?.externalReserved), + ); + addFeeEstimateRow( + rows, + 'External message reimbursed', + formatFeeAmount(messageFees?.externalReimbursed), + ); + addFeeEstimateRow( + rows, + 'External message remainder', + formatFeeAmount(messageFees?.externalRemainder), + ); + addFeeEstimateRow( + rows, + 'Message fees remaining', + formatFeeAmount(messageFees?.remaining), + ); + + return rows; +}); + +const feeEstimateMessages = computed(() => { + return feeEstimateResult.value?.feeReport?.messageReveal?.messages ?? []; +}); + const handleCallReadMethod = async () => { responseMessage.value = ''; isCalling.value = true; @@ -107,11 +435,7 @@ const handleCallWriteMethod = async () => { responseMessageAccepted.value = ''; responseMessageFinalized.value = ''; - const WEI_PER_GEN = BigInt('1000000000000000000'); - const simValue = - props.method.payable && payableValue.value - ? BigInt(payableValue.value) * WEI_PER_GEN - : undefined; + const simValue = payableValueWei(); const result = await simulateWriteMethod({ method: props.name, args: unfoldArgsData({ @@ -137,11 +461,7 @@ const handleCallWriteMethod = async () => { } else { // Real transaction mode // User inputs GEN, convert to wei (1 GEN = 10^18 wei) - const WEI_PER_GEN = BigInt('1000000000000000000'); - const txValue = - props.method.payable && payableValue.value - ? BigInt(payableValue.value) * WEI_PER_GEN - : BigInt(0); + const txValue = payableValueWei() ?? BigInt(0); await callWriteMethod({ method: props.name, executionMode: props.executionMode, @@ -173,6 +493,52 @@ const handleCallWriteMethod = async () => { isCalling.value = false; } }; + +const handleEstimateFees = async () => { + isEstimatingFees.value = true; + feeEstimateMessage.value = ''; + feeEstimateResult.value = null; + + try { + const result = await estimateWriteMethodFees({ + method: props.name, + args: unfoldArgsData({ + args: calldataArguments.value.args, + kwargs: calldataArguments.value.kwargs, + }), + value: payableValueWei(), + }); + + feeEstimateResult.value = result; + feeEstimateMessage.value = JSON.stringify( + { + scenario: result.scenario, + feeReport: result.feeReport, + recommendedPreset: result.recommendedPreset, + }, + null, + 2, + ); + + notify({ + text: 'Fee estimate completed', + type: 'success', + }); + + trackEvent('estimated_write_method_fees', { + contract_name: contract.value?.name || '', + method_name: props.name, + }); + } catch (error) { + notify({ + title: 'Error', + text: (error as Error)?.message || 'Error estimating transaction fees', + type: 'error', + }); + } finally { + isEstimatingFees.value = false; + } +};