diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..26f5a381 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Hyerin (April) Kim + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/projects/HashScope/LICENSE b/projects/HashScope/LICENSE new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/projects/HashScope/LICENSE @@ -0,0 +1 @@ + diff --git a/projects/HashScope/MCP/README.md b/projects/HashScope/MCP/README.md new file mode 100644 index 00000000..d8ef8c2a --- /dev/null +++ b/projects/HashScope/MCP/README.md @@ -0,0 +1,160 @@ +# HashScope MCP - LangChain Tool Integration + +HashScope MCP는 AI Agent가 HashScope API를 통해 암호화폐 데이터에 쉽게 접근할 수 있도록 하는 LangChain Tool 통합 라이브러리입니다. + +## 배포 링크 + +- **API 엔드포인트**: [https://hashkey.sungwoonsong.com/api](https://hashkey.sungwoonsong.com/api) +- **API 문서**: [https://hashkey.sungwoonsong.com/docs](https://hashkey.sungwoonsong.com/docs) + +## 설치 방법 + +```bash +pip install -e . +``` + +## 사용 방법 + +### 기본 사용법 + +```python +from langchain.agents import initialize_agent, AgentType +from langchain.llms import OpenAI +from hashscope_mcp import HashScopeToolkit + +# HashScope API 키 설정 +api_key_id = "hsk_your_api_key_id" +api_key_secret = "sk_your_api_key_secret" + +# HashScope 도구 초기화 (로컬 서버 사용) +toolkit = HashScopeToolkit(api_key_id=api_key_id, api_key_secret=api_key_secret, base_url="https://hashkey.sungwoonsong.com") +tools = toolkit.get_tools() + +# LangChain Agent 초기화 +llm = OpenAI(temperature=0) +agent = initialize_agent( + tools=tools, + llm=llm, + agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, + verbose=True +) + +# Agent 실행 +agent.run("비트코인의 현재 가격과 김치 프리미엄을 알려줘") +``` + +### 개별 도구 사용 + +```python +from hashscope_mcp import ( + get_btc_usd_tool, + get_btc_krw_tool, + get_kimchi_premium_tool +) +from hashscope_mcp.hashscope_client import HashScopeClient + +# HashScope 클라이언트 초기화 +client = HashScopeClient( + api_key_id="hsk_your_api_key_id", + api_key_secret="sk_your_api_key_secret", + base_url="https://hashkey.sungwoonsong.com" +) + +# 개별 도구 생성 +btc_usd_tool = get_btc_usd_tool(client) +btc_krw_tool = get_btc_krw_tool(client) +kimchi_premium_tool = get_kimchi_premium_tool(client) + +# 도구 사용 +btc_usd = btc_usd_tool.run() +print(btc_usd) + +btc_krw = btc_krw_tool.run() +print(btc_krw) + +kimchi_premium = kimchi_premium_tool.run() +print(kimchi_premium) +``` + +## 사용 가능한 도구 + +HashScope MCP는 다음과 같은 도구를 제공합니다: + +### 암호화폐 데이터 +1. **get_btc_usd_price**: BTC/USD 가격 조회 (Binance) +2. **get_btc_krw_price**: BTC/KRW 가격 조회 (Upbit) +3. **get_usdt_krw_price**: USDT/KRW 가격 조회 (Upbit) +4. **get_kimchi_premium**: 김치 프리미엄 조회 + +### 소셜 미디어 데이터 +5. **get_trump_posts**: Donald Trump의 최신 포스트 조회 +6. **get_elon_posts**: Elon Musk의 최신 포스트 조회 +7. **get_x_trends**: X(Twitter) 트렌드 조회 + +### 파생상품 시장 데이터 +8. **get_funding_rates**: 암호화폐 선물 시장의 펀딩 비율 조회 +9. **get_open_interest**: 암호화폐 파생상품의 미결제 약정 비율 조회 + +### 블록체인 프로젝트 데이터 +10. **get_hsk_updates**: HashKey Chain의 최신 업데이트 조회 +11. **get_ethereum_standards**: 이더리움 표준 및 제안 정보 조회 +12. **get_solana_updates**: Solana 블록체인의 최신 업데이트 조회 + +### 오픈소스 데이터 +13. **get_bitcoin_activity**: Bitcoin Core 저장소 활동 조회 +14. **get_ethereum_activity**: Ethereum Core 저장소 활동 조회 + +## 예제: LangChain과 함께 사용하기 + +```python +from langchain.agents import initialize_agent, AgentType +from langchain.chat_models import ChatOpenAI +from hashscope_mcp import HashScopeToolkit + +# API 키 설정 +api_key_id = "hsk_your_api_key_id" +api_key_secret = "sk_your_api_key_secret" + +# HashScope 도구 초기화 +toolkit = HashScopeToolkit(api_key_id=api_key_id, api_key_secret=api_key_secret, base_url="https://hashkey.sungwoonsong.com") +tools = toolkit.get_tools() + +# LangChain Agent 초기화 +llm = ChatOpenAI(model="gpt-4", temperature=0) +agent = initialize_agent( + tools=tools, + llm=llm, + agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, + verbose=True +) + +# 복잡한 질문에 대한 응답 +response = agent.run(""" +다음 질문에 답해줘: +1. 비트코인의 현재 가격은 얼마인가? (USD와 KRW 모두) +2. 현재 김치 프리미엄은 얼마인가? +3. 최근 Elon Musk는 어떤 내용을 포스팅했는가? +4. HashKey Chain의 최신 업데이트는 무엇인가? +""") + +print(response) +``` + +## API 키 발급 방법 + +HashScope API 키를 발급받으려면: + +1. [HashScope 웹사이트](https://hashscope.vercel.app/)에 방문하여 계정을 생성합니다. +2. 대시보드에서 "API 키 관리" 섹션으로 이동합니다. +3. "새 API 키 생성" 버튼을 클릭합니다. +4. API 키 ID와 시크릿을 안전하게 저장합니다. + +## 주의사항 + +- API 키 시크릿은 절대로 공개 저장소에 커밋하지 마세요. +- 환경 변수나 안전한 시크릿 관리 도구를 사용하여 API 키를 관리하세요. +- API 사용량에 따라 과금될 수 있으므로 사용량을 모니터링하세요. + +## 라이선스 + +MIT License diff --git a/projects/HashScope/MCP/__init__.py b/projects/HashScope/MCP/__init__.py new file mode 100644 index 00000000..bb043785 --- /dev/null +++ b/projects/HashScope/MCP/__init__.py @@ -0,0 +1,21 @@ +""" +HashScope MCP - LangChain Tool Integration + +This package provides tools for integrating HashScope API with LangChain. +""" + +from .hashscope_tools import ( + HashScopeToolkit, + get_crypto_price_tool, + get_crypto_market_data_tool, + get_crypto_onchain_data_tool, + get_crypto_social_data_tool, +) + +__all__ = [ + "HashScopeToolkit", + "get_crypto_price_tool", + "get_crypto_market_data_tool", + "get_crypto_onchain_data_tool", + "get_crypto_social_data_tool", +] diff --git a/projects/HashScope/MCP/example.py b/projects/HashScope/MCP/example.py new file mode 100644 index 00000000..506da2b0 --- /dev/null +++ b/projects/HashScope/MCP/example.py @@ -0,0 +1,66 @@ +""" +HashScope MCP Example - Using HashScope with LangChain + +This example demonstrates how to use HashScope API with LangChain to create +an AI agent that can access cryptocurrency data. +""" + +import os +from langchain.agents import initialize_agent, AgentType +from langchain.chat_models import ChatOpenAI +from hashscope_tools import HashScopeToolkit + +def main(): + # API 키 설정 (환경 변수에서 가져오거나 직접 입력) + api_key_id = os.environ.get("HASHSCOPE_API_KEY_ID", "hsk_your_api_key_id") + api_key_secret = os.environ.get("HASHSCOPE_API_KEY_SECRET", "sk_your_api_key_secret") + + # 개발 환경에서는 로컬 서버 사용 + base_url = os.environ.get("HASHSCOPE_API_URL", "http://localhost:8001") + + print(f"Connecting to HashScope API at {base_url}") + + # HashScope 도구 초기화 + toolkit = HashScopeToolkit( + api_key_id=api_key_id, + api_key_secret=api_key_secret, + base_url=base_url + ) + tools = toolkit.get_tools() + + # 사용 가능한 도구 출력 + print(f"Available tools: {len(tools)}") + for tool in tools: + print(f"- {tool.name}: {tool.description}") + + # OpenAI API 키 확인 + openai_api_key = os.environ.get("OPENAI_API_KEY") + if not openai_api_key: + print("Warning: OPENAI_API_KEY not set. LangChain agent example will not work.") + return + + # LangChain Agent 초기화 + llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0) + agent = initialize_agent( + tools=tools, + llm=llm, + agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, + verbose=True + ) + + # 사용자 입력 받기 + query = input("\nEnter your cryptocurrency question (or press Enter to use default): ") + if not query: + query = "비트코인의 현재 가격과 지난 7일간의 가격 변화, 그리고 소셜 미디어에서의 감성을 분석해줘." + print(f"Using default query: {query}") + + # Agent 실행 + try: + response = agent.run(query) + print("\nAgent Response:") + print(response) + except Exception as e: + print(f"Error running agent: {e}") + +if __name__ == "__main__": + main() diff --git a/projects/HashScope/MCP/hashscope_client.py b/projects/HashScope/MCP/hashscope_client.py new file mode 100644 index 00000000..386d797f --- /dev/null +++ b/projects/HashScope/MCP/hashscope_client.py @@ -0,0 +1,207 @@ +""" +HashScope API Client + +This module provides a client for interacting with the HashScope API. +""" + +import requests +from typing import Dict, Any, Optional, List, Union +import json + +class HashScopeClient: + """ + Client for interacting with the HashScope API. + """ + + def __init__(self, api_key_id: str, api_key_secret: str, base_url: str = "https://hashkey.sungwoonsong.com/api"): + """ + Initialize the HashScope API client. + + Args: + api_key_id: The API key ID + api_key_secret: The API key secret + base_url: The base URL for the HashScope API (default: https://hashkey.sungwoonsong.com/api) + """ + self.api_key_id = api_key_id + self.api_key_secret = api_key_secret + self.base_url = base_url.rstrip('/') + self.session = requests.Session() + self.session.headers.update({ + 'api-key-id': api_key_id, + 'api-key-secret': api_key_secret, + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }) + + def _make_request(self, method: str, endpoint: str, params: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + """ + Make a request to the HashScope API. + + Args: + method: The HTTP method to use + endpoint: The API endpoint + params: Query parameters + data: Request body data + + Returns: + The API response as a dictionary + """ + url = f"{self.base_url}/{endpoint.lstrip('/')}" + + try: + if method.lower() == 'get': + response = self.session.get(url, params=params) + elif method.lower() == 'post': + response = self.session.post(url, params=params, json=data) + elif method.lower() == 'put': + response = self.session.put(url, params=params, json=data) + elif method.lower() == 'delete': + response = self.session.delete(url, params=params, json=data) + else: + raise ValueError(f"Unsupported HTTP method: {method}") + + response.raise_for_status() + return response.json() + except requests.exceptions.RequestException as e: + if hasattr(e, 'response') and e.response is not None: + try: + error_data = e.response.json() + error_message = error_data.get('detail', str(e)) + except ValueError: + error_message = e.response.text or str(e) + else: + error_message = str(e) + + raise Exception(f"HashScope API error: {error_message}") + + # Crypto Price API + def get_btc_usd(self) -> Dict[str, Any]: + """ + Get the current BTC price in USD from Binance. + + Returns: + The current BTC/USD price data + """ + return self._make_request('get', '/crypto/btc/usd') + + def get_btc_krw(self) -> Dict[str, Any]: + """ + Get the current BTC price in KRW from Upbit. + + Returns: + The current BTC/KRW price data + """ + return self._make_request('get', '/crypto/btc/krw') + + def get_usdt_krw(self) -> Dict[str, Any]: + """ + Get the current USDT price in KRW from Upbit. + + Returns: + The current USDT/KRW price data + """ + return self._make_request('get', '/crypto/usdt/krw') + + def get_kimchi_premium(self) -> Dict[str, Any]: + """ + Get the kimchi premium percentage between Korean and global markets. + + Returns: + The current kimchi premium data + """ + return self._make_request('get', '/crypto/kimchi-premium') + + # Social Media API + def get_trump_posts(self) -> Dict[str, Any]: + """ + Get Donald Trump's latest posts from Truth Social. + + Returns: + Latest posts from Donald Trump + """ + return self._make_request('get', '/social/trump') + + def get_elon_posts(self) -> Dict[str, Any]: + """ + Get Elon Musk's latest posts from X (Twitter). + + Returns: + Latest posts from Elon Musk + """ + return self._make_request('get', '/social/elon') + + def get_x_trends(self) -> Dict[str, Any]: + """ + Get current trending topics on X (Twitter). + + Returns: + Current trending topics on X + """ + return self._make_request('get', '/social/x/trends') + + # Derivatives Market API + def get_funding_rates(self) -> Dict[str, Any]: + """ + Get current funding rates for major cryptocurrency futures markets. + + Returns: + Current funding rates data + """ + return self._make_request('get', '/derivatives/funding-rates') + + def get_open_interest(self) -> Dict[str, Any]: + """ + Get open interest ratios for major cryptocurrency derivatives. + + Returns: + Open interest data + """ + return self._make_request('get', '/derivatives/open-interest') + + # Blockchain Projects API + def get_hsk_updates(self) -> Dict[str, Any]: + """ + Get latest updates and developments from HashKey Chain. + + Returns: + Latest updates from HashKey Chain + """ + return self._make_request('get', '/projects/hsk') + + def get_ethereum_standards(self) -> Dict[str, Any]: + """ + Get information about new Ethereum standards and proposals. + + Returns: + Information about Ethereum standards + """ + return self._make_request('get', '/projects/ethereum/standards') + + def get_solana_updates(self) -> Dict[str, Any]: + """ + Get latest updates and developments from Solana blockchain. + + Returns: + Latest updates from Solana blockchain + """ + return self._make_request('get', '/projects/solana') + + # Open Source API + def get_bitcoin_activity(self) -> Dict[str, Any]: + """ + Get latest pull requests, stars, and activities from Bitcoin Core repository. + + Returns: + Latest activities from Bitcoin Core + """ + return self._make_request('get', '/opensource/bitcoin') + + def get_ethereum_activity(self) -> Dict[str, Any]: + """ + Get latest pull requests, stars, and activities from Ethereum Core repositories. + + Returns: + Latest activities from Ethereum Core + """ + return self._make_request('get', '/opensource/ethereum') diff --git a/projects/HashScope/MCP/hashscope_tools.py b/projects/HashScope/MCP/hashscope_tools.py new file mode 100644 index 00000000..f0aeafd1 --- /dev/null +++ b/projects/HashScope/MCP/hashscope_tools.py @@ -0,0 +1,382 @@ +""" +HashScope LangChain Tools + +This module provides LangChain tools for interacting with the HashScope API. +""" + +from typing import Dict, Any, List, Optional +from langchain.tools import BaseTool, StructuredTool, Tool +from langchain.pydantic_v1 import BaseModel, Field +from .hashscope_client import HashScopeClient + +class HashScopeToolkit: + """ + Toolkit for HashScope API tools. + """ + + def __init__(self, api_key_id: str, api_key_secret: str, base_url: str = "https://hashkey.sungwoonsong.com"): + """ + Initialize the HashScope toolkit. + + Args: + api_key_id: The API key ID + api_key_secret: The API key secret + base_url: The base URL for the HashScope API (default: https://hashkey.sungwoonsong.com) + """ + self.client = HashScopeClient(api_key_id, api_key_secret, base_url) + + def get_tools(self) -> List[BaseTool]: + """ + Get all available HashScope tools. + + Returns: + A list of LangChain tools + """ + return [ + get_btc_usd_tool(self.client), + get_btc_krw_tool(self.client), + get_usdt_krw_tool(self.client), + get_kimchi_premium_tool(self.client), + get_trump_posts_tool(self.client), + get_elon_posts_tool(self.client), + get_x_trends_tool(self.client), + get_funding_rates_tool(self.client), + get_open_interest_tool(self.client), + get_hsk_updates_tool(self.client), + get_ethereum_standards_tool(self.client), + get_solana_updates_tool(self.client), + get_bitcoin_activity_tool(self.client), + get_ethereum_activity_tool(self.client), + ] + + +# Tool Definitions for Cryptocurrency Data +def get_btc_usd_tool(client: HashScopeClient) -> BaseTool: + """Create a tool for getting BTC/USD price.""" + + def _run() -> str: + try: + result = client.get_btc_usd() + return f"Current price of BTC is {result['price']} USD. Last updated: {result['timestamp']}" + except Exception as e: + return f"Error fetching BTC/USD price: {str(e)}" + + return Tool.from_function( + func=_run, + name="get_btc_usd_price", + description="Get the current BTC price in USD from Binance.", + ) + +def get_btc_krw_tool(client: HashScopeClient) -> BaseTool: + """Create a tool for getting BTC/KRW price.""" + + def _run() -> str: + try: + result = client.get_btc_krw() + return f"Current price of BTC is {result['price']} KRW. Last updated: {result['timestamp']}" + except Exception as e: + return f"Error fetching BTC/KRW price: {str(e)}" + + return Tool.from_function( + func=_run, + name="get_btc_krw_price", + description="Get the current BTC price in KRW from Upbit.", + ) + +def get_usdt_krw_tool(client: HashScopeClient) -> BaseTool: + """Create a tool for getting USDT/KRW price.""" + + def _run() -> str: + try: + result = client.get_usdt_krw() + return f"Current price of USDT is {result['price']} KRW. Last updated: {result['timestamp']}" + except Exception as e: + return f"Error fetching USDT/KRW price: {str(e)}" + + return Tool.from_function( + func=_run, + name="get_usdt_krw_price", + description="Get the current USDT price in KRW from Upbit.", + ) + +def get_kimchi_premium_tool(client: HashScopeClient) -> BaseTool: + """Create a tool for getting kimchi premium.""" + + def _run() -> str: + try: + result = client.get_kimchi_premium() + return f"Current kimchi premium is {result['premium']}%. BTC/USD: ${result['btc_usd']}, BTC/KRW: ₩{result['btc_krw']}, USDT/KRW: ₩{result['usdt_krw']}" + except Exception as e: + return f"Error fetching kimchi premium: {str(e)}" + + return Tool.from_function( + func=_run, + name="get_kimchi_premium", + description="Get the kimchi premium percentage between Korean and global markets.", + ) + +# Tool Definitions for Social Media +def get_trump_posts_tool(client: HashScopeClient) -> BaseTool: + """Create a tool for getting Trump's latest posts.""" + + def _run() -> str: + try: + posts = client.get_trump_posts() + response = "Donald Trump's latest posts from Truth Social:\n\n" + + for i, post in enumerate(posts, 1): + response += f"{i}. [{post['timestamp']}] {post['content']}\n" + response += f" Likes: {post['likes']}, Reposts: {post['reposts']}\n\n" + + return response + except Exception as e: + return f"Error fetching Trump's posts: {str(e)}" + + return Tool.from_function( + func=_run, + name="get_trump_posts", + description="Get Donald Trump's latest posts from Truth Social.", + ) + +def get_elon_posts_tool(client: HashScopeClient) -> BaseTool: + """Create a tool for getting Elon Musk's latest posts.""" + + def _run() -> str: + try: + posts = client.get_elon_posts() + response = "Elon Musk's latest posts from X (Twitter):\n\n" + + for i, post in enumerate(posts, 1): + response += f"{i}. [{post['timestamp']}] {post['content']}\n" + response += f" Likes: {post['likes']}, Reposts: {post['reposts']}\n\n" + + return response + except Exception as e: + return f"Error fetching Elon Musk's posts: {str(e)}" + + return Tool.from_function( + func=_run, + name="get_elon_posts", + description="Get Elon Musk's latest posts from X (Twitter).", + ) + +def get_x_trends_tool(client: HashScopeClient) -> BaseTool: + """Create a tool for getting X (Twitter) trends.""" + + def _run() -> str: + try: + trends = client.get_x_trends() + response = "Current trending topics on X (Twitter):\n\n" + + for i, trend in enumerate(trends, 1): + response += f"{i}. {trend['name']} - {trend['tweet_count']} tweets\n" + response += f" Category: {trend['category']}\n\n" + + return response + except Exception as e: + return f"Error fetching X trends: {str(e)}" + + return Tool.from_function( + func=_run, + name="get_x_trends", + description="Get current trending topics on X (Twitter).", + ) + +# Tool Definitions for Derivatives Market +def get_funding_rates_tool(client: HashScopeClient) -> BaseTool: + """Create a tool for getting funding rates.""" + + def _run() -> str: + try: + rates = client.get_funding_rates() + response = "Current funding rates for major cryptocurrency futures markets:\n\n" + + for i, rate in enumerate(rates, 1): + response += f"{i}. {rate['symbol']} on {rate['exchange']}: {rate['rate']*100:.4f}%\n" + response += f" Next funding time: {rate['next_funding_time']}, Interval: {rate['interval']}\n\n" + + return response + except Exception as e: + return f"Error fetching funding rates: {str(e)}" + + return Tool.from_function( + func=_run, + name="get_funding_rates", + description="Get current funding rates for major cryptocurrency futures markets.", + ) + +def get_open_interest_tool(client: HashScopeClient) -> BaseTool: + """Create a tool for getting open interest data.""" + + def _run() -> str: + try: + data = client.get_open_interest() + response = "Open interest ratios for major cryptocurrency derivatives:\n\n" + + for i, item in enumerate(data, 1): + response += f"{i}. {item['symbol']} on {item['exchange']}:\n" + response += f" Open Interest: {item['open_interest']} coins (${item['open_interest_usd']:,.2f})\n" + response += f" 24h Change: {item['change_24h']}%\n\n" + + return response + except Exception as e: + return f"Error fetching open interest data: {str(e)}" + + return Tool.from_function( + func=_run, + name="get_open_interest", + description="Get open interest ratios for major cryptocurrency derivatives.", + ) + +# Tool Definitions for Blockchain Projects +def get_hsk_updates_tool(client: HashScopeClient) -> BaseTool: + """Create a tool for getting HashKey Chain updates.""" + + def _run() -> str: + try: + updates = client.get_hsk_updates() + response = "Latest updates and developments from HashKey Chain:\n\n" + + for i, update in enumerate(updates, 1): + response += f"{i}. {update['title']}\n" + response += f" Date: {update['date']}\n" + response += f" Type: {update['type']}\n" + response += f" Description: {update['description']}\n\n" + + return response + except Exception as e: + return f"Error fetching HSK updates: {str(e)}" + + return Tool.from_function( + func=_run, + name="get_hsk_updates", + description="Get latest updates and developments from HashKey Chain.", + ) + +def get_ethereum_standards_tool(client: HashScopeClient) -> BaseTool: + """Create a tool for getting Ethereum standards information.""" + + def _run() -> str: + try: + standards = client.get_ethereum_standards() + response = "Information about new Ethereum standards and proposals:\n\n" + + for i, standard in enumerate(standards, 1): + response += f"{i}. EIP-{standard['eip_number']}: {standard['title']}\n" + response += f" Status: {standard['status']}, Type: {standard['type']}, Category: {standard['category']}\n" + response += f" Author: {standard['author']}, Created: {standard['created']}\n" + response += f" Description: {standard['description']}\n\n" + + return response + except Exception as e: + return f"Error fetching Ethereum standards: {str(e)}" + + return Tool.from_function( + func=_run, + name="get_ethereum_standards", + description="Get information about new Ethereum standards and proposals.", + ) + +def get_solana_updates_tool(client: HashScopeClient) -> BaseTool: + """Create a tool for getting Solana updates.""" + + def _run() -> str: + try: + updates = client.get_solana_updates() + response = "Latest updates and developments from Solana blockchain:\n\n" + + for i, update in enumerate(updates, 1): + response += f"{i}. {update['title']}\n" + response += f" Date: {update['date']}\n" + response += f" Type: {update['type']}\n" + response += f" Description: {update['description']}\n\n" + + return response + except Exception as e: + return f"Error fetching Solana updates: {str(e)}" + + return Tool.from_function( + func=_run, + name="get_solana_updates", + description="Get latest updates and developments from Solana blockchain.", + ) + +# Tool Definitions for Open Source +def get_bitcoin_activity_tool(client: HashScopeClient) -> BaseTool: + """Create a tool for getting Bitcoin Core repository activity.""" + + def _run() -> str: + try: + activity = client.get_bitcoin_activity() + stats = activity['stats'] + prs = activity['pull_requests'] + + response = "Latest activities from Bitcoin Core repository:\n\n" + response += f"Repository Stats:\n" + response += f"- Stars: {stats['stars']:,}\n" + response += f"- Forks: {stats['forks']:,}\n" + response += f"- Open Issues: {stats['open_issues']:,}\n" + response += f"- Last Commit: {stats['last_commit']}\n" + response += f"- Release Version: {stats['release_version']}\n\n" + + response += f"Recent Pull Requests:\n" + for i, pr in enumerate(prs, 1): + response += f"{i}. {pr['title']} (#{pr['id']})\n" + response += f" Author: {pr['author']}, State: {pr['state']}\n" + response += f" Created: {pr['created_at']}, Comments: {pr['comments']}\n" + response += f" Changes: +{pr['additions']}, -{pr['deletions']}\n\n" + + return response + except Exception as e: + return f"Error fetching Bitcoin Core activity: {str(e)}" + + return Tool.from_function( + func=_run, + name="get_bitcoin_activity", + description="Get latest pull requests, stars, and activities from Bitcoin Core repository.", + ) + +def get_ethereum_activity_tool(client: HashScopeClient) -> BaseTool: + """Create a tool for getting Ethereum Core repositories activity.""" + + def _run() -> str: + try: + activity = client.get_ethereum_activity() + + response = "Latest activities from Ethereum Core repositories:\n\n" + + # Go-Ethereum + go_eth = activity['go-ethereum'] + response += f"Go-Ethereum Repository:\n" + response += f"- Stars: {go_eth['stats']['stars']:,}\n" + response += f"- Forks: {go_eth['stats']['forks']:,}\n" + response += f"- Open Issues: {go_eth['stats']['open_issues']:,}\n" + response += f"- Release Version: {go_eth['stats']['release_version']}\n\n" + + response += f"Recent Pull Requests:\n" + for i, pr in enumerate(go_eth['pull_requests'], 1): + response += f"{i}. {pr['title']} (#{pr['id']})\n" + response += f" Author: {pr['author']}, State: {pr['state']}\n\n" + + # Consensus-Specs + cons_specs = activity['consensus-specs'] + response += f"\nConsensus-Specs Repository:\n" + response += f"- Stars: {cons_specs['stats']['stars']:,}\n" + response += f"- Forks: {cons_specs['stats']['forks']:,}\n" + response += f"- Open Issues: {cons_specs['stats']['open_issues']:,}\n" + response += f"- Release Version: {cons_specs['stats']['release_version']}\n\n" + + response += f"Recent Pull Requests:\n" + for i, pr in enumerate(cons_specs['pull_requests'], 1): + response += f"{i}. {pr['title']} (#{pr['id']})\n" + response += f" Author: {pr['author']}, State: {pr['state']}\n\n" + + return response + except Exception as e: + return f"Error fetching Ethereum Core activity: {str(e)}" + + return Tool.from_function( + func=_run, + name="get_ethereum_activity", + description="Get latest pull requests, stars, and activities from Ethereum Core repositories.", + ) diff --git a/projects/HashScope/MCP/setup.py b/projects/HashScope/MCP/setup.py new file mode 100644 index 00000000..232b286b --- /dev/null +++ b/projects/HashScope/MCP/setup.py @@ -0,0 +1,29 @@ +""" +Setup script for HashScope MCP package. +""" + +from setuptools import setup, find_packages + +setup( + name="hashscope-mcp", + version="0.1.0", + description="HashScope API integration with LangChain", + author="HashScope Team", + author_email="info@hashscope.io", + packages=find_packages(), + install_requires=[ + "requests>=2.25.0", + "langchain>=0.0.267", + "pydantic>=2.0.0", + ], + classifiers=[ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + ], + python_requires=">=3.8", +) diff --git a/projects/HashScope/README.md b/projects/HashScope/README.md new file mode 100644 index 00000000..519a7050 --- /dev/null +++ b/projects/HashScope/README.md @@ -0,0 +1,152 @@ +# Project Name + +HashScope + +## Overview + +HashScope is an API platform built on the HSK blockchain that bridges real-world data with blockchain technology, rewarding data contributors with HSK tokens. Our platform provides high-quality, real-time data through standardized APIs, while also offering Model Context Protocol (MCP) integration to make this data easily accessible to AI agents. + +Users connect their HSK wallets, stake HSK tokens, and receive API keys to access our comprehensive data ecosystem. As they consume data, fees are automatically deducted from their staked tokens and distributed to data providers, creating a sustainable data economy. This token-based incentive system enables us to connect the physical world with blockchain by rewarding those who contribute valuable data sources. + +Our core mission is to create a decentralized bridge between real-world information and blockchain technology, using HSK as the connecting medium. By standardizing diverse data sources—from cryptocurrency markets and social media trends to blockchain project updates—we empower AI agents to make informed decisions while ensuring data providers are fairly compensated for their contributions. + +![image](https://github.com/user-attachments/assets/e93b9f1f-ec79-4371-bc52-b4f1e3122938) + +## Tech Stack + +- Frontend: Next.js, Tailwind CSS, Shadcn UI, Figma +- Backend: FastAPI, SQLite3, Open Zeppelin, Solidity +- Other: Vercel, AWS EC2, Git + +## Demo + +- Frontend: [HashScope Website](https://hashscope.vercel.app/) +- Backend API: [Backend Endpoint](https://hashkey.sungwoonsong.com) +- API Docs: [Swagger Doc](https://hashkey.sungwoonsong.com/docs) +- SmartContract: [SmartContract](https://hashkey.blockscout.com/address/0x0D313B22601E7AD450DC9b8b78aB0b0014022269) + +- **Project Deck**: [Google Slides link](https://1drv.ms/p/c/a49340658cb1b089/EV4YLr3VLCxKou00o40euyABre1pkPaP9RTihl3T-uyFyg) + +## Team + +- SeungWoon Song - PM, Backend (0xDbCeE5A0F6804f36930EAA33aB4cef11a7964398) +- Hyerin Kim - Design, Frontend (0xdEcBa0C08ca92D302Ad037AB708496466B89bc23) + +## API Flow + +1. **User Registration & Authentication** + - Users connect their HSK wallet + - Backend verifies wallet signature + - User receives JWT token for authentication + +2. **API Key Management** + - Users can generate API keys through the dashboard + - Each API key has a unique ID and secret + - Rate limits can be configured per API key + +3. **Token Staking & Fee Deduction** + - Users stake HSK tokens to use the API + - Fees are automatically deducted based on API usage + - Every 10 API calls result in a 0.01 HSK deduction(Only for PoC, will be changed later) + +4. **API Usage & Monitoring** + - Users can monitor their API usage through the dashboard + - Usage statistics include call counts, endpoints, and costs + - Historical data is available for analysis + +## Available APIs + +### Cryptocurrency Data APIs + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/crypto/btc/usd` | GET | Get BTC price in USD from Binance | +| `/crypto/btc/krw` | GET | Get BTC price in KRW from Upbit | +| `/crypto/usdt/krw` | GET | Get USDT price in KRW from Upbit | +| `/crypto/kimchi-premium` | GET | Get kimchi premium percentage between Korean and global markets | +| `/crypto/prices` | GET | Get major cryptocurrency prices (BTC, ETH, XRP) | + +### Social Media APIs + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/social/trump` | GET | Get Donald Trump's latest posts from Truth Social | +| `/social/elon` | GET | Get Elon Musk's latest posts from X (Twitter) | +| `/social/x/trends` | GET | Get current trending topics on X (Twitter) | + +### Derivatives Market APIs + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/derivatives/funding-rates` | GET | Get current funding rates for major cryptocurrency futures markets | +| `/derivatives/open-interest` | GET | Get open interest ratios for major cryptocurrency derivatives | + +### Blockchain Projects APIs + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/projects/hsk` | GET | Get latest updates and developments from HashKey Chain | +| `/projects/ethereum/standards` | GET | Get information about new Ethereum standards and proposals | +| `/projects/solana` | GET | Get latest updates and developments from Solana blockchain | + +### Open Source APIs + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/opensource/bitcoin` | GET | Get latest pull requests, stars, and activities from Bitcoin Core repository | +| `/opensource/ethereum` | GET | Get latest pull requests, stars, and activities from Ethereum Core repositories | + +### API Management Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/api-keys` | POST | Create a new API key | +| `/api-keys` | GET | List all API keys | +| `/api-keys/{key_id}` | GET | Get API key details | +| `/api-keys/{key_id}/usage` | GET | Get API key usage statistics | +| `/api-keys/{key_id}/history` | GET | Get detailed API usage history | + +## Smart Contracts + +The HashScope platform utilizes smart contracts built on the HSK blockchain to handle token staking, fee deduction, and reward distribution. + +### HSKDeposit Contract + +The main contract that handles HSK token deposits and usage fee deductions. + +**Key Features:** +- Native HSK deposits and withdrawals +- Automated fee deduction based on API usage +- Event emission for tracking transactions +- Owner-controlled fee adjustment + +**Main Functions:** +- `deposit()`: Allows users to deposit HSK tokens +- `withdraw(uint256 amount)`: Allows users to withdraw their HSK tokens +- `deductForUsage(address user, uint256 amount, address recipient)`: Deducts usage fees from user's balance +- `getBalance(address user)`: Returns the user's current balance + +**Events:** +- `Deposited(address indexed user, uint256 amount)` +- `Withdrawn(address indexed user, uint256 amount)` +- `UsageDeducted(address indexed user, uint256 amount, address recipient)` + +### Contract Deployment + +The smart contracts are deployed on the HSK blockchain at the following addresses: +- HSKDeposit: `0x0D313B22601E7AD450DC9b8b78aB0b0014022269` + +### Architecture + +![alt text](image/image.png) + +### API Class diagram + +![alt text](image/image-1.png) + +### Data Flow +![alt text](image/image-2.png) + +### Token Economy +![alt text](image/image-3.png) + diff --git a/projects/HashScope/backend/app/__init__.py b/projects/HashScope/backend/app/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/projects/HashScope/backend/app/__init__.py @@ -0,0 +1 @@ + diff --git a/projects/HashScope/backend/app/auth/__init__.py b/projects/HashScope/backend/app/auth/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/projects/HashScope/backend/app/auth/__init__.py @@ -0,0 +1 @@ + diff --git a/projects/HashScope/backend/app/auth/api_key.py b/projects/HashScope/backend/app/auth/api_key.py new file mode 100644 index 00000000..ad1c5f9d --- /dev/null +++ b/projects/HashScope/backend/app/auth/api_key.py @@ -0,0 +1,197 @@ +from fastapi import Depends, HTTPException, Header, status, Request +from sqlalchemy.orm import Session +from datetime import datetime +from typing import Optional +import hashlib +import uuid +import os +from dotenv import load_dotenv + +from app.database import get_db +from app.models import APIKey, APIUsage, User, Transaction +from app.blockchain.contracts import deduct_for_usage + +# .env 파일 로드 +load_dotenv() + +def verify_api_key( + api_key_id: str = Header(..., alias="api-key-id"), + api_key_secret: str = Header(..., alias="api-key-secret"), + request: Request = None, + db: Session = Depends(get_db) +): + """ + API 키 검증 함수 (ID와 Secret 모두 검증) + + Args: + api_key_id (str): API 키 ID + api_key_secret (str): API 키 Secret + request (Request): 요청 객체 (경로 및 메서드 추적용) + db (Session): 데이터베이스 세션 + + Returns: + APIKey: 검증된 API 키 객체 + """ + if not api_key_id or not api_key_secret: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="API 키 ID와 Secret이 모두 필요합니다" + ) + + # 데이터베이스에서 API 키 조회 + db_api_key = db.query(APIKey).filter(APIKey.key_id == api_key_id).first() + + if not db_api_key: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="유효하지 않은 API 키입니다" + ) + + # Secret 키 검증 + hashed_secret = hashlib.sha256(api_key_secret.encode()).hexdigest() + if db_api_key.secret_key_hash != hashed_secret: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="유효하지 않은 API 키 Secret입니다" + ) + + if not db_api_key.is_active: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="비활성화된 API 키입니다" + ) + + # API 키 사용량 업데이트 + db_api_key.call_count += 1 + db_api_key.last_used_at = datetime.utcnow() + + # 요청 경로 및 메서드 추적 (요청 객체가 제공된 경우) + if request: + # API 사용 기록 저장 + endpoint = request.url.path + method = request.method + + # 새 API 사용 기록 생성 + api_usage = APIUsage( + api_key_id=db_api_key.id, + endpoint=endpoint, + method=method, + timestamp=datetime.utcnow() + ) + db.add(api_usage) + + db.commit() + + return db_api_key + +def get_api_key_with_tracking( + api_key_id: str = Header(..., alias="api-key-id"), + api_key_secret: str = Header(..., alias="api-key-secret"), + request: Request = None, + db: Session = Depends(get_db) +): + """ + API 키 검증 및 사용량 추적 함수 + + Args: + api_key_id (str): API 키 ID + api_key_secret (str): API 키 Secret + request (Request): 요청 객체 + db (Session): 데이터베이스 세션 + + Returns: + APIKey: 검증된 API 키 객체 + """ + # API 키 검증 + api_key = verify_api_key(api_key_id, api_key_secret, request, db) + + # 현재 시간 기록 + now = datetime.utcnow() + + # API 사용량 추적 + if request: + # 엔드포인트 및 메서드 정보 추출 + path = request.url.path + method = request.method + + # 콜당 비용 설정 (0.001 HSK = 10^15 wei) + cost_per_call = 10**14 # 0.001 HSK in wei + + # API 사용량 기록 + usage = APIUsage( + api_key_id=api_key.id, + endpoint=path, + method=method, + timestamp=now, + cost=cost_per_call, + is_billed=False + ) + db.add(usage) + + # API 키 사용 횟수 증가 및 마지막 사용 시간 업데이트 + api_key.call_count += 1 + api_key.last_used_at = now + + # 사용자 정보 가져오기 - user_id로 조회 + user = db.query(User).filter(User.wallet_address == api_key.user.wallet_address).first() + + if user: + # 미청구된 사용량 계산 + unbilled_usages = db.query(APIUsage).filter( + APIUsage.api_key_id == api_key.id, + APIUsage.is_billed == False + ).all() + + # 미청구 사용량이 10개 이상이면 실제 차감 진행 + if len(unbilled_usages) >= 10: + # 총 차감 비용 계산 + total_cost = sum(usage.cost for usage in unbilled_usages) + + try: + # 관리자 주소 (수수료 수취 주소) - .env 파일에서 가져오거나 기본값 사용 + admin_address = os.getenv("FEE_RECIPIENT_ADDRESS", "0xf91aAB71fC16dA79c8ACFAD67aF7C9b39588B246") # 수수료 수취 지갑 주소 + + # 로그 기록 - 정확한 HSK 값 표시 + print(f"Deducting {total_cost / 10**18:.6f} HSK from {user.wallet_address}") + print(f"Total cost in wei: {total_cost}") + print(f"Fee recipient: {admin_address}") + + # 온체인에서 직접 차감 실행 + success, result = deduct_for_usage(user.wallet_address, total_cost, admin_address) + + if success: + tx_hash = result + status = "pending" + print(f"Successfully deducted usage fee. Transaction hash: {tx_hash}") + else: + # 실패 시 고유한 ID 생성 (중복 방지) + tx_hash = f"failed-{uuid.uuid4()}" + status = "failed" + print(f"Failed to deduct usage fee: {result}") + + # Transaction 모델에 차감 요청 기록 + tx = Transaction( + user_wallet=user.wallet_address, + tx_hash=tx_hash, + amount=total_cost, + tx_type="usage_deduct", + status=status, + created_at=now + ) + db.add(tx) + + # 청구 완료로 표시 (성공 여부와 관계없이) + for usage in unbilled_usages: + usage.is_billed = True + + # 변경사항 저장 + db.commit() + + except Exception as e: + print(f"Error deducting usage cost: {str(e)}") + # 오류가 발생해도 API 키는 반환 + else: + # 변경사항 저장 + db.commit() + + return api_key diff --git a/projects/HashScope/backend/app/auth/dependencies.py b/projects/HashScope/backend/app/auth/dependencies.py new file mode 100644 index 00000000..cd1d052f --- /dev/null +++ b/projects/HashScope/backend/app/auth/dependencies.py @@ -0,0 +1,75 @@ +from fastapi import Depends, HTTPException, status, Header +from fastapi.security import OAuth2PasswordBearer, HTTPBearer +from sqlalchemy.orm import Session +from jose import jwt, JWTError +from typing import Optional + +from app.database import get_db +from app.models import User, APIKey +from app.auth.jwt import verify_token, SECRET_KEY, ALGORITHM + +# OAuth2 scheme for JWT token authentication - auto_error=False로 설정하여 Swagger UI에서 자동 인증 요구를 비활성화 +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login", auto_error=False) +security = HTTPBearer(auto_error=False) + +async def get_current_user(token: Optional[str] = Depends(oauth2_scheme), db: Session = Depends(get_db)): + """ + Get the current user from the JWT token + """ + if token is None: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Not authenticated", + headers={"WWW-Authenticate": "Bearer"}, + ) + + credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + + # Verify token + payload = verify_token(token) + if payload is None: + raise credentials_exception + + # Extract wallet address from token + wallet_address: str = payload.get("sub") + if wallet_address is None: + raise credentials_exception + + # Get user from database + user = db.query(User).filter(User.wallet_address == wallet_address).first() + if user is None: + raise credentials_exception + + return user + +async def get_current_admin_user(current_user: User = Depends(get_current_user)): + """ + Check if the current user is an admin + """ + # 관리자 권한 확인 (wallet_address가 컨트랙트 소유자와 일치하는지 확인) + # 실제 구현에서는 DB에 admin 필드를 추가하거나 다른 방식으로 관리자 권한을 확인할 수 있습니다. + if not hasattr(current_user, "is_admin") or not current_user.is_admin: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Not enough permissions" + ) + return current_user + +async def get_api_key(api_key: str, db: Session = Depends(get_db)): + """ + Validate API key and return the associated API key object + """ + # Find API key in database + api_key_obj = db.query(APIKey).filter(APIKey.key_id == api_key, APIKey.is_active == True).first() + + if not api_key_obj: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid API key", + ) + + return api_key_obj diff --git a/projects/HashScope/backend/app/auth/jwt.py b/projects/HashScope/backend/app/auth/jwt.py new file mode 100644 index 00000000..c4d98515 --- /dev/null +++ b/projects/HashScope/backend/app/auth/jwt.py @@ -0,0 +1,39 @@ +from datetime import datetime, timedelta +from typing import Optional +import os +from jose import JWTError, jwt +from dotenv import load_dotenv + +# Load environment variables +load_dotenv() + +# JWT settings +SECRET_KEY = os.getenv("JWT_SECRET_KEY", "your-secret-key-for-development") +ALGORITHM = "HS256" +ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "1440")) # 24 hours by default + +def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str: + """ + Create a JWT access token + """ + to_encode = data.copy() + + if expires_delta: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + + to_encode.update({"exp": expire}) + encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + + return encoded_jwt + +def verify_token(token: str) -> Optional[dict]: + """ + Verify a JWT token and return the payload + """ + try: + payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + return payload + except JWTError: + return None diff --git a/projects/HashScope/backend/app/auth/wallet.py b/projects/HashScope/backend/app/auth/wallet.py new file mode 100644 index 00000000..b96648ce --- /dev/null +++ b/projects/HashScope/backend/app/auth/wallet.py @@ -0,0 +1,39 @@ +import secrets +import string +from eth_account.messages import encode_defunct +from web3 import Web3 +from eth_account import Account +from typing import Optional + +def generate_nonce(length: int = 32) -> str: + """ + Generate a random nonce for wallet authentication + """ + alphabet = string.ascii_letters + string.digits + return ''.join(secrets.choice(alphabet) for _ in range(length)) + +def create_auth_message(wallet_address: str, nonce: str) -> str: + """ + Create a message to be signed by the wallet + """ + return f"Sign this message to authenticate with HashScope API\n\nWallet: {wallet_address}\nNonce: {nonce}" + +def verify_signature(message: str, signature: str, wallet_address: str) -> bool: + """ + Verify that the signature was signed by the wallet address + """ + try: + # Convert wallet address to checksum address + wallet_address = Web3.to_checksum_address(wallet_address) + + # Create the message hash that was signed + message_hash = encode_defunct(text=message) + + # Recover the address from the signature + recovered_address = Account.recover_message(message_hash, signature=signature) + + # Check if the recovered address matches the provided wallet address + return recovered_address.lower() == wallet_address.lower() + except Exception as e: + print(f"Signature verification error: {e}") + return False diff --git a/projects/HashScope/backend/app/blockchain/abi/HSKDeposit.json b/projects/HashScope/backend/app/blockchain/abi/HSKDeposit.json new file mode 100644 index 00000000..2980f739 --- /dev/null +++ b/projects/HashScope/backend/app/blockchain/abi/HSKDeposit.json @@ -0,0 +1,247 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "UsageDeducted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Withdraw", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "balances", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "deductForUsage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "deposit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "getBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getContractBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] diff --git a/projects/HashScope/backend/app/blockchain/contracts.py b/projects/HashScope/backend/app/blockchain/contracts.py new file mode 100644 index 00000000..e076f47e --- /dev/null +++ b/projects/HashScope/backend/app/blockchain/contracts.py @@ -0,0 +1,311 @@ +import os +import json +from web3 import Web3 +from dotenv import load_dotenv +from eth_account import Account +from typing import Dict, Any, Optional + +load_dotenv() + +# HSK 네트워크 연결 설정 +HSK_RPC_URL = os.getenv("HSK_RPC_URL", "https://mainnet.hsk.xyz") +w3 = Web3(Web3.HTTPProvider(HSK_RPC_URL)) + +# 예치 컨트랙트 주소 설정 +DEPOSIT_CONTRACT_ADDRESS = os.getenv("DEPOSIT_CONTRACT_ADDRESS") + +# 컨트랙트 소유자 개인키 설정 +CONTRACT_OWNER_PRIVATE_KEY = os.getenv("CONTRACT_OWNER_PRIVATE_KEY") + +# 예치 컨트랙트 ABI 로드 +with open("app/blockchain/abi/HSKDeposit.json", "r") as f: + DEPOSIT_CONTRACT_ABI = json.load(f) + +# 예치 컨트랙트 인스턴스 생성 +deposit_contract = w3.eth.contract(address=DEPOSIT_CONTRACT_ADDRESS, abi=DEPOSIT_CONTRACT_ABI) + +# 단위 변환 유틸리티 함수 +def wei_to_hsk(wei_amount: int) -> float: + """ + Wei 단위를 HSK 단위로 변환합니다. (1 HSK = 10^18 wei) + """ + return float(wei_amount) / 10**18 + +def hsk_to_wei(hsk_amount: float) -> int: + """ + HSK 단위를 Wei 단위로 변환합니다. (1 HSK = 10^18 wei) + """ + return int(hsk_amount * 10**18) + +def format_wei_to_hsk(wei_amount: int) -> str: + """ + Wei 단위를 HSK 단위로 변환하여 포맷팅합니다. + """ + hsk_amount = wei_to_hsk(wei_amount) + return f"{hsk_amount:.6f} HSK" + +def get_balance(address): + """ + 사용자의 예치된 HSK 잔액을 조회합니다. + """ + try: + # 주소를 체크섬 주소로 변환 + checksum_addr = Web3.to_checksum_address(address) + print(f"Converting address {address} to checksum format: {checksum_addr}") + balance = deposit_contract.functions.getBalance(checksum_addr).call() + return balance + except Exception as e: + print(f"Error getting balance: {e}") + return 0 + +def get_contract_balance(): + """ + 컨트랙트의 총 HSK 잔액을 조회합니다. + """ + try: + balance = deposit_contract.functions.getContractBalance().call() + return balance + except Exception as e: + print(f"Error getting contract balance: {e}") + return 0 + +def get_wallet_balance(address): + """ + 지갑의 HSK 잔액을 조회합니다. + """ + try: + balance = w3.eth.get_balance(address) + return balance + except Exception as e: + print(f"Error getting wallet balance: {e}") + return 0 + +def sign_transaction(private_key: str, transaction: Dict[str, Any]) -> str: + """ + 트랜잭션에 서명합니다. + + Args: + private_key: 개인 키 + transaction: 트랜잭션 데이터 + + Returns: + 서명된 트랜잭션의 해시 + """ + try: + # 트랜잭션 서명 + signed_tx = w3.eth.account.sign_transaction(transaction, private_key) + + # 서명된 트랜잭션 전송 + tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction) + + return tx_hash.hex() + except Exception as e: + print(f"Error signing transaction: {e}") + raise e + +def build_deposit_transaction(from_address: str, amount_wei: int, gas_price: Optional[int] = None) -> Dict[str, Any]: + """ + 예치 트랜잭션을 생성합니다. + + Args: + from_address: 송신자 주소 + amount_wei: 예치할 금액 (wei 단위) + gas_price: 가스 가격 (wei 단위) + + Returns: + 트랜잭션 데이터 + """ + try: + # 가스 가격이 지정되지 않은 경우 네트워크에서 가져옴 + if gas_price is None: + gas_price = w3.eth.gas_price + + # 트랜잭션 데이터 생성 + tx = { + 'from': from_address, + 'to': DEPOSIT_CONTRACT_ADDRESS, + 'value': amount_wei, + 'gas': 100000, # 예상 가스 한도 + 'gasPrice': gas_price, + 'nonce': w3.eth.get_transaction_count(from_address), + 'chainId': w3.eth.chain_id, + } + + return tx + except Exception as e: + print(f"Error building deposit transaction: {e}") + raise e + +def verify_deposit_transaction(tx_hash): + """ + 예치 트랜잭션을 검증합니다. + """ + try: + # 트랜잭션 정보 가져오기 + tx_receipt = w3.eth.get_transaction_receipt(tx_hash) + + # 트랜잭션이 성공했는지 확인 + if tx_receipt and tx_receipt["status"] == 1: + # 이벤트 로그에서 Deposit 이벤트 찾기 + for log in tx_receipt["logs"]: + # 체크섬 주소로 변환하여 비교 + contract_addr = Web3.to_checksum_address(DEPOSIT_CONTRACT_ADDRESS) + log_addr = Web3.to_checksum_address(log["address"]) + if log_addr == contract_addr: + # 이벤트 디코딩 + try: + event = deposit_contract.events.Deposit().process_receipt(tx_receipt) + if event: + for ev in event: + return { + "user": ev["args"]["user"], + "amount": ev["args"]["amount"], + "success": True + } + except Exception as e: + print(f"Error decoding event: {e}") + + # Deposit 이벤트를 찾지 못했지만 트랜잭션이 성공한 경우 + # 일반 전송일 수 있으므로 트랜잭션 정보 확인 + tx = w3.eth.get_transaction(tx_hash) + if tx and tx["to"]: + # 체크섬 주소로 변환하여 비교 + contract_addr = Web3.to_checksum_address(DEPOSIT_CONTRACT_ADDRESS) + tx_to_addr = Web3.to_checksum_address(tx["to"]) + if tx_to_addr == contract_addr: + return { + "user": tx["from"], + "amount": tx["value"], + "success": True + } + + return {"success": False, "message": "Transaction failed or no Deposit event found"} + except Exception as e: + print(f"Error verifying deposit transaction: {e}") + return {"success": False, "message": str(e)} + +def verify_withdraw_transaction(tx_hash): + """ + 인출 트랜잭션을 검증합니다. + """ + try: + # 트랜잭션 정보 가져오기 + tx_receipt = w3.eth.get_transaction_receipt(tx_hash) + + # 트랜잭션이 성공했는지 확인 + if tx_receipt and tx_receipt["status"] == 1: + # 이벤트 로그에서 Withdraw 이벤트 찾기 + for log in tx_receipt["logs"]: + if log["address"].lower() == DEPOSIT_CONTRACT_ADDRESS.lower(): + # 이벤트 디코딩 + try: + event = deposit_contract.events.Withdraw().process_receipt(tx_receipt) + if event: + for ev in event: + return { + "user": ev["args"]["user"], + "amount": ev["args"]["amount"], + "success": True + } + except Exception as e: + print(f"Error decoding event: {e}") + + return {"success": False, "message": "Transaction failed or no Withdraw event found"} + except Exception as e: + print(f"Error verifying withdraw transaction: {e}") + return {"success": False, "message": str(e)} + +def verify_usage_deduction_transaction(tx_hash): + """ + 사용량 차감 트랜잭션을 검증합니다. + """ + try: + # 트랜잭션 조회 + tx_receipt = w3.eth.get_transaction_receipt(tx_hash) + if not tx_receipt or not tx_receipt.get('status'): + return False, "트랜잭션이 실패했거나 존재하지 않습니다." + + # 이벤트 로그 확인 + logs = deposit_contract.events.UsageDeducted().process_receipt(tx_receipt) + if not logs: + return False, "UsageDeducted 이벤트가 없습니다." + + return True, logs[0].args + except Exception as e: + return False, str(e) + +def deduct_for_usage(user_address, amount_wei, recipient_address): + """ + 사용자의 예치금에서 API 사용 수수료를 차감합니다. + + Args: + user_address (str): 사용자 지갑 주소 + amount_wei (int or float): 차감할 금액 (wei 단위) + recipient_address (str): 수수료 수취 주소 + + Returns: + tuple: (성공 여부, 트랜잭션 해시 또는 오류 메시지) + """ + try: + if not CONTRACT_OWNER_PRIVATE_KEY: + return False, "컨트랙트 소유자 개인키가 설정되지 않았습니다." + + # 주소를 체크섬 주소로 변환 + user_address = Web3.to_checksum_address(user_address) + recipient_address = Web3.to_checksum_address(recipient_address) + + # float 타입을 int 타입으로 변환 (Solidity uint256과 호환) + amount_wei_int = int(amount_wei) + + # 사용자의 현재 잔액 확인 + balance = deposit_contract.functions.getBalance(user_address).call() + if balance < amount_wei_int: + return False, f"잔액 부족: {wei_to_hsk(balance)} HSK (필요: {wei_to_hsk(amount_wei_int)} HSK)" + + # 트랜잭션 생성 + nonce = w3.eth.get_transaction_count(w3.eth.account.from_key(CONTRACT_OWNER_PRIVATE_KEY).address) + gas_price = w3.eth.gas_price + + # deductForUsage 함수 호출 트랜잭션 생성 + tx = deposit_contract.functions.deductForUsage( + user_address, + amount_wei_int, # int 타입으로 변환된 값 사용 + recipient_address + ).build_transaction({ + 'chainId': w3.eth.chain_id, + 'gas': 200000, # 가스 한도 설정 + 'gasPrice': gas_price, + 'nonce': nonce, + }) + + # 트랜잭션 서명 + signed_tx = w3.eth.account.sign_transaction(tx, CONTRACT_OWNER_PRIVATE_KEY) + + # 트랜잭션 전송 + tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction) + + # 트랜잭션 해시 반환 + return True, w3.to_hex(tx_hash) + except Exception as e: + return False, f"차감 실패: {str(e)}" + +def get_transaction_status(tx_hash): + """ + 트랜잭션 상태를 조회합니다. + """ + try: + # 트랜잭션 정보 가져오기 + tx_receipt = w3.eth.get_transaction_receipt(tx_hash) + + if tx_receipt: + return { + "status": "confirmed" if tx_receipt["status"] == 1 else "failed", + "block_number": tx_receipt["blockNumber"], + "gas_used": tx_receipt["gasUsed"] + } + else: + # 트랜잭션이 아직 처리되지 않은 경우 + return {"status": "pending"} + except Exception as e: + print(f"Error getting transaction status: {e}") + return {"status": "error", "message": str(e)} diff --git a/projects/HashScope/backend/app/database.py b/projects/HashScope/backend/app/database.py new file mode 100644 index 00000000..f7de77d3 --- /dev/null +++ b/projects/HashScope/backend/app/database.py @@ -0,0 +1,53 @@ +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +import os +from dotenv import load_dotenv +from pathlib import Path + +# Load environment variables +load_dotenv() + +# Get project root directory (HashScope folder) +PROJECT_ROOT = Path(__file__).parent.parent # app -> backend +DATABASE_DIR = PROJECT_ROOT / "database" + +# Ensure database directory exists +os.makedirs(DATABASE_DIR, exist_ok=True) + +# Database URL from environment variable or default to SQLite in database folder +# 임시로 SQLite를 사용하고 나중에 MySQL로 전환할 예정입니다. +# MySQL 전환 시 사용할 URL 형식: mysql+pymysql://username:password@host:port/database_name +DATABASE_URL = os.getenv("DATABASE_URL", f"sqlite:///{DATABASE_DIR}/hashscope.db") + +# Create SQLAlchemy engine +engine = create_engine( + DATABASE_URL, + connect_args={"check_same_thread": False} if DATABASE_URL.startswith("sqlite") else {} +) + +# Create session factory +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +# Base class for models +Base = declarative_base() + +def get_db(): + """ + Dependency for getting DB session + """ + db = SessionLocal() + try: + yield db + finally: + db.close() + +def init_db(): + """ + Initialize database tables + """ + # Import models here to ensure they are registered with Base + from app.models import User, Transaction, APIKey + + # Create tables + Base.metadata.create_all(bind=engine) diff --git a/projects/HashScope/backend/app/db/database.py b/projects/HashScope/backend/app/db/database.py new file mode 100644 index 00000000..46cf899d --- /dev/null +++ b/projects/HashScope/backend/app/db/database.py @@ -0,0 +1,47 @@ +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +import os +from dotenv import load_dotenv +from pathlib import Path + +# Load environment variables +load_dotenv() + +# Get project root directory (HashScope folder) +PROJECT_ROOT = Path(__file__).parents[3] # backend/app/db -> backend -> HashScope +DATABASE_DIR = PROJECT_ROOT / "database" + +# Database URL from environment variable or default to SQLite in database folder +# 임시로 SQLite를 사용하고 나중에 MySQL로 전환할 예정입니다. +# MySQL 전환 시 사용할 URL 형식: mysql+pymysql://username:password@host:port/database_name +DATABASE_URL = os.getenv("DATABASE_URL", f"sqlite:///{DATABASE_DIR}/hashscope.db") + +# Create SQLAlchemy engine +engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False} if DATABASE_URL.startswith("sqlite") else {}) + +# Create session factory +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +# Base class for models +Base = declarative_base() + +def get_db(): + """ + Dependency for getting DB session + """ + db = SessionLocal() + try: + yield db + finally: + db.close() + +def init_db(): + """ + Initialize database tables + """ + # Import models here to ensure they are registered with Base + from app.models import user, api_key + + # Create tables + Base.metadata.create_all(bind=engine) diff --git a/projects/HashScope/backend/app/main.py b/projects/HashScope/backend/app/main.py new file mode 100644 index 00000000..862a3d5e --- /dev/null +++ b/projects/HashScope/backend/app/main.py @@ -0,0 +1,302 @@ +from fastapi import FastAPI, Depends, HTTPException, status +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse +from fastapi.openapi.utils import get_openapi +from fastapi.openapi.docs import get_swagger_ui_html +import os + +from app.routers import users, auth, api_keys, crypto, api_catalog, social, derivatives, projects, opensource +from app.database import engine, Base, init_db, get_db +from app.auth.dependencies import get_current_user + +# 데이터베이스 초기화 +init_db() + +app = FastAPI( + title="HashScope API", + description="""HashScope - 실시간 크립토 시장 데이터 API 플랫폼 + +HashScope는 AI 에이전트에 실시간 크립토 시장 데이터를 제공하는 API 플랫폼입니다. + +## 주요 기능 + +* 블록체인 지갑 인증: HSK 체인 기반 지갑으로 로그인 +* API 키 관리: HSK 토큰 예치 후 API 키 발급 및 관리 +* 데이터 API: 다양한 크립토 데이터 제공 (온체인 데이터, 거래소 데이터, SNS 데이터 등) +* 토큰 예치 및 과금: 사용량에 따른 자동 과금 시스템 +* 데이터 제공자 보상: 데이터 소스 등록 및 보상 시스템 + +## 인증 방식 + +1. 지갑 인증: `/auth/nonce`와 `/auth/verify` 엔드포인트를 통해 지갑 서명 기반 인증 +2. API 키 인증: 데이터 API 호출 시 발급받은 API 키 ID와 Secret 모두 사용 (중요: 두 값 모두 필수) + +## 지갑 로그인 플로우 + +1. Nonce 요청: 사용자의 지갑 주소로 `/auth/nonce` API를 호출하여 서명할 메시지를 받습니다. +2. 메시지 서명: 받은 메시지를 MetaMask 등의 지갑으로 서명합니다. +3. 서명 검증: 서명된 메시지와 지갑 주소를 `/auth/verify` API로 전송하여 검증합니다. +4. JWT 토큰 발급: 서명이 유효하면 JWT 토큰이 발급됩니다. +5. 인증된 요청: 이후 모든 API 요청에 `Authorization: Bearer {token}` 헤더를 포함시킵니다. + +### 코드 예시 (프론트엔드) + +```javascript +// 1. Nonce 요청 +async function requestNonce(walletAddress) { + const response = await fetch('/auth/nonce', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ wallet_address: walletAddress }) + }); + return await response.json(); +} + +// 2. 메시지 서명 (MetaMask 사용) +async function signMessage(message, walletAddress) { + try { + const accounts = await ethereum.request({ method: 'eth_requestAccounts' }); + const signature = await ethereum.request({ + method: 'personal_sign', + params: [message, walletAddress] + }); + return signature; + } catch (error) { + console.error('Error signing message:', error); + throw error; + } +} + +// 3. 서명 검증 및 JWT 토큰 발급 +async function verifySignature(walletAddress, signature) { + const response = await fetch('/auth/verify', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + wallet_address: walletAddress, + signature: signature + }) + }); + return await response.json(); +} + +// 4. 전체 로그인 플로우 +async function login() { + // 지갑 연결 + const accounts = await ethereum.request({ method: 'eth_requestAccounts' }); + const walletAddress = accounts[0]; + + // Nonce 요청 + const { message } = await requestNonce(walletAddress); + + // 메시지 서명 + const signature = await signMessage(message, walletAddress); + + // 서명 검증 및 토큰 발급 + const { access_token } = await verifySignature(walletAddress, signature); + + // 토큰 저장 + localStorage.setItem('token', access_token); + + return access_token; +} + +// 5. 인증된 API 요청 예시 +async function fetchUserProfile() { + const token = localStorage.getItem('token'); + const response = await fetch('/users/me', { + headers: { + 'Authorization': `Bearer ${token}` + } + }); + return await response.json(); +} +``` + +## HSK Network Information + +- **Network Name**: HSK Network +- **RPC URL**: https://mainnet.hsk.xyz +- **Chain ID**: 177 +- **Block Explorer**: https://hashkey.blockscout.com + +## Smart Contract Addresses + +- **Deposit Contract**: 0x0D313B22601E7AD450DC9b8b78aB0b0014022269 + +""", + version="0.1.0", +) + +# CORS 설정 +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# 라우터 등록 +app.include_router(auth.router, prefix="/auth", tags=["Authentication"]) +app.include_router(users.router, prefix="/users", tags=["Users"]) +app.include_router(api_keys.router, prefix="/api-keys", tags=["API Keys"]) +app.include_router(crypto.router, prefix="/crypto", tags=["Cryptocurrency Data"]) +app.include_router(social.router, prefix="/social", tags=["Social Media"]) +app.include_router(derivatives.router, prefix="/derivatives", tags=["Derivatives Market"]) +app.include_router(projects.router, prefix="/projects", tags=["Blockchain Projects"]) +app.include_router(opensource.router, prefix="/opensource", tags=["Open Source"]) +app.include_router(api_catalog.router, prefix="/api-catalog", tags=["API Catalog"]) + +# API 카탈로그에 앱 인스턴스 설정 +api_catalog.set_app_instance(app) + +# 커스텀 OpenAPI 스키마 정의 +def custom_openapi(): + if app.openapi_schema: + return app.openapi_schema + + openapi_schema = get_openapi( + title="HashScope API", + version="1.0.0", + description="""HashScope API는 HSK 네트워크의 네이티브 HSK를 관리하기 위한 API입니다. + +## 주요 기능 + +### 1. 예치(Deposit) +- 사용자는 네이티브 HSK를 예치 컨트랙트에 예치할 수 있습니다. +- 예치는 컨트랙트의 `deposit()` 함수를 호출하거나 직접 HSK를 전송하여 수행할 수 있습니다. + +### 2. 인출(Withdraw) +- 인출은 관리자만 수행할 수 있습니다. +- 사용자는 인출 요청을 제출하고, 관리자가 이를 처리합니다. + +### 3. 사용량 차감(Usage Deduction) +- 관리자는 사용자의 사용량에 따라 HSK를 차감할 수 있습니다. +- 차감된 HSK는 지정된 수신자 주소로 전송됩니다. + +## API 키 인증 방식 (중요 업데이트) + +모든 API 엔드포인트는 두 개의 헤더를 통한 인증이 필요합니다: + +1. `api-key-id`: API 키 ID (예: hsk_1234567890abcdef) +2. `api-key-secret`: API 키 Secret (예: sk_1234567890abcdef1234567890abcdef) + +두 값 모두 API 키 발급 시 제공되며, 모든 요청에 반드시 포함되어야 합니다. + +### API 키 인증 예시 (cURL) + +```bash +curl -X GET "https://api.hashscope.io/crypto/btc-price" \\ + -H "api-key-id: hsk_your_api_key_id" \\ + -H "api-key-secret: sk_your_api_key_secret" +``` + +### API 키 인증 예시 (JavaScript) + +```javascript +const response = await fetch('https://api.hashscope.io/crypto/btc-price', { + method: 'GET', + headers: { + 'api-key-id': 'hsk_your_api_key_id', + 'api-key-secret': 'sk_your_api_key_secret' + } +}); +``` + +## 지갑 인증 방식 + +지갑 인증은 다음 단계로 이루어집니다: + +1. `/auth/nonce` 엔드포인트를 호출하여 서명할 메시지를 받습니다. +2. 받은 메시지를 지갑으로 서명합니다. +3. 서명된 메시지와 지갑 주소를 `/auth/verify` 엔드포인트로 전송하여 검증합니다. +4. 검증이 성공하면 JWT 토큰이 발급됩니다. +5. 발급받은 JWT 토큰을 `Authorization: Bearer {token}` 헤더에 포함하여 요청합니다. + +## 컨트랙트 주소 +- **HSK 예치 컨트랙트**: 0x0D313B22601E7AD450DC9b8b78aB0b0014022269 + +## 참고 사항 +- HSK 네트워크의 RPC URL: https://mainnet.hsk.xyz +- 모든 금액은 wei 단위로 표시됩니다 (1 HSK = 10^18 wei). +""", + routes=app.routes, + ) + + # API 키 인증 보안 스키마 정의 + if "components" not in openapi_schema: + openapi_schema["components"] = {} + + if "securitySchemes" not in openapi_schema["components"]: + openapi_schema["components"]["securitySchemes"] = {} + + # API 키 인증 스키마 추가 + openapi_schema["components"]["securitySchemes"]["ApiKeyId"] = { + "type": "apiKey", + "in": "header", + "name": "api-key-id", + "description": "API 키 ID (필수)" + } + + openapi_schema["components"]["securitySchemes"]["ApiKeySecret"] = { + "type": "apiKey", + "in": "header", + "name": "api-key-secret", + "description": "API 키 Secret (필수)" + } + + # JWT 인증 스키마 추가 + openapi_schema["components"]["securitySchemes"]["BearerAuth"] = { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "JWT 토큰 인증" + } + + # OAuth2PasswordBearer 스키마 제거 + if "securitySchemes" in openapi_schema["components"]: + if "OAuth2PasswordBearer" in openapi_schema["components"]["securitySchemes"]: + del openapi_schema["components"]["securitySchemes"]["OAuth2PasswordBearer"] + + # 전역 보안 요구사항 설정 + openapi_schema["security"] = [{"ApiKeyId": [], "ApiKeySecret": []}] + + # auth 관련 엔드포인트와 루트 엔드포인트는 보안 요구사항 제외 + for path in openapi_schema["paths"]: + if path.startswith("/auth") or path == "/" or path == "/health": + path_item = openapi_schema["paths"][path] + for method_key in list(path_item.keys()): + # 유효한 HTTP 메서드인지 확인 + if method_key.lower() in ["get", "post", "put", "delete", "options", "head", "patch", "trace"]: + path_item[method_key]["security"] = [] + + app.openapi_schema = openapi_schema + return app.openapi_schema + +app.openapi = custom_openapi + +@app.get("/", tags=["root"]) +async def root(): + return { + "message": "Welcome to HashScope API", + "docs": "/docs", + "deposit_contract": os.getenv("DEPOSIT_CONTRACT_ADDRESS", "0x0D313B22601E7AD450DC9b8b78aB0b0014022269"), + "hsk_rpc_url": os.getenv("HSK_RPC_URL", "https://mainnet.hsk.xyz") + } + +@app.get("/health") +def health_check(): + return {"status": "healthy"} + +# Custom Swagger UI +@app.get("/docs", include_in_schema=False) +async def custom_swagger_ui_html(): + return get_swagger_ui_html( + openapi_url=app.openapi_url, + title=f"{app.title} - API 문서", + oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url, + swagger_js_url="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.9.0/swagger-ui-bundle.js", + swagger_css_url="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.9.0/swagger-ui.css", + swagger_favicon_url="/favicon.ico", + ) diff --git a/projects/HashScope/backend/app/models.py b/projects/HashScope/backend/app/models.py new file mode 100644 index 00000000..8e7aec7a --- /dev/null +++ b/projects/HashScope/backend/app/models.py @@ -0,0 +1,100 @@ +from sqlalchemy import Column, Integer, String, Float, DateTime, ForeignKey, Boolean +from sqlalchemy.sql import func +from sqlalchemy.orm import relationship +from sqlalchemy.ext.declarative import declared_attr +from sqlalchemy.orm import validates + +from app.database import Base +from app.utils.wallet import normalize_address, checksum_address + +class User(Base): + __tablename__ = "users" + + id = Column(Integer, primary_key=True, index=True) + username = Column(String, unique=True, index=True) + email = Column(String, unique=True, index=True) + wallet_address = Column(String, unique=True, index=True) + balance = Column(Integer, default=0) # 잔액은 블록체인에서 조회하므로 참조용 + is_admin = Column(Boolean, default=False) # 관리자 여부 + nonce = Column(String, nullable=True) # 인증에 사용되는 nonce + created_at = Column(DateTime(timezone=True), server_default=func.now()) + updated_at = Column(DateTime(timezone=True), onupdate=func.now()) + + transactions = relationship("Transaction", back_populates="user") + api_keys = relationship("APIKey", back_populates="user") + + @validates('wallet_address') + def validate_wallet_address(self, key, address): + """지갑 주소를 소문자로 정규화합니다.""" + if address: + return normalize_address(address) + return address + +class Transaction(Base): + __tablename__ = "transactions" + + id = Column(Integer, primary_key=True, index=True) + user_id = Column(Integer, ForeignKey("users.id")) + user_wallet = Column(String, index=True) # 사용자 지갑 주소 + tx_hash = Column(String, unique=True, index=True) + amount = Column(Integer) # wei 단위 + tx_type = Column(String) # deposit, withdraw, withdraw_request, usage_request, usage_deduction + status = Column(String) # pending, confirmed, failed + created_at = Column(DateTime(timezone=True), server_default=func.now()) + updated_at = Column(DateTime(timezone=True), onupdate=func.now()) + recipient = Column(String, nullable=True) # 수신자 지갑 주소 (사용량 차감 시) + + user = relationship("User", back_populates="transactions") + + @validates('user_wallet', 'recipient') + def validate_wallet_address(self, key, address): + """지갑 주소를 소문자로 정규화합니다.""" + if address: + return normalize_address(address) + return address + +class APIKey(Base): + __tablename__ = "api_keys" + + id = Column(Integer, primary_key=True, index=True) + key_id = Column(String, unique=True, index=True, nullable=False) # Public identifier + secret_key_hash = Column(String, nullable=False) # Hashed secret key + user_id = Column(Integer, ForeignKey("users.id"), nullable=False) + name = Column(String, nullable=True) # Optional name for the API key + is_active = Column(Boolean, default=True) + created_at = Column(DateTime(timezone=True), server_default=func.now()) + expires_at = Column(DateTime(timezone=True), nullable=True) + + # Usage tracking + call_count = Column(Integer, default=0) + last_used_at = Column(DateTime(timezone=True), nullable=True) + rate_limit_per_minute = Column(Integer, default=60) # Default rate limit + + # Token consumption + token_consumption_rate = Column(Float, default=0.01) # Tokens consumed per API call + + # Relationship + user = relationship("User", back_populates="api_keys") + usages = relationship("APIUsage", back_populates="api_key") + + def __repr__(self): + return f"" + +class APIUsage(Base): + __tablename__ = "api_usages" + + id = Column(Integer, primary_key=True, index=True) + api_key_id = Column(Integer, ForeignKey("api_keys.id"), nullable=False) + endpoint = Column(String, nullable=False) # 호출된 API 엔드포인트 + method = Column(String, nullable=False) # HTTP 메서드 (GET, POST 등) + timestamp = Column(DateTime(timezone=True), server_default=func.now()) + response_time = Column(Float, nullable=True) # 응답 시간 (초) + status_code = Column(Integer, nullable=True) # HTTP 상태 코드 + cost = Column(Float, default=0.0) # API 호출당 비용 + is_billed = Column(Boolean, default=False) # 과금 여부 + + # Relationship + api_key = relationship("APIKey", back_populates="usages") + + def __repr__(self): + return f"" diff --git a/projects/HashScope/backend/app/models/__init__.py b/projects/HashScope/backend/app/models/__init__.py new file mode 100644 index 00000000..d93bcc01 --- /dev/null +++ b/projects/HashScope/backend/app/models/__init__.py @@ -0,0 +1,8 @@ +# 모델 패키지 초기화 파일 +# 모든 모델을 여기서 임포트하여 app.models에서 직접 접근할 수 있도록 함 + +from app.models.user import User +from app.models.api_key import APIKey, APIUsage +from app.models.deposit import Transaction + +__all__ = ["User", "APIKey", "Transaction", "APIUsage"] diff --git a/projects/HashScope/backend/app/models/api_key.py b/projects/HashScope/backend/app/models/api_key.py new file mode 100644 index 00000000..4a5e0204 --- /dev/null +++ b/projects/HashScope/backend/app/models/api_key.py @@ -0,0 +1,53 @@ +from sqlalchemy import Column, Integer, String, Float, DateTime, ForeignKey, Boolean +from sqlalchemy.sql import func +from sqlalchemy.orm import relationship + +from app.database import Base + +class APIKey(Base): + __tablename__ = "api_keys" + + id = Column(Integer, primary_key=True, index=True) + key_id = Column(String, unique=True, index=True, nullable=False) # Public identifier + secret_key_hash = Column(String, nullable=False) # Hashed secret key + user_wallet = Column(String, ForeignKey("users.wallet_address"), nullable=False) + name = Column(String, nullable=True) # Optional name for the API key + is_active = Column(Boolean, default=True) + created_at = Column(DateTime(timezone=True), server_default=func.now()) + expires_at = Column(DateTime(timezone=True), nullable=True) + + # Usage tracking + call_count = Column(Integer, default=0) + last_used_at = Column(DateTime(timezone=True), nullable=True) + + # Rate limiting + rate_limit_per_minute = Column(Integer, default=60) + + # Token consumption + token_consumption_rate = Column(Float, default=0.01) # Tokens consumed per API call + + # Relationship + user = relationship("User", back_populates="api_keys") + usages = relationship("APIUsage", back_populates="api_key") + + def __repr__(self): + return f"" + +class APIUsage(Base): + __tablename__ = "api_usages" + + id = Column(Integer, primary_key=True, index=True) + api_key_id = Column(Integer, ForeignKey("api_keys.id"), nullable=False) + endpoint = Column(String, nullable=False) # 호출된 API 엔드포인트 + method = Column(String, nullable=False) # HTTP 메서드 (GET, POST 등) + timestamp = Column(DateTime(timezone=True), server_default=func.now()) + response_time = Column(Float, nullable=True) # 응답 시간 (초) + status_code = Column(Integer, nullable=True) # HTTP 상태 코드 + cost = Column(Float, default=0.0) # API 호출당 비용 + is_billed = Column(Boolean, default=False) # 과금 여부 + + # Relationship + api_key = relationship("APIKey", back_populates="usages") + + def __repr__(self): + return f"" diff --git a/projects/HashScope/backend/app/models/deposit.py b/projects/HashScope/backend/app/models/deposit.py new file mode 100644 index 00000000..dce91479 --- /dev/null +++ b/projects/HashScope/backend/app/models/deposit.py @@ -0,0 +1,19 @@ +from sqlalchemy import Column, Integer, String, Float, DateTime, ForeignKey, Boolean +from sqlalchemy.sql import func +from sqlalchemy.orm import relationship + +from app.database import Base + +class Transaction(Base): + __tablename__ = "transactions" + + id = Column(Integer, primary_key=True, index=True) + user_wallet = Column(String, ForeignKey("users.wallet_address")) + tx_hash = Column(String, unique=True, index=True) + amount = Column(Integer) # wei 단위 + tx_type = Column(String) # deposit, withdraw, withdraw_request, usage_request, usage_deduction + status = Column(String) # pending, confirmed, failed + created_at = Column(DateTime(timezone=True), server_default=func.now()) + updated_at = Column(DateTime(timezone=True), onupdate=func.now()) + + user = relationship("User", back_populates="transactions") diff --git a/projects/HashScope/backend/app/models/user.py b/projects/HashScope/backend/app/models/user.py new file mode 100644 index 00000000..4430583a --- /dev/null +++ b/projects/HashScope/backend/app/models/user.py @@ -0,0 +1,23 @@ +from sqlalchemy import Column, Integer, String, Float, DateTime, ForeignKey, Boolean +from sqlalchemy.sql import func +from sqlalchemy.orm import relationship +from datetime import datetime + +from app.database import Base + +class User(Base): + __tablename__ = "users" + + wallet_address = Column(String, primary_key=True, index=True) # 지갑 주소를 PK로 사용 + balance = Column(Integer, default=0) # 잔액은 블록체인에서 조회하므로 참조용 + is_admin = Column(Boolean, default=False) # 관리자 여부 + nonce = Column(String, nullable=True) # 인증에 사용되는 nonce + created_at = Column(DateTime(timezone=True), server_default=func.now()) + last_login_at = Column(DateTime(timezone=True), nullable=True) + updated_at = Column(DateTime(timezone=True), onupdate=func.now()) + + transactions = relationship("Transaction", back_populates="user") + api_keys = relationship("APIKey", back_populates="user") + + def __repr__(self): + return f"" diff --git a/projects/HashScope/backend/app/routers/api_catalog.py b/projects/HashScope/backend/app/routers/api_catalog.py new file mode 100644 index 00000000..c3f22bee --- /dev/null +++ b/projects/HashScope/backend/app/routers/api_catalog.py @@ -0,0 +1,199 @@ +from fastapi import APIRouter, Depends, Query, Request +from fastapi.routing import APIRoute +from typing import Dict, List, Optional, Set +from pydantic import BaseModel +import inspect +import re + +router = APIRouter() + +# API 정보를 담는 모델 +class APIInfo(BaseModel): + path: str + method: str + summary: str = "" + description: str = "" + category: str + tags: List[str] = [] + +# API 목록 응답 모델 +class APICatalogResponse(BaseModel): + total_count: int + apis: List[APIInfo] + +# API 카테고리 정의 - 실용적인 정보 API만 포함 +API_CATEGORIES = { + "crypto": "Cryptocurrency Data", + "social": "Social Media", + "derivatives": "Derivatives Market", + "projects": "Blockchain Projects", + "opensource": "Open Source", + # 새로운 실용적 정보 카테고리는 여기에 추가 +} + +# 태그와 카테고리 매핑 - 실용적인 정보 API만 포함 +TAG_TO_CATEGORY = { + "Cryptocurrency Data": "crypto", + "Social Media": "social", + "Derivatives Market": "derivatives", + "Blockchain Projects": "projects", + "Open Source": "opensource", + # 새로운 태그-카테고리 매핑은 여기에 추가 +} + +# 제외할 경로 패턴 +EXCLUDED_PATH_PATTERNS = { + r"^/$", # 루트 경로 + r"^/docs", # 문서 경로 + r"^/redoc", # ReDoc 경로 + r"^/openapi\.json", # OpenAPI 스키마 + r"^/health", # 상태 확인 엔드포인트 + r"^/auth", # 인증 관련 엔드포인트 + r"^/users", # 사용자 관련 엔드포인트 + r"^/api-keys", # API 키 관련 엔드포인트 +} + +# 제외할 카테고리 +EXCLUDED_CATEGORIES = { + "root", "docs", "health", "openapi.json", "redoc", + "auth", "users", "api_keys" +} + +# 포함할 카테고리 - 실용적인 정보 API만 포함 +INCLUDED_CATEGORIES = { + "crypto", "social", "derivatives", "projects", "opensource", "api_catalog" +} + +# 경로에서 카테고리 추출하는 함수 +def extract_category_from_path(path: str) -> str: + # 경로의 첫 번째 부분을 카테고리로 사용 + # 예: /crypto/btc/usd -> crypto + match = re.match(r"/([^/]+)", path) + if match: + category = match.group(1).lower() + return category + return "other" + +# API 태그에서 카테고리 추출하는 함수 +def extract_category_from_tags(tags: List[str]) -> str: + # 태그 기반으로 카테고리 결정 + if tags and len(tags) > 0: + for tag in tags: + if tag in TAG_TO_CATEGORY: + return TAG_TO_CATEGORY[tag].lower() + return "other" + +# 경로가 제외 패턴에 해당하는지 확인하는 함수 +def is_excluded_path(path: str) -> bool: + for pattern in EXCLUDED_PATH_PATTERNS: + if re.match(pattern, path): + return True + return False + +# 모든 API 경로 수집 함수 +def collect_api_routes(app) -> List[APIInfo]: + api_routes = [] + + # 모든 라우트 순회 + for route in app.routes: + if isinstance(route, APIRoute): + # 경로, 메서드, 요약, 설명 추출 + path = route.path + + # 제외 경로 필터링 + if is_excluded_path(path): + continue + + method = route.methods.pop() if route.methods else "GET" + summary = route.summary or "" + description = route.description or "" + + # 태그 추출 + tags = getattr(route, "tags", []) + + # 카테고리 결정 + category = extract_category_from_tags(tags) + if category == "other": + category = extract_category_from_path(path) + + # 제외 카테고리 필터링 + if category in EXCLUDED_CATEGORIES: + continue + + # 포함 카테고리 필터링 - 실용적인 정보 API만 포함 + if category not in INCLUDED_CATEGORIES: + continue + + # API 정보 생성 + api_info = APIInfo( + path=path, + method=method, + summary=summary, + description=description, + category=category, + tags=tags + ) + + api_routes.append(api_info) + + return api_routes + +# 앱 인스턴스를 저장할 변수 +_app_instance = None + +# 앱 인스턴스 설정 함수 +def set_app_instance(app): + global _app_instance + _app_instance = app + +# API 목록 조회 엔드포인트 +@router.get("/list", response_model=APICatalogResponse, summary="List available APIs") +async def get_api_list( + request: Request, + category: Optional[str] = Query(None, description="API category to filter") +): + """ + Get a list of available APIs. + + - **category**: Optionally filter APIs by specific category. + + Returns all APIs if no category is specified or if the category is invalid. + """ + global _app_instance + + # 앱 인스턴스가 설정되지 않은 경우 request에서 가져옴 + if _app_instance is None: + app = request.app + else: + app = _app_instance + + # 모든 API 경로 수집 + all_apis = collect_api_routes(app) + + # 카테고리별 필터링 (대소문자 구분 없이) + if category: + category_lower = category.lower() + # 카테고리 이름 또는 경로 기반으로 필터링 + filtered_apis = [ + api for api in all_apis + if api.category.lower() == category_lower or + extract_category_from_path(api.path).lower() == category_lower + ] + else: + filtered_apis = all_apis + + # API 목록 반환 + return APICatalogResponse( + total_count=len(filtered_apis), + apis=filtered_apis + ) + +# 카테고리 목록 조회 엔드포인트 +@router.get("/categories", summary="List API categories") +async def get_categories( + request: Request +): + """ + Get a list of available API categories. + """ + return API_CATEGORIES diff --git a/projects/HashScope/backend/app/routers/api_keys.py b/projects/HashScope/backend/app/routers/api_keys.py new file mode 100644 index 00000000..50a91a1f --- /dev/null +++ b/projects/HashScope/backend/app/routers/api_keys.py @@ -0,0 +1,306 @@ +from fastapi import APIRouter, Depends, HTTPException, status, Path +from sqlalchemy.orm import Session +from typing import List, Optional, Dict +from datetime import datetime, timedelta +import secrets +import hashlib +from sqlalchemy import func + +from app.database import get_db +from app.models import User, APIKey, APIUsage +from app.auth.dependencies import get_current_user +from pydantic import BaseModel, Field + +router = APIRouter() + +# 스키마 정의 +class APIKeyCreate(BaseModel): + name: str = Field(None, description="Optional name for the API key") + rate_limit_per_minute: int = Field(60, description="Rate limit per minute", ge=1, le=1000) + +class APIKeyResponse(BaseModel): + key_id: str + name: Optional[str] = None + is_active: bool + created_at: datetime + expires_at: Optional[datetime] = None + rate_limit_per_minute: int + call_count: int + last_used_at: Optional[datetime] = None + + class Config: + from_attributes = True + +class APIKeyWithSecret(BaseModel): + key_id: str + secret_key: str + name: Optional[str] = None + is_active: bool + created_at: datetime + expires_at: Optional[datetime] = None + rate_limit_per_minute: int + +class APIKeyUsage(BaseModel): + key_id: str + call_count: int + last_used_at: Optional[datetime] = None + rate_limit_per_minute: int + token_consumption_rate: float + +class APIEndpointUsage(BaseModel): + endpoint: str + method: str + call_count: int + last_used_at: Optional[datetime] = None + total_cost: float + +class APIKeyHistoryResponse(BaseModel): + key_id: str + total_calls: int + total_cost: float + endpoints: List[APIEndpointUsage] + +# API 키 생성 및 관리 유틸리티 함수 +def generate_api_key_pair(): + """Generate a new API key pair (key_id and secret_key)""" + key_id = f"hsk_{secrets.token_hex(16)}" + secret_key = f"sk_{secrets.token_hex(32)}" + secret_key_hash = hashlib.sha256(secret_key.encode()).hexdigest() + + return { + "key_id": key_id, + "secret_key": secret_key, + "secret_key_hash": secret_key_hash + } + +def calculate_expiry_date(days=365): + """Calculate expiry date for API key""" + return datetime.utcnow() + timedelta(days=days) + +@router.post("/", response_model=APIKeyWithSecret, summary="Create new API key") +async def create_api_key( + api_key_data: APIKeyCreate, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """ + Create a new API key for the authenticated user. + + - **name**: Optional name for the API key + - **rate_limit_per_minute**: Optional rate limit per minute (default: 60) + + Returns the created API key with the secret key. The secret key will only be shown once. + + Requires authentication via JWT token. + """ + # Generate API key pair + key_pair = generate_api_key_pair() + + # Create API key in database + api_key = APIKey( + key_id=key_pair["key_id"], + secret_key_hash=key_pair["secret_key_hash"], + user_wallet=current_user.wallet_address, + name=api_key_data.name, + rate_limit_per_minute=api_key_data.rate_limit_per_minute, + expires_at=calculate_expiry_date(days=365) + ) + + db.add(api_key) + db.commit() + db.refresh(api_key) + + # Return API key with secret (only shown once) + return { + "key_id": api_key.key_id, + "secret_key": key_pair["secret_key"], # Only returned once + "name": api_key.name, + "is_active": api_key.is_active, + "created_at": api_key.created_at, + "expires_at": api_key.expires_at, + "rate_limit_per_minute": api_key.rate_limit_per_minute + } + +@router.get("/", response_model=List[APIKeyResponse], summary="List all API keys") +async def list_api_keys( + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """ + List all API keys for the authenticated user. + + Returns a list of API keys without the secret keys. + + Requires authentication via JWT token. + """ + api_keys = db.query(APIKey).filter(APIKey.user_wallet == current_user.wallet_address).all() + return api_keys + +@router.get("/{key_id}", response_model=APIKeyResponse, summary="Get API key details") +async def get_api_key( + key_id: str = Path(..., description="The ID of the API key"), + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """ + Get details of a specific API key. + + - **key_id**: The ID of the API key + + Returns the API key details without the secret key. + + Requires authentication via JWT token. + """ + api_key = db.query(APIKey).filter( + APIKey.key_id == key_id, + APIKey.user_wallet == current_user.wallet_address + ).first() + + if not api_key: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="API key not found" + ) + + return api_key + +@router.get("/{key_id}/usage", response_model=APIKeyUsage, summary="Get API key usage") +async def get_api_key_usage( + key_id: str = Path(..., description="The ID of the API key"), + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """ + Get usage statistics for a specific API key. + + - **key_id**: The ID of the API key + + Returns the API key usage statistics. + + Requires authentication via JWT token. + """ + api_key = db.query(APIKey).filter( + APIKey.key_id == key_id, + APIKey.user_wallet == current_user.wallet_address + ).first() + + if not api_key: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="API key not found" + ) + + return { + "key_id": api_key.key_id, + "call_count": api_key.call_count, + "last_used_at": api_key.last_used_at, + "rate_limit_per_minute": api_key.rate_limit_per_minute, + "token_consumption_rate": api_key.token_consumption_rate + } + +@router.get("/{key_id}/history", response_model=APIKeyHistoryResponse, summary="Get API key usage history") +async def get_api_key_history( + key_id: str = Path(..., description="The ID of the API key"), + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """ + Get the usage history for a specific API key. + + - **key_id**: The ID of the API key + + Returns: + - Total number of calls + - Total cost in HSK + - Usage statistics per endpoint (endpoint, HTTP method, call count, last used time, total cost in HSK) + + Requires authentication via JWT token. + """ + # API 키 존재 여부 확인 + api_key = db.query(APIKey).filter( + APIKey.key_id == key_id, + APIKey.user_wallet == current_user.wallet_address + ).first() + + if not api_key: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="API key not found" + ) + + # API 키 ID로 API 사용량 기록 조회 + api_usages = db.query(APIUsage).filter( + APIUsage.api_key_id == api_key.id + ).all() + + # 엔드포인트별 통계 계산 + endpoint_stats = {} + total_calls = 0 + total_cost = 0.0 + + # Wei to HSK 변환 상수 (1 HSK = 10^18 wei) + WEI_TO_HSK = 10**18 + + for usage in api_usages: + endpoint_key = f"{usage.method}:{usage.endpoint}" + total_calls += 1 + + # Wei를 HSK로 변환 + cost_in_hsk = usage.cost / WEI_TO_HSK if usage.cost else 0 + total_cost += cost_in_hsk + + if endpoint_key not in endpoint_stats: + endpoint_stats[endpoint_key] = { + "endpoint": usage.endpoint, + "method": usage.method, + "call_count": 1, + "last_used_at": usage.timestamp, + "total_cost": cost_in_hsk + } + else: + endpoint_stats[endpoint_key]["call_count"] += 1 + endpoint_stats[endpoint_key]["total_cost"] += cost_in_hsk + if usage.timestamp > endpoint_stats[endpoint_key]["last_used_at"]: + endpoint_stats[endpoint_key]["last_used_at"] = usage.timestamp + + # 엔드포인트별 통계를 리스트로 변환 + endpoints = [APIEndpointUsage(**stats) for stats in endpoint_stats.values()] + + # 결과 반환 + return { + "key_id": api_key.key_id, + "total_calls": total_calls, + "total_cost": total_cost, + "endpoints": endpoints + } + +@router.delete("/{key_id}", summary="Delete API key") +async def delete_api_key( + key_id: str = Path(..., description="The ID of the API key"), + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """ + Delete a specific API key. + + - **key_id**: The ID of the API key + + Returns a success message. + + Requires authentication via JWT token. + """ + api_key = db.query(APIKey).filter( + APIKey.key_id == key_id, + APIKey.user_wallet == current_user.wallet_address + ).first() + + if not api_key: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="API key not found" + ) + + db.delete(api_key) + db.commit() + + return {"message": "API key deleted successfully"} diff --git a/projects/HashScope/backend/app/routers/auth.py b/projects/HashScope/backend/app/routers/auth.py new file mode 100644 index 00000000..5f9813f2 --- /dev/null +++ b/projects/HashScope/backend/app/routers/auth.py @@ -0,0 +1,133 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from fastapi.security import OAuth2PasswordRequestForm +from sqlalchemy.orm import Session +import secrets +import string +from datetime import datetime + +from app.database import get_db +from app.models import User +from app.auth.wallet import create_auth_message, verify_signature, generate_nonce +from app.auth.jwt import create_access_token +from app.utils.wallet import normalize_address, checksum_address, is_valid_address +from pydantic import BaseModel + +router = APIRouter() + +# 스키마 정의 +class NonceRequest(BaseModel): + wallet_address: str + +class NonceResponse(BaseModel): + wallet_address: str + nonce: str + message: str + + def dict(self, *args, **kwargs): + # 기본 dict 메서드 호출 + data = super().dict(*args, **kwargs) + # 지갑 주소를 체크섬 형식으로 변환 + if 'wallet_address' in data: + data['wallet_address'] = checksum_address(data['wallet_address']) + return data + +class VerifySignatureRequest(BaseModel): + wallet_address: str + signature: str + +class TokenResponse(BaseModel): + access_token: str + wallet_address: str + token_type: str = "bearer" + + def dict(self, *args, **kwargs): + # 기본 dict 메서드 호출 + data = super().dict(*args, **kwargs) + # 지갑 주소를 체크섬 형식으로 변환 + if 'wallet_address' in data: + data['wallet_address'] = checksum_address(data['wallet_address']) + return data + +@router.post("/nonce", response_model=NonceResponse, summary="Get authentication nonce") +async def get_nonce(request: NonceRequest, db: Session = Depends(get_db)): + """ + Get a nonce for wallet authentication. + + - **wallet_address**: Ethereum wallet address + + Returns a nonce and message to be signed by the wallet. + """ + # Normalize wallet address + wallet_address = normalize_address(request.wallet_address) + + # Check if user exists + user = db.query(User).filter(User.wallet_address == wallet_address).first() + + # Generate new nonce + nonce = generate_nonce() + + if user: + # Update existing user's nonce + user.nonce = nonce + else: + # Create new user with wallet address only + user = User(wallet_address=wallet_address, nonce=nonce) + db.add(user) + + db.commit() + + # Create message to be signed + message = create_auth_message(wallet_address, nonce) + + return NonceResponse( + wallet_address=wallet_address, + nonce=nonce, + message=message + ) + +@router.post("/verify", response_model=TokenResponse, summary="Verify wallet signature") +async def verify_wallet_signature(request: VerifySignatureRequest, db: Session = Depends(get_db)): + """ + Verify wallet signature and issue JWT token. + + - **wallet_address**: Ethereum wallet address + - **signature**: Signature of the nonce message + + Returns a JWT token if signature is valid. + """ + # Normalize wallet address + wallet_address = normalize_address(request.wallet_address) + + # Get user from database + user = db.query(User).filter(User.wallet_address == wallet_address).first() + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid wallet address" + ) + + # Create message that should have been signed + message = create_auth_message(wallet_address, user.nonce) + + # Verify signature + if not verify_signature(message, request.signature, wallet_address): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid signature" + ) + + # Generate new nonce for security (prevent replay attacks) + user.nonce = generate_nonce() + + # Update last login time + user.last_login_at = datetime.utcnow() + + db.commit() + + # Create access token + access_token = create_access_token(data={"sub": wallet_address}) + + return TokenResponse( + access_token=access_token, + wallet_address=wallet_address + ) diff --git a/projects/HashScope/backend/app/routers/crypto.py b/projects/HashScope/backend/app/routers/crypto.py new file mode 100644 index 00000000..70f16c9c --- /dev/null +++ b/projects/HashScope/backend/app/routers/crypto.py @@ -0,0 +1,555 @@ +from fastapi import APIRouter, Depends, HTTPException, status, Header, Request +from sqlalchemy.orm import Session +from typing import Dict, Optional, List +import os +import time +import hmac +import hashlib +import requests +import json +from datetime import datetime +import logging +from requests.adapters import HTTPAdapter +from urllib3.util.retry import Retry +import yfinance as yf +from bs4 import BeautifulSoup +import concurrent.futures + +from app.database import get_db +from app.models import User, APIKey, APIUsage +from app.auth.dependencies import get_current_user +from app.auth.api_key import verify_api_key, get_api_key_with_tracking +from pydantic import BaseModel, Field + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + handlers=[ + logging.StreamHandler() + ] +) +logger = logging.getLogger('crypto_api') + +router = APIRouter() + +# 스키마 정의 +class CryptoPrice(BaseModel): + price: float + currency: str + timestamp: datetime = Field(default_factory=datetime.utcnow) + +class CryptoPriceList(BaseModel): + prices: Dict[str, float] + currency: str + timestamp: datetime = Field(default_factory=datetime.utcnow) + +class KimchiPremium(BaseModel): + premium_percentage: float + binance_price_usd: float + upbit_price_krw: float + exchange_rate: float + timestamp: datetime = Field(default_factory=datetime.utcnow) + +# 요청 세션 생성 함수 +def get_session_with_retries( + retries=3, + backoff_factor=0.3, + status_forcelist=(500, 502, 504), + allowed_methods=None +): + """ + Create a request session with retry functionality + + Args: + retries: Number of retry attempts + backoff_factor: Time delay factor between retries + status_forcelist: HTTP status codes to retry + allowed_methods: HTTP methods to retry + + Returns: + requests.Session: Session with retry functionality + """ + if allowed_methods is None: + allowed_methods = ["HEAD", "GET", "OPTIONS"] + + retry = Retry( + total=retries, + read=retries, + connect=retries, + backoff_factor=backoff_factor, + status_forcelist=status_forcelist, + allowed_methods=allowed_methods, + ) + + adapter = HTTPAdapter(max_retries=retry) + session = requests.Session() + session.mount("http://", adapter) + session.mount("https://", adapter) + + return session + +# 바이낸스 API에서 암호화폐 가격 조회 +def get_binance_price(symbol, max_retries=3, retry_delay=2): + """ + Get the recent trading price of a specific cryptocurrency symbol from Binance API + + Args: + symbol (str): Trading pair symbol (e.g., 'BTCUSDT', 'ETHUSDT') + max_retries (int): Maximum number of retry attempts + retry_delay (int): Delay between retries in seconds + + Returns: + float: Latest price of the cryptocurrency + """ + endpoint = "https://api.binance.com/api/v3/trades" + + # 요청 파라미터 + params = { + 'symbol': symbol, + 'limit': 1 # 가장 최근 거래만 필요 + } + + # 세션 생성 + session = get_session_with_retries() + + for attempt in range(max_retries + 1): + try: + logger.info(f"{symbol} 가격 조회 중 (시도 {attempt + 1}/{max_retries + 1})") + response = session.get(endpoint, params=params, timeout=10) + response.raise_for_status() + + trades = response.json() + if trades and len(trades) > 0: + price = float(trades[0]['price']) + logger.info(f"{symbol} 가격 조회 성공: {price}") + return price + else: + logger.warning(f"{symbol}에 대한 거래 정보가 없습니다") + if attempt < max_retries: + logger.info(f"{retry_delay}초 후 재시도...") + time.sleep(retry_delay) + continue + return None + + except requests.exceptions.RequestException as e: + logger.error(f"{symbol} 가격 조회 오류: {e}") + if attempt < max_retries: + logger.info(f"{retry_delay}초 후 재시도...") + time.sleep(retry_delay) + else: + logger.error(f"{symbol}에 대한 최대 재시도 횟수에 도달했습니다. 포기합니다.") + return None + except (ValueError, KeyError, TypeError) as e: + logger.error(f"{symbol} 응답 파싱 오류: {e}") + if attempt < max_retries: + logger.info(f"{retry_delay}초 후 재시도...") + time.sleep(retry_delay) + else: + logger.error(f"{symbol}에 대한 최대 재시도 횟수에 도달했습니다. 포기합니다.") + return None + +# 업비트 API에서 BTC 가격 조회 +def get_upbit_btc_price(max_retries=3, retry_delay=2): + """ + Get BTC price from Upbit API + + Args: + max_retries (int): Maximum number of retry attempts + retry_delay (int): Delay between retries in seconds + + Returns: + float: BTC price in KRW + """ + endpoint = "https://api.upbit.com/v1/ticker" + params = {'markets': 'KRW-BTC'} + headers = {'Accept': 'application/json'} + + # 세션 생성 + session = get_session_with_retries() + + for attempt in range(max_retries + 1): + try: + logger.info(f"업비트에서 BTC 가격 조회 중 (시도 {attempt + 1}/{max_retries + 1})") + response = session.get(endpoint, params=params, headers=headers, timeout=10) + response.raise_for_status() + + data = response.json() + if data and len(data) > 0: + price = float(data[0]['trade_price']) + logger.info(f"업비트 BTC 가격 조회 성공: {price} KRW") + return price + else: + logger.warning("업비트에서 데이터를 찾을 수 없습니다") + if attempt < max_retries: + logger.info(f"{retry_delay}초 후 재시도...") + time.sleep(retry_delay) + continue + return None + + except requests.exceptions.RequestException as e: + logger.error(f"업비트 BTC 가격 조회 오류: {e}") + if attempt < max_retries: + logger.info(f"{retry_delay}초 후 재시도...") + time.sleep(retry_delay) + else: + logger.error(f"업비트 BTC 가격에 대한 최대 재시도 횟수에 도달했습니다. 포기합니다.") + return None + except (ValueError, KeyError, TypeError) as e: + logger.error(f"업비트 응답 파싱 오류: {e}") + if attempt < max_retries: + logger.info(f"{retry_delay}초 후 재시도...") + time.sleep(retry_delay) + else: + logger.error(f"업비트 BTC 가격에 대한 최대 재시도 횟수에 도달했습니다. 포기합니다.") + return None + +# 업비트 API에서 USDT 가격 조회 +def get_upbit_usdt_price(max_retries=3, retry_delay=2): + """ + Get USDT price from Upbit API + + Args: + max_retries (int): Maximum number of retry attempts + retry_delay (int): Delay between retries in seconds + + Returns: + float: USDT price in KRW + """ + endpoint = "https://api.upbit.com/v1/ticker" + params = {'markets': 'KRW-USDT'} + headers = {'Accept': 'application/json'} + + # 세션 생성 + session = get_session_with_retries() + + for attempt in range(max_retries + 1): + try: + logger.info(f"업비트에서 USDT 가격 조회 중 (시도 {attempt + 1}/{max_retries + 1})") + response = session.get(endpoint, params=params, headers=headers, timeout=10) + response.raise_for_status() + + data = response.json() + if data and len(data) > 0: + price = float(data[0]['trade_price']) + logger.info(f"업비트 USDT 가격 조회 성공: {price} KRW") + return price + else: + logger.warning("업비트에서 USDT 데이터를 찾을 수 없습니다") + if attempt < max_retries: + logger.info(f"{retry_delay}초 후 재시도...") + time.sleep(retry_delay) + continue + return None + + except requests.exceptions.RequestException as e: + logger.error(f"업비트 USDT 가격 조회 오류: {e}") + if attempt < max_retries: + logger.info(f"{retry_delay}초 후 재시도...") + time.sleep(retry_delay) + else: + logger.error(f"업비트 USDT 가격에 대한 최대 재시도 횟수에 도달했습니다. 포기합니다.") + return None + except (ValueError, KeyError, TypeError) as e: + logger.error(f"업비트 USDT 응답 파싱 오류: {e}") + if attempt < max_retries: + logger.info(f"{retry_delay}초 후 재시도...") + time.sleep(retry_delay) + else: + logger.error(f"업비트 USDT 가격에 대한 최대 재시도 횟수에 도달했습니다. 포기합니다.") + return None + +# 네이버 파이낸스에서 USD/KRW 환율 조회 +def get_usd_krw_rate_naver(max_retries=3, retry_delay=2): + """ + Get USD/KRW exchange rate from Naver Finance + + Args: + max_retries (int): Maximum number of retry attempts + retry_delay (int): Delay between retries in seconds + + Returns: + float: USD/KRW exchange rate + """ + for attempt in range(max_retries + 1): + try: + logger.info(f"네이버 파이낸스에서 USD/KRW 환율 조회 중 (시도 {attempt + 1}/{max_retries + 1})") + + # 네이버 파이낸스 환율 페이지 URL + url = 'https://finance.naver.com/marketindex/' + + # 브라우저 User-Agent 헤더 추가 + headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'} + + # 세션 생성 + session = get_session_with_retries() + + # 웹 페이지 요청 + response = session.get(url, headers=headers, timeout=10) + response.raise_for_status() + + # BeautifulSoup으로 HTML 파싱 + soup = BeautifulSoup(response.text, 'html.parser') + + # 환율 정보가 포함된 요소 찾기 + exchange_rate_element = soup.select_one('#exchangeList > li.on > a.head.usd > div > span.value') + + if exchange_rate_element: + # 환율 텍스트 추출 및 float로 변환 + exchange_rate_text = exchange_rate_element.text.strip().replace(',', '') + exchange_rate = float(exchange_rate_text) + logger.info(f"네이버 파이낸스에서 USD/KRW 환율 조회 성공: {exchange_rate}") + return exchange_rate + else: + logger.warning("네이버 파이낸스 페이지에서 환율 요소를 찾을 수 없습니다") + if attempt < max_retries: + logger.info(f"{retry_delay}초 후 재시도...") + time.sleep(retry_delay) + continue + return None + + except Exception as e: + logger.error(f"네이버 파이낸스에서 USD/KRW 환율 조회 오류: {e}") + if attempt < max_retries: + logger.info(f"{retry_delay}초 후 재시도...") + time.sleep(retry_delay) + else: + logger.error("네이버 파이낸스에서 USD/KRW 환율에 대한 최대 재시도 횟수에 도달했습니다. 포기합니다.") + return None + +# Yahoo Finance에서 USD/KRW 환율 조회 +def get_usd_krw_rate_yahoo(max_retries=3, retry_delay=2): + """ + Get USD/KRW exchange rate from Yahoo Finance + + Args: + max_retries (int): Maximum number of retry attempts + retry_delay (int): Delay between retries in seconds + + Returns: + float: USD/KRW exchange rate + """ + for attempt in range(max_retries + 1): + try: + logger.info(f"Yahoo Finance에서 USD/KRW 환율 조회 중 (시도 {attempt + 1}/{max_retries + 1})") + ticker = 'USDKRW=X' + data = yf.Ticker(ticker) + + # 현재 환율 조회 + exchange_rate = data.info['regularMarketPrice'] + logger.info(f"Yahoo Finance에서 USD/KRW 환율 조회 성공: {exchange_rate}") + return exchange_rate + + except Exception as e: + logger.error(f"Yahoo Finance에서 USD/KRW 환율 조회 오류: {e}") + if attempt < max_retries: + logger.info(f"{retry_delay}초 후 재시도...") + time.sleep(retry_delay) + else: + logger.error("Yahoo Finance에서 USD/KRW 환율에 대한 최대 재시도 횟수에 도달했습니다. 네이버 파이낸스로 시도합니다...") + return None + +# USD/KRW 환율 조회 (네이버 및 Yahoo 폴백 메커니즘) +def get_usd_krw_rate(max_retries=3, retry_delay=2): + """ + Get USD/KRW exchange rate (using fallback mechanism) + First tries Naver Finance, then falls back to Yahoo Finance if that fails + + Args: + max_retries (int): Maximum number of retry attempts + retry_delay (int): Delay between retries in seconds + + Returns: + float: USD/KRW exchange rate + """ + # 먼저 네이버 파이낸스 시도 + rate = get_usd_krw_rate_naver(max_retries, retry_delay) + + # 네이버 파이낸스가 실패하면 Yahoo Finance 시도 + if rate is None: + rate = get_usd_krw_rate_yahoo(max_retries, retry_delay) + + return rate + +# 김치 프리미엄 계산 +def calculate_premium(binance_price, upbit_price_krw, exchange_rate): + """ + Calculate the premium percentage between Upbit and Binance prices + Uses the following formula: + (UPBIT:BTCKRW - BINANCE:BTCUSDT * FX_IDC:USDKRW) / (BINANCE:BTCUSDT * FX_IDC:USDKRW) * 100 + + Args: + binance_price (float): BTC price in USD from Binance (BINANCE:BTCUSDT) + upbit_price_krw (float): BTC price in KRW from Upbit (UPBIT:BTCKRW) + exchange_rate (float): USD/KRW exchange rate (FX_IDC:USDKRW) + + Returns: + float: Premium percentage + """ + if not binance_price or not upbit_price_krw or not exchange_rate: + return None + + # 바이낸스 BTC 가격을 KRW로 변환 + binance_price_krw = binance_price * exchange_rate + + # 공식을 사용하여 프리미엄 계산 + premium_percentage = (upbit_price_krw - binance_price_krw) / binance_price_krw * 100 + + return premium_percentage + +# API 엔드포인트: BTC 달러 가격 +@router.get("/btc/usd", response_model=CryptoPrice, summary="Get BTC price in USD") +async def get_btc_usd_price(request: Request, api_key: APIKey = Depends(get_api_key_with_tracking)): + """ + Get BTC price in USD from Binance API + + Returns: + CryptoPrice: BTC price information in USD + """ + price = get_binance_price('BTCUSDT') + + if price is None: + raise HTTPException( + status_code=status.HTTP_503_SERVICE_UNAVAILABLE, + detail="Failed to retrieve BTC price from Binance API" + ) + + return CryptoPrice( + price=price, + currency="USD", + timestamp=datetime.utcnow() + ) + +# API 엔드포인트: BTC 원화 가격 +@router.get("/btc/krw", response_model=CryptoPrice, summary="Get BTC price in KRW") +async def get_btc_krw_price(request: Request, api_key: APIKey = Depends(get_api_key_with_tracking)): + """ + Get BTC price in KRW from Upbit API + + Returns: + CryptoPrice: BTC price information in KRW + """ + price = get_upbit_btc_price() + + if price is None: + raise HTTPException( + status_code=status.HTTP_503_SERVICE_UNAVAILABLE, + detail="Failed to retrieve BTC price from Upbit API" + ) + + return CryptoPrice( + price=price, + currency="KRW", + timestamp=datetime.utcnow() + ) + +# API 엔드포인트: USDT 원화 가격 +@router.get("/usdt/krw", response_model=CryptoPrice, summary="Get USDT price in KRW") +async def get_usdt_krw_price(request: Request, api_key: APIKey = Depends(get_api_key_with_tracking)): + """ + Get USDT price in KRW from Upbit API + + Returns: + CryptoPrice: USDT price information in KRW + """ + price = get_upbit_usdt_price() + + if price is None: + raise HTTPException( + status_code=status.HTTP_503_SERVICE_UNAVAILABLE, + detail="Failed to retrieve USDT price from Upbit API" + ) + + return CryptoPrice( + price=price, + currency="KRW", + timestamp=datetime.utcnow() + ) + +# API 엔드포인트: 김치 프리미엄 비율 +@router.get("/kimchi-premium", response_model=KimchiPremium, summary="Get kimchi premium percentage") +async def get_kimchi_premium(request: Request, api_key: APIKey = Depends(get_api_key_with_tracking)): + """ + Get kimchi premium percentage between Upbit and Binance prices + + Returns: + KimchiPremium: Kimchi premium information + """ + # ThreadPoolExecutor를 사용하여 모든 가격을 병렬로 조회 + with concurrent.futures.ThreadPoolExecutor() as executor: + # 모든 작업을 executor에 제출 + binance_btc_future = executor.submit(get_binance_price, 'BTCUSDT') + upbit_btc_future = executor.submit(get_upbit_btc_price) + usd_krw_future = executor.submit(get_usd_krw_rate) + + # futures에서 결과 가져오기 + binance_btc_price = binance_btc_future.result() + upbit_btc_price = upbit_btc_future.result() + usd_krw_rate = usd_krw_future.result() + + # 필요한 값 중 하나라도 None인지 확인 + if None in [binance_btc_price, upbit_btc_price, usd_krw_rate]: + raise HTTPException( + status_code=status.HTTP_503_SERVICE_UNAVAILABLE, + detail="Failed to retrieve necessary price information" + ) + + # 프리미엄 계산 + premium = calculate_premium(binance_btc_price, upbit_btc_price, usd_krw_rate) + + if premium is None: + raise HTTPException( + status_code=status.HTTP_503_SERVICE_UNAVAILABLE, + detail="Failed to calculate kimchi premium" + ) + + return KimchiPremium( + premium_percentage=premium, + binance_price_usd=binance_btc_price, + upbit_price_krw=upbit_btc_price, + exchange_rate=usd_krw_rate, + timestamp=datetime.utcnow() + ) + +# API 엔드포인트: 주요 암호화폐 가격 목록 +@router.get("/prices", response_model=CryptoPriceList, summary="Get major cryptocurrency prices") +async def get_crypto_prices(request: Request, api_key: APIKey = Depends(get_api_key_with_tracking)): + """ + Get major cryptocurrency prices (BTC, ETH, XRP) from Binance API + + Returns: + CryptoPriceList: Major cryptocurrency price list + """ + # ThreadPoolExecutor를 사용하여 모든 가격을 병렬로 조회 + with concurrent.futures.ThreadPoolExecutor() as executor: + # 모든 작업을 executor에 제출 + btc_future = executor.submit(get_binance_price, 'BTCUSDT') + eth_future = executor.submit(get_binance_price, 'ETHUSDT') + xrp_future = executor.submit(get_binance_price, 'XRPUSDT') + + # futures에서 결과 가져오기 + btc_price = btc_future.result() + eth_price = eth_future.result() + xrp_price = xrp_future.result() + + # 결과 딕셔너리 생성 + prices = { + 'BTC': btc_price, + 'ETH': eth_price, + 'XRP': xrp_price + } + + # None 값 필터링 + prices = {k: v for k, v in prices.items() if v is not None} + + if not prices: + raise HTTPException( + status_code=status.HTTP_503_SERVICE_UNAVAILABLE, + detail="Failed to retrieve cryptocurrency prices" + ) + + return CryptoPriceList( + prices=prices, + currency="USD", + timestamp=datetime.utcnow() + ) diff --git a/projects/HashScope/backend/app/routers/derivatives.py b/projects/HashScope/backend/app/routers/derivatives.py new file mode 100644 index 00000000..c60e9168 --- /dev/null +++ b/projects/HashScope/backend/app/routers/derivatives.py @@ -0,0 +1,119 @@ +from fastapi import APIRouter, Depends, Request +from typing import Dict, List +from datetime import datetime +from pydantic import BaseModel + +from app.models import APIKey +from app.auth.api_key import get_api_key_with_tracking + +router = APIRouter() + +class FundingRate(BaseModel): + symbol: str + exchange: str + rate: float + next_funding_time: datetime + interval: str + +class OpenInterest(BaseModel): + symbol: str + exchange: str + open_interest: float + open_interest_usd: float + change_24h: float + +# Funding rates for cryptocurrency futures +@router.get("/funding-rates", summary="Get current funding rates for major cryptocurrency futures markets") +async def get_funding_rates(request: Request, api_key: APIKey = Depends(get_api_key_with_tracking)): + """ + Get current funding rates for major cryptocurrency futures markets + + Returns: + List[FundingRate]: List of funding rates for different cryptocurrency pairs + """ + # Dummy data + return [ + FundingRate( + symbol="BTC/USDT", + exchange="Binance", + rate=0.0012, + next_funding_time=datetime.utcnow(), + interval="8h" + ), + FundingRate( + symbol="ETH/USDT", + exchange="Binance", + rate=0.0008, + next_funding_time=datetime.utcnow(), + interval="8h" + ), + FundingRate( + symbol="BTC/USDT", + exchange="Bybit", + rate=0.0010, + next_funding_time=datetime.utcnow(), + interval="8h" + ), + FundingRate( + symbol="ETH/USDT", + exchange="Bybit", + rate=0.0007, + next_funding_time=datetime.utcnow(), + interval="8h" + ), + FundingRate( + symbol="BTC/USD", + exchange="BitMEX", + rate=0.0011, + next_funding_time=datetime.utcnow(), + interval="8h" + ) + ] + +# Open interest for cryptocurrency derivatives +@router.get("/open-interest", summary="Get open interest ratios for major cryptocurrency derivatives") +async def get_open_interest(request: Request, api_key: APIKey = Depends(get_api_key_with_tracking)): + """ + Get open interest ratios for major cryptocurrency derivatives + + Returns: + List[OpenInterest]: List of open interest data for different cryptocurrency pairs + """ + # Dummy data + return [ + OpenInterest( + symbol="BTC/USDT", + exchange="Binance", + open_interest=45000.25, + open_interest_usd=2250012500.0, + change_24h=3.5 + ), + OpenInterest( + symbol="ETH/USDT", + exchange="Binance", + open_interest=350000.75, + open_interest_usd=875001875.0, + change_24h=2.1 + ), + OpenInterest( + symbol="BTC/USDT", + exchange="OKX", + open_interest=38000.5, + open_interest_usd=1900025000.0, + change_24h=1.8 + ), + OpenInterest( + symbol="ETH/USDT", + exchange="OKX", + open_interest=320000.0, + open_interest_usd=800000000.0, + change_24h=1.2 + ), + OpenInterest( + symbol="BTC/USD", + exchange="Deribit", + open_interest=25000.0, + open_interest_usd=1250000000.0, + change_24h=4.2 + ) + ] diff --git a/projects/HashScope/backend/app/routers/opensource.py b/projects/HashScope/backend/app/routers/opensource.py new file mode 100644 index 00000000..51bb16e8 --- /dev/null +++ b/projects/HashScope/backend/app/routers/opensource.py @@ -0,0 +1,193 @@ +from fastapi import APIRouter, Depends, Request +from typing import Dict, List +from datetime import datetime +from pydantic import BaseModel + +from app.models import APIKey +from app.auth.api_key import get_api_key_with_tracking + +router = APIRouter() + +class PullRequest(BaseModel): + id: int + title: str + author: str + created_at: datetime + updated_at: datetime + state: str + url: str + comments: int + additions: int + deletions: int + +class RepositoryStats(BaseModel): + stars: int + forks: int + open_issues: int + watchers: int + last_commit: datetime + contributors_count: int + release_version: str + release_date: datetime + +# Bitcoin Core repository activity +@router.get("/bitcoin", summary="Get latest pull requests, stars, and activities from Bitcoin Core repository") +async def get_bitcoin_activity(request: Request, api_key: APIKey = Depends(get_api_key_with_tracking)): + """ + Get latest pull requests, stars, and activities from Bitcoin Core repository + + Returns: + Dict: Dictionary containing repository stats and recent pull requests + """ + # Dummy data + return { + "stats": RepositoryStats( + stars=72500, + forks=34200, + open_issues=1250, + watchers=4800, + last_commit=datetime.utcnow(), + contributors_count=890, + release_version="25.1", + release_date=datetime.utcnow() + ), + "pull_requests": [ + PullRequest( + id=28123, + title="p2p: Add support for v2 transport protocol", + author="theStack", + created_at=datetime.utcnow(), + updated_at=datetime.utcnow(), + state="open", + url="https://github.com/bitcoin/bitcoin/pull/28123", + comments=45, + additions=2500, + deletions=1200 + ), + PullRequest( + id=28120, + title="wallet: Improve coin selection algorithm efficiency", + author="achow101", + created_at=datetime.utcnow(), + updated_at=datetime.utcnow(), + state="open", + url="https://github.com/bitcoin/bitcoin/pull/28120", + comments=32, + additions=850, + deletions=320 + ), + PullRequest( + id=28115, + title="consensus: Optimize block validation process", + author="sipa", + created_at=datetime.utcnow(), + updated_at=datetime.utcnow(), + state="merged", + url="https://github.com/bitcoin/bitcoin/pull/28115", + comments=78, + additions=1200, + deletions=950 + ), + PullRequest( + id=28110, + title="doc: Update developer documentation for new RPC methods", + author="MarcoFalke", + created_at=datetime.utcnow(), + updated_at=datetime.utcnow(), + state="merged", + url="https://github.com/bitcoin/bitcoin/pull/28110", + comments=15, + additions=450, + deletions=120 + ) + ] + } + +# Ethereum Core repositories activity +@router.get("/ethereum", summary="Get latest pull requests, stars, and activities from Ethereum Core repositories") +async def get_ethereum_activity(request: Request, api_key: APIKey = Depends(get_api_key_with_tracking)): + """ + Get latest pull requests, stars, and activities from Ethereum Core repositories + + Returns: + Dict: Dictionary containing repository stats and recent pull requests for multiple Ethereum repositories + """ + # Dummy data + return { + "go-ethereum": { + "stats": RepositoryStats( + stars=43800, + forks=18500, + open_issues=320, + watchers=2800, + last_commit=datetime.utcnow(), + contributors_count=720, + release_version="1.13.4", + release_date=datetime.utcnow() + ), + "pull_requests": [ + PullRequest( + id=28500, + title="eth: Implement EIP-4844 blob transaction support", + author="karalabe", + created_at=datetime.utcnow(), + updated_at=datetime.utcnow(), + state="merged", + url="https://github.com/ethereum/go-ethereum/pull/28500", + comments=65, + additions=3200, + deletions=1500 + ), + PullRequest( + id=28495, + title="core: Optimize state trie access patterns", + author="holiman", + created_at=datetime.utcnow(), + updated_at=datetime.utcnow(), + state="open", + url="https://github.com/ethereum/go-ethereum/pull/28495", + comments=42, + additions=1800, + deletions=950 + ) + ] + }, + "consensus-specs": { + "stats": RepositoryStats( + stars=12500, + forks=4300, + open_issues=180, + watchers=950, + last_commit=datetime.utcnow(), + contributors_count=320, + release_version="v1.4.0", + release_date=datetime.utcnow() + ), + "pull_requests": [ + PullRequest( + id=3250, + title="specs: Add Verkle tree transition specifications", + author="dankrad", + created_at=datetime.utcnow(), + updated_at=datetime.utcnow(), + state="open", + url="https://github.com/ethereum/consensus-specs/pull/3250", + comments=85, + additions=4500, + deletions=1200 + ), + PullRequest( + id=3245, + title="specs: Improve validator rewards calculation", + author="djrtwo", + created_at=datetime.utcnow(), + updated_at=datetime.utcnow(), + state="merged", + url="https://github.com/ethereum/consensus-specs/pull/3245", + comments=38, + additions=850, + deletions=420 + ) + ] + } + } diff --git a/projects/HashScope/backend/app/routers/projects.py b/projects/HashScope/backend/app/routers/projects.py new file mode 100644 index 00000000..7fc4bcb5 --- /dev/null +++ b/projects/HashScope/backend/app/routers/projects.py @@ -0,0 +1,155 @@ +from fastapi import APIRouter, Depends, Request +from typing import Dict, List +from datetime import datetime +from pydantic import BaseModel + +from app.models import APIKey +from app.auth.api_key import get_api_key_with_tracking + +router = APIRouter() + +class ProjectUpdate(BaseModel): + title: str + description: str + date: datetime + url: str + type: str # "release", "announcement", "partnership", etc. + +class EthereumStandard(BaseModel): + eip_number: int + title: str + status: str + type: str + category: str + author: str + created: datetime + url: str + description: str + +# HashKey Chain updates +@router.get("/hsk", summary="Get latest updates and developments from HashKey Chain") +async def get_hsk_updates(request: Request, api_key: APIKey = Depends(get_api_key_with_tracking)): + """ + Get latest updates and developments from HashKey Chain + + Returns: + List[ProjectUpdate]: List of recent updates from HashKey Chain + """ + # Dummy data + return [ + ProjectUpdate( + title="HashKey Chain Mainnet Upgrade v1.2.0", + description="Major network upgrade improving transaction throughput and reducing gas fees", + date=datetime.utcnow(), + url="https://hashkey.com/blog/mainnet-upgrade-v1-2-0", + type="release" + ), + ProjectUpdate( + title="HashKey Chain Partners with Leading DeFi Protocols", + description="Strategic partnerships with top DeFi protocols to enhance the HSK ecosystem", + date=datetime.utcnow(), + url="https://hashkey.com/blog/defi-partnerships", + type="partnership" + ), + ProjectUpdate( + title="HashKey Chain Developer Grants Program Launches", + description="$10 million fund to support developers building on the HashKey Chain", + date=datetime.utcnow(), + url="https://hashkey.com/blog/developer-grants-program", + type="announcement" + ), + ProjectUpdate( + title="HashKey Chain Integrates with Chainlink Oracle Network", + description="Integration with Chainlink provides secure and reliable oracle services for HSK DApps", + date=datetime.utcnow(), + url="https://hashkey.com/blog/chainlink-integration", + type="integration" + ) + ] + +# Ethereum standards +@router.get("/ethereum/standards", summary="Get information about new Ethereum standards and proposals") +async def get_ethereum_standards(request: Request, api_key: APIKey = Depends(get_api_key_with_tracking)): + """ + Get information about new Ethereum standards and proposals + + Returns: + List[EthereumStandard]: List of recent Ethereum Improvement Proposals (EIPs) + """ + # Dummy data + return [ + EthereumStandard( + eip_number=4844, + title="Shard Blob Transactions", + status="Final", + type="Standards Track", + category="Core", + author="Vitalik Buterin, Dankrad Feist, Diederik Loerakker, George Kadianakis, Matt Garnett, Mofi Taiwo, Proto Lambda", + created=datetime.utcnow(), + url="https://eips.ethereum.org/EIPS/eip-4844", + description="This EIP introduces a new transaction format for 'blob-carrying transactions' which contain a large amount of data that cannot be accessed by EVM execution, but whose commitment can be accessed." + ), + EthereumStandard( + eip_number=1559, + title="Fee market change for ETH 1.0 chain", + status="Final", + type="Standards Track", + category="Core", + author="Vitalik Buterin, Eric Conner, Rick Dudley, Matthew Slipper, Ian Norden, Abdelhamid Bakhta", + created=datetime.utcnow(), + url="https://eips.ethereum.org/EIPS/eip-1559", + description="This EIP introduces a transaction pricing mechanism that includes fixed-per-block network fee that is burned and dynamically expands/contracts block sizes to deal with transient congestion." + ), + EthereumStandard( + eip_number=6551, + title="Non-fungible Token Bound Accounts", + status="Review", + type="Standards Track", + category="ERC", + author="Jayden Windle, Benny Giang, Jaime Armas, Alanah Lam, Brent Castiglione, Shiny Marisa", + created=datetime.utcnow(), + url="https://eips.ethereum.org/EIPS/eip-6551", + description="A standard for NFTs to own assets and execute transactions, allowing them to function like traditional accounts." + ) + ] + +# Solana updates +@router.get("/solana", summary="Get latest updates and developments from Solana blockchain") +async def get_solana_updates(request: Request, api_key: APIKey = Depends(get_api_key_with_tracking)): + """ + Get latest updates and developments from Solana blockchain + + Returns: + List[ProjectUpdate]: List of recent updates from Solana blockchain + """ + # Dummy data + return [ + ProjectUpdate( + title="Solana Mainnet Beta Upgrade to v1.16", + description="Major network upgrade improving transaction processing and reducing network congestion", + date=datetime.utcnow(), + url="https://solana.com/news/mainnet-beta-v1-16", + type="release" + ), + ProjectUpdate( + title="Solana Mobile Stack and Saga Phone Launch", + description="Solana launches mobile stack and Saga phone to bring Web3 to mobile users", + date=datetime.utcnow(), + url="https://solana.com/news/solana-mobile-stack-launch", + type="product" + ), + ProjectUpdate( + title="Solana Breakpoint 2025 Conference Announced", + description="Annual Solana developer conference to be held in Singapore", + date=datetime.utcnow(), + url="https://solana.com/news/breakpoint-2025", + type="event" + ), + ProjectUpdate( + title="Solana Foundation Announces $100M Ecosystem Fund", + description="New fund to support developers and projects building on Solana", + date=datetime.utcnow(), + url="https://solana.com/news/ecosystem-fund", + type="announcement" + ) + ] diff --git a/projects/HashScope/backend/app/routers/social.py b/projects/HashScope/backend/app/routers/social.py new file mode 100644 index 00000000..e6e0addc --- /dev/null +++ b/projects/HashScope/backend/app/routers/social.py @@ -0,0 +1,148 @@ +from fastapi import APIRouter, Depends, Request +from typing import Dict, List +from datetime import datetime +from pydantic import BaseModel + +from app.models import APIKey +from app.auth.api_key import get_api_key_with_tracking + +router = APIRouter() + +class SocialPost(BaseModel): + id: str + username: str + content: str + timestamp: datetime + likes: int + reposts: int + comments: int + +class TrendingTopic(BaseModel): + id: str + name: str + tweet_count: int + category: str + location: str = None + +# Trump's latest posts from Truth Social +@router.get("/trump", summary="Get Donald Trump's latest posts from Truth Social") +async def get_trump_posts(request: Request, api_key: APIKey = Depends(get_api_key_with_tracking)): + """ + Get Donald Trump's latest posts from Truth Social + + Returns: + List[SocialPost]: List of Trump's latest posts + """ + # Dummy data + return [ + SocialPost( + id="ts_123456789", + username="realDonaldTrump", + content="MAKE AMERICA GREAT AGAIN! The economy is booming like never before. Jobs, jobs, jobs!", + timestamp=datetime.utcnow(), + likes=45000, + reposts=20000, + comments=15000 + ), + SocialPost( + id="ts_123456788", + username="realDonaldTrump", + content="The Fake News Media is working overtime to spread disinformation. SAD!", + timestamp=datetime.utcnow(), + likes=38000, + reposts=18000, + comments=12000 + ), + SocialPost( + id="ts_123456787", + username="realDonaldTrump", + content="Just had a great meeting with world leaders. America is respected again!", + timestamp=datetime.utcnow(), + likes=42000, + reposts=19000, + comments=14000 + ) + ] + +# Elon Musk's latest posts from X (Twitter) +@router.get("/elon", summary="Get Elon Musk's latest posts from X (Twitter)") +async def get_elon_posts(request: Request, api_key: APIKey = Depends(get_api_key_with_tracking)): + """ + Get Elon Musk's latest posts from X (Twitter) + + Returns: + List[SocialPost]: List of Elon Musk's latest posts + """ + # Dummy data + return [ + SocialPost( + id="x_987654321", + username="elonmusk", + content="The future of sustainable energy is here. Tesla's new battery technology will revolutionize the industry.", + timestamp=datetime.utcnow(), + likes=120000, + reposts=50000, + comments=30000 + ), + SocialPost( + id="x_987654320", + username="elonmusk", + content="SpaceX Starship successfully completed its orbital test flight. Mars, here we come!", + timestamp=datetime.utcnow(), + likes=200000, + reposts=80000, + comments=45000 + ), + SocialPost( + id="x_987654319", + username="elonmusk", + content="Cryptocurrency is the future of finance. Dogecoin to the moon! 🚀", + timestamp=datetime.utcnow(), + likes=150000, + reposts=70000, + comments=40000 + ) + ] + +# X (Twitter) trending topics +@router.get("/x/trends", summary="Get current trending topics on X (Twitter)") +async def get_x_trends(request: Request, api_key: APIKey = Depends(get_api_key_with_tracking)): + """ + Get current trending topics on X (Twitter) + + Returns: + List[TrendingTopic]: List of trending topics on X (Twitter) + """ + # Dummy data + return [ + TrendingTopic( + id="trend_1", + name="#Crypto", + tweet_count=250000, + category="Business & Finance" + ), + TrendingTopic( + id="trend_2", + name="#HSK", + tweet_count=180000, + category="Cryptocurrency" + ), + TrendingTopic( + id="trend_3", + name="#NFTs", + tweet_count=150000, + category="Technology" + ), + TrendingTopic( + id="trend_4", + name="#Web3", + tweet_count=120000, + category="Technology" + ), + TrendingTopic( + id="trend_5", + name="#DeFi", + tweet_count=100000, + category="Business & Finance" + ) + ] diff --git a/projects/HashScope/backend/app/routers/users.py b/projects/HashScope/backend/app/routers/users.py new file mode 100644 index 00000000..1a56ec1d --- /dev/null +++ b/projects/HashScope/backend/app/routers/users.py @@ -0,0 +1,539 @@ +from fastapi import APIRouter, Depends, HTTPException, status, Body +from sqlalchemy.orm import Session +from typing import List, Optional +from pydantic import BaseModel, Field +import os +from datetime import datetime + +from app.database import get_db +from app.models import User, Transaction +from app.blockchain.contracts import ( + get_balance, + get_contract_balance, + get_wallet_balance, + verify_deposit_transaction, + verify_withdraw_transaction, + verify_usage_deduction_transaction, + get_transaction_status, + build_deposit_transaction, + sign_transaction, + wei_to_hsk, + hsk_to_wei, + format_wei_to_hsk +) +from app.auth.dependencies import get_current_user, get_current_admin_user +from app.utils.wallet import normalize_address, checksum_address, is_valid_address + +router = APIRouter( + tags=["users"], + responses={404: {"description": "Not found"}}, +) + +# 사용자 스키마 +class UserResponse(BaseModel): + wallet_address: str + balance: int = 0 + is_admin: bool = False + created_at: datetime + last_login_at: Optional[datetime] = None + + class Config: + orm_mode = True + + def dict(self, *args, **kwargs): + # 기본 dict 메서드 호출 + data = super().dict(*args, **kwargs) + # 지갑 주소를 체크섬 형식으로 변환 + if 'wallet_address' in data: + data['wallet_address'] = checksum_address(data['wallet_address']) + return data + +# 트랜잭션 스키마 +class TransactionCreate(BaseModel): + user_wallet: str + tx_hash: str + amount: int + tx_type: str = "deposit" + status: str = "pending" + +# 예치 요청 스키마 +class DepositRequest(BaseModel): + wallet_address: str + amount: int + +# 서명 요청 스키마 +class SignTransactionRequest(BaseModel): + wallet_address: str + amount: int + private_key: str = Field(..., description="개인 키는 서버에 저장되지 않으며 트랜잭션 서명에만 사용됩니다") + +# 서명 응답 스키마 +class SignTransactionResponse(BaseModel): + tx_hash: str + amount: int + message: str + +# 예치 응답 스키마 +class DepositResponse(BaseModel): + message: str + deposit_address: str + amount: int + +# 인출 요청 스키마 +class WithdrawRequest(BaseModel): + wallet_address: str + amount: int + +# 인출 응답 스키마 +class WithdrawResponse(BaseModel): + message: str + request_id: int + +# 인출 정보 응답 스키마 +class WithdrawInfoResponse(BaseModel): + message: str + deposit_contract: str + +# 사용량 차감 요청 스키마 +class UsageDeductRequest(BaseModel): + wallet_address: str + amount: int + recipient_address: str + +# 사용량 차감 응답 스키마 +class UsageDeductResponse(BaseModel): + message: str + request_id: int + +# 트랜잭션 알림 스키마 +class TransactionNotify(BaseModel): + tx_hash: str + tx_type: str = "deposit" # deposit, withdraw, usage + +# 트랜잭션 상태 응답 스키마 +class TransactionStatusResponse(BaseModel): + status: str + message: Optional[str] = None + +# 사용자 목록 조회 +@router.get("/", response_model=List[UserResponse]) +def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): + users = db.query(User).offset(skip).limit(limit).all() + return users + +# 사용자 상세 조회 +@router.get("/{wallet_address}", response_model=UserResponse) +def read_user(wallet_address: str, db: Session = Depends(get_db)): + wallet_address = normalize_address(wallet_address) + user = db.query(User).filter(User.wallet_address == wallet_address).first() + if user is None: + raise HTTPException(status_code=404, detail="User not found") + return user + +# 사용자 잔액 조회 +@router.get("/{wallet_address}/balance") +def read_user_balance(wallet_address: str, db: Session = Depends(get_db)): + wallet_address = normalize_address(wallet_address) + user = db.query(User).filter(User.wallet_address == wallet_address).first() + if user is None: + raise HTTPException(status_code=404, detail="User not found") + + # 블록체인에서 실제 잔액 조회 + balance = get_balance(wallet_address) + + return { + "wallet_address": checksum_address(wallet_address), + "balance_wei": balance, + "balance_hsk": wei_to_hsk(balance), + "formatted_balance": format_wei_to_hsk(balance) + } + +# 예치 정보 조회 +@router.get("/deposit/info", response_model=DepositResponse) +def get_deposit_info(): + deposit_contract = os.getenv("DEPOSIT_CONTRACT_ADDRESS") + return { + "message": "아래 주소로 HSK를 전송하여 예치할 수 있습니다.", + "deposit_address": deposit_contract, + "amount": 0 + } + +# 트랜잭션 서명 및 전송 +@router.post("/deposit/sign", response_model=SignTransactionResponse) +async def sign_deposit_transaction( + request: SignTransactionRequest, + current_user: User = Depends(get_current_user) +): + """ + 사용자의 개인 키를 사용하여 예치 트랜잭션에 서명하고 전송합니다. + + - **wallet_address**: 사용자 지갑 주소 + - **amount**: 예치할 금액 (wei 단위) + - **private_key**: 개인 키 (서버에 저장되지 않음) + + Returns: + 서명된 트랜잭션 해시 + """ + # 지갑 주소 정규화 + wallet_address = normalize_address(request.wallet_address) + + # 요청한 사용자와 서명할 지갑 주소가 일치하는지 확인 + if current_user.wallet_address != wallet_address: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="You can only sign transactions for your own wallet" + ) + + try: + # 예치 트랜잭션 생성 + unsigned_tx = build_deposit_transaction(wallet_address, request.amount) + + # 트랜잭션 서명 + tx_hash = sign_transaction(unsigned_tx, request.private_key) + + return { + "tx_hash": tx_hash, + "amount": request.amount, + "message": f"Transaction signed and sent. Amount: {format_wei_to_hsk(request.amount)} HSK" + } + except Exception as e: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Failed to sign transaction: {str(e)}" + ) + +# 예치 트랜잭션 알림 +@router.post("/deposit/notify", response_model=TransactionStatusResponse) +def notify_deposit_transaction(tx_data: TransactionNotify, db: Session = Depends(get_db)): + """ + 예치 트랜잭션이 완료되었음을 알립니다. + + - **tx_hash**: 트랜잭션 해시 + + Returns: + 트랜잭션 상태 + """ + try: + # 트랜잭션 검증 + tx_info = verify_deposit_transaction(tx_data.tx_hash) + + if not tx_info or not tx_info.get("success", False): + return {"status": "pending", "message": tx_info.get("message", "Transaction not found or still pending")} + + # 지갑 주소 정규화 + wallet_address = normalize_address(tx_info["user"]) + + # 사용자 조회 + user = db.query(User).filter(User.wallet_address == wallet_address).first() + + if not user: + # 새 사용자 생성 + user = User( + wallet_address=wallet_address, + balance=0, + is_admin=False, + created_at=datetime.utcnow(), + last_login_at=None + ) + db.add(user) + db.commit() + db.refresh(user) # 이 부분이 중요합니다 - ID를 가져오기 위해 refresh 필요 + + # 트랜잭션 기록 + tx = db.query(Transaction).filter(Transaction.tx_hash == tx_data.tx_hash).first() + + if not tx: + # 새 트랜잭션 생성 - user_id 필드 제외 + tx = Transaction( + user_wallet=wallet_address, + tx_hash=tx_data.tx_hash, + amount=tx_info["amount"], + tx_type="deposit", + status="confirmed", + created_at=datetime.utcnow() + ) + db.add(tx) + + # 사용자 잔액 업데이트 (블록체인에서 최신 잔액 조회) + try: + # 블록체인에서 잔액 조회 + blockchain_balance = get_balance(wallet_address) + print(f"Blockchain balance for {wallet_address}: {blockchain_balance}") + + # 현재 DB에 저장된 잔액 확인 + current_db_balance = user.balance or 0 + print(f"Current DB balance for {wallet_address}: {current_db_balance}") + + # 트랜잭션 금액 + deposit_amount = tx_info["amount"] + print(f"Deposit amount: {deposit_amount}") + + # 두 가지 방법으로 잔액 업데이트 시도 + # 1. 블록체인 잔액 사용 + if blockchain_balance > 0: + user.balance = blockchain_balance + print(f"Updated balance from blockchain: {blockchain_balance}") + # 2. 현재 DB 잔액 + 트랜잭션 금액 + else: + new_balance = current_db_balance + deposit_amount + user.balance = new_balance + print(f"Updated balance by adding deposit amount: {new_balance}") + + db.commit() + print(f"Final user balance after commit: {user.balance}") + except Exception as e: + print(f"Error updating balance: {str(e)}") + # 오류가 발생해도 트랜잭션은 기록 + db.commit() + + return { + "status": "confirmed", + "message": f"Deposit confirmed: {format_wei_to_hsk(tx_info['amount'])} HSK" + } + else: + # 이미 처리된 트랜잭션 + return {"status": tx.status, "message": "Transaction already processed"} + + except Exception as e: + import traceback + print(f"Error in notify_deposit_transaction: {str(e)}") + print(traceback.format_exc()) + return {"status": "error", "message": f"Error processing transaction: {str(e)}"} + +# 인출 정보 조회 +@router.get("/withdraw/info", response_model=WithdrawInfoResponse) +def get_withdraw_info(): + deposit_contract = os.getenv("DEPOSIT_CONTRACT_ADDRESS") + return { + "message": "인출은 관리자만 수행할 수 있습니다. 인출 요청을 제출하면 관리자가 처리합니다.", + "deposit_contract": deposit_contract + } + +# 인출 요청 +@router.post("/withdraw/request", response_model=WithdrawResponse) +def request_withdraw( + request: WithdrawRequest, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """ + 인출 요청을 생성합니다. + + - **wallet_address**: 인출할 지갑 주소 + - **amount**: 인출할 금액 (wei 단위) + + Returns: + 인출 요청 ID + """ + # 지갑 주소 정규화 + wallet_address = normalize_address(request.wallet_address) + + # 요청한 사용자와 인출할 지갑 주소가 일치하는지 확인 + if current_user.wallet_address != wallet_address: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="You can only withdraw to your own wallet" + ) + + # 잔액 확인 + balance = get_balance(wallet_address) + + if balance < request.amount: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Insufficient balance. Available: {format_wei_to_hsk(balance)} HSK" + ) + + # 트랜잭션 생성 + tx = Transaction( + user_wallet=wallet_address, + tx_hash="pending", + amount=request.amount, + tx_type="withdraw", + status="pending", + created_at=datetime.utcnow() + ) + db.add(tx) + db.commit() + db.refresh(tx) + + return { + "message": f"Withdraw request created for {format_wei_to_hsk(request.amount)} HSK", + "request_id": tx.id + } + +# 인출 트랜잭션 알림 +@router.post("/withdraw/notify", response_model=TransactionStatusResponse) +def notify_withdraw_transaction( + tx_data: TransactionNotify, + current_user: User = Depends(get_current_admin_user), + db: Session = Depends(get_db) +): + """ + 인출 트랜잭션이 완료되었음을 알립니다. (관리자 전용) + + - **tx_hash**: 트랜잭션 해시 + + Returns: + 트랜잭션 상태 + """ + try: + # 트랜잭션 검증 + tx_info = verify_withdraw_transaction(tx_data.tx_hash) + + if not tx_info: + return {"status": "pending", "message": "Transaction not found or still pending"} + + # 지갑 주소 정규화 + wallet_address = normalize_address(tx_info["to"]) + + # 사용자 조회 + user = db.query(User).filter(User.wallet_address == wallet_address).first() + + if not user: + return {"status": "error", "message": "User not found"} + + # 트랜잭션 기록 + tx = db.query(Transaction).filter(Transaction.tx_hash == tx_data.tx_hash).first() + + if not tx: + # 새 트랜잭션 생성 + tx = Transaction( + user_wallet=wallet_address, + tx_hash=tx_data.tx_hash, + amount=tx_info["value"], + tx_type="withdraw", + status="confirmed", + created_at=datetime.utcnow() + ) + db.add(tx) + db.commit() + + return { + "status": "confirmed", + "message": f"Withdraw confirmed: {format_wei_to_hsk(tx_info['value'])} HSK" + } + else: + # 이미 처리된 트랜잭션 + return {"status": tx.status, "message": "Transaction already processed"} + + except Exception as e: + return {"status": "error", "message": f"Error processing transaction: {str(e)}"} + +# 사용량 차감 요청 +@router.post("/usage/deduct", response_model=UsageDeductResponse) +def deduct_for_usage( + request: UsageDeductRequest, + current_user: User = Depends(get_current_admin_user), + db: Session = Depends(get_db) +): + """ + 사용량에 따른 차감 요청을 생성합니다. (관리자 전용) + + - **wallet_address**: 차감할 지갑 주소 + - **amount**: 차감할 금액 (wei 단위) + - **recipient_address**: 수신자 지갑 주소 + + Returns: + 차감 요청 ID + """ + # 지갑 주소 정규화 + wallet_address = normalize_address(request.wallet_address) + recipient_address = normalize_address(request.recipient_address) + + # 사용자 조회 + user = db.query(User).filter(User.wallet_address == wallet_address).first() + + if not user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found" + ) + + # 잔액 확인 + balance = get_balance(wallet_address) + + if balance < request.amount: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Insufficient balance. Available: {format_wei_to_hsk(balance)} HSK" + ) + + # 트랜잭션 생성 + tx = Transaction( + user_wallet=wallet_address, + tx_hash="pending", + amount=request.amount, + tx_type="usage_deduct", + status="pending", + created_at=datetime.utcnow(), + recipient=recipient_address + ) + db.add(tx) + db.commit() + db.refresh(tx) + + return { + "message": f"Usage deduction request created for {format_wei_to_hsk(request.amount)} HSK", + "request_id": tx.id + } + +# 사용량 차감 트랜잭션 알림 +@router.post("/usage/notify", response_model=TransactionStatusResponse) +def notify_usage_deduction_transaction( + tx_data: TransactionNotify, + current_user: User = Depends(get_current_admin_user), + db: Session = Depends(get_db) +): + """ + 사용량 차감 트랜잭션이 완료되었음을 알립니다. (관리자 전용) + + - **tx_hash**: 트랜잭션 해시 + + Returns: + 트랜잭션 상태 + """ + try: + # 트랜잭션 검증 + tx_info = verify_usage_deduction_transaction(tx_data.tx_hash) + + if not tx_info: + return {"status": "pending", "message": "Transaction not found or still pending"} + + # 지갑 주소 정규화 + from_address = normalize_address(tx_info["from"]) + to_address = normalize_address(tx_info["to"]) + + # 사용자 조회 + user = db.query(User).filter(User.wallet_address == from_address).first() + + if not user: + return {"status": "error", "message": "User not found"} + + # 트랜잭션 기록 + tx = db.query(Transaction).filter(Transaction.tx_hash == tx_data.tx_hash).first() + + if not tx: + # 새 트랜잭션 생성 + tx = Transaction( + user_wallet=from_address, + tx_hash=tx_data.tx_hash, + amount=tx_info["value"], + tx_type="usage_deduct", + status="confirmed", + created_at=datetime.utcnow(), + recipient=to_address + ) + db.add(tx) + db.commit() + + return { + "status": "confirmed", + "message": f"Usage deduction confirmed: {format_wei_to_hsk(tx_info['value'])} HSK" + } + else: + # 이미 처리된 트랜잭션 + return {"status": tx.status, "message": "Transaction already processed"} + + except Exception as e: + return {"status": "error", "message": f"Error processing transaction: {str(e)}"} diff --git a/projects/HashScope/backend/app/schemas/__init__.py b/projects/HashScope/backend/app/schemas/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/projects/HashScope/backend/app/schemas/__init__.py @@ -0,0 +1 @@ + diff --git a/projects/HashScope/backend/app/schemas/api_key.py b/projects/HashScope/backend/app/schemas/api_key.py new file mode 100644 index 00000000..f3dde18b --- /dev/null +++ b/projects/HashScope/backend/app/schemas/api_key.py @@ -0,0 +1,29 @@ +from pydantic import BaseModel, Field +from typing import Optional +from datetime import datetime + +class APIKeyCreate(BaseModel): + name: Optional[str] = Field(None, description="Optional name for the API key") + rate_limit_per_minute: Optional[int] = Field(60, description="Rate limit per minute") + +class APIKeyResponse(BaseModel): + key_id: str + name: Optional[str] + is_active: bool + created_at: datetime + expires_at: Optional[datetime] + rate_limit_per_minute: int + + class Config: + orm_mode = True + +class APIKeyWithSecret(APIKeyResponse): + secret_key: str = Field(..., description="Secret key (only shown once)") + +class APIKeyUsage(APIKeyResponse): + call_count: int + last_used_at: Optional[datetime] + token_consumption_rate: float + + class Config: + orm_mode = True diff --git a/projects/HashScope/backend/app/schemas/auth.py b/projects/HashScope/backend/app/schemas/auth.py new file mode 100644 index 00000000..b42227f9 --- /dev/null +++ b/projects/HashScope/backend/app/schemas/auth.py @@ -0,0 +1,20 @@ +from pydantic import BaseModel, Field +from typing import Optional + +class NonceRequest(BaseModel): + wallet_address: str = Field(..., description="User's blockchain wallet address") + +class NonceResponse(BaseModel): + wallet_address: str + nonce: str + message: str = Field(..., description="Message to be signed by the wallet") + +class VerifySignatureRequest(BaseModel): + wallet_address: str = Field(..., description="User's blockchain wallet address") + signature: str = Field(..., description="Signature of the nonce message") + +class TokenResponse(BaseModel): + access_token: str + token_type: str = "bearer" + wallet_address: str + token_balance: float = 0.0 diff --git a/projects/HashScope/backend/app/schemas/deposit.py b/projects/HashScope/backend/app/schemas/deposit.py new file mode 100644 index 00000000..65ced956 --- /dev/null +++ b/projects/HashScope/backend/app/schemas/deposit.py @@ -0,0 +1,20 @@ +from pydantic import BaseModel, Field +from typing import Optional +from datetime import datetime + +class DepositTransactionBase(BaseModel): + transaction_hash: str = Field(..., description="Blockchain transaction hash") + amount: float = Field(..., description="Amount of tokens deposited") + +class DepositTransactionCreate(DepositTransactionBase): + pass + +class DepositTransactionResponse(DepositTransactionBase): + id: int + user_id: int + status: str + created_at: datetime + updated_at: Optional[datetime] + + class Config: + orm_mode = True diff --git a/projects/HashScope/backend/app/schemas/user.py b/projects/HashScope/backend/app/schemas/user.py new file mode 100644 index 00000000..f33abed2 --- /dev/null +++ b/projects/HashScope/backend/app/schemas/user.py @@ -0,0 +1,25 @@ +from pydantic import BaseModel, Field +from typing import Optional +from datetime import datetime + +class UserBase(BaseModel): + wallet_address: str = Field(..., description="User's blockchain wallet address") + +class UserCreate(UserBase): + pass + +class UserResponse(UserBase): + id: int + is_active: bool + token_balance: float + created_at: datetime + + class Config: + orm_mode = True + +class UserWithBalance(UserResponse): + token_balance: float + deposit_contract_address: Optional[str] = None + + class Config: + orm_mode = True diff --git a/projects/HashScope/backend/app/static/js/deposit.js b/projects/HashScope/backend/app/static/js/deposit.js new file mode 100644 index 00000000..d99c2550 --- /dev/null +++ b/projects/HashScope/backend/app/static/js/deposit.js @@ -0,0 +1,188 @@ +// HSK Token Deposit Helper +class HSKDepositHelper { + constructor(apiBaseUrl) { + this.apiBaseUrl = apiBaseUrl || ''; + this.token = localStorage.getItem('auth_token'); + } + + // Set authentication token + setToken(token) { + this.token = token; + localStorage.setItem('auth_token', token); + } + + // Get deposit information + async getDepositInfo() { + try { + const response = await fetch(`${this.apiBaseUrl}/users/deposit/info`, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${this.token}`, + 'Content-Type': 'application/json' + } + }); + + if (!response.ok) { + throw new Error(`Error: ${response.status}`); + } + + return await response.json(); + } catch (error) { + console.error('Failed to get deposit info:', error); + throw error; + } + } + + // Get user balance + async getUserBalance() { + try { + const response = await fetch(`${this.apiBaseUrl}/users/balance`, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${this.token}`, + 'Content-Type': 'application/json' + } + }); + + if (!response.ok) { + throw new Error(`Error: ${response.status}`); + } + + return await response.json(); + } catch (error) { + console.error('Failed to get user balance:', error); + throw error; + } + } + + // Notify backend of deposit + async notifyDeposit(transactionHash, amount) { + try { + const response = await fetch(`${this.apiBaseUrl}/users/deposit/notify`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${this.token}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + transaction_hash: transactionHash, + amount: amount + }) + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.detail || `Error: ${response.status}`); + } + + return await response.json(); + } catch (error) { + console.error('Failed to notify deposit:', error); + throw error; + } + } + + // Get deposit history + async getDepositHistory() { + try { + const response = await fetch(`${this.apiBaseUrl}/users/deposit/history`, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${this.token}`, + 'Content-Type': 'application/json' + } + }); + + if (!response.ok) { + throw new Error(`Error: ${response.status}`); + } + + return await response.json(); + } catch (error) { + console.error('Failed to get deposit history:', error); + throw error; + } + } + + // Helper method to connect to MetaMask + async connectWallet() { + if (window.ethereum) { + try { + const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); + return accounts[0]; + } catch (error) { + console.error('User denied account access', error); + throw new Error('Please connect to MetaMask to continue'); + } + } else { + throw new Error('MetaMask is not installed. Please install it to continue'); + } + } + + // Helper method to deposit tokens using web3 + async depositTokens(amount) { + if (!window.ethereum || !window.Web3) { + throw new Error('Web3 is not available. Please install MetaMask'); + } + + try { + // Get deposit info + const depositInfo = await this.getDepositInfo(); + const depositContractAddress = depositInfo.deposit_contract_address; + + // Connect to wallet + const walletAddress = await this.connectWallet(); + + // Check if connected wallet matches user wallet + if (walletAddress.toLowerCase() !== depositInfo.wallet_address.toLowerCase()) { + throw new Error('Connected wallet does not match your registered wallet'); + } + + // Create Web3 instance + const web3 = new Web3(window.ethereum); + + // Get contract ABI (this should be provided by your backend or stored in your frontend) + const depositContractABI = [ + { + "constant": false, + "inputs": [{"name": "amount", "type": "uint256"}], + "name": "deposit", + "outputs": [], + "type": "function" + } + ]; + + // Create contract instance + const depositContract = new web3.eth.Contract(depositContractABI, depositContractAddress); + + // Convert amount to wei (assuming 18 decimals, adjust if different) + const amountInWei = web3.utils.toWei(amount.toString(), 'ether'); + + // Send transaction + const tx = await depositContract.methods.deposit(amountInWei).send({ + from: walletAddress + }); + + // Notify backend + const notifyResult = await this.notifyDeposit(tx.transactionHash, amount); + + return { + transactionHash: tx.transactionHash, + notifyResult: notifyResult + }; + } catch (error) { + console.error('Failed to deposit tokens:', error); + throw error; + } + } +} + +// Example usage: +// const depositHelper = new HSKDepositHelper('http://localhost:8000/api/v1'); +// depositHelper.setToken('your_jwt_token'); +// +// // Get deposit info +// depositHelper.getDepositInfo().then(info => console.log(info)); +// +// // Deposit tokens +// depositHelper.depositTokens(10).then(result => console.log(result)); diff --git a/projects/HashScope/backend/app/utils/__init__.py b/projects/HashScope/backend/app/utils/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/projects/HashScope/backend/app/utils/__init__.py @@ -0,0 +1 @@ + diff --git a/projects/HashScope/backend/app/utils/api_keys.py b/projects/HashScope/backend/app/utils/api_keys.py new file mode 100644 index 00000000..11661d54 --- /dev/null +++ b/projects/HashScope/backend/app/utils/api_keys.py @@ -0,0 +1,39 @@ +import secrets +import string +import hashlib +from datetime import datetime, timedelta +from passlib.context import CryptContext + +# Password context for hashing API keys +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + +def generate_api_key_pair(): + """ + Generate a new API key pair (key_id and secret_key) + """ + # Generate a random key_id (public identifier) + key_id = ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(16)) + + # Generate a random secret_key + secret_key = ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(32)) + + # Hash the secret key for storage + secret_key_hash = pwd_context.hash(secret_key) + + return { + "key_id": key_id, + "secret_key": secret_key, + "secret_key_hash": secret_key_hash + } + +def verify_api_key(plain_secret_key, hashed_secret_key): + """ + Verify an API key against its hash + """ + return pwd_context.verify(plain_secret_key, hashed_secret_key) + +def calculate_expiry_date(days=365): + """ + Calculate an expiry date for API keys + """ + return datetime.utcnow() + timedelta(days=days) diff --git a/projects/HashScope/backend/app/utils/wallet.py b/projects/HashScope/backend/app/utils/wallet.py new file mode 100644 index 00000000..fa9727ca --- /dev/null +++ b/projects/HashScope/backend/app/utils/wallet.py @@ -0,0 +1,65 @@ +""" +지갑 주소 처리를 위한 유틸리티 함수 +""" + +from web3 import Web3 +from typing import Optional + +def normalize_address(address: str) -> str: + """ + 지갑 주소를 소문자로 정규화합니다. + + Args: + address: 이더리움 지갑 주소 + + Returns: + 소문자로 정규화된 지갑 주소 + """ + if not address: + return "" + + # 주소 형식 검증 및 0x 접두사 추가 + if not address.startswith('0x'): + address = '0x' + address + + # 소문자로 변환 + return address.lower() + +def checksum_address(address: str) -> str: + """ + 지갑 주소를 EIP-55 체크섬 형식으로 변환합니다. + + Args: + address: 이더리움 지갑 주소 + + Returns: + 체크섬 형식(대소문자 혼합)의 지갑 주소 + """ + if not address: + return "" + + try: + # Web3 라이브러리를 사용하여 체크섬 주소로 변환 + return Web3.to_checksum_address(address) + except: + # 변환 실패 시 원래 주소 반환 + return address + +def is_valid_address(address: str) -> bool: + """ + 지갑 주소가 유효한지 확인합니다. + + Args: + address: 이더리움 지갑 주소 + + Returns: + 유효한 주소인지 여부 + """ + if not address: + return False + + try: + # 주소 형식 검증 + return Web3.is_address(address) + except: + return False diff --git a/projects/HashScope/backend/requirements.txt b/projects/HashScope/backend/requirements.txt new file mode 100644 index 00000000..a4be9efa --- /dev/null +++ b/projects/HashScope/backend/requirements.txt @@ -0,0 +1,17 @@ +fastapi==0.103.1 +uvicorn==0.23.2 +pydantic==2.3.0 +sqlalchemy==2.0.20 +alembic==1.12.0 +python-jose==3.3.0 +passlib==1.7.4 +python-multipart==0.0.6 +web3==6.9.0 +eth-account==0.9.0 +python-dotenv==1.0.0 +psycopg2-binary==2.9.7 +pytest==7.4.2 +httpx==0.24.1 +requests==2.31.0 +beautifulsoup4==4.12.2 +yfinance==0.2.31 diff --git a/projects/HashScope/backend/tests/__init__.py b/projects/HashScope/backend/tests/__init__.py new file mode 100644 index 00000000..9ba353dd --- /dev/null +++ b/projects/HashScope/backend/tests/__init__.py @@ -0,0 +1 @@ +# 테스트 패키지 초기화 파일 diff --git a/projects/HashScope/backend/tests/conftest.py b/projects/HashScope/backend/tests/conftest.py new file mode 100644 index 00000000..a0efac1f --- /dev/null +++ b/projects/HashScope/backend/tests/conftest.py @@ -0,0 +1,51 @@ +import pytest +import os +import sys +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from fastapi.testclient import TestClient + +# 프로젝트 루트 디렉토리를 Python 경로에 추가 +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from app.database import Base, get_db +from app.main import app +from app.models import User, APIKey, Transaction + +# 테스트용 데이터베이스 설정 +TEST_DATABASE_URL = "sqlite:///./test.db" +engine = create_engine(TEST_DATABASE_URL, connect_args={"check_same_thread": False}) +TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +@pytest.fixture(scope="function") +def test_db(): + # 테스트용 데이터베이스 생성 + Base.metadata.create_all(bind=engine) + + # 테스트 세션 생성 + db = TestingSessionLocal() + try: + yield db + finally: + db.close() + # 테스트 후 데이터베이스 초기화 + Base.metadata.drop_all(bind=engine) + +@pytest.fixture(scope="function") +def client(test_db): + # 테스트용 의존성 오버라이드 + def override_get_db(): + try: + yield test_db + finally: + pass + + # 의존성 오버라이드 적용 + app.dependency_overrides[get_db] = override_get_db + + # 테스트 클라이언트 생성 + with TestClient(app) as client: + yield client + + # 테스트 후 의존성 오버라이드 제거 + app.dependency_overrides = {} diff --git a/projects/HashScope/backend/tests/test_api_deduction.py b/projects/HashScope/backend/tests/test_api_deduction.py new file mode 100644 index 00000000..cc46bbec --- /dev/null +++ b/projects/HashScope/backend/tests/test_api_deduction.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +""" +API 차감 기능 테스트 스크립트 + +이 스크립트는 제공된 API 키를 사용하여 여러 API를 호출하고, +호출당 0.001 HSK가 차감되는 기능을 테스트합니다. +총 10번의 API 호출을 수행하여 차감 요청이 생성되는지 확인합니다. +""" + +import requests +import time +import json +from datetime import datetime +from dotenv import load_dotenv +import os + +load_dotenv() + +API_KEY_ID = os.getenv("API_KEY_ID") +API_KEY_SECRET = os.getenv("API_KEY_SECRET") + +# 서버 URL 설정 +BASE_URL = "https://hashkey.sungwoonsong.com" + +# 헤더 설정 +headers = { + "api-key-id": API_KEY_ID, + "api-key-secret": API_KEY_SECRET, + "Content-Type": "application/json" +} + +# 테스트할 API 엔드포인트 목록 +apis = [ + "/crypto/btc/usd", # BTC 달러 가격 + "/crypto/btc/krw", # BTC 원화 가격 + "/crypto/usdt/krw", # USDT 원화 가격 + "/crypto/kimchi-premium", # 김치 프리미엄 + "/crypto/prices" # 주요 암호화폐 가격 목록 +] + +def call_api(endpoint): + """API를 호출하고 응답을 반환합니다.""" + url = f"{BASE_URL}{endpoint}" + print(f"Calling API: {url}") + + try: + response = requests.get(url, headers=headers) + print(f"Status Code: {response.status_code}") + + if response.status_code == 200: + return response.json() + else: + print(f"Error: {response.text}") + return None + except Exception as e: + print(f"Exception: {str(e)}") + return None + +def main(): + """메인 테스트 함수""" + print(f"Starting API deduction test at {datetime.now()}") + print(f"Using API Key ID: {API_KEY_ID}") + print("=" * 50) + + # 총 10번의 API 호출 수행 + total_calls = 10 + call_count = 0 + + while call_count < total_calls: + # 5개의 API를 순환하며 호출 + api_index = call_count % len(apis) + endpoint = apis[api_index] + + # API 호출 + result = call_api(endpoint) + call_count += 1 + + print(f"Call {call_count}/{total_calls} completed") + print(f"Endpoint: {endpoint}") + if result: + print(f"Response: {json.dumps(result, indent=2, ensure_ascii=False)}") + else: + print("Response: Failed to get response") + print("-" * 30) + + # 호출 간격 설정 (서버 부하 방지) + time.sleep(1) + + print("=" * 50) + print(f"Test completed at {datetime.now()}") + print(f"Total API calls: {call_count}") + print("차감 요청이 생성되었는지 데이터베이스에서 확인하세요.") + +if __name__ == "__main__": + main() diff --git a/projects/HashScope/backend/tests/test_api_keys.py b/projects/HashScope/backend/tests/test_api_keys.py new file mode 100644 index 00000000..c1131756 --- /dev/null +++ b/projects/HashScope/backend/tests/test_api_keys.py @@ -0,0 +1,141 @@ +import pytest +import os +import sys +from fastapi.testclient import TestClient +from eth_account import Account +import random +import string + +# 프로젝트 루트 디렉토리를 Python 경로에 추가 +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from app.main import app +from tests.test_users import generate_random_address, get_auth_token + +client = TestClient(app) + +def test_create_api_key(): + """API 키 생성 테스트""" + # 테스트용 계정 생성 + wallet_address, private_key = generate_random_address() + username = f"test_user_{random.randint(1000, 9999)}" + email = f"{username}@example.com" + + # 사용자 생성 + response = client.post( + "/users/", + json={ + "username": username, + "email": email, + "wallet_address": wallet_address + } + ) + + assert response.status_code == 200 + user_data = response.json() + + # 인증 토큰 획득 + token = get_auth_token(wallet_address, private_key) + headers = {"Authorization": f"Bearer {token}"} + + # API 키 생성 + response = client.post( + "/api-keys/", + json={"name": f"Test Key for {username}"}, + headers=headers + ) + + assert response.status_code == 200 + api_key_data = response.json() + assert "key" in api_key_data + assert "name" in api_key_data + assert api_key_data["name"] == f"Test Key for {username}" + + return api_key_data, headers + +def test_list_api_keys(): + """API 키 목록 조회 테스트""" + # API 키 생성 + _, headers = test_create_api_key() + + # API 키 목록 조회 + response = client.get( + "/api-keys/", + headers=headers + ) + + assert response.status_code == 200 + api_keys = response.json() + assert isinstance(api_keys, list) + assert len(api_keys) > 0 + +def test_revoke_api_key(): + """API 키 폐기 테스트""" + # API 키 생성 + api_key_data, headers = test_create_api_key() + api_key_id = api_key_data["id"] + + # API 키 폐기 + response = client.delete( + f"/api-keys/{api_key_id}", + headers=headers + ) + + assert response.status_code == 200 + result = response.json() + assert "message" in result + assert "revoked" in result["message"].lower() + + # 폐기된 키 확인 + response = client.get( + "/api-keys/", + headers=headers + ) + + assert response.status_code == 200 + api_keys = response.json() + + # 폐기된 키는 목록에서 제외되거나 is_active가 False로 설정됨 + found = False + for key in api_keys: + if key["id"] == api_key_id: + found = True + assert key["is_active"] == False + + if not found: + # 폐기된 키가 목록에서 제외된 경우도 테스트 통과 + pass + +def test_api_key_authentication(): + """API 키 인증 테스트""" + # API 키 생성 + api_key_data, _ = test_create_api_key() + api_key = api_key_data["key"] + + # API 키를 사용한 요청 + headers = {"X-API-Key": api_key} + + # 예치 정보 조회 (API 키로 접근 가능한 엔드포인트) + response = client.get( + "/users/deposit/info", + headers=headers + ) + + # API 키 인증이 구현되어 있다면 200 응답이 와야 함 + # 구현되지 않았다면 401 또는 403 응답이 올 수 있음 + print(f"API 키 인증 테스트 응답 코드: {response.status_code}") + + # 테스트 결과 확인 (API 키 인증이 구현되어 있는 경우) + if response.status_code == 200: + deposit_info = response.json() + assert "deposit_address" in deposit_info + else: + print("API 키 인증이 아직 구현되지 않았거나 이 엔드포인트에 적용되지 않았습니다.") + +if __name__ == "__main__": + # 테스트 실행 + test_create_api_key() + test_list_api_keys() + test_revoke_api_key() + test_api_key_authentication() + print("API 키 관리 테스트 완료!") diff --git a/projects/HashScope/backend/tests/test_auth.py b/projects/HashScope/backend/tests/test_auth.py new file mode 100644 index 00000000..8e33a308 --- /dev/null +++ b/projects/HashScope/backend/tests/test_auth.py @@ -0,0 +1,106 @@ +import pytest +import os +import sys +from fastapi.testclient import TestClient +from eth_account import Account +from eth_account.messages import encode_defunct +import random +import string + +# 프로젝트 루트 디렉토리를 Python 경로에 추가 +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from app.main import app +from app.auth.wallet import create_auth_message + +client = TestClient(app) + +def generate_random_address(): + """테스트용 랜덤 이더리움 주소 생성""" + private_key = "0x" + "".join(random.choices(string.hexdigits, k=64)).lower() + account = Account.from_key(private_key) + return account.address, private_key + +def test_get_nonce(): + """인증 nonce 요청 테스트""" + wallet_address, _ = generate_random_address() + + response = client.post( + "/auth/nonce", + json={"wallet_address": wallet_address} + ) + + assert response.status_code == 200 + data = response.json() + assert "nonce" in data + assert "message" in data + assert data["wallet_address"] == wallet_address.lower() + +def test_verify_signature(): + """서명 검증 테스트""" + # 테스트용 계정 생성 + wallet_address, private_key = generate_random_address() + account = Account.from_key(private_key) + + # Nonce 요청 + response = client.post( + "/auth/nonce", + json={"wallet_address": wallet_address} + ) + + assert response.status_code == 200 + nonce_data = response.json() + nonce = nonce_data["nonce"] + message = nonce_data["message"] + + # 메시지 서명 + message_hash = encode_defunct(text=message) + signed_message = account.sign_message(message_hash) + signature = signed_message.signature.hex() + + # 서명 검증 + response = client.post( + "/auth/verify", + json={ + "wallet_address": wallet_address, + "signature": signature + } + ) + + assert response.status_code == 200 + token_data = response.json() + assert "access_token" in token_data + assert token_data["wallet_address"] == wallet_address.lower() + +def test_invalid_signature(): + """잘못된 서명 테스트""" + # 테스트용 계정 생성 + wallet_address, _ = generate_random_address() + + # Nonce 요청 + response = client.post( + "/auth/nonce", + json={"wallet_address": wallet_address} + ) + + assert response.status_code == 200 + + # 잘못된 서명으로 검증 요청 + invalid_signature = "0x" + "".join(random.choices(string.hexdigits, k=130)).lower() + response = client.post( + "/auth/verify", + json={ + "wallet_address": wallet_address, + "signature": invalid_signature + } + ) + + assert response.status_code == 401 + assert "Invalid signature" in response.json()["detail"] + +if __name__ == "__main__": + # 테스트 실행 + test_get_nonce() + test_verify_signature() + test_invalid_signature() + print("인증 시스템 테스트 완료!") diff --git a/projects/HashScope/backend/tests/test_blockchain.py b/projects/HashScope/backend/tests/test_blockchain.py new file mode 100644 index 00000000..fd410707 --- /dev/null +++ b/projects/HashScope/backend/tests/test_blockchain.py @@ -0,0 +1,124 @@ +import pytest +import os +import sys +from web3 import Web3 +import random +import string +from dotenv import load_dotenv + +# 프로젝트 루트 디렉토리를 Python 경로에 추가 +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from app.blockchain.contracts import ( + w3, + deposit_contract, + get_balance, + get_contract_balance, + get_wallet_balance, + hsk_to_wei, + wei_to_hsk, + format_wei_to_hsk +) + +# 환경 변수 로드 +load_dotenv() + +def test_web3_connection(): + """Web3 연결 테스트""" + assert w3.is_connected() + print(f"Web3 연결 성공: {w3.provider}") + + # 현재 블록 번호 확인 + current_block = w3.eth.block_number + print(f"현재 블록 번호: {current_block}") + assert current_block > 0 + +def test_contract_connection(): + """컨트랙트 연결 테스트""" + # 컨트랙트 주소 확인 + contract_address = deposit_contract.address + print(f"예치 컨트랙트 주소: {contract_address}") + assert Web3.is_address(contract_address) + + # 컨트랙트 함수 확인 + functions = dir(deposit_contract.functions) + required_functions = ["deposit", "withdraw", "getBalance"] + + for func in required_functions: + assert func in functions, f"{func} 함수가 컨트랙트에 없습니다" + + print(f"컨트랙트 필수 함수 확인 완료: {', '.join(required_functions)}") + +def test_balance_functions(): + """잔액 조회 함수 테스트""" + # 테스트용 주소 (이더리움 제로 주소) + test_address = "0x0000000000000000000000000000000000000000" + + # 예치 잔액 조회 + deposit_balance = get_balance(test_address) + print(f"예치 잔액: {format_wei_to_hsk(deposit_balance)}") + assert isinstance(deposit_balance, int) + + # 컨트랙트 잔액 조회 + contract_balance = get_contract_balance() + print(f"컨트랙트 잔액: {format_wei_to_hsk(contract_balance)}") + assert isinstance(contract_balance, int) + + # 지갑 잔액 조회 (이 함수는 실제 블록체인 조회가 필요) + try: + wallet_balance = get_wallet_balance(test_address) + print(f"지갑 잔액: {format_wei_to_hsk(wallet_balance)}") + assert isinstance(wallet_balance, int) + except Exception as e: + print(f"지갑 잔액 조회 중 오류 발생 (예상됨): {e}") + +def test_unit_conversion(): + """단위 변환 함수 테스트""" + # HSK -> Wei 변환 + hsk_amount = 1.5 + wei_amount = hsk_to_wei(hsk_amount) + assert wei_amount == 1.5 * 10**18 + + # Wei -> HSK 변환 + converted_hsk = wei_to_hsk(wei_amount) + assert converted_hsk == hsk_amount + + # 포맷팅 테스트 + formatted = format_wei_to_hsk(wei_amount) + assert "1.500000 HSK" == formatted + + print("단위 변환 테스트 완료") + +def test_transaction_build(): + """트랜잭션 생성 테스트""" + from app.blockchain.contracts import build_deposit_transaction + + # 테스트용 주소 (체크섬 형식으로 생성) + random_hex = "".join(random.choices(string.hexdigits, k=40)).lower() + test_address = Web3.to_checksum_address(f"0x{random_hex}") + + # 예치 트랜잭션 생성 + amount_wei = hsk_to_wei(0.1) # 0.1 HSK + try: + tx = build_deposit_transaction(test_address, amount_wei) + + # 트랜잭션 필드 확인 + assert "to" in tx + assert "value" in tx + assert "gas" in tx + assert tx["to"].lower() == deposit_contract.address.lower() + assert tx["value"] == amount_wei + print("트랜잭션 생성 테스트 완료") + except Exception as e: + print(f"트랜잭션 생성 중 오류 발생: {e}") + # 테스트 환경에서는 일부 오류가 예상될 수 있음 + # 실제 네트워크 연결이 필요한 부분이므로 실패해도 테스트 진행 + +if __name__ == "__main__": + # 테스트 실행 + test_web3_connection() + test_contract_connection() + test_balance_functions() + test_unit_conversion() + test_transaction_build() + print("블록체인 컨트랙트 테스트 완료!") diff --git a/projects/HashScope/backend/tests/test_users.py b/projects/HashScope/backend/tests/test_users.py new file mode 100644 index 00000000..3e12849c --- /dev/null +++ b/projects/HashScope/backend/tests/test_users.py @@ -0,0 +1,197 @@ +import pytest +import os +import sys +from fastapi.testclient import TestClient +from eth_account import Account +from eth_account.messages import encode_defunct +import random +import string +import time + +# 프로젝트 루트 디렉토리를 Python 경로에 추가 +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from app.main import app +from app.auth.wallet import create_auth_message +from app.blockchain.contracts import hsk_to_wei, wei_to_hsk + +client = TestClient(app) + +def generate_random_address(): + """테스트용 랜덤 이더리움 주소 생성""" + private_key = "0x" + "".join(random.choices(string.hexdigits, k=64)).lower() + account = Account.from_key(private_key) + return account.address, private_key + +def get_auth_token(wallet_address, private_key): + """인증 토큰 획득""" + # Nonce 요청 + response = client.post( + "/auth/nonce", + json={"wallet_address": wallet_address} + ) + + nonce_data = response.json() + message = nonce_data["message"] + + # 메시지 서명 + account = Account.from_key(private_key) + message_hash = encode_defunct(text=message) + signed_message = account.sign_message(message_hash) + signature = signed_message.signature.hex() + + # 서명 검증 및 토큰 획득 + response = client.post( + "/auth/verify", + json={ + "wallet_address": wallet_address, + "signature": signature + } + ) + + token_data = response.json() + return token_data["access_token"] + +def test_create_user(): + """사용자 생성 테스트""" + wallet_address, _ = generate_random_address() + username = f"test_user_{random.randint(1000, 9999)}" + email = f"{username}@example.com" + + response = client.post( + "/users/", + json={ + "username": username, + "email": email, + "wallet_address": wallet_address + } + ) + + assert response.status_code == 200 + user_data = response.json() + assert user_data["username"] == username + assert user_data["email"] == email + assert user_data["wallet_address"] == wallet_address.lower() + + return user_data + +def test_get_user(): + """사용자 조회 테스트""" + # 새 사용자 생성 + user_data = test_create_user() + user_id = user_data["id"] + + # 사용자 조회 + response = client.get(f"/users/{user_id}") + + assert response.status_code == 200 + retrieved_user = response.json() + assert retrieved_user["id"] == user_id + assert retrieved_user["username"] == user_data["username"] + assert retrieved_user["email"] == user_data["email"] + assert retrieved_user["wallet_address"] == user_data["wallet_address"] + +def test_get_deposit_info(): + """예치 정보 조회 테스트""" + response = client.get("/users/deposit/info") + + assert response.status_code == 200 + deposit_info = response.json() + assert "deposit_address" in deposit_info + assert "message" in deposit_info + +def test_get_withdraw_info(): + """인출 정보 조회 테스트""" + response = client.get("/users/withdraw/info") + + assert response.status_code == 200 + withdraw_info = response.json() + assert "deposit_contract" in withdraw_info + assert "message" in withdraw_info + +def test_user_balance(): + """사용자 잔액 조회 테스트""" + # 새 사용자 생성 + user_data = test_create_user() + user_id = user_data["id"] + + # 잔액 조회 + response = client.get(f"/users/{user_id}/balance") + + assert response.status_code == 200 + balance_data = response.json() + assert "balance" in balance_data + assert "balance_formatted" in balance_data + +def test_transaction_flow(): + """트랜잭션 흐름 테스트 (모의 트랜잭션)""" + # 새 사용자 생성 + wallet_address, private_key = generate_random_address() + username = f"test_user_{random.randint(1000, 9999)}" + email = f"{username}@example.com" + + response = client.post( + "/users/", + json={ + "username": username, + "email": email, + "wallet_address": wallet_address + } + ) + + user_data = response.json() + user_id = user_data["id"] + + # 인증 토큰 획득 + token = get_auth_token(wallet_address, private_key) + headers = {"Authorization": f"Bearer {token}"} + + # 예치 정보 조회 + response = client.get("/users/deposit/info") + deposit_info = response.json() + deposit_address = deposit_info["deposit_address"] + + # 모의 예치 트랜잭션 (실제 블록체인 트랜잭션 없이 테스트) + mock_tx_hash = "0x" + "".join(random.choices(string.hexdigits, k=64)).lower() + deposit_amount = hsk_to_wei(0.1) # 0.1 HSK + + # 트랜잭션 알림 + response = client.post( + "/users/deposit/notify", + json={ + "tx_hash": mock_tx_hash, + "tx_type": "deposit" + }, + headers=headers + ) + + assert response.status_code in [200, 202] # 200 또는 202 (Accepted) 응답 허용 + + # 인출 요청 테스트 (관리자 권한 필요) + # 실제 환경에서는 관리자 계정으로 테스트해야 함 + withdraw_amount = hsk_to_wei(0.05) # 0.05 HSK + + response = client.post( + "/users/withdraw/request", + json={ + "wallet_address": wallet_address, + "amount": withdraw_amount + }, + headers=headers + ) + + # 관리자 권한이 없으면 401 또는 403 응답이 예상됨 + # 여기서는 권한 검사만 테스트 + assert response.status_code in [200, 401, 403] + + print(f"트랜잭션 흐름 테스트 완료: {response.status_code}") + +if __name__ == "__main__": + # 테스트 실행 + test_create_user() + test_get_user() + test_get_deposit_info() + test_get_withdraw_info() + test_user_balance() + test_transaction_flow() + print("사용자 및 트랜잭션 테스트 완료!") diff --git a/projects/HashScope/frontend/.gitignore b/projects/HashScope/frontend/.gitignore new file mode 100644 index 00000000..9e42d7f6 --- /dev/null +++ b/projects/HashScope/frontend/.gitignore @@ -0,0 +1,93 @@ +# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,nextjs,macos +# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,nextjs,macos + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### NextJS ### +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,nextjs,macos + +.env \ No newline at end of file diff --git a/projects/HashScope/frontend/README.md b/projects/HashScope/frontend/README.md new file mode 100644 index 00000000..c949c8cc --- /dev/null +++ b/projects/HashScope/frontend/README.md @@ -0,0 +1,7 @@ +# HashScope Frontend + +## Running the app + +1. Run `npm install` to install the dependencies +2. Run `npm run dev` to start the development server +3. Open `http://localhost:3000` to view the app in the browser diff --git a/projects/HashScope/frontend/components.json b/projects/HashScope/frontend/components.json new file mode 100644 index 00000000..ffe928f5 --- /dev/null +++ b/projects/HashScope/frontend/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/projects/HashScope/frontend/components/ConnectWallet.tsx b/projects/HashScope/frontend/components/ConnectWallet.tsx new file mode 100644 index 00000000..3b117897 --- /dev/null +++ b/projects/HashScope/frontend/components/ConnectWallet.tsx @@ -0,0 +1,83 @@ +import { useSDK } from '@metamask/sdk-react'; +import { useEffect, useState } from 'react'; +import { formatAddress, formatBalance, formatChainAsNum } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; + +const ConnectWallet = () => { + const [account, setAccount] = useState(); + const [balance, setBalance] = useState(); + const [chainId, setChainId] = useState(); + const { sdk, connected, connecting, provider } = useSDK(); + + useEffect(() => { + if (connected && provider) { + // Get account, balance and chainId + const getAccountDetails = async () => { + const accounts = await provider.request({ method: 'eth_requestAccounts' }); + if (!accounts || !Array.isArray(accounts) || accounts.length === 0) return; + + const balance = await provider.request({ + method: 'eth_getBalance', + params: [accounts[0], 'latest'], + }); + const chainId = await provider.request({ method: 'eth_chainId' }); + + setAccount(accounts[0]); + setBalance(formatBalance(balance as string)); + setChainId(formatChainAsNum(chainId as string).toString()); + }; + + getAccountDetails(); + } + }, [connected, provider]); + + const connect = async () => { + try { + await sdk?.connect(); + } catch (err) { + console.warn(`No accounts found`, err); + } + }; + + const disconnect = async () => { + try { + await sdk?.disconnect(); + setAccount(undefined); + setBalance(undefined); + setChainId(undefined); + } catch (err) { + console.warn(`Failed to disconnect`, err); + } + }; + + return ( +
+ {connected ? ( + <> +
+ + {formatAddress(account)} ({balance} ETH) + + Chain ID: {chainId} +
+ + + ) : ( + + )} +
+ ); +}; + +export default ConnectWallet; \ No newline at end of file diff --git a/projects/HashScope/frontend/components/MetaMaskProvider.tsx b/projects/HashScope/frontend/components/MetaMaskProvider.tsx new file mode 100644 index 00000000..16d8777c --- /dev/null +++ b/projects/HashScope/frontend/components/MetaMaskProvider.tsx @@ -0,0 +1,24 @@ +import { MetaMaskProvider as MetaMaskSDKProvider } from '@metamask/sdk-react'; +import { ReactNode } from 'react'; + +interface MetaMaskProviderProps { + children: ReactNode; +} + +const MetaMaskProvider = ({ children }: MetaMaskProviderProps) => { + return ( + + {children} + + ); +}; + +export default MetaMaskProvider; \ No newline at end of file diff --git a/projects/HashScope/frontend/eslint.config.mjs b/projects/HashScope/frontend/eslint.config.mjs new file mode 100644 index 00000000..c85fb67c --- /dev/null +++ b/projects/HashScope/frontend/eslint.config.mjs @@ -0,0 +1,16 @@ +import { dirname } from "path"; +import { fileURLToPath } from "url"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [ + ...compat.extends("next/core-web-vitals", "next/typescript"), +]; + +export default eslintConfig; diff --git a/projects/HashScope/frontend/lib/utils.ts b/projects/HashScope/frontend/lib/utils.ts new file mode 100644 index 00000000..31594673 --- /dev/null +++ b/projects/HashScope/frontend/lib/utils.ts @@ -0,0 +1,20 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +export const formatBalance = (rawBalance: string) => { + const balance = (parseInt(rawBalance) / 1000000000000000000).toFixed(2); + return balance; +}; + +export const formatChainAsNum = (chainIdHex: string) => { + const chainIdNum = parseInt(chainIdHex); + return chainIdNum; +}; + +export const formatAddress = (addr: string | undefined) => { + return `${addr?.substring(0, 8)}...`; +}; \ No newline at end of file diff --git a/projects/HashScope/frontend/next.config.ts b/projects/HashScope/frontend/next.config.ts new file mode 100644 index 00000000..e9ffa308 --- /dev/null +++ b/projects/HashScope/frontend/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + /* config options here */ +}; + +export default nextConfig; diff --git a/projects/HashScope/frontend/package-lock.json b/projects/HashScope/frontend/package-lock.json new file mode 100644 index 00000000..a880903a --- /dev/null +++ b/projects/HashScope/frontend/package-lock.json @@ -0,0 +1,10442 @@ +{ + "name": "HashScope", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "HashScope", + "version": "0.1.0", + "dependencies": { + "@metamask/detect-provider": "^2.0.0", + "@metamask/sdk-react": "^0.32.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-select": "^2.1.6", + "@radix-ui/react-separator": "^1.1.2", + "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-tabs": "^1.1.3", + "@radix-ui/react-tooltip": "^1.1.8", + "@tanstack/react-table": "^8.21.2", + "chart.js": "^4.4.8", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "embla-carousel-react": "^8.5.2", + "ethers": "^5.7.2", + "lucide-react": "^0.479.0", + "next": "15.2.1", + "next-themes": "^0.4.6", + "react": "^18.2.0", + "react-copy-to-clipboard": "^5.1.0", + "react-countup": "^6.5.3", + "react-dom": "^18.2.0", + "recharts": "^2.15.1", + "sonner": "^2.0.2", + "tailwind-merge": "^3.0.2", + "tailwindcss-animate": "^1.0.7", + "web3": "^4.16.0" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^18", + "@types/react-copy-to-clipboard": "^5.0.7", + "@types/react-dom": "^18", + "eslint": "^9", + "eslint-config-next": "15.2.1", + "tailwindcss": "^4", + "typescript": "^5" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz", + "integrity": "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg==", + "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/runtime": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ecies/ciphers": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@ecies/ciphers/-/ciphers-0.2.3.tgz", + "integrity": "sha512-tapn6XhOueMwht3E2UzY0ZZjYokdaw9XtL9kEyjhQ/Fb9vL9xTFbOaI+fV0AWvTpYu4BNloC6getKW6NtSg4mA==", + "license": "MIT", + "engines": { + "bun": ">=1", + "deno": ">=2", + "node": ">=16" + }, + "peerDependencies": { + "@noble/ciphers": "^1.0.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.3.1.tgz", + "integrity": "sha512-pVGjBIt1Y6gg3EJN8jTcfpP/+uuRksIo055oE/OBkDNcjZqVbfkWCksG1Jp4yZnj3iKWyWX8fdG/j6UDYPbFog==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.0.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.1.tgz", + "integrity": "sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", + "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", + "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.0.tgz", + "integrity": "sha512-yJLLmLexii32mGrhW29qvU3QBVTu0GUmEf/J4XsBtVhp4JkIUFN/BjWqTF63yRvGApIDpZm5fa97LtYtINmfeQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", + "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "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.0", + "minimatch": "^3.1.2", + "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/js": { + "version": "9.23.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.23.0.tgz", + "integrity": "sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", + "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.12.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@ethereumjs/common": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-3.2.0.tgz", + "integrity": "sha512-pksvzI0VyLgmuEF2FA/JR/4/y6hcPq8OUail3/AvycBaW1d5VSauOZzqGvJ3RTmR4MU35lWE8KseKOsEhrFRBA==", + "license": "MIT", + "dependencies": { + "@ethereumjs/util": "^8.1.0", + "crc-32": "^1.2.0" + } + }, + "node_modules/@ethereumjs/rlp": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-4.0.1.tgz", + "integrity": "sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==", + "license": "MPL-2.0", + "bin": { + "rlp": "bin/rlp" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@ethereumjs/tx": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-4.2.0.tgz", + "integrity": "sha512-1nc6VO4jtFd172BbSnTnDQVr9IYBFl1y4xPzZdtkrkKIncBCkdbgfdRV+MiTkJYAtTxvV12GRZLqBFT1PNK6Yw==", + "license": "MPL-2.0", + "dependencies": { + "@ethereumjs/common": "^3.2.0", + "@ethereumjs/rlp": "^4.0.1", + "@ethereumjs/util": "^8.1.0", + "ethereum-cryptography": "^2.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@ethereumjs/util": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-8.1.0.tgz", + "integrity": "sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==", + "license": "MPL-2.0", + "dependencies": { + "@ethereumjs/rlp": "^4.0.1", + "ethereum-cryptography": "^2.0.0", + "micro-ftch": "^0.3.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@ethersproject/abi": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.8.0.tgz", + "integrity": "sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/address": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/hash": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, + "node_modules/@ethersproject/abstract-provider": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.8.0.tgz", + "integrity": "sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/networks": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/transactions": "^5.8.0", + "@ethersproject/web": "^5.8.0" + } + }, + "node_modules/@ethersproject/abstract-signer": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.8.0.tgz", + "integrity": "sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-provider": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0" + } + }, + "node_modules/@ethersproject/address": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.8.0.tgz", + "integrity": "sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/rlp": "^5.8.0" + } + }, + "node_modules/@ethersproject/base64": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.8.0.tgz", + "integrity": "sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0" + } + }, + "node_modules/@ethersproject/basex": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.8.0.tgz", + "integrity": "sha512-PIgTszMlDRmNwW9nhS6iqtVfdTAKosA7llYXNmGPw4YAI1PUyMv28988wAb41/gHF/WqGdoLv0erHaRcHRKW2Q==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/properties": "^5.8.0" + } + }, + "node_modules/@ethersproject/bignumber": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.8.0.tgz", + "integrity": "sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "bn.js": "^5.2.1" + } + }, + "node_modules/@ethersproject/bytes": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.8.0.tgz", + "integrity": "sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/constants": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.8.0.tgz", + "integrity": "sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0" + } + }, + "node_modules/@ethersproject/contracts": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.8.0.tgz", + "integrity": "sha512-0eFjGz9GtuAi6MZwhb4uvUM216F38xiuR0yYCjKJpNfSEy4HUM8hvqqBj9Jmm0IUz8l0xKEhWwLIhPgxNY0yvQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abi": "^5.8.0", + "@ethersproject/abstract-provider": "^5.8.0", + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/address": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/transactions": "^5.8.0" + } + }, + "node_modules/@ethersproject/hash": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.8.0.tgz", + "integrity": "sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/address": "^5.8.0", + "@ethersproject/base64": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, + "node_modules/@ethersproject/hdnode": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.8.0.tgz", + "integrity": "sha512-4bK1VF6E83/3/Im0ERnnUeWOY3P1BZml4ZD3wcH8Ys0/d1h1xaFt6Zc+Dh9zXf9TapGro0T4wvO71UTCp3/uoA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/basex": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/pbkdf2": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/sha2": "^5.8.0", + "@ethersproject/signing-key": "^5.8.0", + "@ethersproject/strings": "^5.8.0", + "@ethersproject/transactions": "^5.8.0", + "@ethersproject/wordlists": "^5.8.0" + } + }, + "node_modules/@ethersproject/json-wallets": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.8.0.tgz", + "integrity": "sha512-HxblNck8FVUtNxS3VTEYJAcwiKYsBIF77W15HufqlBF9gGfhmYOJtYZp8fSDZtn9y5EaXTE87zDwzxRoTFk11w==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/address": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/hdnode": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/pbkdf2": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/random": "^5.8.0", + "@ethersproject/strings": "^5.8.0", + "@ethersproject/transactions": "^5.8.0", + "aes-js": "3.0.0", + "scrypt-js": "3.0.1" + } + }, + "node_modules/@ethersproject/keccak256": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.8.0.tgz", + "integrity": "sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "js-sha3": "0.8.0" + } + }, + "node_modules/@ethersproject/logger": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.8.0.tgz", + "integrity": "sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT" + }, + "node_modules/@ethersproject/networks": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.8.0.tgz", + "integrity": "sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/pbkdf2": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.8.0.tgz", + "integrity": "sha512-wuHiv97BrzCmfEaPbUFpMjlVg/IDkZThp9Ri88BpjRleg4iePJaj2SW8AIyE8cXn5V1tuAaMj6lzvsGJkGWskg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/sha2": "^5.8.0" + } + }, + "node_modules/@ethersproject/properties": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.8.0.tgz", + "integrity": "sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/providers": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.8.0.tgz", + "integrity": "sha512-3Il3oTzEx3o6kzcg9ZzbE+oCZYyY+3Zh83sKkn4s1DZfTUjIegHnN2Cm0kbn9YFy45FDVcuCLLONhU7ny0SsCw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-provider": "^5.8.0", + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/address": "^5.8.0", + "@ethersproject/base64": "^5.8.0", + "@ethersproject/basex": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/hash": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/networks": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/random": "^5.8.0", + "@ethersproject/rlp": "^5.8.0", + "@ethersproject/sha2": "^5.8.0", + "@ethersproject/strings": "^5.8.0", + "@ethersproject/transactions": "^5.8.0", + "@ethersproject/web": "^5.8.0", + "bech32": "1.1.4", + "ws": "8.18.0" + } + }, + "node_modules/@ethersproject/providers/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@ethersproject/random": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.8.0.tgz", + "integrity": "sha512-E4I5TDl7SVqyg4/kkA/qTfuLWAQGXmSOgYyO01So8hLfwgKvYK5snIlzxJMk72IFdG/7oh8yuSqY2KX7MMwg+A==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/rlp": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.8.0.tgz", + "integrity": "sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/sha2": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.8.0.tgz", + "integrity": "sha512-dDOUrXr9wF/YFltgTBYS0tKslPEKr6AekjqDW2dbn1L1xmjGR+9GiKu4ajxovnrDbwxAKdHjW8jNcwfz8PAz4A==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/signing-key": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.8.0.tgz", + "integrity": "sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "bn.js": "^5.2.1", + "elliptic": "6.6.1", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/solidity": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.8.0.tgz", + "integrity": "sha512-4CxFeCgmIWamOHwYN9d+QWGxye9qQLilpgTU0XhYs1OahkclF+ewO+3V1U0mvpiuQxm5EHHmv8f7ClVII8EHsA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/sha2": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, + "node_modules/@ethersproject/strings": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.8.0.tgz", + "integrity": "sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/transactions": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.8.0.tgz", + "integrity": "sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/address": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/rlp": "^5.8.0", + "@ethersproject/signing-key": "^5.8.0" + } + }, + "node_modules/@ethersproject/units": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.8.0.tgz", + "integrity": "sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/logger": "^5.8.0" + } + }, + "node_modules/@ethersproject/wallet": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.8.0.tgz", + "integrity": "sha512-G+jnzmgg6UxurVKRKvw27h0kvG75YKXZKdlLYmAHeF32TGUzHkOFd7Zn6QHOTYRFWnfjtSSFjBowKo7vfrXzPA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-provider": "^5.8.0", + "@ethersproject/abstract-signer": "^5.8.0", + "@ethersproject/address": "^5.8.0", + "@ethersproject/bignumber": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/hash": "^5.8.0", + "@ethersproject/hdnode": "^5.8.0", + "@ethersproject/json-wallets": "^5.8.0", + "@ethersproject/keccak256": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/random": "^5.8.0", + "@ethersproject/signing-key": "^5.8.0", + "@ethersproject/transactions": "^5.8.0", + "@ethersproject/wordlists": "^5.8.0" + } + }, + "node_modules/@ethersproject/web": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.8.0.tgz", + "integrity": "sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/base64": "^5.8.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, + "node_modules/@ethersproject/wordlists": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.8.0.tgz", + "integrity": "sha512-2df9bbXicZws2Sb5S6ET493uJ0Z84Fjr3pC4tu/qlnZERibZCeUVuqdtt+7Tv9xxhUxHoIekIA7avrKUWHrezg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/hash": "^5.8.0", + "@ethersproject/logger": "^5.8.0", + "@ethersproject/properties": "^5.8.0", + "@ethersproject/strings": "^5.8.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.13", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", + "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "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==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "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.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "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.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "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.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "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.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "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.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "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.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "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.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "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.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "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.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "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.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "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.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "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.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "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.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "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.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "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.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "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.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "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.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "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.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "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/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, + "node_modules/@metamask/detect-provider": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@metamask/detect-provider/-/detect-provider-2.0.0.tgz", + "integrity": "sha512-sFpN+TX13E9fdBDh9lvQeZdJn4qYoRb/6QF2oZZK/Pn559IhCFacPMU1rMuqyXoFQF3JSJfii2l98B87QDPeCQ==", + "license": "ISC", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@metamask/json-rpc-engine": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@metamask/json-rpc-engine/-/json-rpc-engine-8.0.2.tgz", + "integrity": "sha512-IoQPmql8q7ABLruW7i4EYVHWUbF74yrp63bRuXV5Zf9BQwcn5H9Ww1eLtROYvI1bUXwOiHZ6qT5CWTrDc/t/AA==", + "license": "ISC", + "dependencies": { + "@metamask/rpc-errors": "^6.2.1", + "@metamask/safe-event-emitter": "^3.0.0", + "@metamask/utils": "^8.3.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/json-rpc-middleware-stream": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@metamask/json-rpc-middleware-stream/-/json-rpc-middleware-stream-7.0.2.tgz", + "integrity": "sha512-yUdzsJK04Ev98Ck4D7lmRNQ8FPioXYhEUZOMS01LXW8qTvPGiRVXmVltj2p4wrLkh0vW7u6nv0mNl5xzC5Qmfg==", + "license": "ISC", + "dependencies": { + "@metamask/json-rpc-engine": "^8.0.2", + "@metamask/safe-event-emitter": "^3.0.0", + "@metamask/utils": "^8.3.0", + "readable-stream": "^3.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/object-multiplex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@metamask/object-multiplex/-/object-multiplex-2.1.0.tgz", + "integrity": "sha512-4vKIiv0DQxljcXwfpnbsXcfa5glMj5Zg9mqn4xpIWqkv6uJ2ma5/GtUfLFSxhlxnR8asRMv8dDmWya1Tc1sDFA==", + "license": "ISC", + "dependencies": { + "once": "^1.4.0", + "readable-stream": "^3.6.2" + }, + "engines": { + "node": "^16.20 || ^18.16 || >=20" + } + }, + "node_modules/@metamask/onboarding": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@metamask/onboarding/-/onboarding-1.0.1.tgz", + "integrity": "sha512-FqHhAsCI+Vacx2qa5mAFcWNSrTcVGMNjzxVgaX8ECSny/BJ9/vgXP9V7WF/8vb9DltPeQkxr+Fnfmm6GHfmdTQ==", + "license": "MIT", + "dependencies": { + "bowser": "^2.9.0" + } + }, + "node_modules/@metamask/providers": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/@metamask/providers/-/providers-16.1.0.tgz", + "integrity": "sha512-znVCvux30+3SaUwcUGaSf+pUckzT5ukPRpcBmy+muBLC0yaWnBcvDqGfcsw6CBIenUdFrVoAFa8B6jsuCY/a+g==", + "license": "MIT", + "dependencies": { + "@metamask/json-rpc-engine": "^8.0.1", + "@metamask/json-rpc-middleware-stream": "^7.0.1", + "@metamask/object-multiplex": "^2.0.0", + "@metamask/rpc-errors": "^6.2.1", + "@metamask/safe-event-emitter": "^3.1.1", + "@metamask/utils": "^8.3.0", + "detect-browser": "^5.2.0", + "extension-port-stream": "^3.0.0", + "fast-deep-equal": "^3.1.3", + "is-stream": "^2.0.0", + "readable-stream": "^3.6.2", + "webextension-polyfill": "^0.10.0" + }, + "engines": { + "node": "^18.18 || >=20" + } + }, + "node_modules/@metamask/rpc-errors": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@metamask/rpc-errors/-/rpc-errors-6.4.0.tgz", + "integrity": "sha512-1ugFO1UoirU2esS3juZanS/Fo8C8XYocCuBpfZI5N7ECtoG+zu0wF+uWZASik6CkO6w9n/Iebt4iI4pT0vptpg==", + "license": "MIT", + "dependencies": { + "@metamask/utils": "^9.0.0", + "fast-safe-stringify": "^2.0.6" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/rpc-errors/node_modules/@metamask/utils": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-9.3.0.tgz", + "integrity": "sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g==", + "license": "ISC", + "dependencies": { + "@ethereumjs/tx": "^4.2.0", + "@metamask/superstruct": "^3.1.0", + "@noble/hashes": "^1.3.1", + "@scure/base": "^1.1.3", + "@types/debug": "^4.1.7", + "debug": "^4.3.4", + "pony-cause": "^2.1.10", + "semver": "^7.5.4", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/rpc-errors/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@metamask/safe-event-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@metamask/safe-event-emitter/-/safe-event-emitter-3.1.2.tgz", + "integrity": "sha512-5yb2gMI1BDm0JybZezeoX/3XhPDOtTbcFvpTXM9kxsoZjPZFh4XciqRbpD6N86HYZqWDhEaKUDuOyR0sQHEjMA==", + "license": "ISC", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@metamask/sdk": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@metamask/sdk/-/sdk-0.32.1.tgz", + "integrity": "sha512-ulvScxyuX+A8VYgQ9FGugtpH5l2U8AF0lOjaw6XyqwnL7I/U0Lk9yyz9pns4Zyq356Z4+nIBzxmb6czWLzp8UQ==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@metamask/onboarding": "^1.0.1", + "@metamask/providers": "16.1.0", + "@metamask/sdk-communication-layer": "0.32.0", + "@metamask/sdk-install-modal-web": "0.32.1", + "@paulmillr/qr": "^0.2.1", + "bowser": "^2.9.0", + "cross-fetch": "^4.0.0", + "debug": "^4.3.4", + "eciesjs": "^0.4.11", + "eth-rpc-errors": "^4.0.3", + "eventemitter2": "^6.4.9", + "obj-multiplex": "^1.0.0", + "pump": "^3.0.0", + "readable-stream": "^3.6.2", + "socket.io-client": "^4.5.1", + "tslib": "^2.6.0", + "util": "^0.12.4", + "uuid": "^8.3.2" + } + }, + "node_modules/@metamask/sdk-communication-layer": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@metamask/sdk-communication-layer/-/sdk-communication-layer-0.32.0.tgz", + "integrity": "sha512-dmj/KFjMi1fsdZGIOtbhxdg3amxhKL/A5BqSU4uh/SyDKPub/OT+x5pX8bGjpTL1WPWY/Q0OIlvFyX3VWnT06Q==", + "dependencies": { + "bufferutil": "^4.0.8", + "date-fns": "^2.29.3", + "debug": "^4.3.4", + "utf-8-validate": "^5.0.2", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "cross-fetch": "^4.0.0", + "eciesjs": "*", + "eventemitter2": "^6.4.9", + "readable-stream": "^3.6.2", + "socket.io-client": "^4.5.1" + } + }, + "node_modules/@metamask/sdk-install-modal-web": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@metamask/sdk-install-modal-web/-/sdk-install-modal-web-0.32.1.tgz", + "integrity": "sha512-MGmAo6qSjf1tuYXhCu2EZLftq+DSt5Z7fsIKr2P+lDgdTPWgLfZB1tJKzNcwKKOdf6q9Qmmxn7lJuI/gq5LrKw==", + "dependencies": { + "@paulmillr/qr": "^0.2.1" + } + }, + "node_modules/@metamask/sdk-react": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@metamask/sdk-react/-/sdk-react-0.32.1.tgz", + "integrity": "sha512-RVOQrddr+nURxE/62OtJz47TulHwZuSH+5pkIoRN+bJJInHP4G05tkmiOIpFgINoV74sXQX3RQZ2Drc531DuAg==", + "dependencies": { + "@metamask/sdk": "^0.32.1", + "debug": "^4.3.4", + "eth-rpc-errors": "^4.0.3", + "rollup-plugin-node-builtins": "^2.1.2", + "rollup-plugin-node-globals": "^1.4.0" + }, + "peerDependencies": { + "@react-native-async-storage/async-storage": "^1.19.6", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-native": "*" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + }, + "react": { + "optional": false + }, + "react-dom": { + "optional": false + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@metamask/superstruct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@metamask/superstruct/-/superstruct-3.1.0.tgz", + "integrity": "sha512-N08M56HdOgBfRKkrgCMZvQppkZGcArEop3kixNEtVbJKm6P9Cfg0YkI6X0s1g78sNrj2fWUwvJADdZuzJgFttA==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/utils": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-8.5.0.tgz", + "integrity": "sha512-I6bkduevXb72TIM9q2LRO63JSsF9EXduh3sBr9oybNX2hNNpr/j1tEjXrsG0Uabm4MJ1xkGAQEMwifvKZIkyxQ==", + "license": "ISC", + "dependencies": { + "@ethereumjs/tx": "^4.2.0", + "@metamask/superstruct": "^3.0.0", + "@noble/hashes": "^1.3.1", + "@scure/base": "^1.1.3", + "@types/debug": "^4.1.7", + "debug": "^4.3.4", + "pony-cause": "^2.1.10", + "semver": "^7.5.4", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/utils/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.7.tgz", + "integrity": "sha512-5yximcFK5FNompXfJFoWanu5l8v1hNGqNHh9du1xETp9HWk/B/PzvchX55WYOPaIeNglG8++68AAiauBAtbnzw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.3.1", + "@emnapi/runtime": "^1.3.1", + "@tybys/wasm-util": "^0.9.0" + } + }, + "node_modules/@next/env": { + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.2.1.tgz", + "integrity": "sha512-JmY0qvnPuS2NCWOz2bbby3Pe0VzdAQ7XpEB6uLIHmtXNfAsAO0KLQLkuAoc42Bxbo3/jMC3dcn9cdf+piCcG2Q==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.2.1.tgz", + "integrity": "sha512-6ppeToFd02z38SllzWxayLxjjNfzvc7Wm07gQOKSLjyASvKcXjNStZrLXMHuaWkhjqxe+cnhb2uzfWXm1VEj/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.2.1.tgz", + "integrity": "sha512-aWXT+5KEREoy3K5AKtiKwioeblmOvFFjd+F3dVleLvvLiQ/mD//jOOuUcx5hzcO9ISSw4lrqtUPntTpK32uXXQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.2.1.tgz", + "integrity": "sha512-E/w8ervu4fcG5SkLhvn1NE/2POuDCDEy5gFbfhmnYXkyONZR68qbUlJlZwuN82o7BrBVAw+tkR8nTIjGiMW1jQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.2.1.tgz", + "integrity": "sha512-gXDX5lIboebbjhiMT6kFgu4svQyjoSed6dHyjx5uZsjlvTwOAnZpn13w9XDaIMFFHw7K8CpBK7HfDKw0VZvUXQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.2.1.tgz", + "integrity": "sha512-3v0pF/adKZkBWfUffmB/ROa+QcNTrnmYG4/SS+r52HPwAK479XcWoES2I+7F7lcbqc7mTeVXrIvb4h6rR/iDKg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.2.1.tgz", + "integrity": "sha512-RbsVq2iB6KFJRZ2cHrU67jLVLKeuOIhnQB05ygu5fCNgg8oTewxweJE8XlLV+Ii6Y6u4EHwETdUiRNXIAfpBww==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.2.1.tgz", + "integrity": "sha512-QHsMLAyAIu6/fWjHmkN/F78EFPKmhQlyX5C8pRIS2RwVA7z+t9cTb0IaYWC3EHLOTjsU7MNQW+n2xGXr11QPpg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.2.1.tgz", + "integrity": "sha512-Gk42XZXo1cE89i3hPLa/9KZ8OuupTjkDmhLaMKFohjf9brOeZVEa3BQy1J9s9TWUqPhgAEbwv6B2+ciGfe54Vw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.2.1.tgz", + "integrity": "sha512-YjqXCl8QGhVlMR8uBftWk0iTmvtntr41PhG1kvzGp0sUP/5ehTM+cwx25hKE54J0CRnHYjSGjSH3gkHEaHIN9g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@noble/ciphers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz", + "integrity": "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.1.tgz", + "integrity": "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.7.1" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", + "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", + "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/@paulmillr/qr": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@paulmillr/qr/-/qr-0.2.1.tgz", + "integrity": "sha512-IHnV6A+zxU7XwmKFinmYjUcwlyK9+xkG3/s9KcQhI9BjQKycrJ1JRO+FbNYPwZiPKW3je/DR0k7w8/gLa5eaxQ==", + "deprecated": "The package is now available as \"qr\": npm install qr", + "license": "(MIT OR Apache-2.0)", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", + "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", + "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.2.tgz", + "integrity": "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.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-collection": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.2.tgz", + "integrity": "sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.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-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "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.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "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.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.6.tgz", + "integrity": "sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.2", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "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-direction": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", + "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", + "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.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.5.tgz", + "integrity": "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "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.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", + "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", + "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.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.2.tgz", + "integrity": "sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "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.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "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-label": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.2.tgz", + "integrity": "sha512-zo1uGMTaNlHehDyFQcDZXRJhUPDuukcnHz0/jnrup0JA6qL+AFpAnty+7VKa9esuU5xTblAZzTGYJKSKaBxBhw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.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-popper": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.2.tgz", + "integrity": "sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-rect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/rect": "1.1.0" + }, + "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.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.4.tgz", + "integrity": "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "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.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", + "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "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.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", + "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.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-roving-focus": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.2.tgz", + "integrity": "sha512-zgMQWkNO169GtGqRvYrzb0Zf8NhMHS2DuEB/TiEmVnpr5OqPU3i8lfbxaAmC2J/KYuIQxyoQQ6DxepyXp61/xw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "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.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.6.tgz", + "integrity": "sha512-T6ajELxRvTuAMWH0YmRJ1qez+x4/7Nq7QIx7zJ0VK3qaEWdnWpNbEDnmWldG1zBDwqrLy5aLMUWcoGirVj5kMg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.2", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.2", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.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-separator": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.2.tgz", + "integrity": "sha512-oZfHcaAp2Y6KFBX6I5P1u7CQoy4lheCGiYj+pGFrHy8E/VNRb5E39TkTr3JrV520csPBTZjkuKFdEsjS5EUNKQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.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-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "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-tabs": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.3.tgz", + "integrity": "sha512-9mFyI30cuRDImbmFF6O2KUJdgEOsGh9Vmx9x/Dh9tOhL7BngmQPQfwW4aejKm5OHpfWIdmeV6ySyuxoOGjtNng==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-roving-focus": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "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.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.8.tgz", + "integrity": "sha512-YAA2cu48EkJZdAMHC0dqo9kialOcRStbtiY4nJPaht7Ptrhcvpo+eDChaM6BIs8kL6a8Z5l5poiqLnXcNduOkA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.2", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.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-use-callback-ref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", + "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", + "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.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "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.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", + "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "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.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", + "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", + "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.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz", + "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==", + "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.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", + "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.0" + }, + "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.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", + "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "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.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.2.tgz", + "integrity": "sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.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/rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", + "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==", + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.11.0.tgz", + "integrity": "sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@scure/base": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.4.tgz", + "integrity": "sha512-5Yy9czTO47mqz+/J8GM6GIId4umdCk1wc1q8rKERQulIoc8VP9pzDcghv10Tl2E7R96ZUx/PhND3ESYUQX8NuQ==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", + "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.4.0", + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@scure/base": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", + "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39/node_modules/@scure/base": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, + "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/@tailwindcss/node": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.17.tgz", + "integrity": "sha512-LIdNwcqyY7578VpofXyqjH6f+3fP4nrz7FBLki5HpzqjYfXdF2m/eW18ZfoKePtDGg90Bvvfpov9d2gy5XVCbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", + "tailwindcss": "4.0.17" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.17.tgz", + "integrity": "sha512-B4OaUIRD2uVrULpAD1Yksx2+wNarQr2rQh65nXqaqbLY1jCd8fO+3KLh/+TH4Hzh2NTHQvgxVbPdUDOtLk7vAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.0.17", + "@tailwindcss/oxide-darwin-arm64": "4.0.17", + "@tailwindcss/oxide-darwin-x64": "4.0.17", + "@tailwindcss/oxide-freebsd-x64": "4.0.17", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.17", + "@tailwindcss/oxide-linux-arm64-gnu": "4.0.17", + "@tailwindcss/oxide-linux-arm64-musl": "4.0.17", + "@tailwindcss/oxide-linux-x64-gnu": "4.0.17", + "@tailwindcss/oxide-linux-x64-musl": "4.0.17", + "@tailwindcss/oxide-win32-arm64-msvc": "4.0.17", + "@tailwindcss/oxide-win32-x64-msvc": "4.0.17" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.17.tgz", + "integrity": "sha512-3RfO0ZK64WAhop+EbHeyxGThyDr/fYhxPzDbEQjD2+v7ZhKTb2svTWy+KK+J1PHATus2/CQGAGp7pHY/8M8ugg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.17.tgz", + "integrity": "sha512-e1uayxFQCCDuzTk9s8q7MC5jFN42IY7nzcr5n0Mw/AcUHwD6JaBkXnATkD924ZsHyPDvddnusIEvkgLd2CiREg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.17.tgz", + "integrity": "sha512-d6z7HSdOKfXQ0HPlVx1jduUf/YtBuCCtEDIEFeBCzgRRtDsUuRtofPqxIVaSCUTOk5+OfRLonje6n9dF6AH8wQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.17.tgz", + "integrity": "sha512-EjrVa6lx3wzXz3l5MsdOGtYIsRjgs5Mru6lDv4RuiXpguWeOb3UzGJ7vw7PEzcFadKNvNslEQqoAABeMezprxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.17.tgz", + "integrity": "sha512-65zXfCOdi8wuaY0Ye6qMR5LAXokHYtrGvo9t/NmxvSZtCCitXV/gzJ/WP5ksXPhff1SV5rov0S+ZIZU+/4eyCQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.17.tgz", + "integrity": "sha512-+aaq6hJ8ioTdbJV5IA1WjWgLmun4T7eYLTvJIToiXLHy5JzUERRbIZjAcjgK9qXMwnvuu7rqpxzej+hGoEcG5g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.17.tgz", + "integrity": "sha512-/FhWgZCdUGAeYHYnZKekiOC0aXFiBIoNCA0bwzkICiMYS5Rtx2KxFfMUXQVnl4uZRblG5ypt5vpPhVaXgGk80w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.17.tgz", + "integrity": "sha512-gELJzOHK6GDoIpm/539Golvk+QWZjxQcbkKq9eB2kzNkOvrP0xc5UPgO9bIMNt1M48mO8ZeNenCMGt6tfkvVBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.17.tgz", + "integrity": "sha512-68NwxcJrZn94IOW4TysMIbYv5AlM6So1luTlbYUDIGnKma1yTFGBRNEJ+SacJ3PZE2rgcTBNRHX1TB4EQ/XEHw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.17.tgz", + "integrity": "sha512-AkBO8efP2/7wkEXkNlXzRD4f/7WerqKHlc6PWb5v0jGbbm22DFBLbIM19IJQ3b+tNewQZa+WnPOaGm0SmwMNjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.17.tgz", + "integrity": "sha512-7/DTEvXcoWlqX0dAlcN0zlmcEu9xSermuo7VNGX9tJ3nYMdo735SHvbrHDln1+LYfF6NhJ3hjbpbjkMOAGmkDg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.0.17.tgz", + "integrity": "sha512-qeJbRTB5FMZXmuJF+eePd235EGY6IyJZF0Bh0YM6uMcCI4L9Z7dy+lPuLAhxOJzxnajsbjPoDAKOuAqZRtf1PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.0.17", + "@tailwindcss/oxide": "4.0.17", + "lightningcss": "1.29.2", + "postcss": "^8.4.41", + "tailwindcss": "4.0.17" + } + }, + "node_modules/@tanstack/react-table": { + "version": "8.21.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.2.tgz", + "integrity": "sha512-11tNlEDTdIhMJba2RBH+ecJ9l1zgS2kjmexDPAraulc8jeNA4xocSNeyzextT0XJyASil4XsCYlJmf5jEWAtYg==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.21.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.21.2", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.2.tgz", + "integrity": "sha512-uvXk/U4cBiFMxt+p9/G7yUWI/UbHYbyghLCjlpWZ3mLeIZiUBSKcUnw9UnKkdRz7Z/N4UBuFLWQdJCjUe7HjvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", + "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.17.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.28.tgz", + "integrity": "sha512-DHlH/fNL6Mho38jTy7/JT7sn2wnXI+wULR6PV4gy4VHLVvnrV/d3pHAMQHhc4gjdLmK2ZiPoMxzp6B3yRajLSQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.20", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.20.tgz", + "integrity": "sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-copy-to-clipboard": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.7.tgz", + "integrity": "sha512-Gft19D+as4M+9Whq1oglhmK49vqPhcLzk8WfvfLvaYMIPYanyfLy0+CwFucMJfdKoSFyySPmkkWn8/E6voQXjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz", + "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/ws": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", + "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.28.0.tgz", + "integrity": "sha512-lvFK3TCGAHsItNdWZ/1FkvpzCxTHUVuFrdnOGLMa0GGCFIbCgQWVk3CzCGdA7kM3qGVc+dfW9tr0Z/sHnGDFyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.28.0", + "@typescript-eslint/type-utils": "8.28.0", + "@typescript-eslint/utils": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.0.1" + }, + "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.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.28.0.tgz", + "integrity": "sha512-LPcw1yHD3ToaDEoljFEfQ9j2xShY367h7FZ1sq5NJT9I3yj4LHer1Xd1yRSOdYy9BpsrxU7R+eoDokChYM53lQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.28.0", + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/typescript-estree": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0", + "debug": "^4.3.4" + }, + "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", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.28.0.tgz", + "integrity": "sha512-u2oITX3BJwzWCapoZ/pXw6BCOl8rJP4Ij/3wPoGvY8XwvXflOzd1kLrDUUUAIEdJSFh+ASwdTHqtan9xSg8buw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.28.0.tgz", + "integrity": "sha512-oRoXu2v0Rsy/VoOGhtWrOKDiIehvI+YNrDk5Oqj40Mwm0Yt01FC/Q7nFqg088d3yAsR1ZcZFVfPCTTFCe/KPwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.28.0", + "@typescript-eslint/utils": "8.28.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.0.1" + }, + "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", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.28.0.tgz", + "integrity": "sha512-bn4WS1bkKEjx7HqiwG2JNB3YJdC1q6Ue7GyGlwPHyt0TnVq6TtD/hiOdTZt71sq0s7UzqBFXD8t8o2e63tXgwA==", + "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.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.28.0.tgz", + "integrity": "sha512-H74nHEeBGeklctAVUvmDkxB1mk+PAZ9FiOMPFncdqeRBXxk1lWSYraHw8V12b7aa6Sg9HOBNbGdSHobBPuQSuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.0.1" + }, + "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 <5.9.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "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.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/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/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.28.0.tgz", + "integrity": "sha512-OELa9hbTYciYITqgurT1u/SzpQVtDLmQMFzy/N8pQE+tefOyCWT79jHsav294aTqV1q1u+VzqDGbuujvRYaeSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.28.0", + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/typescript-estree": "8.28.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", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.28.0.tgz", + "integrity": "sha512-hbn8SZ8w4u2pRwgQ1GlUrPKE+t2XvcCW5tTRF7j6SMYIuYG37XuzIW44JCZPa36evi0Oy2SnM664BlIaAuQcvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.28.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.3.2.tgz", + "integrity": "sha512-ddnlXgRi0Fog5+7U5Q1qY62wl95Q1lB4tXQX1UIA9YHmRCHN2twaQW0/4tDVGCvTVEU3xEayU7VemEr7GcBYUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.3.2.tgz", + "integrity": "sha512-tnl9xoEeg503jis+LW5cuq4hyLGQyqaoBL8VdPSqcewo/FL1C8POHbzl+AL25TidWYJD+R6bGUTE381kA1sT9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.3.2.tgz", + "integrity": "sha512-zyPn9LFCCjhKPeCtECZaiMUgkYN/VpLb4a9Xv7QriJmTaQxsuDtXqOHifrzUXIhorJTyS+5MOKDuNL0X9I4EHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.3.2.tgz", + "integrity": "sha512-UWx56Wh59Ro69fe+Wfvld4E1n9KG0e3zeouWLn8eSasyi/yVH/7ZW3CLTVFQ81oMKSpXwr5u6RpzttDXZKiO4g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.3.2.tgz", + "integrity": "sha512-VYGQXsOEJtfaoY2fOm8Z9ii5idFaHFYlrq3yMFZPaFKo8ufOXYm8hnfru7qetbM9MX116iWaPC0ZX5sK+1Dr+g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.3.2.tgz", + "integrity": "sha512-3zP420zxJfYPD1rGp2/OTIBxF8E3+/6VqCG+DEO6kkDgBiloa7Y8pw1o7N9BfgAC+VC8FPZsFXhV2lpx+lLRMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.3.2.tgz", + "integrity": "sha512-ZWjSleUgr88H4Kei7yT4PlPqySTuWN1OYDDcdbmMCtLWFly3ed+rkrcCb3gvqXdDbYrGOtzv3g2qPEN+WWNv5Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.3.2.tgz", + "integrity": "sha512-p+5OvYJ2UOlpjes3WfBlxyvQok2u26hLyPxLFHkYlfzhZW0juhvBf/tvewz1LDFe30M7zL9cF4OOO5dcvtk+cw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.3.2.tgz", + "integrity": "sha512-yweY7I6SqNn3kvj6vE4PQRo7j8Oz6+NiUhmgciBNAUOuI3Jq0bnW29hbHJdxZRSN1kYkQnSkbbA1tT8VnK816w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.3.2.tgz", + "integrity": "sha512-fNIvtzJcGN9hzWTIayrTSk2+KHQrqKbbY+I88xMVMOFV9t4AXha4veJdKaIuuks+2JNr6GuuNdsL7+exywZ32w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.3.2.tgz", + "integrity": "sha512-OaFEw8WAjiwBGxutQgkWhoAGB5BQqZJ8Gjt/mW+m6DWNjimcxU22uWCuEtfw1CIwLlKPOzsgH0429fWmZcTGkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.3.2.tgz", + "integrity": "sha512-u+sumtO7M0AGQ9bNQrF4BHNpUyxo23FM/yXZfmVAicTQ+mXtG06O7pm5zQUw3Mr4jRs2I84uh4O0hd8bdouuvQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.7" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.3.2.tgz", + "integrity": "sha512-ZAJKy95vmDIHsRFuPNqPQRON8r2mSMf3p9DoX+OMOhvu2c8OXGg8MvhGRf3PNg45ozRrPdXDnngURKgaFfpGoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.3.2.tgz", + "integrity": "sha512-nQG4YFAS2BLoKVQFK/FrWJvFATI5DQUWQrcPcsWG9Ve5BLLHZuPOrJ2SpAJwLXQrRv6XHSFAYGI8wQpBg/CiFA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.3.2.tgz", + "integrity": "sha512-XBWpUP0mHya6yGBwNefhyEa6V7HgYKCxEAY4qhTm/PcAQyBPNmjj97VZJOJkVdUsyuuii7xmq0pXWX/c2aToHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/abitype": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-0.7.1.tgz", + "integrity": "sha512-VBkRHTDZf9Myaek/dO3yMmOzB/y2s3Zo6nVU7yaw1G+TvCHAjwaJzNGN9yo4K5D8bU/VZXKP1EJpRhFr862PlQ==", + "license": "MIT", + "peerDependencies": { + "typescript": ">=4.9.4", + "zod": "^3 >=3.19.1" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/abstract-leveldown": { + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-0.12.4.tgz", + "integrity": "sha512-TOod9d5RDExo6STLMGa+04HGkl+TlMfbDnTyN93/ETJ9DpQ0DaYLqcMZlbXvdc4W3vVo1Qrl+WhSp8zvDsJ+jA==", + "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", + "license": "MIT", + "dependencies": { + "xtend": "~3.0.0" + } + }, + "node_modules/abstract-leveldown/node_modules/xtend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", + "integrity": "sha512-sp/sT9OALMjRW1fKDlPeuSZlDQpkqReA0pyJukniWbTGoEKefHxhGJynE3PNhUMlcM8qWIjPwecwCw4LArS5Eg==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", + "license": "MIT" + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", + "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", + "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==", + "dev": true, + "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.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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/asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "license": "MIT" + }, + "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==", + "dev": true, + "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.10.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", + "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", + "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": "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/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "license": "MIT" + }, + "node_modules/bl": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-0.8.2.tgz", + "integrity": "sha512-pfqikmByp+lifZCS0p6j6KreV6kNU6Apzpm2nKOk+94cZb/jvle55+JxWiByUQ0Wo/+XnDXEy5MxxKMb6r0VIw==", + "license": "MIT", + "dependencies": { + "readable-stream": "~1.0.26" + } + }, + "node_modules/bl/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "license": "MIT" + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/bl/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "license": "MIT" + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "license": "MIT" + }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "license": "MIT" + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "license": "MIT", + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "license": "MIT", + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-fs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browserify-fs/-/browserify-fs-1.0.0.tgz", + "integrity": "sha512-8LqHRPuAEKvyTX34R6tsw4bO2ro6j9DmlYBhiYWHRM26Zv2cBw1fJOU0NeUQ0RkXkPn/PFBjhA0dm4AgaBurTg==", + "dependencies": { + "level-filesystem": "^1.0.1", + "level-js": "^2.1.3", + "levelup": "^0.18.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz", + "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", + "license": "MIT", + "dependencies": { + "bn.js": "^5.2.1", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.3.tgz", + "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==", + "license": "ISC", + "dependencies": { + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.5", + "hash-base": "~3.0", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.7", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/browserify-sign/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/browserify-sign/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/browserify-sign/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/browserify-sign/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/browserify-sign/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/buffer-es6": { + "version": "4.9.3", + "resolved": "https://registry.npmjs.org/buffer-es6/-/buffer-es6-4.9.3.tgz", + "integrity": "sha512-Ibt+oXxhmeYJSsCkODPqNpPmyegefiD8rfutH1NYGhMZQhSp95Rz7haemgnJ6dxa6LT+JLLbtgOMORRluwKktw==", + "license": "MIT" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "license": "MIT" + }, + "node_modules/bufferutil": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz", + "integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001707", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001707.tgz", + "integrity": "sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==", + "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/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chart.js": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.8.tgz", + "integrity": "sha512-IkGZlVpXP+83QpMm4uxEiGqSI7jFizwVtF3+n5Pc3k7sMO+tkd0qxh2OzLhenM0K80xtmAONWGBn082EiBQSDA==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/cipher-base": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.6.tgz", + "integrity": "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "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/clone": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/clone/-/clone-0.1.19.tgz", + "integrity": "sha512-IO78I0y6JcSpEPHzK4obKdsL7E7oLdRVDVOLwr2Hkbjsb+Eoz0dxW6tef0WizoKu0gLC4oZSZuEF4U2K6w1WQw==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "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": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "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==", + "devOptional": true, + "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==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "license": "MIT", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/countup.js": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/countup.js/-/countup.js-2.8.0.tgz", + "integrity": "sha512-f7xEhX0awl4NOElHulrl4XRfKoNH3rB+qfNSZZyjSZhaAoUk6elvhH+MNxMmlmuUJ2/QNTWPSA7U4mNtIAKljQ==", + "license": "MIT" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "license": "MIT" + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/cross-fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", + "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-browserify": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz", + "integrity": "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==", + "license": "MIT", + "dependencies": { + "browserify-cipher": "^1.0.1", + "browserify-sign": "^4.2.3", + "create-ecdh": "^4.0.4", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "diffie-hellman": "^5.0.3", + "hash-base": "~3.0.4", + "inherits": "^2.0.4", + "pbkdf2": "^3.1.2", + "public-encrypt": "^4.0.3", + "randombytes": "^2.1.0", + "randomfill": "^1.0.4" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/deferred-leveldown": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-0.2.0.tgz", + "integrity": "sha512-+WCbb4+ez/SZ77Sdy1iadagFiVzMB89IKOBhglgnUkVxOxRWmmFsz8UDSNWh4Rhq+3wr/vMFlYj+rdEwWUDdng==", + "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", + "license": "MIT", + "dependencies": { + "abstract-leveldown": "~0.12.1" + } + }, + "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==", + "dev": true, + "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/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/detect-browser": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.3.0.tgz", + "integrity": "sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "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/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "license": "MIT" + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "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/eciesjs": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.14.tgz", + "integrity": "sha512-eJAgf9pdv214Hn98FlUzclRMYWF7WfoLlkS9nWMTm1qcCwn6Ad4EGD9lr9HXMBfSrZhYQujRE+p0adPRkctC6A==", + "license": "MIT", + "dependencies": { + "@ecies/ciphers": "^0.2.2", + "@noble/ciphers": "^1.0.0", + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0" + }, + "engines": { + "bun": ">=1", + "deno": ">=2", + "node": ">=16" + } + }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "license": "MIT" + }, + "node_modules/embla-carousel": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.5.2.tgz", + "integrity": "sha512-xQ9oVLrun/eCG/7ru3R+I5bJ7shsD8fFwLEY7yPe27/+fDHCNj0OT5EoG5ZbFyOxOcG6yTwW8oTz/dWyFnyGpg==", + "license": "MIT" + }, + "node_modules/embla-carousel-react": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.5.2.tgz", + "integrity": "sha512-Tmx+uY3MqseIGdwp0ScyUuxpBgx5jX1f7od4Cm5mDwg/dptEiTKf9xp6tw0lZN2VA9JbnVMl/aikmbc53c6QFA==", + "license": "MIT", + "dependencies": { + "embla-carousel": "8.5.2", + "embla-carousel-reactive-utils": "8.5.2" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/embla-carousel-reactive-utils": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.5.2.tgz", + "integrity": "sha512-QC8/hYSK/pEmqEdU1IO5O+XNc/Ptmmq7uCB44vKplgLKhB/l0+yvYx0+Cv0sF6Ena8Srld5vUErZkT+yTahtDg==", + "license": "MIT", + "peerDependencies": { + "embla-carousel": "8.5.2" + } + }, + "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/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "license": "MIT", + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/es-abstract": { + "version": "1.23.9", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", + "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", + "dev": true, + "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.3", + "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.0.0", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.0", + "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-regex": "^1.2.1", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.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.18" + }, + "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.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "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-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "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.4", + "safe-array-concat": "^1.1.3" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.23.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.23.0.tgz", + "integrity": "sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.2", + "@eslint/config-helpers": "^0.2.0", + "@eslint/core": "^0.12.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.23.0", + "@eslint/plugin-kit": "^0.2.7", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.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.2", + "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": "15.2.1", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.2.1.tgz", + "integrity": "sha512-mhsprz7l0no8X+PdDnVHF4dZKu9YBJp2Rf6ztWbXBLJ4h6gxmW//owbbGJMBVUU+PibGJDAqZhW4pt8SC8HSow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "15.2.1", + "@rushstack/eslint-patch": "^1.10.3", + "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^5.0.0" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.0.tgz", + "integrity": "sha512-aV3/dVsT0/H9BtpNwbaqvl+0xGMRGzncLyhm793NFGvbwGGvzyAykqWZ8oZlZuGwuHkwJjhWJkG1cM3ynvd2pQ==", + "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.12", + "unrs-resolver": "^1.3.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-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "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-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "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-plugin-react": { + "version": "7.37.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.4.tgz", + "integrity": "sha512-BGP0jRmfYyvOyvMoRX/uoUeW+GqNj9y16bPQzqAHf3AYII/tDs+jMN0dBVkl88/OZwNGwrVFxE7riHsXVfy/LQ==", + "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.8", + "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-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.5.2.tgz", + "integrity": "sha512-XpCnW/AE10ws/kDAs37cngSkvgIR8aN3G0MS85m7dUpuK2EREo9VJ00uvw6Dg/hXEpfsE1I1TvJOJr+Z+TL+ig==", + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eth-rpc-errors": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eth-rpc-errors/-/eth-rpc-errors-4.0.3.tgz", + "integrity": "sha512-Z3ymjopaoft7JDoxZcEb3pwdGh7yiYMhOwm2doUt6ASXlMavpNlK6Cre0+IMl2VSGyEU9rkiperQhp5iRxn5Pg==", + "license": "MIT", + "dependencies": { + "fast-safe-stringify": "^2.0.6" + } + }, + "node_modules/ethereum-cryptography": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", + "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", + "license": "MIT", + "dependencies": { + "@noble/curves": "1.4.2", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0" + } + }, + "node_modules/ethereum-cryptography/node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethereum-cryptography/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethers": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.8.0.tgz", + "integrity": "sha512-DUq+7fHrCg1aPDFCHx6UIPb3nmt2XMpM7Y/g2gLhsl3lIBqeAfOJIl1qEvRf2uq3BiKxmh6Fh5pfp2ieyek7Kg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abi": "5.8.0", + "@ethersproject/abstract-provider": "5.8.0", + "@ethersproject/abstract-signer": "5.8.0", + "@ethersproject/address": "5.8.0", + "@ethersproject/base64": "5.8.0", + "@ethersproject/basex": "5.8.0", + "@ethersproject/bignumber": "5.8.0", + "@ethersproject/bytes": "5.8.0", + "@ethersproject/constants": "5.8.0", + "@ethersproject/contracts": "5.8.0", + "@ethersproject/hash": "5.8.0", + "@ethersproject/hdnode": "5.8.0", + "@ethersproject/json-wallets": "5.8.0", + "@ethersproject/keccak256": "5.8.0", + "@ethersproject/logger": "5.8.0", + "@ethersproject/networks": "5.8.0", + "@ethersproject/pbkdf2": "5.8.0", + "@ethersproject/properties": "5.8.0", + "@ethersproject/providers": "5.8.0", + "@ethersproject/random": "5.8.0", + "@ethersproject/rlp": "5.8.0", + "@ethersproject/sha2": "5.8.0", + "@ethersproject/signing-key": "5.8.0", + "@ethersproject/solidity": "5.8.0", + "@ethersproject/strings": "5.8.0", + "@ethersproject/transactions": "5.8.0", + "@ethersproject/units": "5.8.0", + "@ethersproject/wallet": "5.8.0", + "@ethersproject/web": "5.8.0", + "@ethersproject/wordlists": "5.8.0" + } + }, + "node_modules/eventemitter2": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", + "license": "MIT" + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "license": "MIT", + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/extension-port-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/extension-port-stream/-/extension-port-stream-3.0.0.tgz", + "integrity": "sha512-an2S5quJMiy5bnZKEf6AkfH/7r8CzHvhchU40gxN+OM6HPhe7Z9T1FUychcf2M9PpPOO0Hf7BAEfJkw2TDIBDw==", + "license": "ISC", + "dependencies": { + "readable-stream": "^3.6.2 || ^4.4.2", + "webextension-polyfill": ">=0.10.0 <1.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "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-equals": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", + "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "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/foreach": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", + "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==", + "license": "MIT" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fwd-stream": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fwd-stream/-/fwd-stream-1.0.4.tgz", + "integrity": "sha512-q2qaK2B38W07wfPSQDKMiKOD5Nzv2XyuvQlrmh1q0pxyHNanKHq8lwQ6n9zHucAwA5EbzRJKEgds2orn88rYTg==", + "dependencies": { + "readable-stream": "~1.0.26-4" + } + }, + "node_modules/fwd-stream/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "license": "MIT" + }, + "node_modules/fwd-stream/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/fwd-stream/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "license": "MIT" + }, + "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==", + "dev": true, + "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.10.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", + "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "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==", + "dev": true, + "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/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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/hash-base": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz", + "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/idb-wrapper": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/idb-wrapper/-/idb-wrapper-1.7.2.tgz", + "integrity": "sha512-zfNREywMuf0NzDo9mVsL0yegjsirJxHpKHvWcyRozIqQy89g0a3U+oBPOCN4cc0oCiOuYgZHimzaW/R46G1Mpg==", + "license": "MIT" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/is/-/is-0.2.7.tgz", + "integrity": "sha512-ajQCouIvkcSnl2iRdK70Jug9mohIHVX9uKpoWnl115ov0R5mzBvRrXxrnHbsA+8AdwCwc/sfw7HXmd4I5EJBdQ==", + "engines": { + "node": "*" + } + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "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-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==", + "dev": true, + "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-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT", + "optional": true + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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-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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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-object": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-0.1.2.tgz", + "integrity": "sha512-GkfZZlIZtpkFrqyAXPQSRBMsaHAw+CgoKe2HXAkjd/sfoI9+hS8PT4wg2rJxdQyUKr7N2vHJbg7/jQtE5l5vBQ==" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/isbuffer": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/isbuffer/-/isbuffer-0.0.0.tgz", + "integrity": "sha512-xU+NoHp+YtKQkaM2HsQchYn0sltxMxew0HavMfHbjnucBoTSGbw745tL+Z7QBANleWM1eEQMenEpi174mIeS4g==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "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.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "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==", + "dev": true, + "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/level-blobs": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/level-blobs/-/level-blobs-0.1.7.tgz", + "integrity": "sha512-n0iYYCGozLd36m/Pzm206+brIgXP8mxPZazZ6ZvgKr+8YwOZ8/PPpYC5zMUu2qFygRN8RO6WC/HH3XWMW7RMVg==", + "dependencies": { + "level-peek": "1.0.6", + "once": "^1.3.0", + "readable-stream": "^1.0.26-4" + } + }, + "node_modules/level-blobs/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "license": "MIT" + }, + "node_modules/level-blobs/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/level-blobs/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "license": "MIT" + }, + "node_modules/level-filesystem": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/level-filesystem/-/level-filesystem-1.2.0.tgz", + "integrity": "sha512-PhXDuCNYpngpxp3jwMT9AYBMgOvB6zxj3DeuIywNKmZqFj2djj9XfT2XDVslfqmo0Ip79cAd3SBy3FsfOZPJ1g==", + "dependencies": { + "concat-stream": "^1.4.4", + "errno": "^0.1.1", + "fwd-stream": "^1.0.4", + "level-blobs": "^0.1.7", + "level-peek": "^1.0.6", + "level-sublevel": "^5.2.0", + "octal": "^1.0.0", + "once": "^1.3.0", + "xtend": "^2.2.0" + } + }, + "node_modules/level-fix-range": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/level-fix-range/-/level-fix-range-1.0.2.tgz", + "integrity": "sha512-9llaVn6uqBiSlBP+wKiIEoBa01FwEISFgHSZiyec2S0KpyLUkGR4afW/FCZ/X8y+QJvzS0u4PGOlZDdh1/1avQ==", + "license": "MIT" + }, + "node_modules/level-hooks": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/level-hooks/-/level-hooks-4.5.0.tgz", + "integrity": "sha512-fxLNny/vL/G4PnkLhWsbHnEaRi+A/k8r5EH/M77npZwYL62RHi2fV0S824z3QdpAk6VTgisJwIRywzBHLK4ZVA==", + "dependencies": { + "string-range": "~1.2" + } + }, + "node_modules/level-js": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/level-js/-/level-js-2.2.4.tgz", + "integrity": "sha512-lZtjt4ZwHE00UMC1vAb271p9qzg8vKlnDeXfIesH3zL0KxhHRDjClQLGLWhyR0nK4XARnd4wc/9eD1ffd4PshQ==", + "deprecated": "Superseded by browser-level (https://github.com/Level/community#faq)", + "license": "BSD-2-Clause", + "dependencies": { + "abstract-leveldown": "~0.12.0", + "idb-wrapper": "^1.5.0", + "isbuffer": "~0.0.0", + "ltgt": "^2.1.2", + "typedarray-to-buffer": "~1.0.0", + "xtend": "~2.1.2" + } + }, + "node_modules/level-js/node_modules/object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha512-ncrLw+X55z7bkl5PnUvHwFK9FcGuFYo9gtjws2XtSzL+aZ8tm830P60WJ0dSmFVaSalWieW5MD7kEdnXda9yJw==", + "license": "MIT" + }, + "node_modules/level-js/node_modules/xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==", + "dependencies": { + "object-keys": "~0.4.0" + }, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/level-peek": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/level-peek/-/level-peek-1.0.6.tgz", + "integrity": "sha512-TKEzH5TxROTjQxWMczt9sizVgnmJ4F3hotBI48xCTYvOKd/4gA/uY0XjKkhJFo6BMic8Tqjf6jFMLWeg3MAbqQ==", + "license": "MIT", + "dependencies": { + "level-fix-range": "~1.0.2" + } + }, + "node_modules/level-sublevel": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/level-sublevel/-/level-sublevel-5.2.3.tgz", + "integrity": "sha512-tO8jrFp+QZYrxx/Gnmjawuh1UBiifpvKNAcm4KCogesWr1Nm2+ckARitf+Oo7xg4OHqMW76eAqQ204BoIlscjA==", + "license": "MIT", + "dependencies": { + "level-fix-range": "2.0", + "level-hooks": ">=4.4.0 <5", + "string-range": "~1.2.1", + "xtend": "~2.0.4" + } + }, + "node_modules/level-sublevel/node_modules/level-fix-range": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/level-fix-range/-/level-fix-range-2.0.0.tgz", + "integrity": "sha512-WrLfGWgwWbYPrHsYzJau+5+te89dUbENBg3/lsxOs4p2tYOhCHjbgXxBAj4DFqp3k/XBwitcRXoCh8RoCogASA==", + "license": "MIT", + "dependencies": { + "clone": "~0.1.9" + } + }, + "node_modules/level-sublevel/node_modules/object-keys": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.2.0.tgz", + "integrity": "sha512-XODjdR2pBh/1qrjPcbSeSgEtKbYo7LqYNq64/TPuCf7j9SfDD3i21yatKoIy39yIWNvVM59iutfQQpCv1RfFzA==", + "deprecated": "Please update to the latest object-keys", + "license": "MIT", + "dependencies": { + "foreach": "~2.0.1", + "indexof": "~0.0.1", + "is": "~0.2.6" + } + }, + "node_modules/level-sublevel/node_modules/xtend": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.0.6.tgz", + "integrity": "sha512-fOZg4ECOlrMl+A6Msr7EIFcON1L26mb4NY5rurSkOex/TWhazOrg6eXD/B0XkuiYcYhQDWLXzQxLMVJ7LXwokg==", + "dependencies": { + "is-object": "~0.1.2", + "object-keys": "~0.2.0" + }, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/levelup": { + "version": "0.18.6", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-0.18.6.tgz", + "integrity": "sha512-uB0auyRqIVXx+hrpIUtol4VAPhLRcnxcOsd2i2m6rbFIDarO5dnrupLOStYYpEcu8ZT087Z9HEuYw1wjr6RL6Q==", + "deprecated": "Superseded by abstract-level (https://github.com/Level/community#faq)", + "license": "MIT", + "dependencies": { + "bl": "~0.8.1", + "deferred-leveldown": "~0.2.0", + "errno": "~0.1.1", + "prr": "~0.0.0", + "readable-stream": "~1.0.26", + "semver": "~2.3.1", + "xtend": "~3.0.0" + } + }, + "node_modules/levelup/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "license": "MIT" + }, + "node_modules/levelup/node_modules/prr": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz", + "integrity": "sha512-LmUECmrW7RVj6mDWKjTXfKug7TFGdiz9P18HMcO4RHL+RW7MCOGNvpj5j47Rnp6ne6r4fZ2VzyUWEpKbg+tsjQ==", + "license": "MIT" + }, + "node_modules/levelup/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/levelup/node_modules/semver": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-2.3.2.tgz", + "integrity": "sha512-abLdIKCosKfpnmhS52NCTjO4RiLspDfsn37prjzGrp9im5DPJOgh82Os92vtwGh6XdQryKI/7SREZnV+aqiXrA==", + "license": "BSD", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/levelup/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "license": "MIT" + }, + "node_modules/levelup/node_modules/xtend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", + "integrity": "sha512-sp/sT9OALMjRW1fKDlPeuSZlDQpkqReA0pyJukniWbTGoEKefHxhGJynE3PNhUMlcM8qWIjPwecwCw4LArS5Eg==", + "engines": { + "node": ">=0.4" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz", + "integrity": "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==", + "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-darwin-arm64": "1.29.2", + "lightningcss-darwin-x64": "1.29.2", + "lightningcss-freebsd-x64": "1.29.2", + "lightningcss-linux-arm-gnueabihf": "1.29.2", + "lightningcss-linux-arm64-gnu": "1.29.2", + "lightningcss-linux-arm64-musl": "1.29.2", + "lightningcss-linux-x64-gnu": "1.29.2", + "lightningcss-linux-x64-musl": "1.29.2", + "lightningcss-win32-arm64-msvc": "1.29.2", + "lightningcss-win32-x64-msvc": "1.29.2" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.2.tgz", + "integrity": "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==", + "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.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.2.tgz", + "integrity": "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==", + "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.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.2.tgz", + "integrity": "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==", + "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.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.2.tgz", + "integrity": "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==", + "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.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.2.tgz", + "integrity": "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==", + "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.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.2.tgz", + "integrity": "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==", + "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.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.2.tgz", + "integrity": "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==", + "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.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.2.tgz", + "integrity": "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==", + "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.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.2.tgz", + "integrity": "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==", + "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.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.2.tgz", + "integrity": "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "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==", + "dev": true, + "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==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/ltgt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", + "integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==", + "license": "MIT" + }, + "node_modules/lucide-react": { + "version": "0.479.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.479.0.tgz", + "integrity": "sha512-aBhNnveRhorBOK7uA4gDjgaf+YlHMdMhQ/3cupk6exM10hWlEU+2QtWYOfhXhjAsmdb6LeKR+NZnow4UxRRiTQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.22.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz", + "integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==", + "license": "MIT", + "dependencies": { + "vlq": "^0.2.2" + } + }, + "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/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "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/micro-ftch": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/micro-ftch/-/micro-ftch-0.3.1.tgz", + "integrity": "sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==", + "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/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "license": "MIT" + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "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/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/next": { + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/next/-/next-15.2.1.tgz", + "integrity": "sha512-zxbsdQv3OqWXybK5tMkPCBKyhIz63RstJ+NvlfkaLMc/m5MwXgz2e92k+hSKcyBpyADhMk2C31RIiaDjUZae7g==", + "license": "MIT", + "dependencies": { + "@next/env": "15.2.1", + "@swc/counter": "0.1.3", + "@swc/helpers": "0.5.15", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.2.1", + "@next/swc-darwin-x64": "15.2.1", + "@next/swc-linux-arm64-gnu": "15.2.1", + "@next/swc-linux-arm64-musl": "15.2.1", + "@next/swc-linux-x64-gnu": "15.2.1", + "@next/swc-linux-x64-musl": "15.2.1", + "@next/swc-win32-arm64-msvc": "15.2.1", + "@next/swc-win32-x64-msvc": "15.2.1", + "sharp": "^0.33.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "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-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/obj-multiplex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/obj-multiplex/-/obj-multiplex-1.0.0.tgz", + "integrity": "sha512-0GNJAOsHoBHeNTvl5Vt6IWnpUEcc3uSRxzBri7EDyIcMgYvnY2JL2qdeV5zTMjWQX5OHcD5amcW2HFfDh0gjIA==", + "license": "ISC", + "dependencies": { + "end-of-stream": "^1.4.0", + "once": "^1.4.0", + "readable-stream": "^2.3.3" + } + }, + "node_modules/obj-multiplex/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/obj-multiplex/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/obj-multiplex/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/obj-multiplex/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "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==", + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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/octal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/octal/-/octal-1.0.0.tgz", + "integrity": "sha512-nnda7W8d+A3vEIY+UrDQzzboPf1vhs4JYVhff5CDkq9QNoZY7Xrxeo/htox37j9dZf7yNHevZzqtejWgy1vCqQ==", + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/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==", + "dev": true, + "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/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz", + "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", + "license": "ISC", + "dependencies": { + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "hash-base": "~3.0", + "pbkdf2": "^3.1.2", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "license": "MIT", + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pony-cause": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/pony-cause/-/pony-cause-2.1.11.tgz", + "integrity": "sha512-M7LhCsdNbNgiLYiP4WjsfLUuFmCfnjdF6jKe2R9NKl4WFN+HZPGHJZ9lnLP7f9ZnKe3U9nuWD0szirmj+migUg==", + "license": "0BSD", + "engines": { + "node": ">=12.0.0" + } + }, + "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.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "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.8", + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/process-es6": { + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/process-es6/-/process-es6-0.11.6.tgz", + "integrity": "sha512-GYBRQtL4v3wgigq10Pv58jmTbFXlIiTbSfgnNqZLY0ldUPqy1rRxDI5fCjoCpnM6TqmHQI8ydzTBXW86OYc0gA==", + "license": "MIT" + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "license": "MIT" + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "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/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "license": "MIT", + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-copy-to-clipboard": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz", + "integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==", + "license": "MIT", + "dependencies": { + "copy-to-clipboard": "^3.3.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^15.3.0 || 16 || 17 || 18" + } + }, + "node_modules/react-countup": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/react-countup/-/react-countup-6.5.3.tgz", + "integrity": "sha512-udnqVQitxC7QWADSPDOxVWULkLvKUWrDapn5i53HE4DPRVgs+Y5rr4bo25qEl8jSh+0l2cToJgGMx+clxPM3+w==", + "license": "MIT", + "dependencies": { + "countup.js": "^2.8.0" + }, + "peerDependencies": { + "react": ">= 16.3.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/react-remove-scroll": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz", + "integrity": "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==", + "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-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "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/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/recharts": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.1.tgz", + "integrity": "sha512-v8PUTUlyiDe56qUj82w/EDVuzEFXwEHp9/xOowGAZwfLjB9uAy3GllQVIYMWF6nU+qibx85WF75zD7AjqoT54Q==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/recharts/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "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==", + "dev": true, + "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/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "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==", + "dev": true, + "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": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "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==", + "dev": true, + "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/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/rollup-plugin-node-builtins": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-node-builtins/-/rollup-plugin-node-builtins-2.1.2.tgz", + "integrity": "sha512-bxdnJw8jIivr2yEyt8IZSGqZkygIJOGAWypXvHXnwKAbUcN4Q/dGTx7K0oAJryC/m6aq6tKutltSeXtuogU6sw==", + "license": "ISC", + "dependencies": { + "browserify-fs": "^1.0.0", + "buffer-es6": "^4.9.2", + "crypto-browserify": "^3.11.0", + "process-es6": "^0.11.2" + } + }, + "node_modules/rollup-plugin-node-globals": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-node-globals/-/rollup-plugin-node-globals-1.4.0.tgz", + "integrity": "sha512-xRkB+W/m1KLIzPUmG0ofvR+CPNcvuCuNdjVBVS7ALKSxr3EDhnzNceGkGi1m8MToSli13AzKFYH4ie9w3I5L3g==", + "license": "MIT", + "dependencies": { + "acorn": "^5.7.3", + "buffer-es6": "^4.9.3", + "estree-walker": "^0.5.2", + "magic-string": "^0.22.5", + "process-es6": "^0.11.6", + "rollup-pluginutils": "^2.3.1" + } + }, + "node_modules/rollup-plugin-node-globals/node_modules/acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "license": "MIT", + "dependencies": { + "estree-walker": "^0.6.1" + } + }, + "node_modules/rollup-pluginutils/node_modules/estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "license": "MIT" + }, + "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==", + "dev": true, + "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-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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/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==", + "dev": true, + "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.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "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.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/sonner": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.2.tgz", + "integrity": "sha512-xOeXErZ4blqQd11ZnlDmoRmg+ctUJBkTU8H+HVh9rnWi9Ke28xiL39r4iCTeDX31ODTe/s1MaiaY333dUzLCtA==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "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/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/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-range": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/string-range/-/string-range-1.2.2.tgz", + "integrity": "sha512-tYft6IFi8SjplJpxCUxyqisD3b+R2CSkomrtJYCkvuf1KuCAWgz7YXt4O0jip7efpfCemwHEzTEAO8EuOYgh3w==", + "license": "MIT" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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/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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwind-merge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.2.tgz", + "integrity": "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.17.tgz", + "integrity": "sha512-OErSiGzRa6rLiOvaipsDZvLMSpsBZ4ysB4f0VKGXUrjw2jfkJRd6kjRKV2+ZmTCNvwtvgdDam5D7w6WXsdLJZw==", + "license": "MIT" + }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", + "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", + "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "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/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "license": "MIT" + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/typedarray-to-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-1.0.4.tgz", + "integrity": "sha512-vjMKrfSoUDN8/Vnqitw2FmstOfuJ73G6CrSEKnf11A6RmasVxHqfeBcnTb6RsL4pTMuV5Zsv9IiHRphMZyckUw==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "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==", + "dev": true, + "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": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "license": "MIT" + }, + "node_modules/unrs-resolver": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.3.2.tgz", + "integrity": "sha512-ZKQBC351Ubw0PY8xWhneIfb6dygTQeUHtCcNGd0QB618zabD/WbFMYdRyJ7xeVT+6G82K5v/oyZO0QSHFtbIuw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/JounQin" + }, + "optionalDependencies": { + "@unrs/resolver-binding-darwin-arm64": "1.3.2", + "@unrs/resolver-binding-darwin-x64": "1.3.2", + "@unrs/resolver-binding-freebsd-x64": "1.3.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.3.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.3.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.3.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.3.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.3.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.3.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.3.2", + "@unrs/resolver-binding-linux-x64-musl": "1.3.2", + "@unrs/resolver-binding-wasm32-wasi": "1.3.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.3.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.3.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.3.2" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "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/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", + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/vlq": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", + "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==", + "license": "MIT" + }, + "node_modules/web3": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/web3/-/web3-4.16.0.tgz", + "integrity": "sha512-SgoMSBo6EsJ5GFCGar2E/pR2lcR/xmUSuQ61iK6yDqzxmm42aPPxSqZfJz2z/UCR6pk03u77pU8TGV6lgMDdIQ==", + "license": "LGPL-3.0", + "dependencies": { + "web3-core": "^4.7.1", + "web3-errors": "^1.3.1", + "web3-eth": "^4.11.1", + "web3-eth-abi": "^4.4.1", + "web3-eth-accounts": "^4.3.1", + "web3-eth-contract": "^4.7.2", + "web3-eth-ens": "^4.4.0", + "web3-eth-iban": "^4.0.7", + "web3-eth-personal": "^4.1.0", + "web3-net": "^4.1.0", + "web3-providers-http": "^4.2.0", + "web3-providers-ws": "^4.0.8", + "web3-rpc-methods": "^1.3.0", + "web3-rpc-providers": "^1.0.0-rc.4", + "web3-types": "^1.10.0", + "web3-utils": "^4.3.3", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-core": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-4.7.1.tgz", + "integrity": "sha512-9KSeASCb/y6BG7rwhgtYC4CvYY66JfkmGNEYb7q1xgjt9BWfkf09MJPaRyoyT5trdOxYDHkT9tDlypvQWaU8UQ==", + "license": "LGPL-3.0", + "dependencies": { + "web3-errors": "^1.3.1", + "web3-eth-accounts": "^4.3.1", + "web3-eth-iban": "^4.0.7", + "web3-providers-http": "^4.2.0", + "web3-providers-ws": "^4.0.8", + "web3-types": "^1.10.0", + "web3-utils": "^4.3.3", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + }, + "optionalDependencies": { + "web3-providers-ipc": "^4.0.7" + } + }, + "node_modules/web3-errors": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/web3-errors/-/web3-errors-1.3.1.tgz", + "integrity": "sha512-w3NMJujH+ZSW4ltIZZKtdbkbyQEvBzyp3JRn59Ckli0Nz4VMsVq8aF1bLWM7A2kuQ+yVEm3ySeNU+7mSRwx7RQ==", + "license": "LGPL-3.0", + "dependencies": { + "web3-types": "^1.10.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-eth": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-4.11.1.tgz", + "integrity": "sha512-q9zOkzHnbLv44mwgLjLXuyqszHuUgZWsQayD2i/rus2uk0G7hMn11bE2Q3hOVnJS4ws4VCtUznlMxwKQ+38V2w==", + "license": "LGPL-3.0", + "dependencies": { + "setimmediate": "^1.0.5", + "web3-core": "^4.7.1", + "web3-errors": "^1.3.1", + "web3-eth-abi": "^4.4.1", + "web3-eth-accounts": "^4.3.1", + "web3-net": "^4.1.0", + "web3-providers-ws": "^4.0.8", + "web3-rpc-methods": "^1.3.0", + "web3-types": "^1.10.0", + "web3-utils": "^4.3.3", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-eth-abi": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-4.4.1.tgz", + "integrity": "sha512-60ecEkF6kQ9zAfbTY04Nc9q4eEYM0++BySpGi8wZ2PD1tw/c0SDvsKhV6IKURxLJhsDlb08dATc3iD6IbtWJmg==", + "license": "LGPL-3.0", + "dependencies": { + "abitype": "0.7.1", + "web3-errors": "^1.3.1", + "web3-types": "^1.10.0", + "web3-utils": "^4.3.3", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-eth-accounts": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-4.3.1.tgz", + "integrity": "sha512-rTXf+H9OKze6lxi7WMMOF1/2cZvJb2AOnbNQxPhBDssKOllAMzLhg1FbZ4Mf3lWecWfN6luWgRhaeSqO1l+IBQ==", + "license": "LGPL-3.0", + "dependencies": { + "@ethereumjs/rlp": "^4.0.1", + "crc-32": "^1.2.2", + "ethereum-cryptography": "^2.0.0", + "web3-errors": "^1.3.1", + "web3-types": "^1.10.0", + "web3-utils": "^4.3.3", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-eth-contract": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-4.7.2.tgz", + "integrity": "sha512-3ETqs2pMNPEAc7BVY/C3voOhTUeJdkf2aM3X1v+edbngJLHAxbvxKpOqrcO0cjXzC4uc2Q8Zpf8n8zT5r0eLnA==", + "license": "LGPL-3.0", + "dependencies": { + "@ethereumjs/rlp": "^5.0.2", + "web3-core": "^4.7.1", + "web3-errors": "^1.3.1", + "web3-eth": "^4.11.1", + "web3-eth-abi": "^4.4.1", + "web3-types": "^1.10.0", + "web3-utils": "^4.3.3", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-eth-contract/node_modules/@ethereumjs/rlp": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-5.0.2.tgz", + "integrity": "sha512-DziebCdg4JpGlEqEdGgXmjqcFoJi+JGulUXwEjsZGAscAQ7MyD/7LE/GVCP29vEQxKc7AAwjT3A2ywHp2xfoCA==", + "license": "MPL-2.0", + "bin": { + "rlp": "bin/rlp.cjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/web3-eth-ens": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-4.4.0.tgz", + "integrity": "sha512-DeyVIS060hNV9g8dnTx92syqvgbvPricE3MerCxe/DquNZT3tD8aVgFfq65GATtpCgDDJffO2bVeHp3XBemnSQ==", + "license": "LGPL-3.0", + "dependencies": { + "@adraffy/ens-normalize": "^1.8.8", + "web3-core": "^4.5.0", + "web3-errors": "^1.2.0", + "web3-eth": "^4.8.0", + "web3-eth-contract": "^4.5.0", + "web3-net": "^4.1.0", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.0", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-eth-iban": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-4.0.7.tgz", + "integrity": "sha512-8weKLa9KuKRzibC87vNLdkinpUE30gn0IGY027F8doeJdcPUfsa4IlBgNC4k4HLBembBB2CTU0Kr/HAOqMeYVQ==", + "license": "LGPL-3.0", + "dependencies": { + "web3-errors": "^1.1.3", + "web3-types": "^1.3.0", + "web3-utils": "^4.0.7", + "web3-validator": "^2.0.3" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-eth-personal": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-4.1.0.tgz", + "integrity": "sha512-RFN83uMuvA5cu1zIwwJh9A/bAj0OBxmGN3tgx19OD/9ygeUZbifOL06jgFzN0t+1ekHqm3DXYQM8UfHpXi7yDQ==", + "license": "LGPL-3.0", + "dependencies": { + "web3-core": "^4.6.0", + "web3-eth": "^4.9.0", + "web3-rpc-methods": "^1.3.0", + "web3-types": "^1.8.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-net": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-4.1.0.tgz", + "integrity": "sha512-WWmfvHVIXWEoBDWdgKNYKN8rAy6SgluZ0abyRyXOL3ESr7ym7pKWbfP4fjApIHlYTh8tNqkrdPfM4Dyi6CA0SA==", + "license": "LGPL-3.0", + "dependencies": { + "web3-core": "^4.4.0", + "web3-rpc-methods": "^1.3.0", + "web3-types": "^1.6.0", + "web3-utils": "^4.3.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-providers-http": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-4.2.0.tgz", + "integrity": "sha512-IPMnDtHB7dVwaB7/mMxAZzyq7d5ezfO1+Vw0bNfAeIi7gaDlJiggp85SdyAfOgov8AMUA/dyiY72kQ0KmjXKvQ==", + "license": "LGPL-3.0", + "dependencies": { + "cross-fetch": "^4.0.0", + "web3-errors": "^1.3.0", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-providers-ipc": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-4.0.7.tgz", + "integrity": "sha512-YbNqY4zUvIaK2MHr1lQFE53/8t/ejHtJchrWn9zVbFMGXlTsOAbNoIoZWROrg1v+hCBvT2c9z8xt7e/+uz5p1g==", + "license": "LGPL-3.0", + "optional": true, + "dependencies": { + "web3-errors": "^1.1.3", + "web3-types": "^1.3.0", + "web3-utils": "^4.0.7" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-providers-ws": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-4.0.8.tgz", + "integrity": "sha512-goJdgata7v4pyzHRsg9fSegUG4gVnHZSHODhNnn6J93ykHkBI1nz4fjlGpcQLUMi4jAMz6SHl9Ibzs2jj9xqPw==", + "license": "LGPL-3.0", + "dependencies": { + "@types/ws": "8.5.3", + "isomorphic-ws": "^5.0.0", + "web3-errors": "^1.2.0", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "ws": "^8.17.1" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-rpc-methods": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-rpc-methods/-/web3-rpc-methods-1.3.0.tgz", + "integrity": "sha512-/CHmzGN+IYgdBOme7PdqzF+FNeMleefzqs0LVOduncSaqsppeOEoskLXb2anSpzmQAP3xZJPaTrkQPWSJMORig==", + "license": "LGPL-3.0", + "dependencies": { + "web3-core": "^4.4.0", + "web3-types": "^1.6.0", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-rpc-providers": { + "version": "1.0.0-rc.4", + "resolved": "https://registry.npmjs.org/web3-rpc-providers/-/web3-rpc-providers-1.0.0-rc.4.tgz", + "integrity": "sha512-PXosCqHW0EADrYzgmueNHP3Y5jcSmSwH+Dkqvn7EYD0T2jcsdDAIHqk6szBiwIdhumM7gv9Raprsu/s/f7h1fw==", + "license": "LGPL-3.0", + "dependencies": { + "web3-errors": "^1.3.1", + "web3-providers-http": "^4.2.0", + "web3-providers-ws": "^4.0.8", + "web3-types": "^1.10.0", + "web3-utils": "^4.3.3", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-types": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/web3-types/-/web3-types-1.10.0.tgz", + "integrity": "sha512-0IXoaAFtFc8Yin7cCdQfB9ZmjafrbP6BO0f0KT/khMhXKUpoJ6yShrVhiNpyRBo8QQjuOagsWzwSK2H49I7sbw==", + "license": "LGPL-3.0", + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-4.3.3.tgz", + "integrity": "sha512-kZUeCwaQm+RNc2Bf1V3BYbF29lQQKz28L0y+FA4G0lS8IxtJVGi5SeDTUkpwqqkdHHC7JcapPDnyyzJ1lfWlOw==", + "license": "LGPL-3.0", + "dependencies": { + "ethereum-cryptography": "^2.0.0", + "eventemitter3": "^5.0.1", + "web3-errors": "^1.3.1", + "web3-types": "^1.10.0", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-utils/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/web3-validator": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/web3-validator/-/web3-validator-2.0.6.tgz", + "integrity": "sha512-qn9id0/l1bWmvH4XfnG/JtGKKwut2Vokl6YXP5Kfg424npysmtRLe9DgiNBM9Op7QL/aSiaA0TVXibuIuWcizg==", + "license": "LGPL-3.0", + "dependencies": { + "ethereum-cryptography": "^2.0.0", + "util": "^0.12.5", + "web3-errors": "^1.2.0", + "web3-types": "^1.6.0", + "zod": "^3.21.4" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/webextension-polyfill": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz", + "integrity": "sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==", + "license": "MPL-2.0" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/xtend": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.2.0.tgz", + "integrity": "sha512-SLt5uylT+4aoXxXuwtQp5ZnMMzhDb1Xkg4pEqc00WUJCQifPfV9Ub1VrNhp9kXkrjZD2I2Hl8WnjP37jzZLPZw==", + "engines": { + "node": ">=0.4" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.24.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", + "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/projects/HashScope/frontend/package.json b/projects/HashScope/frontend/package.json new file mode 100644 index 00000000..f373d8e8 --- /dev/null +++ b/projects/HashScope/frontend/package.json @@ -0,0 +1,52 @@ +{ + "name": "HashScope", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev --turbopack", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@metamask/detect-provider": "^2.0.0", + "@metamask/sdk-react": "^0.32.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-select": "^2.1.6", + "@radix-ui/react-separator": "^1.1.2", + "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-tabs": "^1.1.3", + "@radix-ui/react-tooltip": "^1.1.8", + "@tanstack/react-table": "^8.21.2", + "chart.js": "^4.4.8", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "embla-carousel-react": "^8.5.2", + "ethers": "^5.7.2", + "lucide-react": "^0.479.0", + "next": "15.2.1", + "next-themes": "^0.4.6", + "react": "^18.2.0", + "react-copy-to-clipboard": "^5.1.0", + "react-countup": "^6.5.3", + "react-dom": "^18.2.0", + "recharts": "^2.15.1", + "sonner": "^2.0.2", + "tailwind-merge": "^3.0.2", + "tailwindcss-animate": "^1.0.7", + "web3": "^4.16.0" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^18", + "@types/react-copy-to-clipboard": "^5.0.7", + "@types/react-dom": "^18", + "eslint": "^9", + "eslint-config-next": "15.2.1", + "tailwindcss": "^4", + "typescript": "^5" + } +} diff --git a/projects/HashScope/frontend/postcss.config.mjs b/projects/HashScope/frontend/postcss.config.mjs new file mode 100644 index 00000000..c7bcb4b1 --- /dev/null +++ b/projects/HashScope/frontend/postcss.config.mjs @@ -0,0 +1,5 @@ +const config = { + plugins: ["@tailwindcss/postcss"], +}; + +export default config; diff --git a/projects/HashScope/frontend/public/MetaMask_Fox.svg b/projects/HashScope/frontend/public/MetaMask_Fox.svg new file mode 100644 index 00000000..47ccb58a --- /dev/null +++ b/projects/HashScope/frontend/public/MetaMask_Fox.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/projects/HashScope/frontend/public/logo-192.png b/projects/HashScope/frontend/public/logo-192.png new file mode 100644 index 00000000..3be227bc Binary files /dev/null and b/projects/HashScope/frontend/public/logo-192.png differ diff --git a/projects/HashScope/frontend/public/logo-500.png b/projects/HashScope/frontend/public/logo-500.png new file mode 100644 index 00000000..648940df Binary files /dev/null and b/projects/HashScope/frontend/public/logo-500.png differ diff --git a/projects/HashScope/frontend/src/app/components/Header.tsx b/projects/HashScope/frontend/src/app/components/Header.tsx new file mode 100644 index 00000000..2e0b0d96 --- /dev/null +++ b/projects/HashScope/frontend/src/app/components/Header.tsx @@ -0,0 +1,59 @@ +'use client'; + +import React from 'react'; +import Image from 'next/image'; +import { useSDK } from '@metamask/sdk-react'; +import { formatAddress } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; +import Link from "next/link" + +export default function Header() { + const { sdk, connected, connecting, account } = useSDK(); + + const connect = async () => { + try { + const accounts = await sdk?.connect() as string[]; + if (!accounts?.[0]) throw new Error('No accounts found'); + } catch (err) { + console.warn(`No accounts found`, err); + } + }; + + const disconnect = async () => { + try { + await sdk?.disconnect(); + } catch (err) { + console.warn(`Failed to disconnect`, err); + } + }; + + return ( +
+
+
+
+ + HashScope + +
+ {connected ? ( + + ) : ( + + )} +
+
+
+ ); +} \ No newline at end of file diff --git a/projects/HashScope/frontend/src/app/components/app-sidebar.tsx b/projects/HashScope/frontend/src/app/components/app-sidebar.tsx new file mode 100644 index 00000000..b2d0abc9 --- /dev/null +++ b/projects/HashScope/frontend/src/app/components/app-sidebar.tsx @@ -0,0 +1,104 @@ +import { Home, Flame, Search, Activity, User, Key, Wallet, ChevronsUp } from "lucide-react" +import { + Sidebar, + SidebarContent, + SidebarGroup, + SidebarGroupContent, + SidebarGroupLabel, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from "@/components/ui/sidebar" +import Image from "next/image" + +const mainItems = [ + { + title: "Home", + url: "/", + icon: Home, + }, + { + title: "Hot", + url: "/hot", + icon: Flame, + }, + { + title: "Search APIs", + url: "/search", + icon: Search, + }, + { + title: "Upgrade", + url: "/upgrade", + icon: ChevronsUp, + }, +] + +const myItems = [ + { + title: "API Key Usage", + url: "/my/usage", + icon: Activity, + }, + { + title: "Deposit", + url: "/my/deposit", + icon: Wallet, + }, + { + title: "API Key", + url: "/my/api-key", + icon: Key, + }, + { + title: "Profile", + url: "/my/profile", + icon: User, + }, +] + +export function AppSidebar() { + return ( + + + + + logo + + + + {mainItems.map((item) => ( + + + + + {item.title} + + + + ))} + + + + + + My + + + {myItems.map((item) => ( + + + + + {item.title} + + + + ))} + + + + + + ) +} diff --git a/projects/HashScope/frontend/src/app/components/top-bar.tsx b/projects/HashScope/frontend/src/app/components/top-bar.tsx new file mode 100644 index 00000000..75f580f5 --- /dev/null +++ b/projects/HashScope/frontend/src/app/components/top-bar.tsx @@ -0,0 +1,108 @@ +'use client'; + +import { SidebarTrigger } from "@/components/ui/sidebar" +import Image from 'next/image'; +import { useSDK } from '@metamask/sdk-react'; +import { formatAddress } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; +import Link from "next/link"; +import { Coins } from 'lucide-react'; +import { useEffect, useState } from 'react'; +import { Skeleton } from "@/components/ui/skeleton"; + +export function TopBar() { + const { connected, account } = useSDK(); + const [tokenBalance, setTokenBalance] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + if (connected && account) { + fetchUserBalance(account); + } else { + setTokenBalance(null); + setIsLoading(false); + } + }, [connected, account]); + + const fetchUserBalance = async (userAddress: string) => { + try { + const token = localStorage.getItem("authToken"); + if (!token) { + throw new Error("No authentication token found"); + } + + const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/users/${userAddress}/balance`, { + headers: { + 'Authorization': `Bearer ${token}`, + }, + }); + + if (!response.ok) { + throw new Error("Failed to fetch balance"); + } + + const data = await response.json(); + + if (data.formatted_balance) { + // Handle both string and number formats + const balance = typeof data.formatted_balance === 'string' + ? parseFloat(data.formatted_balance.replace(/,/g, '')) + : data.formatted_balance; + + setTokenBalance(isNaN(balance) ? 0 : balance); + } else { + setTokenBalance(0); + } + } catch (error) { + console.error('Balance fetch error:', error); + setTokenBalance(0); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
+
+ + + HashScope + +
+
+ {connected && ( +
+ + + {isLoading ? ( + + ) : ( + `${tokenBalance || '0'} HSK` + )} + +
+ )} + {isLoading ? ( + + ) : ( + + )} +
+
+
+ ) +} \ No newline at end of file diff --git a/projects/HashScope/frontend/src/app/favicon.ico b/projects/HashScope/frontend/src/app/favicon.ico new file mode 100644 index 00000000..2977d1ad Binary files /dev/null and b/projects/HashScope/frontend/src/app/favicon.ico differ diff --git a/projects/HashScope/frontend/src/app/globals.css b/projects/HashScope/frontend/src/app/globals.css new file mode 100644 index 00000000..744265af --- /dev/null +++ b/projects/HashScope/frontend/src/app/globals.css @@ -0,0 +1,132 @@ +@import "tailwindcss"; + +@custom-variant dark (&:is(.dark *)); +@config "../../tailwind.config.ts"; + +:root { + --background: #ffffff; + --foreground: #171717; + --sidebar: oklch(0.21 0.034 264.665); + --sidebar-foreground: hsl(220 13% 95%); + --sidebar-primary: hsl(240 5.9% 10%); + --sidebar-primary-foreground: hsl(0 0% 98%); + --sidebar-accent: hsl(240 4.8% 95.9%); + --sidebar-accent-foreground: hsl(240 5.9% 10%); + --sidebar-border: hsl(220 13% 91%); + --sidebar-ring: hsl(217.2 91.2% 59.8%); +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-archivo); + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); +} + +@media (prefers-color-scheme: dark) { + :root { + --background: #0a0a0a; + --foreground: #ededed; + } +} + +body { + background: var(--background); + color: var(--foreground); + font-family: var(--font-archivo), Arial, Helvetica, sans-serif; + min-height: 100vh; + width: 100%; + margin: 0; + padding: 0; +} + +/* Custom scrollbar styles for the inner container */ +.scrollbar-container::-webkit-scrollbar { + width: 8px; +} + +.scrollbar-container::-webkit-scrollbar-track { + background: transparent; +} + +.scrollbar-container::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.2); + border-radius: 4px; +} + +.scrollbar-container::-webkit-scrollbar-thumb:hover { + background-color: rgba(0, 0, 0, 0.3); +} + +/* For Firefox */ +.scrollbar-container { + scrollbar-width: thin; + scrollbar-color: rgba(0, 0, 0, 0.2) transparent; +} + +/* Ensure content containers handle overflow properly */ +.scrollable-content { + overflow-y: auto; + -webkit-overflow-scrolling: touch; + height: 100%; + width: 100%; +} + +.dark { + --sidebar: hsl(240 5.9% 10%); + --sidebar-foreground: hsl(240 4.8% 95.9%); + --sidebar-primary: hsl(224.3 76.3% 48%); + --sidebar-primary-foreground: hsl(0 0% 100%); + --sidebar-accent: hsl(240 3.7% 15.9%); + --sidebar-accent-foreground: hsl(240 4.8% 95.9%); + --sidebar-border: hsl(240 3.7% 15.9%); + --sidebar-ring: hsl(217.2 91.2% 59.8%); +} + +@utility border-border { + border-color: var(--border); +} + +@utility outline-ring/50 { + outline-color: var(--ring); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} + +/* Animation for search suggestions */ +@keyframes fadeInOut { + 0% { + opacity: 0; + transform: translateY(10px); + } + 20% { + opacity: 1; + transform: translateY(0); + } + 80% { + opacity: 1; + transform: translateY(0); + } + 100% { + opacity: 0; + transform: translateY(-10px); + } +} + +.animate-fade-in-out { + animation: fadeInOut 2s ease-in-out infinite; +} diff --git a/projects/HashScope/frontend/src/app/hot/page.tsx b/projects/HashScope/frontend/src/app/hot/page.tsx new file mode 100644 index 00000000..b0a5cfd0 --- /dev/null +++ b/projects/HashScope/frontend/src/app/hot/page.tsx @@ -0,0 +1,178 @@ +'use client'; + +import * as React from "react"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Flame, LineChart, Users, Database, Coins } from 'lucide-react'; +import { Pagination } from "@/components/ui/pagination"; +import apiService, { APICatalogItem, APICategoriesResponse } from "@/lib/api-service"; + +const ITEMS_PER_PAGE = 6; + +// Helper function to get icon based on category +const getCategoryIcon = (category: string) => { + switch (category.toLowerCase()) { + case 'market data': + return ; + case 'social signals': + return ; + case 'on-chain analytics': + return ; + default: + return ; + } +}; + +export default function HotPage() { + const [selectedCategory, setSelectedCategory] = React.useState("all"); + const [currentPage, setCurrentPage] = React.useState(1); + const [apis, setApis] = React.useState([]); + const [categories, setCategories] = React.useState({}); + const [loading, setLoading] = React.useState(true); + + React.useEffect(() => { + const fetchData = async () => { + try { + const [catalogResponse, categoriesResponse] = await Promise.all([ + apiService.listAPICatalog(selectedCategory === "all" ? undefined : selectedCategory), + apiService.listAPICategories() + ]); + setApis(catalogResponse.apis); + setCategories(categoriesResponse); + } catch (error) { + console.error('Error fetching API data:', error); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [selectedCategory]); + + const totalPages = Math.ceil(apis.length / ITEMS_PER_PAGE); + const paginatedAPIs = apis.slice( + (currentPage - 1) * ITEMS_PER_PAGE, + currentPage * ITEMS_PER_PAGE + ); + + // Reset to first page when category changes + React.useEffect(() => { + setCurrentPage(1); + }, [selectedCategory]); + + if (loading) { + return ( +
+
Loading...
+
+ ); + } + + return ( +
+
+ {/* Dynamic Trending Section */} +
+
+
+
+ +
Popular APIs
+
+
+ {apis.slice(0, 5).map((api, index) => ( +
+
{index + 1}
+
+
+ {api.summary} +
+
{api.category}
+
+
+ ))} +
+
+
+ +
+
+

Trending APIs

+

Most popular and fastest growing APIs this week

+
+ +
+ +
+ {paginatedAPIs.map((api, index) => ( +
+
+
+
+ {getCategoryIcon(api.category)} +
+
+

{api.summary}

+

{api.category}

+
+
+
+
+ #{(currentPage - 1) * ITEMS_PER_PAGE + index + 1} +
+
+
+

+ {api.description.split('Returns:').map((part, index) => ( + + {index > 0 &&
} + {index === 0 ? part : `Returns:${part}`} +
+ ))} +

+
+
+ Path: {api.path} +
+
+
+ ))} +
+ + {/* Pagination */} + {apis.length > 0 && ( + + )} +
+
+ ); +} \ No newline at end of file diff --git a/projects/HashScope/frontend/src/app/layout.tsx b/projects/HashScope/frontend/src/app/layout.tsx new file mode 100644 index 00000000..2ac66d58 --- /dev/null +++ b/projects/HashScope/frontend/src/app/layout.tsx @@ -0,0 +1,43 @@ +import type { Metadata } from "next"; +import { Archivo } from "next/font/google"; +import "./globals.css"; +import MetaMaskProviderWrapper from "@/components/providers/MetaMaskProviderWrapper"; +import { SidebarProvider } from "@/components/ui/sidebar" +import { AppSidebar } from "@/app/components/app-sidebar" +import { TopBar } from "@/app/components/top-bar" +import { Toaster } from "@/components/ui/sonner" + +const archivo = Archivo({ + variable: "--font-archivo", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "HashScope", + description: "HashScope", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + + + +
+ +
+ {children} +
+
+ +
+
+ + + ); +} diff --git a/projects/HashScope/frontend/src/app/my/api-key/page.tsx b/projects/HashScope/frontend/src/app/my/api-key/page.tsx new file mode 100644 index 00000000..bbb3ea8f --- /dev/null +++ b/projects/HashScope/frontend/src/app/my/api-key/page.tsx @@ -0,0 +1,375 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { useSDK } from '@metamask/sdk-react'; +import { Loader2, Copy, Check, Trash2 } from 'lucide-react'; +import { toast } from 'sonner'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; + +interface ApiKey { + key_id: string; + name: string; + is_active: boolean; + created_at: string; + expires_at: string; + rate_limit_per_minute: number; + call_count: number; + last_used_at: string; +} + +interface NewApiKey { + key_id: string; + secret_key: string; + name: string; + is_active: boolean; + created_at: string; + expires_at: string; + rate_limit_per_minute: number; +} + +export default function SecretPage() { + const { connected } = useSDK(); + const [apiKeys, setApiKeys] = useState([]); + const [newKey, setNewKey] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [showSecret, setShowSecret] = useState(false); + const [copied, setCopied] = useState(false); + const [deleteKeyId, setDeleteKeyId] = useState(null); + const [formData, setFormData] = useState({ + name: '', + rate_limit_per_minute: 60 + }); + + useEffect(() => { + if (connected) { + fetchApiKeys(); + } + }, [connected]); + + const fetchApiKeys = async () => { + try { + setIsLoading(true); + const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api-keys/`, { + headers: { + 'Authorization': `Bearer ${localStorage.getItem('authToken')}` + } + }); + if (!response.ok) throw new Error('Failed to fetch API keys'); + const data = await response.json(); + setApiKeys(data); + } catch (error) { + toast.error('Failed to fetch API keys'); + console.error('Error fetching API keys:', error); + } finally { + setIsLoading(false); + } + }; + + const createApiKey = async (e: React.FormEvent) => { + e.preventDefault(); + try { + setIsLoading(true); + const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api-keys/`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${localStorage.getItem('authToken')}` + }, + body: JSON.stringify(formData) + }); + if (!response.ok) throw new Error('Failed to create API key'); + const data = await response.json(); + setNewKey(data); + setShowSecret(true); + toast.success('API key created successfully'); + fetchApiKeys(); + } catch (error) { + toast.error('Failed to create API key'); + console.error('Error creating API key:', error); + } finally { + setIsLoading(false); + } + }; + + const copyToClipboard = async (text: string) => { + try { + await navigator.clipboard.writeText(text); + setCopied(true); + toast.success('Copied to clipboard'); + setTimeout(() => setCopied(false), 2000); + } catch (error) { + console.error('Failed to copy to clipboard:', error); + toast.error('Failed to copy to clipboard'); + } + }; + + const deleteApiKey = async (keyId: string) => { + try { + setIsLoading(true); + const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api-keys/${keyId}`, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${localStorage.getItem('authToken')}` + } + }); + + if (!response.ok) throw new Error('Failed to delete API key'); + + toast.success('API key deleted successfully'); + fetchApiKeys(); // Refresh the list + } catch (error) { + toast.error('Failed to delete API key'); + console.error('Error deleting API key:', error); + } finally { + setIsLoading(false); + } + }; + + const handleDeleteClick = (keyId: string) => { + setDeleteKeyId(keyId); + }; + + const handleDeleteConfirm = async () => { + if (deleteKeyId) { + await deleteApiKey(deleteKeyId); + setDeleteKeyId(null); + } + }; + + if (!connected) { + return ( +
+
+
+

Please connect your wallet

+

You need to connect your wallet to access the Secret page.

+
+
+
+ ); + } + + return ( +
+
+

API Key Management

+ + {/* API Host Information */} + + + API Host Information + Base URL for all API endpoints + + +
+ + {process.env.NEXT_PUBLIC_API_BASE_URL || 'https://hashkey.sungwoonsong.com'} + + +
+
+
+ + {/* Create New API Key Form */} + + + Create New API Key + Generate a new API key for your application + + +
+
+ + setFormData({ ...formData, name: e.target.value })} + placeholder="Enter a name for your API key" + className="bg-gray-800 border-gray-700 text-white placeholder:text-gray-400" + /> +
+
+ + setFormData({ ...formData, rate_limit_per_minute: parseInt(e.target.value) })} + min="1" + max="1000" + className="bg-gray-800 border-gray-700 text-white" + /> +
+ +
+
+
+ + {/* New API Key Display */} + {newKey && showSecret && ( + + + New API Key Created + + Make sure to copy your secret key now. You won't be able to see it again! + + + +
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+
+ )} + + {/* API Keys List */} + + + Your API Keys + Manage your existing API keys + + + {isLoading ? ( +
+ +
+ ) : apiKeys.length === 0 ? ( +

No API keys found

+ ) : ( +
+ {apiKeys.map((key) => ( + + +
+
+

{key.name || 'Unnamed Key'}

+

ID: {key.key_id}

+

+ Created: {new Date(key.created_at).toLocaleDateString()} +

+

+ Request Limit: {key.rate_limit_per_minute} requests/minute +

+

+ Calls: {key.call_count} +

+

+ Last Used: {new Date(key.last_used_at).toLocaleDateString()} +

+
+
+ + {key.is_active ? 'Active' : 'Inactive'} + + +
+
+
+
+ ))} +
+ )} +
+
+ + {/* Delete Confirmation Modal */} + setDeleteKeyId(null)}> + + + Delete API Key + + Are you sure you want to delete this API key? This action cannot be undone. + + + + + + + + +
+
+ ); +} \ No newline at end of file diff --git a/projects/HashScope/frontend/src/app/my/deposit/page.tsx b/projects/HashScope/frontend/src/app/my/deposit/page.tsx new file mode 100644 index 00000000..94d7d42f --- /dev/null +++ b/projects/HashScope/frontend/src/app/my/deposit/page.tsx @@ -0,0 +1,278 @@ +"use client" + +import { useEffect, useState } from "react" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Button } from "@/components/ui/button" +import { toast } from "sonner" +import { ethers } from "ethers" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" + +interface DepositInfo { + message: string + deposit_address: string + amount: number +} + +// HSKDeposit contract ABI +const HSKDepositABI = [ + { + "inputs": [], + "name": "deposit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "getBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] + +// HSK network settings +// const HSK_RPC_URL = process.env.NEXT_PUBLIC_HSK_RPC_URL || 'https://mainnet.hsk.xyz' + +export default function DepositPage() { + const [depositInfo, setDepositInfo] = useState(null) + const [isLoading, setIsLoading] = useState(false) + const [account, setAccount] = useState(null) + const [userBalance, setUserBalance] = useState('0') + const [depositAmount, setDepositAmount] = useState('0.1') + + useEffect(() => { + checkConnection() + fetchDepositInfo() + + // Account change event listener + if (window.ethereum) { + window.ethereum.on('accountsChanged', (accounts: string[]) => { + setAccount(accounts[0]) + if (accounts[0]) { + fetchUserBalance(accounts[0]) + } + }) + } + + return () => { + if (window.ethereum) { + window.ethereum.removeListener('accountsChanged', () => {}) + } + } + }, []) + + const checkConnection = async () => { + if (window.ethereum) { + try { + const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }) as string[] + setAccount(accounts[0]) + if (accounts[0]) { + await fetchUserBalance(accounts[0]) + } + } catch (error) { + console.error('Connection error:', error) + toast.error("Failed to connect wallet") + } + } else { + toast.error("Please install HashKey wallet") + } + } + + const fetchUserBalance = async (userAddress: string) => { + try { + const token = localStorage.getItem("authToken") + if (!token) { + throw new Error("No authentication token found") + } + + const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/users/${userAddress}/balance`, { + headers: { + 'Authorization': `Bearer ${token}`, + }, + }) + + if (!response.ok) { + throw new Error("Failed to fetch balance") + } + + const data = await response.json() + setUserBalance(data.formatted_balance || '0') + } catch (error) { + console.error('Balance fetch error:', error) + toast.error("Failed to fetch balance") + } + } + + const fetchDepositInfo = async () => { + try { + const token = localStorage.getItem("authToken") + if (!token) { + throw new Error("No authentication token found") + } + + const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/users/deposit/info`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + + if (!response.ok) { + throw new Error("Failed to fetch deposit info") + } + const data = await response.json() + setDepositInfo(data) + + // Fetch balance after getting deposit info + if (account) { + await fetchUserBalance(account) + } + } catch (error) { + toast.error(error instanceof Error ? error.message : "Failed to fetch deposit information") + } + } + + const handleDeposit = async () => { + if (!depositInfo || !account) return + + // Validate deposit amount + const amount = parseFloat(depositAmount) + if (isNaN(amount) || amount < 0.001) { + toast.error("Deposit amount must be at least 0.001 HSK") + return + } + + setIsLoading(true) + try { + if (!window.ethereum) { + throw new Error("HashKey wallet is not installed") + } + + // Switch to HSK network (chain ID needs to be updated for HSK network) + await window.ethereum.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: '0xb1' }], // Update with actual HSK network chain ID + }) + + // Connect wallet + const provider = new ethers.providers.Web3Provider(window.ethereum) + const signer = provider.getSigner() + const contract = new ethers.Contract(depositInfo.deposit_address, HSKDepositABI, signer) + + // Convert amount to wei + const amountWei = ethers.utils.parseEther(depositAmount) + + // Call deposit function + const tx = await contract.deposit({ value: amountWei }) + toast.info("Transaction submitted. Waiting for confirmation...") + + // Wait for transaction confirmation + await tx.wait() + + // Notify backend about the successful deposit + try { + const token = localStorage.getItem("authToken") + if (!token) { + throw new Error("No authentication token found") + } + + const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/users/deposit/notify`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + body: JSON.stringify({ + tx_hash: tx.hash, + tx_type: "deposit" + }) + }) + + if (!response.ok) { + throw new Error("Failed to notify deposit transaction") + } + } catch (error) { + console.error('Failed to notify backend:', error) + toast.error("Failed to notify backend") + } + + // Update balance after successful deposit + await fetchUserBalance(account) + + toast.success("Deposit successful!") + } catch (error) { + toast.error(error instanceof Error ? error.message : "Failed to send transaction") + } finally { + setIsLoading(false) + } + } + + return ( +
+
+

Deposit

+

Deposit HSK tokens to your account using your HashKey wallet.

+ + + Deposit Information + + + {depositInfo ? ( +
+
+

Connected Wallet

+

{account || "Not connected"}

+
+
+

Current Balance

+

{userBalance}

+
+
+ +

Minimum deposit amount: 0.001 HSK

+ { + const value = e.target.value; + if (value === '' || (parseFloat(value) >= 0.001 && parseFloat(value) <= 1000000)) { + setDepositAmount(value); + } + }} + className="bg-gray-800 border-gray-600 text-white" + /> +
+ +
+ ) : ( +

Loading deposit information...

+ )} +
+
+
+
+ ) +} \ No newline at end of file diff --git a/projects/HashScope/frontend/src/app/my/profile/page.tsx b/projects/HashScope/frontend/src/app/my/profile/page.tsx new file mode 100644 index 00000000..b73e3a1c --- /dev/null +++ b/projects/HashScope/frontend/src/app/my/profile/page.tsx @@ -0,0 +1,15 @@ +import MetaMaskAuth from '@/components/MetaMaskAuth'; + +export default function MetaMaskPage() { + return ( +
+
+

My Profile

+

Manage your account settings and wallet connection.

+
+ +
+
+
+ ); +} \ No newline at end of file diff --git a/projects/HashScope/frontend/src/app/my/usage/[key_id]/page.tsx b/projects/HashScope/frontend/src/app/my/usage/[key_id]/page.tsx new file mode 100644 index 00000000..6527bac7 --- /dev/null +++ b/projects/HashScope/frontend/src/app/my/usage/[key_id]/page.tsx @@ -0,0 +1,195 @@ +'use client'; + +import { useEffect, useState } from "react"; +import { useParams } from "next/navigation"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import Link from "next/link"; + +type EndpointHistory = { + endpoint: string; + method: string; + call_count: number; + last_used_at: string; + total_cost: number; +}; + +type ApiKeyHistory = { + key_id: string; + total_calls: number; + total_cost: number; + endpoints: EndpointHistory[]; +}; + +type ApiKeyDetails = { + key_id: string; + name: string; + is_active: boolean; + created_at: string; + expires_at: string; + rate_limit_per_minute: number; + call_count: number; + last_used_at: string; +}; + +export default function ApiKeyHistoryPage() { + const params = useParams(); + const [data, setData] = useState(null); + const [apiKeyDetails, setApiKeyDetails] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchData = async () => { + try { + // First fetch API key details + const detailsResponse = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api-keys/${params.key_id}`, { + headers: { + 'Authorization': `Bearer ${localStorage.getItem('authToken')}`, + 'Content-Type': 'application/json', + }, + }); + + if (!detailsResponse.ok) { + throw new Error('Failed to fetch API key details'); + } + + const details = await detailsResponse.json(); + setApiKeyDetails(details); + + // Then fetch history + const historyResponse = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api-keys/${params.key_id}/history`, { + headers: { + 'Authorization': `Bearer ${localStorage.getItem('authToken')}`, + 'Content-Type': 'application/json', + }, + }); + + if (!historyResponse.ok) { + throw new Error('Failed to fetch API key history'); + } + + const history = await historyResponse.json(); + setData(history); + } catch (error) { + console.error('Error fetching data:', error); + setError('Failed to fetch data. Please try again later.'); + } finally { + setIsLoading(false); + } + }; + + fetchData(); + }, [params.key_id]); + + if (isLoading) { + return ( +
+
+
+
+
Loading API key history...
+
+
+
+
+ ); + } + + if (error) { + return ( +
+
+
+
+
{error}
+
+
+
+
+ ); + } + + if (!data || !apiKeyDetails) { + return ( +
+
+
+
+
No data found.
+
+
+
+
+ ); + } + + return ( +
+
+
+ + ← Back to API Keys + +

API Key Usage History

+
+
+
+

+ {apiKeyDetails.name} + + ID: {apiKeyDetails.key_id} +

+
+ +
+
+

Total Calls

+

{data.total_calls.toLocaleString()}

+
+
+

Total Cost

+

{data.total_cost.toLocaleString()} HSK

+
+
+ +
+ + + + Endpoint + Method + Calls + Last Used + Cost (HSK) + + + + {data.endpoints.map((endpoint, index) => ( + + {endpoint.endpoint} + {endpoint.method} + {endpoint.call_count.toLocaleString()} + + {new Date(endpoint.last_used_at).toLocaleString()} + + {endpoint.total_cost.toLocaleString()} + + ))} + +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/projects/HashScope/frontend/src/app/my/usage/page.tsx b/projects/HashScope/frontend/src/app/my/usage/page.tsx new file mode 100644 index 00000000..2be4d15a --- /dev/null +++ b/projects/HashScope/frontend/src/app/my/usage/page.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { UsageTable } from '@/components/UsageTable'; + +export default function UsagePage() { + return ( +
+
+

API Key Usage

+
+ +
+
+
+ ); +} \ No newline at end of file diff --git a/projects/HashScope/frontend/src/app/page.tsx b/projects/HashScope/frontend/src/app/page.tsx new file mode 100644 index 00000000..80b3222b --- /dev/null +++ b/projects/HashScope/frontend/src/app/page.tsx @@ -0,0 +1,101 @@ +'use client'; + +import Link from 'next/link'; +import { ArrowRight, Key, LineChart, Coins, Database } from 'lucide-react'; +import { useSDK } from '@metamask/sdk-react'; +import Image from 'next/image'; +export default function Home() { + const { connected } = useSDK(); + + return ( +
+
+ {/* Hero Section */} +
+ HashScope Logo +

+ HashScope +

+

+ An HSK Chain-based tokenized API platform providing real-time crypto data for Al agents. +

+ + {connected ? 'Explore Hot APIs' : 'Get Started'} + +
+ + {/* Features Grid */} +
+
+
+ +

Secure API Access

+
+

+ Stake HSK tokens to receive your secret key and start using our APIs +

+
+ +
+
+ +

Real-time Data

+
+

+ Access comprehensive crypto market data for AI predictions and automated trading +

+
+ +
+
+ +

Pay-as-you-go

+
+

+ Pay only for what you use with our usage-based billing model +

+
+ +
+
+ +

API Analytics

+
+

+ Track your API usage, performance metrics, and cost analysis in real-time +

+
+ +
+
+ +

API Management

+
+

+ Create and manage API keys with customizable request limits +

+
+ +
+
+ +

Rich API Collection

+
+

+ Access diverse APIs for market data, social signals, and on-chain analytics +

+
+
+
+
+ ); +} diff --git a/projects/HashScope/frontend/src/app/search/page.tsx b/projects/HashScope/frontend/src/app/search/page.tsx new file mode 100644 index 00000000..709ef2c5 --- /dev/null +++ b/projects/HashScope/frontend/src/app/search/page.tsx @@ -0,0 +1,180 @@ +'use client'; + +import * as React from "react"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Search, LineChart, Users, Database, Coins } from 'lucide-react'; +import { Input } from "@/components/ui/input"; +import { Pagination } from "@/components/ui/pagination"; +import apiService, { APICatalogItem, APICategoriesResponse } from "@/lib/api-service"; + +const ITEMS_PER_PAGE = 6; + +// Helper function to get icon based on category +const getCategoryIcon = (category: string) => { + switch (category.toLowerCase()) { + case 'market data': + return ; + case 'social signals': + return ; + case 'on-chain analytics': + return ; + default: + return ; + } +}; + +export default function SearchPage() { + const [selectedCategory, setSelectedCategory] = React.useState("all"); + const [searchQuery, setSearchQuery] = React.useState(""); + const [currentPage, setCurrentPage] = React.useState(1); + const [apis, setApis] = React.useState([]); + const [categories, setCategories] = React.useState({}); + const [loading, setLoading] = React.useState(true); + + React.useEffect(() => { + const fetchData = async () => { + try { + const [catalogResponse, categoriesResponse] = await Promise.all([ + apiService.listAPICatalog(selectedCategory === "all" ? undefined : selectedCategory), + apiService.listAPICategories() + ]); + setApis(catalogResponse.apis); + setCategories(categoriesResponse); + } catch (error) { + console.error('Error fetching API data:', error); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [selectedCategory]); + + const filteredAPIs = apis.filter(api => { + const matchesSearch = api.summary.toLowerCase().includes(searchQuery.toLowerCase()) || + api.description.toLowerCase().includes(searchQuery.toLowerCase()); + return matchesSearch; + }); + + const totalPages = Math.ceil(filteredAPIs.length / ITEMS_PER_PAGE); + const paginatedAPIs = filteredAPIs.slice( + (currentPage - 1) * ITEMS_PER_PAGE, + currentPage * ITEMS_PER_PAGE + ); + + // Reset to first page when filters change + React.useEffect(() => { + setCurrentPage(1); + }, [selectedCategory, searchQuery]); + + if (loading) { + return ( +
+
Loading...
+
+ ); + } + + return ( +
+
+ {/* Search and Filter Section */} +
+
+
+
+ + setSearchQuery(e.target.value)} + className="pl-10 bg-gray-700 border-gray-600 text-white placeholder:text-gray-400" + /> +
+
+ +
+
+ + {/* Results Section */} +
+ {paginatedAPIs.map((api) => ( +
+
+
+
+ {getCategoryIcon(api.category)} +
+
+

{api.summary}

+

{api.category}

+
+
+
+
Method
+
{api.method}
+
+
+

+ {api.description.split('Returns:').map((part, index) => ( + + {index > 0 &&
} + {index === 0 ? part : `Returns:${part}`} +
+ ))} +

+
+
+ Path: {api.path} +
+
+
+ ))} +
+ + {/* No Results Message */} + {filteredAPIs.length === 0 && ( +
+

No APIs found matching your search criteria

+
+ )} + + {/* Pagination */} + {filteredAPIs.length > 0 && ( + + )} +
+
+ ); +} \ No newline at end of file diff --git a/projects/HashScope/frontend/src/app/upgrade/page.tsx b/projects/HashScope/frontend/src/app/upgrade/page.tsx new file mode 100644 index 00000000..75d0b46f --- /dev/null +++ b/projects/HashScope/frontend/src/app/upgrade/page.tsx @@ -0,0 +1,189 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import Link from 'next/link'; +import { Coins, ArrowRight } from 'lucide-react'; + +interface TierInfo { + name: string; + color: string; + price: number; + rateLimit: number; + minBalance: number; + description: string; +} + +const tiers: TierInfo[] = [ + { + name: 'Gold', + color: 'from-yellow-400 to-yellow-600', + price: 50, + rateLimit: 1000, + minBalance: 10000, + description: 'Best for high-volume traders and professional users' + }, + { + name: 'Silver', + color: 'from-gray-400 to-gray-600', + price: 70, + rateLimit: 500, + minBalance: 5000, + description: 'Perfect for active traders and regular users' + }, + { + name: 'Bronze', + color: 'from-amber-700 to-amber-900', + price: 100, + rateLimit: 100, + minBalance: 0, + description: 'Great for getting started with basic features' + } +]; + +export default function UpgradePage() { + const [tokenBalance, setTokenBalance] = useState('0'); + const [currentTier, setCurrentTier] = useState('Bronze'); + const [account, setAccount] = useState(null); + + useEffect(() => { + checkConnection(); + + // Account change event listener + if (window.ethereum) { + window.ethereum.on('accountsChanged', (accounts: string[]) => { + setAccount(accounts[0]); + if (accounts[0]) { + fetchUserBalance(accounts[0]); + } + }); + } + + return () => { + if (window.ethereum) { + window.ethereum.removeListener('accountsChanged', () => {}); + } + }; + }, []); + + const checkConnection = async () => { + if (window.ethereum) { + try { + const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }) as string[]; + setAccount(accounts[0]); + if (accounts[0]) { + await fetchUserBalance(accounts[0]); + } + } catch (error) { + console.error('Connection error:', error); + } + } + }; + + const fetchUserBalance = async (userAddress: string) => { + try { + const token = localStorage.getItem("authToken"); + if (!token) { + throw new Error("No authentication token found"); + } + + const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/users/${userAddress}/balance`, { + headers: { + 'Authorization': `Bearer ${token}`, + }, + }); + + if (!response.ok) { + throw new Error("Failed to fetch balance"); + } + + const data = await response.json(); + setTokenBalance(data.formatted_balance || '0'); + + // Determine current tier based on balance + const balance = parseFloat(data.formatted_balance || '0'); + if (balance >= 10000) { + setCurrentTier('Gold'); + } else if (balance >= 5000) { + setCurrentTier('Silver'); + } else if (balance >= 1000) { + setCurrentTier('Bronze'); + } + } catch (error) { + console.error('Balance fetch error:', error); + } + }; + + return ( +
+
+

Usage Tiers

+ + {/* Tier Boxes */} +
+ {tiers.map((tier) => ( +
+
+
+ {tier.name} +
+
+
+

{tier.name} Tier

+
+

+ Usage Fee: + {tier.price}% +

+

+ Request Limit: + {tier.rateLimit}/min +

+

+ Min Balance: + {tier.minBalance} +

+
+

{tier.description}

+
+
+ ))} +
+ + {/* User Balance Section */} +
+
+
+

Your Current Balance

+
+ + + {account ? `${tokenBalance}` : 'Connect Wallet'} + +
+
+
+

Current Tier

+ {currentTier} +
+
+
+ + {/* Action Button */} +
+ + Deposit and upgrade your tier now + + +
+
+
+ ); +} \ No newline at end of file diff --git a/projects/HashScope/frontend/src/components/MetaMaskAuth.tsx b/projects/HashScope/frontend/src/components/MetaMaskAuth.tsx new file mode 100644 index 00000000..653e7ec0 --- /dev/null +++ b/projects/HashScope/frontend/src/components/MetaMaskAuth.tsx @@ -0,0 +1,257 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import Image from 'next/image'; +import { useSDK } from '@metamask/sdk-react'; +import type Web3 from 'web3'; + +interface NonceResponse { + wallet_address: string; + nonce: string; + message: string; +} + +interface VerifyResponse { + access_token: string; + token_type: string; + wallet_address: string; + token_balance: number; +} + +interface ValidationError { + detail: Array<{ + loc: string[]; + msg: string; + type: string; + }>; +} + +declare global { + interface Window { + web3: Web3; + ethereum: { + request: (args: { method: string; params?: unknown[] }) => Promise; + isConnected: () => boolean; + on: (eventName: string, callback: (accounts: string[]) => void) => void; + removeListener: (eventName: string, callback: (accounts: string[]) => void) => void; + }; + } +} + +export default function MetaMaskAuth() { + const { sdk } = useSDK(); + const [account, setAccount] = useState(null); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + const [tokenBalance, setTokenBalance] = useState(null); + const [isMetaMaskConnected, setIsMetaMaskConnected] = useState(false); + + // Function to check MetaMask connection + const checkMetaMaskConnection = async () => { + try { + if (typeof window.ethereum !== 'undefined') { + const accounts = await window.ethereum.request({ method: 'eth_accounts' }); + if (Array.isArray(accounts) && accounts.length > 0) { + setIsMetaMaskConnected(true); + setAccount(accounts[0] as string); + } else { + setIsMetaMaskConnected(false); + setAccount(null); + } + } + } catch (err) { + console.error('Error checking MetaMask connection:', err); + setIsMetaMaskConnected(false); + setAccount(null); + } + }; + + // Function to handle account changes + const handleAccountsChanged = (accounts: string[]) => { + if (accounts.length === 0) { + setIsMetaMaskConnected(false); + setAccount(null); + localStorage.removeItem('authToken'); + localStorage.removeItem('account'); + localStorage.removeItem('tokenBalance'); + } else { + setIsMetaMaskConnected(true); + setAccount(accounts[0]); + } + }; + + // Check for existing session and MetaMask connection on mount + useEffect(() => { + const checkExistingSession = async () => { + const storedAccount = localStorage.getItem('account'); + const storedToken = localStorage.getItem('authToken'); + const storedBalance = localStorage.getItem('tokenBalance'); + + if (storedAccount && storedToken) { + setAccount(storedAccount); + if (storedBalance) { + setTokenBalance(Number(storedBalance)); + } + } + + // Check MetaMask connection + await checkMetaMaskConnection(); + + // Set up event listeners + if (typeof window.ethereum !== 'undefined') { + window.ethereum.on('accountsChanged', handleAccountsChanged); + window.ethereum.on('chainChanged', () => window.location.reload()); + } + + // Cleanup function + return () => { + if (typeof window.ethereum !== 'undefined') { + window.ethereum.removeListener('accountsChanged', handleAccountsChanged); + window.ethereum.removeListener('chainChanged', () => window.location.reload()); + } + }; + }; + + checkExistingSession(); + }, []); + + const connectAndLogin = async () => { + try { + setLoading(true); + setError(null); + + if (typeof window.ethereum === 'undefined') { + throw new Error('Please install MetaMask!'); + } + + // 1. Connect MetaMask + const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); + if (!Array.isArray(accounts) || accounts.length === 0) { + throw new Error('No accounts found'); + } + const newAccount = accounts[0] as string; + setAccount(newAccount); + setIsMetaMaskConnected(true); + localStorage.setItem('account', newAccount); + + // 2. Get nonce from backend + const nonceResponse = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/auth/nonce`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + wallet_address: newAccount + }), + }); + + if (!nonceResponse.ok) { + const errorData: ValidationError = await nonceResponse.json(); + throw new Error(errorData.detail[0]?.msg || 'Failed to get nonce'); + } + + const nonceData: NonceResponse = await nonceResponse.json(); + + // 3. Sign the message using MetaMask + const signature = await window.ethereum.request({ + method: 'personal_sign', + params: [nonceData.message, newAccount] + }) as string; + + // 4. Verify signature with backend + const verifyResponse = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/auth/verify`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + wallet_address: newAccount, + signature: signature + }), + }); + + if (!verifyResponse.ok) { + const errorData: ValidationError = await verifyResponse.json(); + throw new Error(errorData.detail[0]?.msg || 'Failed to verify signature'); + } + + const verifyData: VerifyResponse = await verifyResponse.json(); + + // 5. Store the token and update balance + localStorage.setItem('authToken', verifyData.access_token); + if (verifyData.token_balance !== undefined) { + localStorage.setItem('tokenBalance', verifyData.token_balance.toString()); + setTokenBalance(verifyData.token_balance); + } + } catch (err) { + console.error('Connect and login error:', err); + setError(err instanceof Error ? err.message : 'Failed to connect and login'); + setIsMetaMaskConnected(false); + } finally { + setLoading(false); + } + }; + + const logout = async () => { + try { + await sdk?.disconnect(); + setAccount(null); + setTokenBalance(null); + setIsMetaMaskConnected(false); + localStorage.removeItem('authToken'); + localStorage.removeItem('account'); + localStorage.removeItem('tokenBalance'); + } catch (err) { + console.warn(`Failed to disconnect`, err); + setError('Failed to disconnect'); + } + }; + + return ( +
+ {error && ( +
+

{error}

+
+ )} + + {isMetaMaskConnected && account && ( +
+
+
+
+
+
+

Connected Account

+
+

{account}

+ {tokenBalance !== null && ( +

HSK Balance: {tokenBalance}

+ )} +
+ )} + +
+ {!isMetaMaskConnected ? ( + + ) : ( + + )} +
+
+ ); +} \ No newline at end of file diff --git a/projects/HashScope/frontend/src/components/UsageTable.tsx b/projects/HashScope/frontend/src/components/UsageTable.tsx new file mode 100644 index 00000000..9dd4f293 --- /dev/null +++ b/projects/HashScope/frontend/src/components/UsageTable.tsx @@ -0,0 +1,173 @@ +'use client'; + +import * as React from "react"; +import { + ColumnDef, + flexRender, + getCoreRowModel, + useReactTable, +} from "@tanstack/react-table"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { useEffect, useState } from "react"; +import Link from "next/link"; + +export type ApiKeyData = { + key_id: string; + name: string; + last_used_at: string; + call_count: number; +}; + +export const columns: ColumnDef[] = [ + { + id: "index", + header: "Index", + cell: ({ row }) => { + return
{row.index + 1}
; + }, + }, + { + accessorKey: "name", + header: "API Key Name(ID)", + cell: ({ row }) => { + return ( + + {row.original.name} ({row.original.key_id}) + + ); + }, + }, + { + accessorKey: "last_used_at", + header: "Last use", + cell: ({ row }) => { + return
{row.original.last_used_at || 'Never'}
; + }, + }, + { + accessorKey: "call_count", + header: "Calls", + cell: ({ row }) => { + return
{row.original.call_count.toLocaleString()}
; + }, + }, +]; + +export function UsageTable() { + const [data, setData] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchApiKeys = async () => { + try { + const response = await fetch('https://hashkey.sungwoonsong.com/api-keys/', { + headers: { + 'Authorization': `Bearer ${localStorage.getItem('authToken')}`, + 'Content-Type': 'application/json', + }, + }); + + if (!response.ok) { + throw new Error('Failed to fetch API keys'); + } + + const apiKeys = await response.json(); + setData(apiKeys); + } catch (error) { + console.error('Error fetching API keys:', error); + setError('Failed to fetch API keys. Please try again later.'); + } finally { + setIsLoading(false); + } + }; + + fetchApiKeys(); + }, []); + + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }); + + if (isLoading) { + return ( +
+
Loading API keys...
+
+ ); + } + + if (error) { + return ( +
+
{error}
+
+ ); + } + + return ( +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} + + )) + ) : ( + + + No API keys found. + + + )} + +
+
+ ); +} \ No newline at end of file diff --git a/projects/HashScope/frontend/src/components/providers/MetaMaskProviderWrapper.tsx b/projects/HashScope/frontend/src/components/providers/MetaMaskProviderWrapper.tsx new file mode 100644 index 00000000..600fe51f --- /dev/null +++ b/projects/HashScope/frontend/src/components/providers/MetaMaskProviderWrapper.tsx @@ -0,0 +1,24 @@ +'use client'; + +import { MetaMaskProvider } from '@metamask/sdk-react'; +import { ReactNode } from 'react'; + +interface MetaMaskProviderWrapperProps { + children: ReactNode; +} + +export default function MetaMaskProviderWrapper({ children }: MetaMaskProviderWrapperProps) { + return ( + + {children} + + ); +} \ No newline at end of file diff --git a/projects/HashScope/frontend/src/components/ui/button.tsx b/projects/HashScope/frontend/src/components/ui/button.tsx new file mode 100644 index 00000000..a2df8dce --- /dev/null +++ b/projects/HashScope/frontend/src/components/ui/button.tsx @@ -0,0 +1,59 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + { + variants: { + variant: { + default: + "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", + destructive: + "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", + secondary: + "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", + ghost: + "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2 has-[>svg]:px-3", + sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + icon: "size-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +function Button({ + className, + variant, + size, + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean + }) { + const Comp = asChild ? Slot : "button" + + return ( + + ) +} + +export { Button, buttonVariants } diff --git a/projects/HashScope/frontend/src/components/ui/card.tsx b/projects/HashScope/frontend/src/components/ui/card.tsx new file mode 100644 index 00000000..a4d81346 --- /dev/null +++ b/projects/HashScope/frontend/src/components/ui/card.tsx @@ -0,0 +1,68 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Card({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/projects/HashScope/frontend/src/components/ui/carousel.tsx b/projects/HashScope/frontend/src/components/ui/carousel.tsx new file mode 100644 index 00000000..0e05a77e --- /dev/null +++ b/projects/HashScope/frontend/src/components/ui/carousel.tsx @@ -0,0 +1,241 @@ +"use client" + +import * as React from "react" +import useEmblaCarousel, { + type UseEmblaCarouselType, +} from "embla-carousel-react" +import { ArrowLeft, ArrowRight } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" + +type CarouselApi = UseEmblaCarouselType[1] +type UseCarouselParameters = Parameters +type CarouselOptions = UseCarouselParameters[0] +type CarouselPlugin = UseCarouselParameters[1] + +type CarouselProps = { + opts?: CarouselOptions + plugins?: CarouselPlugin + orientation?: "horizontal" | "vertical" + setApi?: (api: CarouselApi) => void +} + +type CarouselContextProps = { + carouselRef: ReturnType[0] + api: ReturnType[1] + scrollPrev: () => void + scrollNext: () => void + canScrollPrev: boolean + canScrollNext: boolean +} & CarouselProps + +const CarouselContext = React.createContext(null) + +function useCarousel() { + const context = React.useContext(CarouselContext) + + if (!context) { + throw new Error("useCarousel must be used within a ") + } + + return context +} + +function Carousel({ + orientation = "horizontal", + opts, + setApi, + plugins, + className, + children, + ...props +}: React.ComponentProps<"div"> & CarouselProps) { + const [carouselRef, api] = useEmblaCarousel( + { + ...opts, + axis: orientation === "horizontal" ? "x" : "y", + }, + plugins + ) + const [canScrollPrev, setCanScrollPrev] = React.useState(false) + const [canScrollNext, setCanScrollNext] = React.useState(false) + + const onSelect = React.useCallback((api: CarouselApi) => { + if (!api) return + setCanScrollPrev(api.canScrollPrev()) + setCanScrollNext(api.canScrollNext()) + }, []) + + const scrollPrev = React.useCallback(() => { + api?.scrollPrev() + }, [api]) + + const scrollNext = React.useCallback(() => { + api?.scrollNext() + }, [api]) + + const handleKeyDown = React.useCallback( + (event: React.KeyboardEvent) => { + if (event.key === "ArrowLeft") { + event.preventDefault() + scrollPrev() + } else if (event.key === "ArrowRight") { + event.preventDefault() + scrollNext() + } + }, + [scrollPrev, scrollNext] + ) + + React.useEffect(() => { + if (!api || !setApi) return + setApi(api) + }, [api, setApi]) + + React.useEffect(() => { + if (!api) return + onSelect(api) + api.on("reInit", onSelect) + api.on("select", onSelect) + + return () => { + api?.off("select", onSelect) + } + }, [api, onSelect]) + + return ( + +
+ {children} +
+
+ ) +} + +function CarouselContent({ className, ...props }: React.ComponentProps<"div">) { + const { carouselRef, orientation } = useCarousel() + + return ( +
+
+
+ ) +} + +function CarouselItem({ className, ...props }: React.ComponentProps<"div">) { + const { orientation } = useCarousel() + + return ( +
+ ) +} + +function CarouselPrevious({ + className, + variant = "outline", + size = "icon", + ...props +}: React.ComponentProps) { + const { orientation, scrollPrev, canScrollPrev } = useCarousel() + + return ( + + ) +} + +function CarouselNext({ + className, + variant = "outline", + size = "icon", + ...props +}: React.ComponentProps) { + const { orientation, scrollNext, canScrollNext } = useCarousel() + + return ( + + ) +} + +export { + type CarouselApi, + Carousel, + CarouselContent, + CarouselItem, + CarouselPrevious, + CarouselNext, +} diff --git a/projects/HashScope/frontend/src/components/ui/chart.tsx b/projects/HashScope/frontend/src/components/ui/chart.tsx new file mode 100644 index 00000000..97cc2807 --- /dev/null +++ b/projects/HashScope/frontend/src/components/ui/chart.tsx @@ -0,0 +1,353 @@ +"use client" + +import * as React from "react" +import * as RechartsPrimitive from "recharts" + +import { cn } from "@/lib/utils" + +// Format: { THEME_NAME: CSS_SELECTOR } +const THEMES = { light: "", dark: ".dark" } as const + +export type ChartConfig = { + [k in string]: { + label?: React.ReactNode + icon?: React.ComponentType + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record } + ) +} + +type ChartContextProps = { + config: ChartConfig +} + +const ChartContext = React.createContext(null) + +function useChart() { + const context = React.useContext(ChartContext) + + if (!context) { + throw new Error("useChart must be used within a ") + } + + return context +} + +function ChartContainer({ + id, + className, + children, + config, + ...props +}: React.ComponentProps<"div"> & { + config: ChartConfig + children: React.ComponentProps< + typeof RechartsPrimitive.ResponsiveContainer + >["children"] +}) { + const uniqueId = React.useId() + const chartId = `chart-${id || uniqueId.replace(/:/g, "")}` + + return ( + +
+ + + {children} + +
+
+ ) +} + +const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { + const colorConfig = Object.entries(config).filter( + ([, config]) => config.theme || config.color + ) + + if (!colorConfig.length) { + return null + } + + return ( +