diff --git a/.gitignore b/.gitignore index 2f1b0d1f..41292be9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ __pycache__ .vscode .env storage_credentials.json +env/ diff --git a/apps/data_handler/Dockerfile b/apps/data_handler/Dockerfile index 7cdbc54a..38e54c16 100644 --- a/apps/data_handler/Dockerfile +++ b/apps/data_handler/Dockerfile @@ -5,7 +5,7 @@ WORKDIR /code COPY data_handler/poetry.lock data_handler/pyproject.toml /code/ COPY data_handler/entrypoint.sh /code/entrypoint.sh -RUN pip install poetry \ +RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=/usr/local/ POETRY_VERSION=1.8.3 python3 -\ && poetry config virtualenvs.create false \ && poetry install --no-dev diff --git a/apps/data_handler/handlers/loan_states/zklend/events.py b/apps/data_handler/handlers/loan_states/zklend/events.py index 0d767479..5f12402b 100644 --- a/apps/data_handler/handlers/loan_states/zklend/events.py +++ b/apps/data_handler/handlers/loan_states/zklend/events.py @@ -320,7 +320,6 @@ def process_withdrawal_event(self, event: pd.Series) -> None: # Update the user's deposit and collateral values self.loan_entities[user].deposit.increase_value(token=token, value=-raw_amount) - self.loan_entities[user].deposit.increase_value(token=token, value=-raw_amount) if self.loan_entities[user].collateral_enabled[token]: self.loan_entities[user].collateral.increase_value( diff --git a/apps/data_handler/tests/loan_state/test_zklend_state_entity.py b/apps/data_handler/tests/loan_state/test_zklend_state_entity.py index e33147a9..daf8391c 100644 --- a/apps/data_handler/tests/loan_state/test_zklend_state_entity.py +++ b/apps/data_handler/tests/loan_state/test_zklend_state_entity.py @@ -118,7 +118,7 @@ def test_process_withdrawal_event(self, zklend_state, sample_event, mock_portfol mock_parsed_data.amount = int(1e18) with patch( - "data_handler.handler_tools.data_parser.zklend.ZklendDataParser.parse_withdrawal_event", + "data_handler.handlers.loan_states.zklend.events.ZklendDataParser.parse_withdrawal_event", return_value=mock_parsed_data, ): user = mock_parsed_data.user @@ -130,7 +130,6 @@ def test_process_withdrawal_event(self, zklend_state, sample_event, mock_portfol loan_entity.extra_info = MagicMock() zklend_state.loan_entities[user] = loan_entity - zklend_state.process_withdrawal_event(sample_event) mock_portfolio.increase_value.assert_called() diff --git a/apps/data_handler/tests/order_book/__init__.py b/apps/data_handler/tests/order_book/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/data_handler/tests/order_book/test_ekubo.py b/apps/data_handler/tests/order_book/test_ekubo.py new file mode 100644 index 00000000..00d51af3 --- /dev/null +++ b/apps/data_handler/tests/order_book/test_ekubo.py @@ -0,0 +1,162 @@ +"""Tests for the EkuboOrderBook class.""" +from decimal import Decimal +from unittest.mock import Mock, patch, MagicMock + +import pandas as pd +import pytest +from data_handler.handlers.order_books.ekubo.main import EkuboOrderBook + +@pytest.fixture +def mock_connector(): + """Create a mock EkuboAPIConnector.""" + with patch('data_handler.handlers.order_books.ekubo.main.EkuboAPIConnector') as mock: + connector = mock.return_value + connector.get_pair_price.return_value = {"price": "1.5"} + connector.get_pools.return_value = [{ + "token0": "0x1", + "token1": "0x2", + "key_hash": "0xabc", + "liquidity": "1000000", + "lastUpdate": {"event_id": "123"}, + "tick": 100, + "tick_spacing": 10 + }] + connector.get_pool_liquidity.return_value = { + "data": [ + {"tick": 90, "net_liquidity_delta_diff": "100"}, + {"tick": 100, "net_liquidity_delta_diff": "200"}, + {"tick": 110, "net_liquidity_delta_diff": "300"} + ] + } + yield connector + +@pytest.fixture +def order_book(mock_connector): + """Create an EkuboOrderBook instance with mocked connector.""" + book = EkuboOrderBook("0x1", "0x2") + # Set decimals for token precision + book.token_a_decimal = 18 + book.token_b_decimal = 18 + return book + +def test_initialization(order_book): + """Test EkuboOrderBook initialization.""" + assert order_book.DEX == "Ekubo" + assert order_book.token_a == "0x1" + assert order_book.token_b == "0x2" + assert order_book.asks == [] + assert order_book.bids == [] + +def test_set_current_price(order_book): + """Test setting current price.""" + order_book.set_current_price() + assert order_book.current_price == Decimal("1.5") + +def test_fetch_price_and_liquidity(order_book): + """Test fetching price and liquidity data.""" + order_book.fetch_price_and_liquidity() + assert order_book.block == "123" + assert len(order_book.asks) > 0 + assert len(order_book.bids) > 0 + +def test_calculate_order_book(order_book): + """Test order book calculation.""" + # Create test data with ticks both above and below the current tick (100) + liquidity_data = [ + {"tick": 90, "net_liquidity_delta_diff": "100"}, + {"tick": 95, "net_liquidity_delta_diff": "150"}, + {"tick": 100, "net_liquidity_delta_diff": "200"}, + {"tick": 105, "net_liquidity_delta_diff": "250"}, + {"tick": 110, "net_liquidity_delta_diff": "300"} + ] + + # Create a pandas Series for the row + row_data = { + "tick": 100, + "tick_spacing": 5, + "liquidity": "1000000" + } + row = pd.Series(row_data) + + # Set current price for price range calculation + order_book.current_price = Decimal("1.5") + + # Calculate order book + order_book._calculate_order_book(liquidity_data, 1000000, row) + + # Verify that both asks and bids are populated + assert len(order_book.asks) > 0, "Asks should not be empty" + assert len(order_book.bids) > 0, "Bids should not be empty" + + # Verify the structure of asks and bids + for price, supply in order_book.asks: + assert isinstance(price, Decimal), "Price should be Decimal" + assert isinstance(supply, Decimal), "Supply should be Decimal" + assert price > 0, "Price should be positive" + assert supply > 0, "Supply should be positive" + + for price, supply in order_book.bids: + assert isinstance(price, Decimal), "Price should be Decimal" + assert isinstance(supply, Decimal), "Supply should be Decimal" + assert price > 0, "Price should be positive" + assert supply > 0, "Supply should be positive" + +def test_get_pure_sqrt_ratio(order_book): + """Test square root ratio calculation.""" + result = order_book._get_pure_sqrt_ratio(Decimal("1")) + assert isinstance(result, Decimal) + assert result > 0 + +def test_sort_ticks_by_asks_and_bids(): + """Test sorting ticks into asks and bids.""" + liquidity_data = [ + {"tick": 90}, + {"tick": 100}, + {"tick": 110} + ] + current_tick = 100 + asks, bids = EkuboOrderBook.sort_ticks_by_asks_and_bids(liquidity_data, current_tick) + assert len(asks) == 1 + assert len(bids) == 2 + assert all(tick["tick"] > current_tick for tick in asks) + assert all(tick["tick"] <= current_tick for tick in bids) + +def test_calculate_liquidity_amount(order_book): + """Test liquidity amount calculation.""" + # Set token decimals for proper calculation + order_book.token_a_decimal = 18 + order_book.token_b_decimal = 18 + + tick = Decimal("100") + liquidity_total = Decimal("1000000") + result = order_book.calculate_liquidity_amount(tick, liquidity_total) + assert isinstance(result, Decimal) + assert result > 0 + +@pytest.mark.parametrize("tick,expected_sign", [ + (1000, 1), # positive tick + (-1000, -1), # negative tick + (0, 0) # zero tick +]) +def test_tick_edge_cases(order_book, tick, expected_sign): + """Test tick calculations with edge cases.""" + result = order_book._get_pure_sqrt_ratio(Decimal(tick)) + assert isinstance(result, Decimal) + assert result > 0 # sqrt ratio should always be positive + if expected_sign > 0: + assert result > 1 + elif expected_sign < 0: + assert result < 1 + +def test_empty_liquidity_data(order_book): + """Test behavior with empty liquidity data.""" + row_data = { + "tick": 100, + "tick_spacing": 5, + "liquidity": "1000000" + } + row = pd.Series(row_data) + + order_book._calculate_order_book([], 1000000, row) + assert len(order_book.asks) == 0 + assert len(order_book.bids) == 0 \ No newline at end of file diff --git a/apps/data_handler/tests/order_book/test_haiko.py b/apps/data_handler/tests/order_book/test_haiko.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/data_handler/tests/order_book/test_myswap.py b/apps/data_handler/tests/order_book/test_myswap.py new file mode 100644 index 00000000..e0b08c2a --- /dev/null +++ b/apps/data_handler/tests/order_book/test_myswap.py @@ -0,0 +1,109 @@ +""" Test Cases for MySwap""" + +import asyncio +import math +from decimal import Decimal +from unittest.mock import AsyncMock, Mock, patch + +import pytest + +from ...handlers.order_books.myswap.main import MySwapOrderBook + +MAX_MYSWAP_TICK = Decimal("1774532") + + +@pytest.fixture +def order_book(): + """Mocks""" + obj = MySwapOrderBook( + base_token="0x12345", quote_token="0x67890", apply_filtering=True + ) + obj.base_token = "0x12345" + obj.quote_token = "0x67890" + return obj + + +def test_order_book_initialization(order_book): + """Test that the order book is initialized correctly.""" + assert order_book.base_token == "0x12345" + assert order_book.quote_token == "0x67890" + assert order_book.apply_filtering is True + + +def test_price_to_tick(order_book): + """Test conversion of price to tick.""" + order_book._decimals_diff = Decimal(1) + price = Decimal("1.0001") + tick = order_book._price_to_tick(price) + expected_tick = ( + round( + Decimal(math.log(price / (Decimal(2**128) * order_book._decimals_diff))) + / Decimal(math.log(Decimal("1.0001"))) + ) + + MAX_MYSWAP_TICK + ) + assert tick == expected_tick + + +@patch.object(MySwapOrderBook, "_get_clean_addresses", return_value=("0x123", "0x456")) +def test_filter_pools_data(mock_get_clean_addresses, order_book): + """Test that the pool data is correctly filtered.""" + all_pools = { + "pools": [ + {"token0": {"address": "0x123"}, "token1": {"address": "0x456"}}, + {"token0": {"address": "0x999"}, "token1": {"address": "0x888"}}, + ] + } + filtered = order_book._filter_pools_data(all_pools) + assert len(filtered) == 1 + assert filtered[0]["token0"]["address"] == "0x123" + + +@patch.object( + MySwapOrderBook, "_filter_pools_data", return_value=[{"poolkey": "test_pool"}] +) +@patch.object(MySwapOrderBook, "_calculate_order_book", new_callable=AsyncMock) +def test_async_fetch_price_and_liquidity( + mock_calculate_order_book, mock_filter_pools, order_book +): + """Test the async fetching of price and liquidity.""" + order_book.connector = Mock() + order_book.connector.get_pools_data = Mock(return_value={"pools": []}) + + async def run_test(): + await order_book._async_fetch_price_and_liquidity() + + asyncio.run(run_test()) + mock_calculate_order_book.assert_awaited_once_with("test_pool") + + +@patch.object(MySwapOrderBook, "_get_clean_addresses", return_value=("0x123", "0x456")) +def test_filter_pools_data_no_match(mock_get_clean_addresses, order_book): + """Test filtering when no pools match.""" + all_pools = { + "pools": [ + {"token0": {"address": "0x999"}, "token1": {"address": "0x888"}}, + ] + } + filtered = order_book._filter_pools_data(all_pools) + assert len(filtered) == 0 + + +def test_price_to_tick_invalid(order_book): + """Test invalid price leading to ValueError.""" + order_book._decimals_diff = Decimal(1) + with pytest.raises(ValueError): + order_book._price_to_tick(Decimal(0)) + + +@patch.object(MySwapOrderBook, "_filter_pools_data", return_value=[]) +def test_async_fetch_price_and_liquidity_no_pools(mock_filter_pools, order_book): + """Test the async fetching when no pools are available.""" + order_book.connector = Mock() + order_book.connector.get_pools_data = Mock(return_value={"pools": []}) + + async def run_test(): + await order_book._async_fetch_price_and_liquidity() + + asyncio.run(run_test()) + mock_filter_pools.assert_called_once() diff --git a/apps/data_handler/tests/order_book/test_uniswap.py b/apps/data_handler/tests/order_book/test_uniswap.py new file mode 100644 index 00000000..1ee8d1b7 --- /dev/null +++ b/apps/data_handler/tests/order_book/test_uniswap.py @@ -0,0 +1,115 @@ +from collections.abc import Iterable +from decimal import Decimal +from unittest.mock import MagicMock, patch + +import pytest +from data_handler.db.crud import DBConnector +from data_handler.handlers.order_books.processing import OrderBookProcessor +from data_handler.handlers.order_books.uniswap_v2 import main + + +@pytest.fixture +def order_book(): + """ + Fixture for the order book setup. + token_a and token_b gotten from constants (data_handlers.handlers.order_books.constants) + """ + token_a = "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" + token_b = "0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8" + order_book = main.UniswapV2OrderBook(token_a, token_b) + return order_book + + +class TestUniswapV2OrderBook: + def test_set_token_name(self, order_book: main.UniswapV2OrderBook): + """ + Unit Test for UniswapV2OrderBook._set_token_names. + """ + # set token name + order_book._set_token_names() + + # check that token_names has been set + assert order_book.token_a_name, "token_a_name should be set" + assert order_book.token_b_name, "token_b_name should be set" + + # check that token_names match their mappings + assert order_book.token_a_name == "ETH", "token_a_name should be ETH" + assert order_book.token_b_name == "USDC", "token_b_name should be USDC" + + def test_tick_to_price(self, order_book: main.UniswapV2OrderBook): + """ + Unit test for UniswapV2OrderBook.tick_to_price + Note: calculation is based on the ETH/USDC pair + """ + tick_value = Decimal("500") + uniswap_price_value = order_book.tick_to_price(tick_value) + assert isinstance(uniswap_price_value, Decimal), "price should be a decimal" + + def test_calculate_liquidity_amount(self, order_book: main.UniswapV2OrderBook): + """ " + Unit test for UniswapV2OrderBook.calculate_liquidity_amount + """ + tick = Decimal("500") + final_value = Decimal("9.997500313723646666869072034E-15") + liquidity_amount = order_book.calculate_liquidity_amount(tick, Decimal("10000")) + assert final_value == liquidity_amount, "liquidity amount does not match" + + def test_get_prices_ranges(self, order_book: main.UniswapV2OrderBook): + """ + Unit test for UniswapV2OrderBook.get_price_ranges + """ + price_ranges = order_book.get_prices_range(Decimal("3012.92")) + assert isinstance(price_ranges, Iterable), "Price ranges should be a iterable" + assert ( + len(price_ranges) > 1 + ), "Price ranges list length should be greater than 1" + + def test_fetch_price_and_liquidity(self, order_book: main.UniswapV2OrderBook): + """ + Unit test for UniswapV2OrderBook.fetch_price_and_liquidity + """ + with patch( + "data_handler.handlers.order_books.uniswap_v2.main.UniswapV2OrderBook._async_fetch_price_and_liquidity", + ) as mock_fetch_price_and_liquidity: + order_book.fetch_price_and_liquidity() + mock_fetch_price_and_liquidity.assert_called() + + +class TestUniswapV2OrderBookProcessor: + def test_calculate_price_change_successful(self, mock_db_connector, monkeypatch): + """ + Check whether calculate_price_change method call is successful + """ + monkeypatch.setattr(DBConnector, "__new__", mock_db_connector) + processor = OrderBookProcessor( + "Starknet", + "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + "0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8", + ) + price = processor.calculate_price_change(Decimal("0.05")) + assert isinstance(price, Decimal) # :) + + def test_calculate_price_change_fail_with_invalid_args( + self, mock_db_connector, monkeypatch + ): + """ + Check that ValueError is raised when price_change_ratio argument is + greater than 1 or less than 0 or equal to zero + """ + monkeypatch.setattr(DBConnector, "__new__", mock_db_connector) + processor = OrderBookProcessor( + "Starknet", + "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + "0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8", + ) + with pytest.raises(ValueError): + # check whether it is greater than + price = processor.calculate_price_change(Decimal("5")) + + with pytest.raises(ValueError): + # fails when it is less than + price = processor.calculate_price_change(Decimal("-1")) + + with pytest.raises(ValueError): + # check whether it is greater than + price = processor.calculate_price_change(Decimal("0")) diff --git a/apps/sdk/.env.dev b/apps/sdk/.env.dev new file mode 100644 index 00000000..b34e30bb --- /dev/null +++ b/apps/sdk/.env.dev @@ -0,0 +1,6 @@ +# postgresql +DB_HOST= +DB_NAME= +DB_USER= +DB_PASSWORD= +DB_PORT= diff --git a/apps/sdk/Dockerfile b/apps/sdk/Dockerfile new file mode 100644 index 00000000..04dadaab --- /dev/null +++ b/apps/sdk/Dockerfile @@ -0,0 +1,22 @@ +FROM python:3.11-slim + +WORKDIR /app + +RUN apt-get update && apt-get install -y \ + curl \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +RUN curl -sSL https://install.python-poetry.org/ | POETRY_HOME=/usr/local/ POETRY_VERSION=1.8.3 python3 - + +RUN poetry config virtualenvs.create false + +COPY pyproject.toml poetry.lock ./ + +RUN poetry install --no-root --only main + +COPY . . + +EXPOSE 8000 + +CMD ["uvicorn", "main:app", "--reload", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/apps/sdk/README.md b/apps/sdk/README.md new file mode 100644 index 00000000..65e5047b --- /dev/null +++ b/apps/sdk/README.md @@ -0,0 +1,76 @@ +# Derisk SDK + +This project is a FastAPI-based application, set up with Poetry for dependency management and Docker for containerization. + +## Requirements + +- Docker +- Docker Compose + +## Project Setup + +### Run the Project + +1. **Build and Run the Services** + + Use `docker-compose` to build and run the project: + + ```bash + docker-compose up --build + ``` + + • The backend service will be accessible at http://localhost:8000. + + • The Redis service will run on port 6379. + +2. **Stop the Services** + + To stop the running containers, use: + + ```bash + docker-compose down + ``` + +## Access the API + +Once the services are up, visit http://localhost:8000/docs to access the interactive API documentation (Swagger UI). + +## Local Development Without Docker + +If you prefer running the project locally without Docker: + +1. **Create a virtual environment** + + ```bash + python -m venv .venv + ``` + +2. **Activate virtual environment** + + ```bash + source .venv/bin/activate + ``` + +3. **Install Poetry** + + ```bash + pip install poetry + ``` + +4. **Install Dependencies** + + ```bash + poetry install + ``` + +5. **Activate Poetry shell** + + ```bash + poetry shell + ``` + +6. **Run the Application** + + ```bash + uvicorn main:app --host 0.0.0.0 --port 8000 + ``` diff --git a/apps/sdk/__init__.py b/apps/sdk/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/sdk/api/__init__.py b/apps/sdk/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/sdk/api/loan_state.py b/apps/sdk/api/loan_state.py new file mode 100644 index 00000000..82b56bba --- /dev/null +++ b/apps/sdk/api/loan_state.py @@ -0,0 +1,80 @@ +from fastapi import APIRouter, HTTPException +from fastapi import Depends +from schemas.schemas import UserLoanByWalletParams, UserLoanByWalletResponse +import json +import pandas as pd + +loan_router = APIRouter() + +def parse_json(data): + if isinstance(data, str): + try: + return json.loads(data.replace("'", '"')) + except json.JSONDecodeError: + pass + return {} + + +@loan_router.get("/loan_data_by_wallet_id", response_model=UserLoanByWalletResponse) +async def get_loans_by_wallet_id(params: UserLoanByWalletParams = Depends()): + """ + Retrieve loan data associated with a specific wallet ID. + + endpoint allows users to query their loan details by providing a + wallet ID and optional block range parameters. The response includes + information about the user's collateral, debt, and deposits across + the specified loan protocol. + + Args: + wallet_id (str): The wallet ID of the user + protocol_name (str): The name of the loan protocol + start_block (int): The start block + end_block (int): The end block + + Returns: + UserLoanByWalletResponse: User loan Information + + Raises: + HTTPException: If address is not mapped + """ + + try: + data_path = "apps/sdk/mock_data.csv" + df = pd.read_csv(data_path) + + filter_df = df[ + (df['user'] == params.wallet_id) & + (df['protocol_id'] == params.protocol_name) + ] + + if params.start_block is not None: + filter_df = filter_df[filter_df['block'] >= params.start_block] + + if params.end_block is not None: + filter_df = filter_df[filter_df['block'] <= params.end_block] + + if filter_df.empty: + raise HTTPException( + status_code=404, + detail=f"No data found for user {params.wallet_id} in protocol {params.protocol_name}" + ) + + latest_entry = filter_df.sort_values('timestamp', ascending=False).iloc[0] + + collateral = parse_json(latest_entry['collateral']) + debt = parse_json(latest_entry['debt']) + deposit = parse_json(latest_entry['deposit']) + + return UserLoanByWalletResponse( + wallet_id=params.wallet_id, + protocol_name=params.protocol_name, + collateral=collateral, + debt=debt, + deposit=deposit + ) + + except Exception as e: + raise HTTPException( + status_code=500, + detail=f"Internal server error: {str(e)}" + ) diff --git a/apps/sdk/api/user.py b/apps/sdk/api/user.py new file mode 100644 index 00000000..2388b7e0 --- /dev/null +++ b/apps/sdk/api/user.py @@ -0,0 +1,103 @@ +from typing import Dict +from pydantic import BaseModel +from fastapi import APIRouter, HTTPException, FastAPI +import pandas as pd +import json +from pathlib import Path +from sdk.schemas.schemas import UserCollateralResponse, ResponseModel + +app = FastAPI() + +file_path = "../mock_data.csv" +mock_data = pd.read_csv(file_path) + +def parse_debt_data(row): + try: + return json.loads(row) if row.strip() else {} + except json.JSONDecodeError: + return {} + +mock_data["debt"] = mock_data["debt"].apply(parse_debt_data) + + +debt_data = {} +for _, row in mock_data.iterrows(): + wallet_id = row["user"] + protocol = row["protocol_id"] + debt = row["debt"] + if wallet_id not in debt_data: + debt_data[wallet_id] = {} + debt_data[wallet_id][protocol] = debt + +@app.get("/get_user_debt", response_model=ResponseModel) +def get_user_debt(wallet_id: str, protocol_name: str): + """ + Endpoint to get the debt details of a user for a specific protocol. + + Args: + wallet_id (str): The wallet ID of the user. + protocol_name (str): The protocol name for which debt details are requested. + + Returns: + ResponseModel: The debt details including wallet ID, protocol name, and token-value pairs. + """ + wallet_data = debt_data.get(wallet_id) + debt_data = debt_data.get(protocol) + return {"wallet_id": wallet_id, "protocol": protocol, "debt": debt_data} + +router = APIRouter( + prefix="/user", + tags=["user"], + responses={404: {"description": "Not found"}}, +) + + +@router.get("/debt", response_model=UserCollateralResponse) +async def get_user_debt(wallet_id: str, protocol_name: str) -> UserCollateralResponse: + """ + Get user's collateral information for a specific protocol. + + Args: + wallet_id (str): The wallet ID of the user + protocol_name (str): The name of the protocol (e.g., 'zkLend') + + Returns: + UserCollateralResponse: User's collateral information + + Raises: + HTTPException: If user or protocol not found + """ + try: + data_path = "apps/sdk/mock_data.csv" + df = pd.read_csv(data_path) + + user_data = df[ + (df['user'] == wallet_id) & + (df['protocol_id'] == protocol_name) + ] + + if user_data.empty: + raise HTTPException( + status_code=404, + detail=f"No data found for wallet {wallet_id} in protocol {protocol_name}" + ) + latest_entry = user_data.sort_values('timestamp', ascending=False).iloc[0] + + try: + collateral = json.loads(latest_entry['collateral'].replace("'", '"')) + if not collateral: + collateral = {} + except (json.JSONDecodeError, AttributeError): + collateral = {} + + return UserCollateralResponse( + wallet_id=wallet_id, + protocol_name=protocol_name, + collateral=collateral + ) + + except Exception as e: + raise HTTPException( + status_code=500, + detail=f"Internal server error: {str(e)}" + ) \ No newline at end of file diff --git a/apps/sdk/db_connector.py b/apps/sdk/db_connector.py new file mode 100644 index 00000000..0571c654 --- /dev/null +++ b/apps/sdk/db_connector.py @@ -0,0 +1,135 @@ +import psycopg2 +from dotenv import load_dotenv +import os +import logging + +load_dotenv() + +class DBConnector: + """ + DBConnector manages PostgreSQL database connection and operations. + + Methods: + get_user_debt(protocol_id: str, wallet_id: str) -> float | None: Fetches user debt. + get_user_collateral(protocol_id: str, wallet_id: str) -> float | None: Fetches user collateral. + get_loan_state(protocol_id: str, wallet_id: str) -> str | None: Fetches loan state. + close_connection() -> None: Closes the database connection. + """ + + def __init__(self): + """ + Initializes DBConnector by connecting to the PostgreSQL database. + """ + self.host = os.getenv("DB_HOST") + self.database = os.getenv("DB_NAME") + self.user = os.getenv("DB_USER") + self.password = os.getenv("DB_PASSWORD") + self.port = os.getenv("DB_PORT") + + self.conn = None + self.cur = None + self.connect_to_db() + + def connect_to_db(self): + """ + Make connection to the PostgreSQL DB and set the connection and cursor. + + Returns: + None + """ + if self.conn is None: + try: + self.conn = psycopg2.connect( + host=self.host, + database=self.database, + user=self.user, + password=self.password, + port=self.port + ) + self.cur = self.conn.cursor() + logging.info("Connection opened successfully.") + except psycopg2.DatabaseError as e: + logging.error(f"Error while connecting to the database: {e}") + raise e + + def get_user_debt(self, protocol_id: str, wallet_id: str) -> float | None: + """ + Fetches user debt for a given protocol and wallet. + + Args: + protocol_id (str): Protocol ID. + wallet_id (str): User's wallet ID. + + Returns: + float | None: User debt if found, otherwise None. + """ + try: + sql = """ + SELECT CAST(debt->>'amount' AS FLOAT) AS debt_value + FROM mytable + WHERE protocol_id = %s AND user = %s; + """ + self.cur.execute(sql, (protocol_id, wallet_id)) + result = self.cur.fetchone() + return float(result[0]) if result and result[0] is not None else None + except (Exception, psycopg2.Error) as error: + logging.error(f"Error while fetching user debt: {error}") + raise + + def get_user_collateral(self, protocol_id: str, wallet_id: str) -> float | None: + """ + Fetches user collateral for a given protocol and wallet. + + Args: + protocol_id (str): Protocol ID. + wallet_id (str): User's wallet ID. + + Returns: + float | None: User collateral if found, otherwise None. + """ + try: + sql = """ + SELECT CAST(collateral->>'amount' AS FLOAT) AS collateral_value + FROM mytable + WHERE protocol_id = %s AND user = %s; + """ + self.cur.execute(sql, (protocol_id, wallet_id)) + result = self.cur.fetchone() + return float(result[0]) if result and result[0] is not None else None + except (Exception, psycopg2.Error) as error: + logging.error(f"Error while fetching user collateral: {error}") + raise + + def get_loan_state(self, protocol_id: str, wallet_id: str) -> str | None: + """ + Fetches user loan state for a given protocol and wallet. + + Args: + protocol_id (str): Protocol ID. + wallet_id (str): User's wallet ID. + + Returns: + str | None: User's loan state if found, otherwise None. + """ + try: + sql = """ + SELECT loan_state + FROM mytable + WHERE protocol_id = %s AND user = %s; + """ + self.cur.execute(sql, (protocol_id, wallet_id)) + result = self.cur.fetchone() + return str(result[0]) if result and result[0] is not None else None + except (Exception, psycopg2.Error) as error: + logging.error(f"Error while fetching user loan state: {error}") + raise + + def close_connection(self) -> None: + """ + Closes the database connection if open. + """ + if self.conn: + self.cur.close() + self.conn.close() + logging.info("PostgreSQL connection closed.") + diff --git a/apps/sdk/docker-compose.yaml b/apps/sdk/docker-compose.yaml new file mode 100644 index 00000000..35011528 --- /dev/null +++ b/apps/sdk/docker-compose.yaml @@ -0,0 +1,24 @@ +version: "3.8" + +services: + backend: + build: + context: . + dockerfile: Dockerfile + ports: + - "8000:8000" + environment: + - PYTHONUNBUFFERED=1 + depends_on: + - redis + + redis: + image: redis:latest + restart: always + ports: + - "6379:6379" + volumes: + - redis_data:/data + +volumes: + redis_data: \ No newline at end of file diff --git a/apps/sdk/main.py b/apps/sdk/main.py new file mode 100644 index 00000000..933590b5 --- /dev/null +++ b/apps/sdk/main.py @@ -0,0 +1,35 @@ +from fastapi import FastAPI, Request, HTTPException +from fastapi.routing import APIRouter +from slowapi import Limiter +from slowapi.util import get_remote_address +from slowapi.errors import RateLimitExceeded +from api.loan_state import loan_router +import redis + +app = FastAPI() + +version = "v1" +version_prefix = f"/api/{version}" + +redis_client = redis.Redis(host='localhost', port=6379, decode_responses=True) + +limiter = Limiter(key_func=get_remote_address, storage_uri="redis://localhost:6379") +app.state.limiter = limiter + +@app.middleware("http") +async def rate_limiter_middleware(request: Request, call_next): + try: + response = await limiter(request=request, call_next=call_next) + return response + except RateLimitExceeded: + raise HTTPException( + status_code=429, + detail="Too Many Requests. Please try again later." + ) + +app.include_router(loan_router, prefix=f"{version_prefix}/loans", tags=["loans"]) + +@app.get("/test") +@limiter.limit("5/minute") +async def test_endpoint(request: Request): + return {"message": "This is a rate-limited endpoint"} \ No newline at end of file diff --git a/apps/sdk/mock_data.csv b/apps/sdk/mock_data.csv new file mode 100644 index 00000000..b0cb379a --- /dev/null +++ b/apps/sdk/mock_data.csv @@ -0,0 +1,101 @@ +#,block,timestamp,protocol_id,user,collateral,debt,deposit,id +1,61611,1684875854,zkLend,0x0514f1498aed7c2e4499fb8a9bc216aeef4d4eed13fff28d7ff54d6d6bb01e61,"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 1715403.2769719926}",{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 1715403.2769719926}",0d5e0ecc-4a7a-41e7-9603-d5243ec44a0d +2,85603,1687169814,zkLend,0x04ac055dfef29b0c0180755dbcaefe684158b15b2677ed3d03757f896cda161a,{},{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 5.884258476915483e+16}",74ec527c-9202-4fc5-b364-862ea58bc0dd +3,61611,1684875854,zkLend,0x07f07fa75dce9ef8c1ad1af5abed35eb44505fac86eea61b2cae7169a093291c,{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 1.3047584849033636e+16}","{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 30021523.549181923}",ec994981-74f0-4a85-9c72-7762471812a7 +4,75607,1686154030,zkLend,0x007d4bf9fe59d781198af72e8b2152bb3cf127c0052f0c027b2cebef2598442b,{},{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 7999331321764696.0}",d18d8211-a805-424f-bd2a-037086a74877 +5,79607,1686494339,zkLend,0x03c64c1adff8cd145f4193c08ea9c76daabe04c860990a068f157fe9ac81e35b,{},{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 6679645.777178112}",7902334c-40d0-4b93-8af7-3a182d23650d +6,61611,1684875854,zkLend,0x062d1d2e137c260eeeddc6b78a4dd6d199ab4870ec97eeedbfb385764a5769a9,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 4375955424430207.0}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 4375955424430207.0}",c985cef2-3e27-4352-b5da-599ecc0c8f82 +7,61611,1684875854,zkLend,0x073c32c7287d1d768c008ca127aa551fa30cf87484a5e3d5740147f8a0a4330f,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 9.999776949957578e+16}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 9.999776949957578e+16}",f643a5f3-7775-4e63-ae08-4c73fb95ec51 +8,61611,1684875854,zkLend,0x02b565c74b86051102ba70caf0aa0c434284cf7ec65ede5658307b2da24dbda7,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 3096742317368203.5}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 3096742317368203.5}",94fbb184-2e6a-4908-8317-36fbdd587c11 +9,73608,1685990194,zkLend,0x03e8998a82aa1a245754530cdeaea48c02aa3cbaf9177b42ca988d9babd149b7,{},"{""0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"": -26575855.0}",{},74be2957-7de9-4705-8acb-e2143acbb773 +10,87603,1687430027,zkLend,0x016fb92417edbde99359f72e4233316407b17cd94b60066e9ced64405303f702,"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 17399350.29157371}",{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 17399350.29157371}",4cd1b4c8-7b08-427d-a797-a2b2c96750a2 +11,61611,1684875854,zkLend,0x01bf6ae2ad13308fc9bd3358cba241890bb704687bbfbf9fd4eacf92de4746da,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 121997278586476.3}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 121997278586476.3}",51c2f279-8114-4e88-aab6-6ec1b9c61fcb +12,59612,1684626391,zkLend,0x070225e5d6afc2b9132c716b434ba864e23805bb8ea48d5c972ee4a4d497833e,{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 4.9400683292592696e+16}","{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 1158195.7527528764}",6a906512-3a6a-4923-8666-6e3715a28d52 +13,59612,1684626391,zkLend,0x05c8b14d0e2ae3b68b111b0d7cd112a55682cacedc900febdb46905b0fd9d65a,{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 7.799002778451026e+16}","{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 1812655.954566153}",4321c3e7-4734-4938-8cdb-d1561673926e +14,61611,1684875854,zkLend,0x015430f5bd7573cb3ded9c326b7375f670b83e6d63baa8eee0d568e5b5c2e9c2,"{""0x00da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3"": 9.19465434558463e+17}",{},"{""0x00da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3"": 9.19465434558463e+17}",2951ea99-595d-4bc5-9aef-0d0b1af99e02 +15,61611,1684875854,zkLend,0x03eb266094d13d11b7b19e3d777c88dcc75b2f364cc0d3417bd3042634f6821f,"{""0x00da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3"": 9.940216017490213e+17}",{},"{""0x00da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3"": 9.940216017490213e+17}",f5a81600-1cb2-4ade-87be-168d541c0cac +16,61611,1684875854,zkLend,0x0509dbfb262e61307fff8890615f7fd4bdb6e0fc204fef680effac2c80ab84ff,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 0.0}","{""0x0000000000000000000000000000000000000000000000000597c6c5391e3de9"": -4.17404284944524e+75}",8d323231-31de-44f8-a783-ff0c7d2f7b8b +17,61611,1684875854,zkLend,0x04b57d1a217b75610cd7808b2feeac8737aff413745b20970ba617ae1bdc94ae,{},{},"{""0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"": 26977936.716226805}",1f964880-9ba2-4cb8-b0f4-6f8de0c345af +18,59612,1684626391,zkLend,0x06db5e5560de4a1d209bdfdb7159078686fa2d5e3275798c13ff285430f3096b,{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": -2.168290301468851e+16}","{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 1549423.9894622408}",7c8c705c-3473-4d72-84c6-fcbdd0c669da +19,61611,1684875854,zkLend,0x016fe69358a1344f1004d7629c5f033a2995b6135db32ef1a6cd75bc853caba8,"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 238871.82836611467}",{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 238871.82836611467}",a274dbb1-02f9-420f-b2ba-cddb6af67c3e +20,75607,1686154030,zkLend,0x0205658da74b2534f8123976bb5bc766d7df2be75c2dd14d477bca3add0d3d0f,{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": -6891083896654415.0}","{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 1.0499121236159634e+16}",ac3da125-d9ff-4938-adcc-44fe2ecfb1f3 +21,81604,1686676010,zkLend,0x02896582232078fdc016f3e958ca108c585ce57ff13a931397bbc37f158ef178,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 4.999490626755813e+16}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 4.999490626755813e+16}",d9ba85c3-bed8-4ca7-b804-8f6652013284 +22,87603,1687430027,zkLend,0x067a37bd5fe7b63a3d545ff007db4dd40d39150fbb8e17459438d95a64f685fb,{},{},"{""0x000000000000000000000000000000000000000000000000063f9d7202c633e3"": -4.17404284944524e+75}",cf05f596-1519-4206-bfb8-a6ec78c13d4b +23,135603,1690724042,zkLend,0x02f5d193a9e31053cf409ae820e4ecc73fb2fd1b0b66ee58cb8a781d93a0f3cd,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 3599196522409003.5}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 3599196522409003.5}",8e93c8fb-3e30-4035-a44b-2e52f343c667 +24,75607,1686154030,zkLend,0x062b15e65a1e5b1b65b5f15570cde669aa02b17e606ebc667e0309bc59253bcf,{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": -7191478304156717.0}","{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 8999247422305284.0}",06d29c7a-7092-435a-9b1d-139aea200257 +25,85603,1687169814,zkLend,0x05f61d5f1b5b4f485fc6e355c6b46137c94136f70d7c7a0446ac90802442c0dd,{},{},{},8c507f02-e178-46f3-8a49-24466975345d +26,63610,1685027240,zkLend,0x02db1a8d576b5cac866b43c11b44f4ee8150106b1fce46567fd543503d09a5f0,{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 33551092978967.0}","{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 59997974840308.484}",8aea2404-0444-4bca-b920-51f170f03441 +27,57612,1684449503,zkLend,0x040d3d9bc588b40cb0e5b97e7adea134e4dea32bc0dcc08ff4e84442169871fa,{},{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 34121.349716157674}",8b7d1a45-f446-4ff6-9554-29350a1ea056 +28,59612,1684626391,zkLend,0x01566778cea6c27771b15cb0c00d75a75f710365fc231b6a12d2d61829a5c89f,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 16313997.0}",{},4ecf198c-cbec-4b0f-aaab-bb6debee4f7c +29,59612,1684626391,zkLend,0x02b549ef65b26e236ff8d6a9c988cd077fa311358d5b746334039afaee85d783,"{""0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"": 21528997.28861783}",{},"{""0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"": 21528997.28861783}",4470548e-9d9e-47a3-8449-103bd82f651a +30,59612,1684626391,zkLend,0x07e522ac331392f1524aa8f89bedaaabb500471811f8fb68af3e1d11a87a9104,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 421897988767450.3}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 421897988767450.3}",388913f8-3d7c-43fa-a67a-9ea376f3057f +31,59612,1684626391,zkLend,0x01c8a4c7661db38b59f10ab88e8e25fd07fbf9e14db1b33120402d46e388bd5d,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 3105618594106283.0}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 3105618594106283.0}",7fdecead-434f-4790-8866-14a4dff9cdba +32,59612,1684626391,zkLend,0x0022b064455c3c84f21d610bad448789f2a061b3de3473661d6ef6e4dcef5146,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 16628991.0}",{},0c3683ed-44b1-4994-bb70-405f185436ba +33,59612,1684626391,zkLend,0x014141ec501f93ad22a74b0fc9dff2deb033aea912de39a2ff8dd4a09d4856ad,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 16501746.0}",{},b720d27d-5b37-45f0-888c-384553f2f81e +34,59612,1684626391,zkLend,0x03d01c93c5fc60caa1b57acceb367e1593a87fefa08e96b7ff7c0a9f4bc1abf8,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 1542760.0}",{},1b0fed5c-c7aa-4e4c-85c2-3c9ec6c9f2f5 +35,59612,1684626391,zkLend,0x05adc8f6f70ab519656ac6a3e0720b68f8cbfe50a656ab50bc8995511cb6754a,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 113998047788280.95}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 113998047788280.95}",0b829831-069c-40ec-89f4-eb93b570c21a +36,59612,1684626391,zkLend,0x0075dd0ffe9ae2b5e9d1aa19daf1ecd1e99e7d6898eeea5add935c9b0b478eb1,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": -32327959.0}","{""0x0000000000000000000000000000000000000000000000000069c51c3a65516c"": -4.17404284944524e+75}",22814196-6193-416d-b533-c870859240f6 +37,59612,1684626391,zkLend,0x00b12ca9e6eeb969b2295747a9750ff0c3c7a50595ff3fad307ed89ecffa053c,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 7244059641688963.0}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 7244059641688963.0}",8509fbd1-c66f-4b1e-a91a-834a3379e958 +38,59612,1684626391,zkLend,0x06b3c97638130ecdd9bb396c1e8c59253762d37bd878c4d15fd82f9c6ea4c340,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 15017449.0}",{},87f45bf1-195b-42b3-92da-2bc0aefdaeb1 +39,59612,1684626391,zkLend,0x03e6f95dd0c1dad7dbc00df06b3f98ea0b70c3b7213dbee8c4795ef5f1ee19c8,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 9999828698286534.0}","{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 109983.0}","{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 9999828698286534.0, ""0x0000000000000000000000000000000000000000000000000023411618fa1a69"": -4.17404284944524e+75}",0ff2fc2e-32a2-4c3a-baff-94a258bd837d +40,59612,1684626391,zkLend,0x006d14f1dcf6c0173703a1f127757efad5fbb05b8e9d7c09b8408829215da91e,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 999982869828653.5}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 999982869828653.5}",885d2d2e-35f4-45ea-8a9f-cb29e6b99bbd +41,59612,1684626391,zkLend,0x009166e5afbea54582335ee704e2887faaf96c1d2581b9f70ad562c17b4c7904,"{""0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"": 15550683.806861384}",{},"{""0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"": 15550683.806861384, ""0x0000000000000000000000000000000000000000000000000000000000e3da74"": -5.9343481008916564e+75}",67c8fb3a-cc7b-405a-8b0b-e049092e4a8c +42,59612,1684626391,zkLend,0x0783b1c91fb8602203ef9e4b8b5a2f76d981427c33b60e03a88ebb3c2a8e5414,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 999982867721933.0}","{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 724662.0}","{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 999982867721933.0}",6843a6e5-d58d-47a7-b7d1-49d53217a640 +43,61611,1684875854,zkLend,0x06af64e8f8c190cb02427761fca3c47c6c210b38105bde5f8dfe8687ef59bea8,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 122997255171472.03}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 122997255171472.03}",4a8a5e09-9339-436a-b37f-a481a7c830b3 +44,61611,1684875854,zkLend,0x05edab6a2f2ec49b060a5a2c3b585f519b3aed55cd43c6feccac73338cdb4833,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 499988842160455.44}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 499988842160455.44}",62e5c5f2-ded9-4130-8988-35f3dbd03481 +45,59612,1684626391,zkLend,0x004dcf79146caaae58163bc3679ff19653a54f28c0dbff3b48f6cfb32376f338,{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 2.684984147071774e+16}","{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 625961.9187331528}",2ea8913c-03bd-4025-a13a-db250e00e929 +46,61611,1684875854,zkLend,0x03c9cbff880281a3457f580f667e78e3e8a9277741273b5979cca28469ea3bde,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 519988395048107.06}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 519988395048107.06}",2d3602af-0f8e-4ed4-81e3-d573b33f257d +47,61611,1684875854,zkLend,0x0043cc9f78a97ee58a67faaf7558a6113f3ded96899909fee140c382d300b922,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 1.2629718133572292e+16}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 1.2629718133572292e+16, ""0x000000000000000000000000000000000000000000000000002cddc3559d4400"": -4.17404284944524e+75}",71868ee5-4908-460d-b658-7006c142913d +48,61611,1684875854,zkLend,0x04ec9f751c4f16279a56afcd2a834009e395d611d3d45c7b0634cc95e1167883,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 1.6199638427682366e+16}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 1.6199638427682366e+16, ""0x00000000000000000000000000000000000000000000000000398bfec4126000"": -4.17404284944524e+75}",543bd4d9-b42c-432e-9034-3141fa6ad077 +49,61611,1684875854,zkLend,0x04149d3dc55c5547130a952c843143ad06c8b9975b93fdde67fe5d02da7f8ca3,"{""0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"": 11807102.361621851}","{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 0.0}","{""0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"": 11807102.361621851, ""0x0000000000000000000000000000000000000000000000000000000000b43858"": -5.9343481008916564e+75}",005967ef-576c-4af1-aa83-d6c53f023c37 +50,61611,1684875854,zkLend,0x01c0b416dc591d6869b1a843c7c2ccd9e1f3d132915d2424ff9e99a76374b2a1,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 529988169655133.4}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 529988169655133.4}",68fbc104-a05f-4b90-b0c8-2efa962bb215 +51,61611,1684875854,zkLend,0x0414612b30a448d7547b8e307c08380d80256d24ba32ef13fbf3004b8d1c0127,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 1733777018152083.2}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 1733777018152083.2}",38202a9d-961d-4597-9c99-b78408e79b13 +52,61611,1684875854,zkLend,0x034f10f63672626fb1719ff31b948d0f98f1605fbd79889749133992ea2982ae,"{""0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac"": 0.0, ""0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"": 0.0, ""0x00da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3"": 0.0, ""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 0.0, ""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 0.0}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 0.0, ""0x00da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3"": 0.0, ""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 0.0, ""0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac"": 0.0, ""0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"": 0.0}",d5bc36e0-8e71-47d0-bdc1-cfa7f81673d8 +53,61611,1684875854,zkLend,0x047995626ddd0043523da0a25d6fc7b6cb9700e168c9288e28bef204cd2065b6,{},{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 4999888413924106.0}",863e2810-91da-4ceb-a8e7-053f4b8855ed +54,85603,1687169814,zkLend,0x060d4b837062a60a5668f44bda5abf329cee185fccbb1e72f96e0914f21f2499,{},{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 99962856.01549998}",4c27b1d6-a112-4c15-b6ea-5ec664ab67ba +55,59612,1684626391,zkLend,0x0212072728239a357fe4ecbbf24537322915d64d93b03e20b4d2f06b88d9b761,{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": -1.588599229238132e+16}","{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 1464686.094966671}",9de887cc-2899-42e3-a49b-13e1c6e7e1bd +56,61611,1684875854,zkLend,0x006dafad566e06f656027bb46c32ad4b065777905eca7cda5a890bc77b9051dc,{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": -2719947840475234.0}","{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 88570006.7596581}",75905a23-f707-4f48-95eb-e1e649d2e232 +57,81604,1686676010,zkLend,0x028ad0710469c4fd6167a44a1192e785155bedfbc8fd6bb45b77eb4e9b3d552d,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 4.324884341693242e+16}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 4.324884341693242e+16}",2ab2f622-65a2-46d9-b93d-8ab4063579ce +58,87603,1687430027,zkLend,0x02094e9e2f307b938c6e0317b6ad726c4cf7120ea23490bfa8a6bba1f5876f17,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 0.0}","{""0x00000000000000000000000000000000000000000000000000a87b1a04d55168"": -4.17404284944524e+75}",4d508da1-e12b-4fc4-a9c1-44f66a84c401 +59,81604,1686676010,zkLend,0x033878d42af615430f94b209b4050a6c0bfbeb695053fd8ef84ab637fff74b7c,{},{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 2040601247872122.0}",58668808-f885-4166-9a54-3d18432a999f +60,61611,1684875854,zkLend,0x0146664998753c30c70f1ed76ee23c8dbc35b2b19f6d58962df5c0bce38a177a,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 3.499921865449341e+16}","{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 0.0}","{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 3.499921865449341e+16}",73736b5b-0e92-4c16-b917-7a5cf75bf73d +61,61611,1684875854,zkLend,0x03f2985089e790e1550eaa5a02068bbc188c79720ee513670d1d47234020d4aa,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 35999196261683.39}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 35999196261683.39}",090d5ce8-5616-4b3a-a5d1-734e1add5a96 +62,59612,1684626391,zkLend,0x07bab7ba876ed300ddec11bde9811ed2978bf1df6574b2e688e466a48c87e335,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": -7960037.0}",{},5480a9ba-2e07-4f9f-9c9a-835a0cfa138a +63,99603,1688846108,zkLend,0x072d236cb5708128eb07a35bcd1d41d58aea48f2a46240f1806f1bd5b7b9250d,"{""0x00da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3"": 1.0562186673181542e+19}",{},"{""0x00da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3"": 1.0562186673181542e+19}",85388237-f72c-4b04-bcba-b9d948793aa3 +64,85603,1687169814,zkLend,0x0621ef3c36b0cb9643b0262a13838f0b7039595a291e53702266750cc2787e0d,"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 867954252.1936822}",{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 867954252.1936822, ""0x0000000000000000000000000000000000000000000000000000000033c13ddd"": -4.737153647675251e+75}",11bfea8f-8948-4884-a537-bdc554d8af5c +65,59612,1684626391,zkLend,0x028d0cb0f87e7aea15064d6d35bc8282279e4c5e2f9800c252a00001dd61735d,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 15130115.0}",{},0ec3a5d6-e1e1-48ad-90dc-82ecc2467024 +66,59612,1684626391,zkLend,0x0577d9adbe6691d59b8cff768ab5d4bce8bd0a7aac9ecfd8dcdbcf608d16c491,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 19321118.0}",{},95f0b4f9-b294-48fa-a68c-2fb0058cfec6 +67,59612,1684626391,zkLend,0x034a7e18986ac4a34c6ea3dd7dd5fdb4669c65f9d7cc9a552aa5de827e1f015b,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 18833276.0}",{},368a5df1-100b-4f4a-a079-864c51b8c5d1 +68,59612,1684626391,zkLend,0x0317a4a079a276fb4c19c7378f5c8ca42687953c3d6c563b79effdbbde77761b,"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 9999771.125805598}","{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 7997828.0}","{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 9999771.125805598}",fd490e04-843f-45fa-9d45-4a7042a0e34b +69,59612,1684626391,zkLend,0x05ed35e61326179c0d5257fae96b6b05c3c9b917dd0efba42cb4cad07f331e39,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 15147344.0}",{},21534864-c87f-429b-91c3-538ce9e1443c +70,59612,1684626391,zkLend,0x06583ac0c8b0f92fb1032d780f16bdeae7daa1168d4137a88fb69b790c4390f6,"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 26999381.969801985}",{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 26999381.969801985}",ad01e5ce-0aa6-4fc7-9e59-d8af27b29c8f +71,59612,1684626391,zkLend,0x0061465a045190922d8570ce72fe84df6d39df85a1401ff5ba61ebdded84da97,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 23935597.0}",{},6266a724-f0f4-4b04-9297-803da1004bba +72,59612,1684626391,zkLend,0x07246a11843495bebfb7ac518aae9b33d5a5059babaec8598363fd0fab22f336,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 23934067.0}",{},3cf881af-e081-4785-b49a-14493e27fc31 +73,59612,1684626391,zkLend,0x008bf411b3380bdff6ba14f4c077308c2a9709fbd7477148397352a50f3950c8,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 395993108368267.1}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 395993108368267.1}",1c6be2bb-6297-456a-b760-c396e7bd8299 +74,59612,1684626391,zkLend,0x06b584d232673b6f4e4967f2be78bb4ee449d9efa5cd59f5e545f26db8f1cb5a,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 16030297.0}",{},1251c19c-232a-4319-94f6-80e356735041 +75,59612,1684626391,zkLend,0x07ea1afdb478c6eaddafd4e1f2b801530023347ac502a4f385d92fb6e27a1d9d,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 5999895581337381.0}","{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 2399139291027880.0}","{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 5999895581337381.0}",68be826e-5d95-46ca-a520-142fc652651e +76,59612,1684626391,zkLend,0x0469ff606007d5ac037a70b9f23c5d0fdb37a1b48fc2ebe08046c89e7374e7b6,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 18654642.0}",{},7554931f-8ec1-41a9-beb6-dec0cf642bc7 +77,59612,1684626391,zkLend,0x07f750eef7be4cc707117ba4165ad566297d5f3c2e5053699ae83ed2eb8c7045,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 18087631.0}",{},c578d31f-1cd2-4f43-9beb-66d380113d7a +78,59612,1684626391,zkLend,0x0793aad8330464de5f17be42934c6b3ac289bac0d43f12ed394f0354174d437c,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 22085281.0}",{},eb46ce57-1b4e-4b18-aa22-e2fb55ac641a +79,59612,1684626391,zkLend,0x0771e8852b0447fde64b555e9f6d88718ce599dc723346ff472b73a1012ea8ea,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 14868287.0}",{},027b37f1-c6d5-4717-ab8a-19e9cae8e18f +80,59612,1684626391,zkLend,0x071cd9ea331c000f5bab56085a95f1a835aa5fd947e4eff1656dedd7239a17f0,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 5999895557545727.0}","{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 2447122067144755.0}","{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 5999895557545727.0}",2d3799c8-ff1f-496b-b7c5-1d897fd049f9 +81,59612,1684626391,zkLend,0x01f2d930571648e273cf6f8627128d02d7779686c5e857b311a3fb97fb6ff157,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 5999895557545727.0}","{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 2351156429049209.0}","{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 5999895557545727.0}",b1d0e3a1-abb0-40de-97d2-991fab8fbf4e +82,59612,1684626391,zkLend,0x02ae87a4e4b863541bdbf2045c7f30de2e8d003d368c614e33979e5e50262c41,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 18538692.0}",{},a7557695-8065-430b-b525-bf96f4c3f9bb +83,59612,1684626391,zkLend,0x069b20add092b89c0bb8cf28d29f7a01fe61de98696514ebd95c9afcdcebc06a,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 18147943.0}",{},d6172bc1-5eed-4adc-81f3-0984e2a29c44 +84,59612,1684626391,zkLend,0x054b8a0dc79bc5a7ac25fdfac76ae68c386766a37b29f637feeaa86f60a09154,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 1779560.0}",{},80792a17-01f9-4e24-a389-803993f86925 +85,59612,1684626391,zkLend,0x076c57e5adcfcaea1d98e1e74071c6d7d5305b4edcff5d7437e151067a3c2f19,{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 15058699.0}",{},dc686e70-b899-4dac-aced-dde05425db45 +86,59612,1684626391,zkLend,0x0726ac02881f36637857d3e672e30dfe653c35941d5bbf4d458d99dbdcb47a1d,{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 3.382691600687096e+16}","{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 822071.473575848}",93deb0b5-8a9f-4c48-a5bb-38e55c2a9b3f +87,61611,1684875854,zkLend,0x02102e19e847ab312701ae853ddf7d65570346c8aebbdaf85f0c7f3b8f7272b6,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 1.6732027324039864e+16}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 1.6732027324039864e+16}",d2c95d54-25af-4837-ae0d-21383480d2c6 +88,61611,1684875854,zkLend,0x030d0b091fceea6ddc993ecd862f5e94cfb0e329a773f8cbdfd49a19c80f8d59,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 123997231358727.6}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 123997231358727.6}",6b2ecb17-a7b3-4a61-8013-7aa8ce090a55 +89,61611,1684875854,zkLend,0x05c191ee3b004f0dd3c0f534ceb3dabc22401d2dde4ed2e6c292b1c36fc4283d,"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 51998001.02119159}",{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 51998001.02119159, ""0x00000000000000000000000000000000000000000000000000000000031960af"": -4.737153647675251e+75}",a139bfb3-761a-4b0d-b8de-3baae7603d8d +90,61611,1684875854,zkLend,0x06adddf42b87a1cfca6ce8612ae75adb34c3a4f5f18d7e11ff182f6c33391293,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 529988165082026.7}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 529988165082026.7}",593ad2fa-67b7-4c78-bc78-a92d47c9ce2e +91,61611,1684875854,zkLend,0x020b3815f18bc43b981b0727a09a0e54a04c067a03a5c2dac535d2644ffcce28,"{""0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"": 59575.86155839251}","{""0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"": 20862.0}","{""0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"": 59575.86155839251}",afad3f1c-b929-409c-8f6b-ab2f649ad26f +92,61611,1684875854,zkLend,0x018e81504fedef74ab1a6c7dd1333041531c5dacbd4853e3d490c0f8ee93e3b1,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 584814278997958.8}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 584814278997958.8}",e2eca423-eddd-40de-a660-939fb251f5c1 +93,61611,1684875854,zkLend,0x03967e284f9870e6b8870f5ed4967a4731c3e80e6a8f3852f32efc7bd85a729b,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 119997319904692.12}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 119997319904692.12}",78872b4e-cfe6-4cbf-a08e-f464128a7e0b +94,61611,1684875854,zkLend,0x05b45afebd840f23c11db25650a6fd96e575b2d8235e8bff378534977bee48ae,{},"{""0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"": 20484268.0}",{},7f954f73-8046-4735-9f00-bd133faf5ce1 +95,79607,1686494339,zkLend,0x065180ed76f151e811b5a82ed2e9ada77793d033d29c94b92fb0c16f6f278f1d,{},{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 1317233289293662.0}",4a45578b-00f6-4559-a545-2f68f2e020ce +96,61611,1684875854,zkLend,0x07c00cbe1999de4ffe94ade82174dd4057788281169d3e19b1e68a01683f1429,"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 26762449.047038414, ""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 3720041396012704.5}",{},"{""0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"": 26762449.047038414, ""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 3720041396012704.5}",6373c0fa-0cad-4829-ba18-e32839a9802f +97,61611,1684875854,zkLend,0x022834e46f112471d426834494feea67a29096acf18b20670392b16397e3bc61,"{""0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"": 500414.83294993953}","{""0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"": 79655.0}","{""0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"": 500414.83294993953}",5cd43cef-2c78-48c1-99d8-f10c30c146d1 +98,61611,1684875854,zkLend,0x037b63c8137434b093c0f0d442422a9b999708de645e3b41c39ef41d697773ed,"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 519988385181088.0}",{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 519988385181088.0}",b6f6e652-2dec-4ee6-9cb0-931dd4862956 +99,73608,1685990194,zkLend,0x05f21e7acf61eac8209d6ea75f6fa1a977dc4e32674b72639b5b9ac36ac2154c,{},{},"{""0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"": 999924789753291.0}",88ed2bbf-9474-4ca9-b9d5-967cea385b11 +100,61611,1684875854,zkLend,0x047f8aa321cdb3ef5defcc2f4884aeecddd47f7daaf0fbd2e4a978fef3dfaac4,"{""0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"": 13994094.60013445}","{""0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"": 0.0}","{""0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8"": 13994094.60013445, ""0x0000000000000000000000000000000000000000000000000000000000d59a18"": -5.9343481008916564e+75}",28496cf1-687e-4989-ac8f-8856c14ba600 diff --git a/apps/sdk/poetry.lock b/apps/sdk/poetry.lock new file mode 100644 index 00000000..3dfee738 --- /dev/null +++ b/apps/sdk/poetry.lock @@ -0,0 +1,420 @@ +# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +@@ -17,6 +18,7 @@ version = "4.8.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, + {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, +@@ -32,12 +34,27 @@ doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] +trio = ["trio (>=0.26.1)"] + +[[package]] +name = "async-timeout" +version = "5.0.1" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_full_version < \"3.11.3\"" +files = [ + {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, + {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, +] + +[[package]] +name = "backports-tarfile" +version = "1.2.0" +description = "Backport of CPython tarfile module" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and python_version < \"3.12\"" +files = [ + {file = "backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34"}, + {file = "backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991"}, +@@ -53,6 +70,7 @@ version = "2024.12.14" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, + {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, +@@ -64,6 +82,8 @@ version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\" and platform_python_implementation != \"PyPy\"" +files = [ + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, +@@ -143,6 +163,7 @@ version = "3.4.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, +@@ -244,6 +265,7 @@ version = "8.1.8" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, + {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, +@@ -258,6 +280,8 @@ version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main"] +markers = "platform_system == \"Windows\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +@@ -269,6 +293,8 @@ version = "44.0.0" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = "!=3.9.0,!=3.9.1,>=3.7" +groups = ["main"] +markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\"" +files = [ + {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"}, +@@ -318,6 +344,7 @@ version = "0.21.2" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, + {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, +@@ -329,6 +356,7 @@ version = "0.115.6" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "fastapi-0.115.6-py3-none-any.whl", hash = "sha256:e9240b29e36fa8f4bb7290316988e90c381e5092e0cbe84e7818cc3713bcf305"}, + {file = "fastapi-0.115.6.tar.gz", hash = "sha256:9ec46f7addc14ea472958a96aae5b5de65f39721a46aaf5705c480d9a8b76654"}, +@@ -349,6 +377,7 @@ version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +@@ -360,6 +389,7 @@ version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +@@ -374,6 +404,8 @@ version = "8.6.1" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and python_version < \"3.12\"" +files = [ + {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, + {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, +@@ -397,6 +429,8 @@ version = "3.4.0" +description = "Utility functions for Python class constructs" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\"" +files = [ + {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, + {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, +@@ -415,6 +449,8 @@ version = "6.0.1" +description = "Useful decorators and context managers" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\"" +files = [ + {file = "jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4"}, + {file = "jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3"}, +@@ -433,6 +469,8 @@ version = "4.1.0" +description = "Functools like those found in stdlib" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\"" +files = [ + {file = "jaraco.functools-4.1.0-py3-none-any.whl", hash = "sha256:ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649"}, + {file = "jaraco_functools-4.1.0.tar.gz", hash = "sha256:70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d"}, +@@ -455,6 +493,8 @@ version = "0.8.0" +description = "Low-level, pure Python DBus protocol wrapper." +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\"" +files = [ + {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, + {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, +@@ -470,6 +510,8 @@ version = "25.6.0" +description = "Store and access your passwords safely." +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\"" +files = [ + {file = "keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd"}, + {file = "keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66"}, +@@ -499,6 +541,7 @@ version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +@@ -523,6 +566,7 @@ version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +@@ -534,6 +578,8 @@ version = "10.6.0" +description = "More routines for operating on iterables, beyond itertools" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\"" +files = [ + {file = "more-itertools-10.6.0.tar.gz", hash = "sha256:2cd7fad1009c31cc9fb6a035108509e6547547a7a738374f10bd49a09eb3ee3b"}, + {file = "more_itertools-10.6.0-py3-none-any.whl", hash = "sha256:6eb054cb4b6db1473f6e15fcc676a08e4732548acd47c708f0e179c2c7c01e89"}, +@@ -545,6 +591,7 @@ version = "0.2.20" +description = "Python binding to Ammonia HTML sanitizer Rust crate" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "nh3-0.2.20-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e1061a4ab6681f6bdf72b110eea0c4e1379d57c9de937db3be4202f7ad6043db"}, + {file = "nh3-0.2.20-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb4254b1dac4a1ee49919a5b3f1caf9803ea8dada1816d9e8289e63d3cd0dd9a"}, +@@ -578,6 +625,7 @@ version = "24.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, +@@ -589,6 +637,7 @@ version = "1.12.0" +description = "Query metadata from sdists / bdists / installed packages." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pkginfo-1.12.0-py3-none-any.whl", hash = "sha256:dcd589c9be4da8973eceffa247733c144812759aa67eaf4bbf97016a02f39088"}, + {file = "pkginfo-1.12.0.tar.gz", hash = "sha256:8ad91a0445a036782b9366ef8b8c2c50291f83a553478ba8580c73d3215700cf"}, +@@ -603,6 +652,8 @@ version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\" and platform_python_implementation != \"PyPy\"" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +@@ -614,6 +665,7 @@ version = "2.10.5" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pydantic-2.10.5-py3-none-any.whl", hash = "sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53"}, + {file = "pydantic-2.10.5.tar.gz", hash = "sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff"}, +@@ -634,6 +686,7 @@ version = "2.27.2" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, +@@ -746,6 +799,7 @@ version = "2.19.1" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, + {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, +@@ -760,6 +814,8 @@ version = "0.2.3" +description = "A (partial) reimplementation of pywin32 using ctypes/cffi" +optional = false +python-versions = ">=3.6" +groups = ["main"] +markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"win32\"" +files = [ + {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, + {file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"}, +@@ -771,6 +827,7 @@ version = "44.0" +description = "readme_renderer is a library for rendering readme descriptions for Warehouse" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "readme_renderer-44.0-py3-none-any.whl", hash = "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151"}, + {file = "readme_renderer-44.0.tar.gz", hash = "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1"}, +] + +[package.dependencies] +docutils = ">=0.21.2" +nh3 = ">=0.2.14" +Pygments = ">=2.5.1" + +[package.extras] +md = ["cmarkgfm (>=0.8.0)"] + +[[package]] +name = "redis" +version = "5.2.1" +description = "Python client for Redis database and key-value store" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4"}, + {file = "redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f"}, +] + +[package.dependencies] +async-timeout = {version = ">=4.0.3", markers = "python_full_version < \"3.11.3\""} + +[package.extras] +hiredis = ["hiredis (>=3.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==23.2.1)", "requests (>=2.31.0)"] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +@@ -811,6 +888,7 @@ version = "1.0.0" +description = "A utility belt for advanced users of python-requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] +files = [ + {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, + {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, +@@ -825,6 +903,7 @@ version = "2.0.0" +description = "Validating URI References per RFC 3986" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, + {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, +@@ -839,6 +918,7 @@ version = "13.9.4" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.8.0" +groups = ["main"] +files = [ + {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, + {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, +@@ -857,6 +937,8 @@ version = "3.3.3" +description = "Python bindings to FreeDesktop.org Secret Service API" +optional = false +python-versions = ">=3.6" +groups = ["main"] +markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\"" +files = [ + {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, + {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, +@@ -872,6 +954,7 @@ version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +@@ -883,6 +966,7 @@ version = "0.41.3" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7"}, + {file = "starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835"}, +@@ -900,6 +984,7 @@ version = "6.0.1" +description = "Collection of utilities for publishing packages on PyPI" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "twine-6.0.1-py3-none-any.whl", hash = "sha256:9c6025b203b51521d53e200f4a08b116dee7500a38591668c6a6033117bdc218"}, + {file = "twine-6.0.1.tar.gz", hash = "sha256:36158b09df5406e1c9c1fb8edb24fc2be387709443e7376689b938531582ee27"}, +@@ -925,6 +1010,7 @@ version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +@@ -936,6 +1022,7 @@ version = "2.3.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, + {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, +@@ -953,6 +1040,7 @@ version = "0.34.0" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4"}, + {file = "uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9"}, +@@ -971,6 +1059,8 @@ version = "3.21.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and python_version < \"3.12\"" +files = [ + {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, + {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, +@@ -985,6 +1075,6 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", +type = ["pytest-mypy"] + +[metadata] +lock-version = "2.1" +python-versions = "^3.11" +content-hash = "252bb6ac9d035a43b7db91ecd0d1131ce793fb9c42fba70e125bf0a5861416ba" diff --git a/apps/sdk/pyproject.toml b/apps/sdk/pyproject.toml new file mode 100644 index 00000000..06f28bbc --- /dev/null +++ b/apps/sdk/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "derisk sdk" +version = "0.1.0" +description = "" +authors = ["djeck1432 "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.11" +twine = "^6.0.1" +fastapi = "^0.115.6" +uvicorn = "^0.34.0" +pydantic = "^2.10.5" +redis = "^5.0.0" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/apps/sdk/schemas/__init__.py b/apps/sdk/schemas/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/sdk/schemas/schemas.py b/apps/sdk/schemas/schemas.py new file mode 100644 index 00000000..4359217b --- /dev/null +++ b/apps/sdk/schemas/schemas.py @@ -0,0 +1,52 @@ +from pydantic import BaseModel +from typing import Dict, Optional + + +class UserLoanByWalletParams(BaseModel): + """ + Data model representing the parameters required to query a user's loan details by wallet ID. + + Attributes: + protocol_name: The name of the loan protocol (e.g., zkLend, Nostra). + wallet_id: The unique identifier of the user's wallet address. + start_block: The starting block number to filter loan data (inclusive). + end_block: The ending block number to filter loan data (inclusive). + """ + protocol_name: str + wallet_id: str + start_block: Optional[int] + end_block: Optional[int] + + +class UserLoanByWalletResponse(BaseModel): + """ + Data model representing the response for user loan details by wallet ID. + + Attributes: + wallet_id: The unique identifier of the user's wallet address. + collateral: A dictionary mapping token addresses to collateral values. + debt: A dictionary mapping token addresses to debt values. + deposit: A dictionary mapping token addresses to deposit values. + """ + wallet_id: str + collateral: Dict[str, str] + debt: Dict[str, str] + deposit: Dict[str, str] + + +class UserCollateralResponse(BaseModel): + """ Base class for UserCollateralResponse + + Attributes: + wallet_id: The unique identifier of the user's wallet address. + protocol_name: The name of the loan protocol (e.g., zkLend, Nostra). + collateral: A dictionary mapping token addresses to collateral values. + """ + wallet_id: str + protocol_name: str + collateral: Dict[str, float] + +class ResponseModel(BaseModel): + wallet_id: str + protocol_name: str + debt: Dict[str, float] \ No newline at end of file