75 Commits
Author SHA1 Message Date
discountry d80518853e Merge branch 'main' into dev 2026-02-27 10:51:05 +08:00
discountry 422ee6f465 update maker points config 2026-02-08 09:41:06 +08:00
discountry bee7bdd8fe update maker points 2026-02-08 09:37:39 +08:00
discountry 3f67b99291 Add immediate reprice logic to MakerPointsEngine
- Enhanced the MakerPointsEngine by introducing a new method `shouldTriggerImmediateReprice` to trigger an immediate tick when the market depth deviates beyond a specified minimum reprice basis points threshold.
- Updated the existing depth protection logic to include this new reprice condition.
- Added a comprehensive test suite to validate the immediate reprice functionality and its integration with the MakerPoints engine.
2026-02-08 00:08:38 +08:00
discountry 61e6e4cdde Add Binance depth monitoring configuration to MakerPoints
- Introduced new environment variables `MAKER_POINTS_BINANCE_DEPTH_WINDOW_BPS` and `MAKER_POINTS_BINANCE_DEPTH_IMBALANCE_RATIO` to configure Binance depth monitoring.
- Updated `MakerPointsConfig` interface and implementation to include these new parameters.
- Enhanced translations to reflect dynamic depth window information in the UI.
- Added tests to validate the new configuration options and their integration into the MakerPoints engine.
2026-02-07 23:58:01 +08:00
discountry 858b2f304b Update imbalance ratio in MakerPointsEngine and binance-depth.ts for improved trading strategy
- Adjusted the `ratio` in `MakerPointsEngine` from 8 to 2 to better align with current market conditions.
- Updated `DEFAULT_IMBALANCE_RATIO` in `binance-depth.ts` from 8 to 2 to maintain consistency across the trading strategy.
2026-02-07 23:35:22 +08:00
discountry 6998afebb1 Refactor price calculation in MakerPointsEngine for improved accuracy
- Replaced direct price calculations with a new method `normalizeDepthTargetPrice` to ensure valid target prices for buy and sell orders.
- Updated all instances of price calculations in the MakerPointsEngine to utilize the new normalization method.
- Added boundary tests for `getDepthBetweenPrices` to validate behavior when prices are exactly at the target.
2026-02-07 23:27:42 +08:00
discountry b8942c18e6 Add immediate depth protection logic to MakerPointsEngine
- Introduced a new private property `forceTickRequested` to manage immediate tick requests.
- Implemented `shouldTriggerImmediateDepthProtection` method to trigger a tick when depth falls below the configured threshold.
- Updated the tick processing logic to accommodate immediate depth protection.
- Added unit tests to validate the immediate tick triggering behavior based on depth changes.
2026-02-07 23:10:56 +08:00
discountry 6f85e609f8 Update minimum depth threshold and imbalance ratio for improved trading performance
- Changed `filterMinDepth` in `MakerPointsConfig` from 5 to 10 to enhance trading strategy.
- Adjusted `ratio` in `MakerPointsEngine` and `DEFAULT_IMBALANCE_RATIO` in `binance-depth.ts` from 9 to 8 for better alignment with market conditions.
2026-02-07 22:46:48 +08:00
discountry a851257149 Update minimum depth threshold in MakerPointsConfig from 50 to 5 for improved trading performance 2026-02-07 22:19:39 +08:00
DisneyandGitHub 9355ec5019 Enhance Binance depth health monitoring and defense mode logic (#21)
- Updated translations for Binance depth status messages to include depth window information.
- Modified `MakerPointsEngine` to incorporate health checks for the Binance depth tracker, including handling of unhealthy states.
- Improved defense mode activation logic to respond to Binance depth health status, ensuring appropriate logging and notifications.
- Added integration tests for defense mode behavior based on Binance depth health, validating transitions into and out of defense mode.
- Refactored `BinanceDepthTracker` to support health checks and improved connection management.
2026-02-07 22:01:49 +08:00
DisneyandGitHub fa82d45bfb fix size (#20) 2026-02-04 13:26:06 +08:00
discountry d0154e5721 fix den 2026-02-03 18:48:06 +08:00
discountry 52b6a8a076 Add invitation links for Nado registration in trading tutorial 2026-02-03 12:04:36 +08:00
discountry db6a9cfc68 Add Nado trading tutorial for ritmex-bot 2026-02-03 12:02:55 +08:00
DisneyandGitHub 03b8e53d30 Merge pull request #19 from discountry/feat/arb
Feat/arb
2026-02-01 11:15:42 +08:00
discountry 8d79ace8b3 Refactor triggerType handling in order placement logic
- Updated the triggerType assignment in placeStopLossOrder and related functions to default to "STOP_LOSS" instead of conditionally setting it based on the order side.
- This change simplifies the logic for stop market orders across the order coordinator and GRVT exchange gateway, ensuring consistent behavior.
2026-02-01 11:15:00 +08:00
discountry 1fb6d3d62d Add swing trading configuration options to .env.example
- Added new environment variables for swing trading, including SWING_DIRECTION and SWING_STOP_LOSS_PCT.
- Updated documentation in .env.example to reflect the new swing trading parameters for better clarity and usability.
2026-01-31 16:19:19 +08:00
discountry c4559cb0d7 Add swing trading strategy with RSI signals and Binance integration
- Introduced a new swing trading strategy utilizing the RSI indicator on the ETHBTC pair from Binance.
- Implemented the `SwingEngine` to manage trading logic, including entry and exit conditions based on RSI thresholds.
- Added configuration options for swing direction, trade amount, and RSI parameters in `config.ts`.
- Created new documentation for the swing strategy, detailing its behavior and configuration.
- Enhanced CLI to support the new swing strategy option.
- Added tests for swing logic to ensure correct behavior under various market conditions.
2026-01-31 16:15:07 +08:00
discountry 1d88ddefb5 Enhance account snapshot handling and staleness checks in MakerPointsEngine
- Updated `emitAccountSnapshot` method in `StandxGateway` to accept an optional `updateTime` parameter, allowing for more accurate timestamping.
- Introduced logic to determine the appropriate `updateTime` based on the latest position or balance data.
- Added `time` property to `StandxPosition` interface for improved timestamp management.
- Implemented `applyAccountSnapshot` method in `MakerPointsEngine` to streamline account snapshot processing and ensure accurate time tracking.
- Added tests to validate the behavior of account staleness checks and defense mode activation based on account data freshness.
2026-01-24 23:53:36 +08:00
discountry 683352f737 Add changeMarginMode method to ExchangeAdapter and Standx classes
- Introduced `changeMarginMode` method in `ExchangeAdapter` interface to allow margin mode adjustments.
- Implemented the `changeMarginMode` method in `StandxExchangeAdapter` to interact with the gateway for changing margin modes.
- Added corresponding `changeMarginMode` method in `StandxGateway` to handle API requests for margin mode changes.
- Enhanced `MakerPointsEngine` to ensure isolated margin mode before order placement, with appropriate logging and defense mode activation if the change fails.
- Created tests for margin mode functionality to validate behavior under different scenarios.
2026-01-24 22:57:03 +08:00
discountry a629bc940c Enhance environment variable parsing and account snapshot validation
- Introduced `normalizeEnvValue` function to improve handling of environment variable values, including trimming, unquoting, and stripping inline comments.
- Updated `resolveSymbolFromEnv` and parsing functions to utilize the new normalization logic.
- Added `validateAccountSnapshotForSymbol` function to validate account snapshots, ensuring numeric fields are correctly formatted and flagging any issues.
- Implemented tests for environment variable parsing and account snapshot validation to ensure robustness and correctness.
2026-01-24 22:46:14 +08:00
discountry fe7b8eb6f3 Update Binance WebSocket configuration and enhance depth handling
- Changed WebSocket base URL to support both spot and futures trading.
- Adjusted depth tracking parameters for improved performance, increasing the ratio and reducing speed.
- Enhanced payload parsing to accommodate additional data structures from Binance, ensuring robust handling of bids and asks.
- Updated comments for clarity on connection behavior and heartbeat monitoring.
2026-01-22 10:23:07 +08:00
discountry 24339929dc Enhance MakerPoints functionality and configuration
- Updated `filterMinDepth` in `config.ts` from 1 to 50 to improve depth filtering logic.
- Added new translation entries for band depth display in `i18n/index.ts`.
- Introduced `bandDepths` to `MakerPointsSnapshot` in `maker-points-engine.ts` to track depth across different bands.
- Enhanced `BinanceDepthTracker` to support dynamic depth levels and speed settings.
- Updated `MakerPointsApp` to display band depth information, improving user interface clarity.
2026-01-22 02:27:45 +08:00
discountry ed855f6859 Refine data staleness checks in MakerPointsEngine
- Updated the logic to only consider depth data for staleness checks, excluding account data from the criteria.
- Removed unnecessary account staleness checks from defense mode activation, streamlining the data validation process.
- Enhanced comments for clarity on the rationale behind the changes.
2026-01-21 16:30:51 +08:00
discountry e144c1822f Implement data staleness defense mode in MakerPointsEngine
- Introduced a defense mode that activates when data from StandX or Binance is stale for over 5 seconds.
- Added methods to check data freshness, enter and exit defense mode, and cancel all orders during defense mode.
- Enhanced logging to provide insights into data staleness and defense mode transitions.
- Updated connection state management for clarity and consistency.
2026-01-21 16:24:17 +08:00
discountry 69271d33ca Refactor MakerPointsEngine and BinanceDepthTracker for improved connection management
- Renamed connection state variable in MakerPointsEngine for clarity.
- Added connection state change listeners in BinanceDepthTracker to handle connection status updates.
- Implemented heartbeat monitoring and connection duration checks in BinanceDepthTracker to enhance WebSocket reliability.
- Introduced data staleness checks and improved error handling for WebSocket connections.
- Enhanced logging for connection events to provide better insights into connection status changes.
2026-01-21 16:03:34 +08:00
discountry f1140f106a Enhance WebSocket connection management and data handling
- Introduced constants for WebSocket reconnection delays, heartbeat timeout, and data staleness thresholds.
- Implemented heartbeat monitoring to ensure timely reconnections on inactivity.
- Added data staleness checks to trigger REST API calls when market or account data is outdated.
- Enhanced the StandxGateway class with methods for managing heartbeat and data checks, improving overall connection reliability and data integrity.
2026-01-21 15:40:28 +08:00
discountry 3b935b7979 Refactor MakerPoints configuration and depth handling
- Renamed `band0To10MinDepth` to `filterMinDepth` in `config.ts` for clarity.
- Updated `MakerPointsEngine` to utilize the new `filterMinDepth` for depth checks across all bands.
- Introduced a method to track depth status changes, enhancing order placement logic based on market depth.
- Improved logging for depth-related order skips to provide clearer insights into trading decisions.
2026-01-21 11:26:21 +08:00
discountry 00388f9166 add filter 2026-01-21 11:11:58 +08:00
discountry a32efa2ba0 Refine target price calculation in LiquidityMakerEngine
- Updated target price logic to consider entry price when no recent fills are available, enhancing order placement accuracy.
- Adjusted conditions to ensure target prices are set appropriately based on market conditions and entry prices, preventing potential losses.
- Improved comments for clarity on the logic behind target price adjustments.
2026-01-20 01:28:24 +08:00
discountry 76704b6bdd Enhance entry price logic in Maker and Liquidity Maker strategies
- Added `entryDepthLevel` configuration option to `MakerConfig` and `LiquidityMakerConfig` for specifying order entry levels.
- Implemented `getPricesAtLevel` utility function to retrieve bid and ask prices at specified depth levels.
- Updated `MakerEngine`, `LiquidityMakerEngine`, and `OffsetMakerEngine` to utilize the new entry level logic for determining opening prices based on market depth.
- Improved price handling to ensure more accurate order placements in varying market conditions.
2026-01-20 01:03:38 +08:00
discountry 168d8cbb08 Add Claude instructions and enhance stop-loss logic
- Introduced a new `CLAUDE.md` file with instructions for using Bun as the package manager.
- Adjusted stop-loss cooldown and check intervals in `MakerPointsEngine` for improved responsiveness.
- Implemented a new method to compute real-time PnL using live depth data, enhancing stop-loss decision-making.
- Added retry logic for stop-loss execution to ensure positions are closed effectively, with detailed logging for failures.
2026-01-20 00:51:15 +08:00
discountry 9629c22496 Enhance MakerPoints configuration and logic
- Added new configuration options for band-specific order amounts in `config.ts`.
- Implemented conditional logic in `MakerPointsEngine` to utilize the new band amounts based on the Binance depth cancel setting.
- Refactored order amount handling to improve clarity and maintainability.
2026-01-18 01:49:36 +08:00
discountry a34d06f9b4 fix slprice 2026-01-16 23:05:30 +08:00
discountry 2ba3e80ad9 fix sl 2026-01-16 22:59:38 +08:00
discountry 12e8e3e064 Update API token creation date in documentation and configuration
- Revised the `.env.example` and `maker-points-guide.md` to reflect the updated token creation date from 2025-01-15 to 2026-01-15.
- Enhanced the `order-coordinator.ts`, `order-schema.ts`, and `types.ts` files to support stop-loss and take-profit price parameters in order intents.
- Updated the `StandxGateway` and `order.ts` to handle new stop-loss and take-profit parameters in order creation.
- Improved the `MakerPointsEngine` to calculate stop-loss prices based on order type, enhancing order management capabilities.
2026-01-16 11:29:26 +08:00
discountry aa24995d28 Enhance WebSocket and API documentation; implement connection protection features
- Added a note in the HTTP API documentation regarding the non-guaranteed sequence of price levels in order book responses.
- Updated WebSocket documentation to include a connection duration limit and a note on local sorting requirements for price levels.
- Introduced connection event handling in the ExchangeAdapter interface, allowing for disconnection and reconnection events.
- Implemented connection protection logic in the StandxExchangeAdapter and MakerPointsEngine to manage order states during connection disruptions.
- Enhanced the StandxGateway with methods for querying open orders and forcefully canceling all orders, improving reliability during network issues.
2026-01-16 10:49:16 +08:00
discountry d493642935 update doc 2026-01-15 22:06:10 +08:00
discountry 86670486a6 Update StandX API documentation and configuration
- Revised `.env.example` to reflect new API token generation process, emphasizing the use of creation date and validity days for token expiry management.
- Enhanced `auth.md` with detailed instructions for obtaining API tokens and signing transactions for both EVM and Solana wallets.
- Updated `maker-points-guide.md` to clarify the API token retrieval process and the significance of the Ed25519 private key.
- Refactored `config.ts` and `gateway.ts` to support new token expiry configuration methods and improved private key handling, including Base58 decoding.
- Improved overall documentation clarity and user guidance for new and existing users.
2026-01-15 16:11:08 +08:00
discountry 6496011d8f Add Nado exchange support to README
- Included details for the Nado USDC perpetuals, specifying required environment variables and configuration options.
- Updated both English and Chinese versions of the README to reflect the new exchange integration, enhancing user guidance and clarity.
2026-01-14 18:28:14 +08:00
discountry 792351ab8a Add Liquidity Maker strategy and related configurations
- Introduced a new `LiquidityMakerConfig` interface and corresponding configuration settings in `config.ts`.
- Updated CLI argument handling to include the new "liquidity-maker" strategy option.
- Implemented the `LiquidityMakerEngine` class to manage the liquidity making strategy, including order handling and risk management.
- Added a new `LiquidityMakerApp` component for user interaction and display of strategy status.
- Enhanced internationalization support with translations for the liquidity maker strategy.
- Updated the main application to integrate the new liquidity maker strategy into the existing framework.
2026-01-14 00:56:29 +08:00
discountry 4915dc574e Implement precision error handling in MakerPointsEngine
- Added a new `isPrecisionError` function to identify precision-related errors in the error utility module.
- Updated the MakerPointsEngine to handle precision errors by logging warnings and synchronizing precision when such errors occur during order processing and stop-loss execution.
- Enhanced the `syncPrecision` method to allow forced synchronization, improving the handling of precision-related issues.
2026-01-13 20:23:18 +08:00
discountry 9866e8068f Clarify instructions in Maker Points guide regarding the proxy wallet private key format and environment variable setup. Emphasize that the private key should be copied as is, without the '0x' prefix, to enhance user understanding and security practices. 2026-01-12 18:03:26 +08:00
discountry 099af3ce01 Update Maker Points guide to clarify proxy wallet private key format and environment variable instructions. Specify that the private key should generally not include the '0x' prefix, enhancing user understanding and security practices. 2026-01-12 18:02:05 +08:00
discountry 445e634aa1 Refactor Telegram notification handling and remove unused functions
- Removed deprecated functions for masking sensitive data and previewing text, streamlining the Telegram notification process.
- Simplified logging by eliminating unnecessary console outputs related to notification configuration and sending.
- Updated the `TelegramNotifier` class to enhance clarity and maintainability, focusing on essential notification functionality.
2026-01-12 12:39:37 +08:00
discountry 4bb1fee995 Refactor Telegram notification handling in MakerPointsEngine
- Introduced a dedicated `notify` method to streamline notification sending and improve logging for Telegram notifications.
- Added a new environment variable check for enabling debug logging of Telegram notifications.
- Enhanced logging to include detailed information about notification attempts, including masked sensitive data for security.
- Updated various notification calls to utilize the new `notify` method, ensuring consistent logging and functionality.
2026-01-12 12:23:24 +08:00
discountry aad14395e0 Enhance Telegram notification functionality
- Introduced functions to mask sensitive information and preview notification text for improved logging and security.
- Added detailed logging for notification sending process, including configuration details and response handling.
- Implemented checks to prevent sending notifications when bot token or chat ID is missing, with appropriate warnings logged.
2026-01-12 12:12:14 +08:00
discountry 598f2a0eb6 Add token expiry and Telegram notification features
- Introduced `STANDX_TOKEN_EXPIRY` configuration to manage token expiration, including handling logic for active, expired, and silent states.
- Implemented Telegram notifications for key events such as order filled, position opened/closed, stop loss triggered, and token expiration.
- Updated Maker Points engine to integrate token expiry checks and notification sending, enhancing user awareness of trading conditions.
- Enhanced documentation to include details on configuring token expiry and Telegram notifications for improved user guidance.
2026-01-10 12:44:52 +08:00
discountry cb1cef6f1b Revise Maker Points guide to provide a comprehensive step-by-step tutorial for new users. Update installation instructions for Bun, enhance clarity on obtaining StandX login credentials, and improve environment variable configuration details. Add safety tips and common troubleshooting questions to support user onboarding. 2026-01-09 01:11:10 +08:00
discountry fd034d493f Enhance README with language setting instructions, updated referral links, and additional documentation for StandX and Nado exchanges. Clarify environment variable setup and improve formatting for better readability. 2026-01-07 23:39:40 +08:00
discountry 2551670874 Update README and Maker Points guide to clarify the export of StandX login credentials, specifying token and proxy wallet private key for enhanced user security. 2026-01-07 02:46:38 +08:00
discountry 3ec7e9b8d6 Update Maker Points guide to include details on exporting the generated proxy wallet private key along with the token, enhancing security instructions for users. 2026-01-06 21:49:28 +08:00
discountry 499ee692da Merge branch 'main' into feat/mm 2026-01-06 20:17:49 +08:00
discountry de13142950 Add StandX Maker Points strategy tutorial and update README with configuration details. Include steps for obtaining login token and environment variable setup for new users, enhancing documentation clarity and usability. 2026-01-06 20:08:38 +08:00
DisneyandGitHub 597e41f053 Merge pull request #16 from discountry/feat/mm
Feat/mm
2026-01-06 17:50:38 +08:00
discountry b0a33a58d1 Remove market maker documentation from StandX. This deletion includes all content related to Maker Points, order management, and WebSocket API details, streamlining the documentation for clarity and focus. 2026-01-06 17:49:29 +08:00
discountry 65b9f21981 Add new PM2 start command for Maker Points strategy in package.json. This includes a dedicated command for 'maker-points' with specific exchange settings, enhancing deployment options for the application. 2026-01-06 16:37:51 +08:00
discountry 33b5407245 Refactor Maker Points logic by removing dislocation calculations and related UI elements. Update MakerPointsEngine to utilize new price fetching methods and streamline order synchronization. Adjust translations and tests accordingly to reflect these changes. 2026-01-06 16:18:54 +08:00
discountry 7aafc3b69d Add Maker Points strategy support in StandX. Introduce new configuration for Maker Points, including point bands and order management logic. Implement MakerPointsEngine for handling order placement and tracking. Update CLI and UI components to integrate Maker Points functionality, enhancing user experience and strategy options. 2026-01-06 16:01:10 +08:00
discountry 9a093459bc Add debugging options and enhance WebSocket handling in StandxGateway. Introduce debugWs and debugWsRaw flags for improved logging of WebSocket events and payloads. Implement decrossDepthBook function for better order book management and update message handling to support multiple JSON payloads. 2025-12-21 16:54:14 +08:00
discountry aa36b0cfdc Implement depth level normalization in StandxGateway for improved order book handling. Refactor depth data processing to utilize the new normalizeDepthLevels function, ensuring consistent bid and ask sorting. Update symbol handling to accommodate fallback logic. 2025-12-21 16:15:44 +08:00
discountry 93c6409688 Integrate StandX exchange support by updating configuration files, adding environment variables, and enhancing documentation. Include new API endpoints and authentication details for StandX in README and dedicated documentation files. Update CLI and adapter logic to accommodate StandX functionalities. 2025-12-21 15:37:03 +08:00
discountry 84d5e1f3d7 Update README files to include Nado referral link and detailed setup instructions for Nado integration, enhancing user guidance for configuration and usage. 2025-12-20 14:53:25 +08:00
discountry 6e5413ec1e Refactor order status handling in NadoGateway and MakerEngine. Introduce isOrderActiveStatus utility to streamline order filtering logic. Add tests for error handling and order status utilities. 2025-12-20 13:10:48 +08:00
discountry 84e8ce1d43 Enhance NadoGateway with min size policy handling and related utility functions. Update .env.example to include NADO_MIN_SIZE_POLICY configuration option. 2025-12-19 11:11:35 +08:00
discountry c69ea72860 Add Nado documentation and examples, including new API endpoints, FAQs, and guides for using the TypeScript SDK. Update .env.example with additional configuration options. 2025-12-19 01:38:09 +08:00
discountry 624fecfa70 add nado packages 2025-12-18 03:14:20 +08:00
discountry 6dcf13481d Enhance README.md with a detailed description of the Bun-powered multi-exchange perpetuals workstation, highlighting key features such as the SMA30 trend engine, Guardian stop sentinel, and market-making modes. 2025-12-09 00:38:23 +08:00
discountry 3fd0f715a1 Update README.md to instruct users to set LANG=en in .env for English interface support. 2025-12-09 00:36:50 +08:00
discountry 85e7f245c0 Implement internationalization support by adding translation functionality and updating UI components to use translated strings. Add language configuration in .env.example and integrate translations across various strategy and UI components. 2025-12-09 00:35:28 +08:00
discountry 03df1006cc Update README.md to include a link for English users and remove the outdated English README reference. 2025-12-09 00:00:28 +08:00
DisneyandGitHub 3099cb1319 Merge pull request #13 from discountry/feat/lighter-spot
Feat/lighter spot
2025-12-08 23:57:07 +08:00
discountry 732525b394 Merge branch 'main' into dev 2025-10-06 20:15:05 +08:00
discountry 627ed36de4 feat: 添加 EdgeX 交易所适配器及相关客户端实现,支持订单、深度和K线数据处理 2025-10-04 20:15:41 +08:00
269 changed files with 49006 additions and 477 deletions
+70 -1
View File
@@ -1,14 +1,36 @@
# UI language (zh | en)
LANG=zh
# Exchange selection
EXCHANGE=aster # Pick aster (default) or grvt/lighter/backpack/paradex
EXCHANGE=aster # Pick aster (default) or standx/grvt/lighter/backpack/paradex/nado
# Aster API credentials
ASTER_API_KEY=
ASTER_API_SECRET=
# StandX authentication (set when EXCHANGE=standx)
STANDX_TOKEN=
STANDX_SYMBOL=BTC-USD
# STANDX_BASE_URL=https://perps.standx.com
# STANDX_WS_URL=wss://perps.standx.com/ws-stream/v1
# STANDX_SESSION_ID=
# Optional: request signing key (ed25519 private key, supports hex or base58 format)
# STANDX_REQUEST_PRIVATE_KEY=
# Token expiry configuration (recommended method: creation date + validity days)
# Get these values when generating API token at https://standx.com/user/session
# STANDX_TOKEN_CREATE_DATE=2026-01-15 # Token creation date (YYYY-MM-DD format)
# STANDX_TOKEN_VALIDITY_DAYS=30 # Token validity period in days
# Legacy method: direct expiry timestamp (Unix seconds)
# STANDX_TOKEN_EXPIRY=1737092800
# Core trading symbol and sizing
TRADE_SYMBOL=BTCUSDT # Trading pair symbol
TRADE_AMOUNT=0.001 # Base order quantity (base asset, e.g. BTC)
# Swing Trading
SWING_DIRECTION=short # short | long | both
SWING_STOP_LOSS_PCT=0.05 # 0.05 = 5%
# Risk management (USD amounts unless noted)
LOSS_LIMIT=0.04 # Max loss per trade in USDT before forced close
TRAILING_PROFIT=0.2 # Trailing stop activation profit (USDT)
@@ -37,6 +59,10 @@ MAKER_REFRESH_INTERVAL_MS=500 # Maker refresh cadence (ms)
MAKER_MAX_CLOSE_SLIPPAGE_PCT=0.05 # Maker close slippage guard (fallbacks to MAX_CLOSE_SLIPPAGE_PCT)
MAKER_PRICE_TICK=0.1 # Maker price tick size (defaults to PRICE_TICK)
# Maker-points Binance depth imbalance monitor
MAKER_POINTS_BINANCE_DEPTH_WINDOW_BPS=3 # Binance depth monitor window around best bid/ask (bps)
MAKER_POINTS_BINANCE_DEPTH_IMBALANCE_RATIO=9 # Imbalance threshold ratio (e.g. 9 => one side >= 9x)
# Grid strategy defaults
GRID_LOWER_PRICE=25000 # Grid lower bound price (quote currency)
GRID_UPPER_PRICE=35000 # Grid upper bound price
@@ -95,6 +121,16 @@ BACKPACK_SYMBOL=BTC_USD_PERP
# Enable verbose adapter logging: set to "1" or "true"
BACKPACK_DEBUG=false
# EdgeX exchange configuration
EDGEX_ACCOUNT_ID=
EDGEX_PRIVATE_KEY=
# EDGEX_POSITION_ID= # Defaults to EDGEX_ACCOUNT_ID when omitted
# EDGEX_BASE_URL=https://pro.edgex.exchange
# EDGEX_WS_PUBLIC_URL=wss://quote.edgex.exchange
# EDGEX_WS_PRIVATE_URL=wss://quote.edgex.exchange
# EDGEX_ORDER_TTL_MS=21600000 # Order expiration window (ms), default 6 hours
# Paradex exchange configuration
# Provide the EVM private key & wallet address for onboarded accounts.
# When EXCHANGE=paradex these values are used automatically.
@@ -114,3 +150,36 @@ PARADEX_WALLET_ADDRESS=
# Enable verbose adapter logging: set to "1" or "true"
# PARADEX_DEBUG=false
# Nado exchange configuration (Ink mainnet)
# Requires a linked signer private key + your original subaccount owner EVM address.
# When EXCHANGE=nado these values are used automatically.
NADO_ENV=inkMainnet # inkMainnet | inkTestnet
NADO_SYMBOL=BTC-PERP # Trading product symbol (e.g., BTC-PERP / ETH-PERP)
NADO_SIGNER_PRIVATE_KEY= # 32-byte 0x-prefixed private key (0x...)
NADO_SUBACCOUNT_OWNER= # EVM address of the subaccount owner (0x...)
NADO_SUBACCOUNT_NAME=default # Subaccount name (bytes12, default "default")
# Optional: market-order slippage buffer (used for IOC limit-as-market, e.g. 0.01 => 1%)
NADO_MARKET_SLIPPAGE_PCT=0.01
# Optional: stop trigger source for STOP_MARKET orders (oracle | last | mid)
NADO_STOP_TRIGGER_SOURCE=oracle
# Optional: how to handle orders smaller than Nado min_size (USDT0 notional)
# - adjust: round quantity up to the minimum allowed size (default)
# - reject: throw an error instead of auto-adjusting
NADO_MIN_SIZE_POLICY=adjust
# Optional endpoint overrides
# NADO_GATEWAY_WS_URL=wss://gateway.prod.nado.xyz/v1/ws
# NADO_SUBSCRIPTIONS_WS_URL=wss://gateway.prod.nado.xyz/v1/subscribe
# NADO_ARCHIVE_URL=https://archive.prod.nado.xyz/v1
# NADO_TRIGGER_URL=https://trigger.prod.nado.xyz/v1
# NADO_DEBUG=false
# Telegram notification configuration
# TELEGRAM_BOT_TOKEN= # Telegram bot token from @BotFather
# TELEGRAM_CHAT_ID= # Chat ID to receive notifications
# TELEGRAM_ACCOUNT_LABEL= # Account label to distinguish multiple bot instances (e.g., "Account-A")
+12
View File
@@ -0,0 +1,12 @@
# RitMEX Bot - Claude Instructions
## Package Manager
**必须使用 Bun** - 这个项目使用 Bun 作为包管理器和运行时。所有能用 bun 执行的命令都必须使用 bun:
- 安装依赖: `bun install`
- 运行脚本: `bun run <script>`
- 执行测试: `bun test`
- 类型检查: `bun run typecheck`
**不要使用 npm、yarn 或 npx**
+48 -2
View File
@@ -1,20 +1,27 @@
# ritmex-bot
> For English users, please see [README_en.md](README_en.md).
Please set `LANG=en` in `.env` for English interface.
A Bun-powered multi-exchange perpetuals workstation that ships an SMA30 trend engine, a Guardian stop sentinel, and two market-making modes. It offers instant restarts, realtime market data, structured logging, and an Ink-based CLI dashboard.
基于 Bun 的多交易所永续合约量化终端,内置趋势跟随(SMA30)、Guardian 防守与做市策略,支持快速恢复、实时行情订阅、日志追踪与 CLI 仪表盘。
如果您希望获取优惠并支持本项目,请考虑使用以下注册链接:
* [Lighter 手续费优惠注册链接](https://app.lighter.xyz/?referral=111909FA)
* [Aster 手续费优惠注册链接](https://www.asterdex.com/zh-CN/referral/4665f3)
* [StandX 手续费优惠注册链接](https://standx.com/referral?code=xingxingjun)
* [Binance 手续费优惠注册链接](https://www.binance.com/join?ref=KNKCA9XC)
* [GRVT 手续费优惠注册链接](https://grvt.io/exchange/sign-up?ref=sea)
* [Nado 手续费优惠注册链接](https://app.nado.xyz?join=LKbIUs5)
* [Backpack 手续费优惠注册链接](https://backpack.exchange/join/ritmex)
* [edgex 手续费优惠注册链接](https://pro.edgex.exchange/referral/BULL)
* [Paradex 手续费优惠注册链接](https://paradex.io/ref/xingxingjun)
* [Apex 手续费优惠注册链接](https://join.omni.apex.exchange/SEA)
## 文档索引
- [English README](README_en.md)
- [简明上手指南(零基础)](simple-readme.md)
- [基础网格策略使用教程](grid-trading.md)
@@ -29,10 +36,12 @@
| 交易所 | 合约类型 | 必填环境变量 | 备注 |
| --- | --- | --- | --- |
| Aster | USDT 永续 | `ASTER_API_KEY`, `ASTER_API_SECRET` | 默认交易所;兼容脚本引导
| StandX | USD 永续 | `STANDX_TOKEN` | 使用 JWT Token 登录,优先走 WebSocket 推送
| GRVT | USDT 永续 | `GRVT_API_KEY`, `GRVT_API_SECRET`, `GRVT_SUB_ACCOUNT_ID` | `GRVT_ENV` 可切换 `prod`/`testnet`
| Lighter | zkLighter 永续 | `LIGHTER_ACCOUNT_INDEX`, `LIGHTER_API_PRIVATE_KEY` | 默认 `LIGHTER_ENV=testnet`
| Backpack | USDC 永续 | `BACKPACK_API_KEY`, `BACKPACK_API_SECRET`, `BACKPACK_PASSWORD` | `BACKPACK_SANDBOX=true` 启用沙盒
| Paradex | StarkEx 永续 | `PARADEX_PRIVATE_KEY`, `PARADEX_WALLET_ADDRESS` | `PARADEX_SANDBOX=true` 使用测试网
| Nado | USDC 永续 | `NADO_SIGNER_PRIVATE_KEY`, `NADO_SUBACCOUNT_OWNER` | `NADO_ENV` 可切换 `inkMainnet`/`inkTestnet`
## 系统要求
- Bun ≥ 1.2(需同时包含 `bun``bunx` 命令)
@@ -77,7 +86,7 @@ curl -fsSL https://github.com/discountry/ritmex-bot/raw/refs/heads/main/setup.sh
| 变量 | 说明 |
| --- | --- |
| `EXCHANGE` | 选择交易所(`aster`/`grvt`/`lighter`/`backpack`/`paradex` |
| `EXCHANGE` | 选择交易所(`aster`/`standx`/`grvt`/`lighter`/`backpack`/`paradex`/`nado` |
| `TRADE_SYMBOL` | 交易对(默认 `BTCUSDT` |
| `TRADE_AMOUNT` | 单笔下单数量(标的资产计) |
| `LOSS_LIMIT` | 单笔最大亏损触发的强平额度(USDT) |
@@ -102,6 +111,36 @@ curl -fsSL https://github.com/discountry/ritmex-bot/raw/refs/heads/main/setup.sh
3. 根据交易对调整 `TRADE_SYMBOL`、`PRICE_TICK`、`QTY_STEP` 等精度参数。
4. 一键脚本会自动写入这些变量,手动部署时需自行维护。
### StandX
* [StandX 做市策略教程](docs/standx/maker-points-guide.md)
策略需要 StandX 的 API Token 和签名私钥才能下单。
**获取方式(使用 StandX 官方 API 生成功能):**
1. 打开 StandX 官方 API 创建页面:https://standx.com/user/session
2. 连接钱包并登录
3. 点击 **"Generate API Token"** 按钮
4. 页面会显示以下信息:
- **Token**(以 `eyJ` 开头的 JWT 字符串)→ 填入 `STANDX_TOKEN`
- **Ed25519 Private Key**Base58 格式私钥,类似 `HdsyJD7oWgT...`)→ 填入 `STANDX_REQUEST_PRIVATE_KEY`
- **创建日期** 和 **有效期天数** → 用于配置 Token 过期提醒
> Ed25519 Private Key 是系统自动生成的签名私钥,仅用于交易请求签名,你的资产仍在主钱包中,非常安全。
请妥善保存这些凭证,不要分享给他人。
**配置步骤:**
1. 设置 `EXCHANGE=standx`。
2. 填写 `STANDX_TOKEN`Perps API 的 JWT Token)。
3. 填写 `STANDX_REQUEST_PRIVATE_KEY`Ed25519 签名私钥,Base58 格式)。
4. 设置 `STANDX_SYMBOL`(默认 `BTC-USD`),并校准 `PRICE_TICK` / `QTY_STEP`。
5. 推荐配置 Token 过期时间:
- `STANDX_TOKEN_CREATE_DATE`(创建日期,格式 `YYYY-MM-DD`
- `STANDX_TOKEN_VALIDITY_DAYS`(有效期天数)
6. 可选:`STANDX_BASE_URL`、`STANDX_WS_URL`、`STANDX_SESSION_ID` 用于自定义环境。
### GRVT
1. 在 `.env` 中设置 `EXCHANGE=grvt`。
2. 填写 `GRVT_API_KEY`、`GRVT_API_SECRET`、`GRVT_SUB_ACCOUNT_ID`。
@@ -126,6 +165,13 @@ curl -fsSL https://github.com/discountry/ritmex-bot/raw/refs/heads/main/setup.sh
3. 默认连接主网,若需测试网,将 `PARADEX_SANDBOX=true` 并根据需要调整 `PARADEX_SYMBOL`。
4. 复杂环境可额外设置 `PARADEX_USE_PRO`、`PARADEX_RECONNECT_DELAY_MS` 或调试开关。
### Nado
1. 设置 `EXCHANGE=nado`。
2. 在 Nado 官网(交易界面)打开开发者工具(F12)→ 切换到 `Application` → `Local Storage`,找到 `nado.userSettings`,在其内容中取出 `privateKey` 字段并填入 `.env` 的 `NADO_SIGNER_PRIVATE_KEY`。
3. 提供 `NADO_SUBACCOUNT_OWNER`(或 `NADO_EVM_ADDRESS`)。
4. 选择网络 `NADO_ENV=inkMainnet`(主网)或 `inkTestnet`(测试网)。
5. 设置交易品种 `NADO_SYMBOL`(交易对格式类似 `BTC-PERP`;也支持输入 `BTCUSDT0`,会自动映射为 `BTC-PERP`)。
## 命令速查
```bash
bun run index.ts # 启动 CLI(默认入口)
+53 -8
View File
@@ -1,19 +1,25 @@
# ritmex-bot
**Language Setting**: Set `LANG=en` in your `.env` file to display the CLI interface in English.
A Bun-powered multi-exchange perpetuals workstation that ships an SMA30 trend engine, a Guardian stop sentinel, and two market-making modes. It offers instant restarts, realtime market data, structured logging, and an Ink-based CLI dashboard.
If you'd like to support this project and get fee discounts, please consider using these referral links:
* [Lighter referral link](https://app.lighter.xyz/?referral=111909FA)
* [Aster referral link](https://www.asterdex.com/en/referral/4665f3)
* [StandX referral link](https://standx.com/referral?code=xingxingjun)
* [Binance referral link](https://www.binance.com/join?ref=KNKCA9XC)
* [GRVT referral link](https://grvt.io/exchange/sign-up?ref=sea)
* [Nado referral link](https://app.nado.xyz?join=LKbIUs5)
* [Backpack referral link](https://backpack.exchange/join/ritmex)
* [edgex referral link](https://pro.edgex.exchange/referral/BULL)
* [Paradex referral link](https://paradex.io/ref/xingxingjun)
* [Apex referral link](https://join.omni.apex.exchange/RITHMEX)
* [Apex referral link](https://join.omni.apex.exchange/SEA)
## Documentation Map
- [中文 README](README.md)
- [Beginner-friendly Quick Start](simple-readme.md)
- [Grid Trading Strategy Guide](grid-trading.md)
## Highlights
- **Live data & risk sync** via websockets with REST fallbacks and full reconciliation on restart.
@@ -26,13 +32,15 @@ A Bun-powered multi-exchange perpetuals workstation that ships an SMA30 trend en
| Exchange | Contract Type | Required Environment Variables | Notes |
| --- | --- | --- | --- |
| Aster | USDT perpetuals | `ASTER_API_KEY`, `ASTER_API_SECRET` | Default venue; works with the bootstrap script |
| StandX | USD perpetuals | `STANDX_TOKEN` | Uses JWT token auth; prefer websocket streams |
| GRVT | USDT perpetuals | `GRVT_API_KEY`, `GRVT_API_SECRET`, `GRVT_SUB_ACCOUNT_ID` | Switch `GRVT_ENV` between `prod` and `testnet` |
| Lighter | zkLighter perpetuals | `LIGHTER_ACCOUNT_INDEX`, `LIGHTER_API_PRIVATE_KEY` | Defaults to `LIGHTER_ENV=testnet` |
| Backpack | USDC perpetuals | `BACKPACK_API_KEY`, `BACKPACK_API_SECRET`, `BACKPACK_PASSWORD` | Set `BACKPACK_SANDBOX=true` for the sandbox |
| Paradex | StarkEx perpetuals | `PARADEX_PRIVATE_KEY`, `PARADEX_WALLET_ADDRESS` | Toggle `PARADEX_SANDBOX=true` for the testnet |
| Nado | USDC perpetuals | `NADO_SIGNER_PRIVATE_KEY`, `NADO_SUBACCOUNT_OWNER` | Switch `NADO_ENV` between `inkMainnet` and `inkTestnet` |
## Requirements
- Bun 1.2 (both `bun` and `bunx` on PATH)
- Bun >= 1.2 (both `bun` and `bunx` on PATH)
- macOS, Linux, or Windows via WSL (native Windows works but WSL is recommended)
- Node.js is optional unless your tooling requires it
@@ -74,7 +82,7 @@ The script installs Bun, project dependencies, collects Aster API credentials, g
| Variable | Purpose |
| --- | --- |
| `EXCHANGE` | Choose the venue (`aster` / `grvt` / `lighter` / `backpack` / `paradex`) |
| `EXCHANGE` | Choose the venue (`aster` / `standx` / `grvt` / `lighter` / `backpack` / `paradex` / `nado`) |
| `TRADE_SYMBOL` | Contract symbol (defaults to `BTCUSDT`) |
| `TRADE_AMOUNT` | Order size in base asset units |
| `LOSS_LIMIT` | Max per-trade loss in USDT before forced close |
@@ -93,12 +101,42 @@ The script installs Bun, project dependencies, collects Aster API credentials, g
> ```
## Exchange Setup Guides
### Aster
1. Keep `EXCHANGE=aster` (default value).
2. Supply `ASTER_API_KEY` and `ASTER_API_SECRET`.
3. Adjust `TRADE_SYMBOL`, `PRICE_TICK`, and `QTY_STEP` to match the requested market.
4. The bootstrap script auto-populates these variables; manual installs must maintain them.
### StandX
* [StandX Maker Points Strategy Guide](docs/standx/maker-points-guide.md)
The strategy requires a StandX API Token and signing private key to place orders.
**How to obtain (using StandX's official API generation feature):**
1. Open the StandX official API creation page: https://standx.com/user/session
2. Connect your wallet and log in
3. Click the **"Generate API Token"** button
4. The page will display the following information:
- **Token** (JWT string starting with `eyJ`) → Fill in `STANDX_TOKEN`
- **Ed25519 Private Key** (Base58 format, like `HdsyJD7oWgT...`) → Fill in `STANDX_REQUEST_PRIVATE_KEY`
- **Creation date** and **Validity days** → Used to configure token expiry reminders
> The Ed25519 Private Key is an auto-generated signing key used only for trade request signatures. Your assets remain in your main wallet and are completely safe.
Please keep these credentials safe and do not share them with anyone.
**Configuration steps:**
1. Set `EXCHANGE=standx`.
2. Provide `STANDX_TOKEN` (JWT token for perps API).
3. Provide `STANDX_REQUEST_PRIVATE_KEY` (Ed25519 signing private key, Base58 format).
4. Set `STANDX_SYMBOL` (defaults to `BTC-USD`) and align `PRICE_TICK` / `QTY_STEP`.
5. Recommended: configure token expiry settings:
- `STANDX_TOKEN_CREATE_DATE` (creation date, format `YYYY-MM-DD`)
- `STANDX_TOKEN_VALIDITY_DAYS` (validity days)
6. Optional: `STANDX_BASE_URL`, `STANDX_WS_URL`, or `STANDX_SESSION_ID` for custom endpoints.
### GRVT
1. Set `EXCHANGE=grvt` inside `.env`.
2. Fill `GRVT_API_KEY`, `GRVT_API_SECRET`, and `GRVT_SUB_ACCOUNT_ID`.
@@ -107,22 +145,29 @@ The script installs Bun, project dependencies, collects Aster API credentials, g
### Lighter
1. Set `EXCHANGE=lighter`.
2. Provide `LIGHTER_ACCOUNT_INDEX` and `LIGHTER_API_PRIVATE_KEY` (40-byte hex private key).
2. Provide `LIGHTER_ACCOUNT_INDEX` and `LIGHTER_API_PRIVATE_KEY` (40-byte hex private key). `LIGHTER_ACCOUNT_INDEX` is your account index, which you can find by opening DevTools (F12) on the official website and observing API requests. `LIGHTER_API_PRIVATE_KEY` is your API private key.
3. Switch `LIGHTER_ENV` to `mainnet`, `staging`, or `dev` when necessary; override `LIGHTER_BASE_URL` if endpoints differ.
4. `LIGHTER_SYMBOL` defaults to `BTCUSDT`; override price/size decimals when markets differ.
### Backpack
1. Set `EXCHANGE=backpack`.
2. Populate `BACKPACK_API_KEY`, `BACKPACK_API_SECRET`, and `BACKPACK_PASSWORD`; add `BACKPACK_SUBACCOUNT` if you trade from a subaccount.
2. Populate `BACKPACK_API_KEY`, `BACKPACK_API_SECRET`, and `BACKPACK_PASSWORD`; add `BACKPACK_SUBACCOUNT` if you trade from a subaccount (defaults to main account ID).
3. Toggle `BACKPACK_SANDBOX=true` for the sandbox environment and verify `BACKPACK_SYMBOL` matches the contract (defaults to `BTC_USD_PERP`).
4. Enable `BACKPACK_DEBUG=true` for verbose adapter logging.
### Paradex
1. Set `EXCHANGE=paradex`.
2. Provide `PARADEX_PRIVATE_KEY` (EVM private key) and `PARADEX_WALLET_ADDRESS`.
2. Provide `PARADEX_PRIVATE_KEY` (EVM private key) and `PARADEX_WALLET_ADDRESS`. Note: These are your EVM wallet address and private key. It is recommended to create a brand new wallet and avoid storing unrelated assets in it.
3. The adapter connects to mainnet by default; enable `PARADEX_SANDBOX=true` and adjust `PARADEX_SYMBOL` for testnet usage.
4. Advanced tuning: use `PARADEX_USE_PRO`, `PARADEX_RECONNECT_DELAY_MS`, or debug flags as needed.
### Nado
1. Set `EXCHANGE=nado`.
2. On the Nado web app (trading interface), open DevTools (F12) -> switch to the `Application` tab -> `Local Storage`, locate `nado.userSettings`, then grab the `privateKey` field from its JSON value and paste it into `.env` as `NADO_SIGNER_PRIVATE_KEY`.
3. Provide `NADO_SUBACCOUNT_OWNER` (or `NADO_EVM_ADDRESS`).
4. Select network via `NADO_ENV=inkMainnet` (mainnet) or `inkTestnet` (testnet).
5. Set `NADO_SYMBOL` using Nado product symbols like `BTC-PERP` (it also accepts `BTCUSDT0` and maps it to `BTC-PERP`).
## Command Cheatsheet
```bash
bun run index.ts # Launch the CLI (default entrypoint)
@@ -170,7 +215,7 @@ bun x vitest --watch
```
## Troubleshooting
- Keep at least 50100 USDT in the account before deploying a live strategy.
- Keep at least 50-100 USDT in the account before deploying a live strategy.
- Configure leverage on the exchange manually (~50x is recommended); the bot will not change it.
- Ensure your server or workstation clock is in sync to avoid signature errors.
- Accounts must run in one-way position mode.
+390 -98
View File
@@ -1,136 +1,162 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "ritmex-bot",
"dependencies": {
"@grvt/client": "^1.6.4",
"@grvt/client": "^1.6.25",
"@nadohq/client": "^0.1.0-alpha.45",
"@noble/ed25519": "^3.0.0",
"axios": "^1.12.2",
"ccxt": "^4.5.12",
"dotenv": "^17.2.2",
"ethereum-cryptography": "^2.1.3",
"ink": "^6.3.1",
"react": "^19.1.1",
"ws": "^8.18.3",
"@starkware-industries/starkware-crypto-utils": "^0.2.1",
"axios": "^1.13.4",
"bignumber.js": "^9.3.1",
"ccxt": "^4.5.35",
"dotenv": "^17.2.3",
"ethereum-cryptography": "^2.2.1",
"ink": "^6.6.0",
"react": "^19.2.4",
"trading-signals": "^7.4.3",
"viem": "^2.45.1",
"ws": "^8.19.0",
},
"devDependencies": {
"@types/bun": "latest",
"vitest": "^3.2.4",
},
"peerDependencies": {
"typescript": "^5",
"typescript": "^5.9.2",
},
},
},
"packages": {
"@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-qI/5TaaaCZE4yeSZ83lu0+xi1r88JSxUjnH4OP/iZF7+KKZ75u3ee5isd0LxX+6N8U0npL61YrpbthILHB6BnA=="],
"@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.1", "", {}, "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.10", "", { "os": "aix", "cpu": "ppc64" }, "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw=="],
"@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.10", "", { "os": "android", "cpu": "arm" }, "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.10", "", { "os": "android", "cpu": "arm64" }, "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.10", "", { "os": "android", "cpu": "x64" }, "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.10", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.10", "", { "os": "freebsd", "cpu": "x64" }, "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.10", "", { "os": "linux", "cpu": "arm" }, "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.10", "", { "os": "linux", "cpu": "ia32" }, "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.10", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.10", "", { "os": "linux", "cpu": "s390x" }, "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.10", "", { "os": "linux", "cpu": "x64" }, "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.10", "", { "os": "none", "cpu": "x64" }, "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.10", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.10", "", { "os": "openbsd", "cpu": "x64" }, "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.10", "", { "os": "sunos", "cpu": "x64" }, "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.10", "", { "os": "win32", "cpu": "ia32" }, "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.10", "", { "os": "win32", "cpu": "x64" }, "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
"@grvt/client": ["@grvt/client@1.6.4", "", { "dependencies": { "axios": "^1.12.2" } }, "sha512-yZfEvsC/BtkcdMjoB7Eh6gshBraz/9810qCllvNXDx6OmdxR+NfxPO+ucNlPbyjEt1HfSVzHBWB1yGnVl1usNA=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
"@grvt/client": ["@grvt/client@1.6.26", "", { "dependencies": { "axios": "^1.13.5" } }, "sha512-Y6uWa3rirdYPU0BgQS7vYKghscTR7AwykQYZVSjib2GGbMW4JZJc/XX/IcZq0aL59W/S7ISechw+DhU/Amw2SQ=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@nadohq/client": ["@nadohq/client@0.1.0-alpha.50", "", { "dependencies": { "@nadohq/engine-client": "0.1.0-alpha.50", "@nadohq/indexer-client": "0.1.0-alpha.50", "@nadohq/shared": "0.1.0-alpha.50", "@nadohq/trigger-client": "0.1.0-alpha.50", "ts-mixer": "*" }, "peerDependencies": { "viem": "*" } }, "sha512-9A7I5pDuvdKJCGrhadxmORceaW3YVlrMTXoSQg+dYPBflsQpKLJQaEXZZyn1Hb9tz9K6rNUxdt33aXL0gb6IKw=="],
"@nadohq/engine-client": ["@nadohq/engine-client@0.1.0-alpha.50", "", { "dependencies": { "@nadohq/shared": "0.1.0-alpha.50", "axios": "*", "ts-mixer": "*" }, "peerDependencies": { "viem": "*" } }, "sha512-/mC0tH/h7oqmvj4lo+kWUTwDjGWtQl930WpvFdxQThIWJzXFxnb1eM0oGW5tCm2OxB05dwo5wHCmXH2fj+SO2Q=="],
"@nadohq/indexer-client": ["@nadohq/indexer-client@0.1.0-alpha.50", "", { "dependencies": { "@nadohq/engine-client": "0.1.0-alpha.50", "@nadohq/shared": "0.1.0-alpha.50", "axios": "*", "ts-mixer": "*" }, "peerDependencies": { "viem": "*" } }, "sha512-65+I8w/OWQBueUUwimhZf4QAYLH3pNOD19ZWNrkd9jdgigkDnqdra/620ZE1+RR1lUUSzvFy6Apus3M//uwxvw=="],
"@nadohq/shared": ["@nadohq/shared@0.1.0-alpha.50", "", { "dependencies": { "abitype": "^1.2.3" }, "peerDependencies": { "bignumber.js": "^9.3.0", "viem": "*" } }, "sha512-7WndVvF4M71yNiuuB8WNp61+QCN/J3Ke11xRQUO83hO6FZDd0Hc3NWINt2BUmgBnDDpmFkOopA59pYjxJX1v+A=="],
"@nadohq/trigger-client": ["@nadohq/trigger-client@0.1.0-alpha.50", "", { "dependencies": { "@nadohq/engine-client": "0.1.0-alpha.50", "@nadohq/shared": "0.1.0-alpha.50", "axios": "*", "ts-mixer": "*" }, "peerDependencies": { "viem": "*" } }, "sha512-kg5ytYVI/wszuGo6aG24iDoEk24ePygRl+Mq02OEz1CPbgt/S2aqRcTVmKLsw3R+6DK/zLFVSXo7odeJGXpZ3g=="],
"@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="],
"@noble/curves": ["@noble/curves@1.4.2", "", { "dependencies": { "@noble/hashes": "1.4.0" } }, "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw=="],
"@noble/ed25519": ["@noble/ed25519@3.0.0", "", {}, "sha512-QyteqMNm0GLqfa5SoYbSC3+Pvykwpn95Zgth4MFVSMKBB75ELl9tX1LAVsN4c3HXOrakHsF2gL4zWDAYCcsnzg=="],
"@noble/hashes": ["@noble/hashes@1.4.0", "", {}, "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.0", "", { "os": "android", "cpu": "arm" }, "sha512-VxDYCDqOaR7NXzAtvRx7G1u54d2kEHopb28YH/pKzY6y0qmogP3gG7CSiWsq9WvDFxOQMpNEyjVAHZFXfH3o/A=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.0", "", { "os": "android", "cpu": "arm64" }, "sha512-pqDirm8koABIKvzL59YI9W9DWbRlTX7RWhN+auR8HXJxo89m4mjqbah7nJZjeKNTNYopqL+yGg+0mhCpf3xZtQ=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.52.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-YCdWlY/8ltN6H78HnMsRHYlPiKvqKagBP1r+D7SSylxX+HnsgXGCmLiV3Y4nSyY9hW8qr8U9LDUx/Lo7M6MfmQ=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.59.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.52.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-z4nw6y1j+OOSGzuVbSWdIp1IUks9qNw4dc7z7lWuWDKojY38VMWBlEN7F9jk5UXOkUcp97vA1N213DF+Lz8BRg=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.59.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.52.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-Q/dv9Yvyr5rKlK8WQJZVrp5g2SOYeZUs9u/t2f9cQ2E0gJjYB/BWoedXfUT0EcDJefi2zzVfhcOj8drWCzTviw=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.59.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.52.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kdBsLs4Uile/fbjZVvCRcKB4q64R+1mUq0Yd7oU1CMm1Av336ajIFqNFovByipciuUQjBCPMxwJhCgfG2re3rg=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.59.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.52.0", "", { "os": "linux", "cpu": "arm" }, "sha512-aL6hRwu0k7MTUESgkg7QHY6CoqPgr6gdQXRJI1/VbFlUMwsSzPGSR7sG5d+MCbYnJmJwThc2ol3nixj1fvI/zQ=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.52.0", "", { "os": "linux", "cpu": "arm" }, "sha512-BTs0M5s1EJejgIBJhCeiFo7GZZ2IXWkFGcyZhxX4+8usnIo5Mti57108vjXFIQmmJaRyDwmV59Tw64Ap1dkwMw=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.52.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-uj672IVOU9m08DBGvoPKPi/J8jlVgjh12C9GmjjBxCTQc3XtVmRkRKyeHSmIKQpvJ7fIm1EJieBUcnGSzDVFyw=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.52.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-/+IVbeDMDCtB/HP/wiWsSzduD10SEGzIZX2945KSgZRNi4TSkjHqRJtNTVtVb8IRwhJ65ssI56krlLik+zFWkw=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.52.0", "", { "os": "linux", "cpu": "none" }, "sha512-U1vVzvSWtSMWKKrGoROPBXMh3Vwn93TA9V35PldokHGqiUbF6erSzox/5qrSMKp6SzakvyjcPiVF8yB1xKr9Pg=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.52.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-X/4WfuBAdQRH8cK3DYl8zC00XEE6aM472W+QCycpQJeLWVnHfkv7RyBFVaTqNUMsTgIX8ihMjCvFF9OUgeABzw=="],
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.52.0", "", { "os": "linux", "cpu": "none" }, "sha512-xIRYc58HfWDBZoLmWfWXg2Sq8VCa2iJ32B7mqfWnkx5mekekl0tMe7FHpY8I72RXEcUkaWawRvl3qA55og+cwQ=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.52.0", "", { "os": "linux", "cpu": "none" }, "sha512-mbsoUey05WJIOz8U1WzNdf+6UMYGwE3fZZnQqsM22FZ3wh1N887HT6jAOjXs6CNEK3Ntu2OBsyQDXfIjouI4dw=="],
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.52.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-qP6aP970bucEi5KKKR4AuPFd8aTx9EF6BvutvYxmZuWLJHmnq4LvBfp0U+yFDMGwJ+AIJEH5sIP+SNypauMWzg=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.52.0", "", { "os": "linux", "cpu": "x64" }, "sha512-nmSVN+F2i1yKZ7rJNKO3G7ZzmxJgoQBQZ/6c4MuS553Grmr7WqR7LLDcYG53Z2m9409z3JLt4sCOhLdbKQ3HmA=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.52.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2d0qRo33G6TfQVjaMR71P+yJVGODrt5V6+T0BDYH4EMfGgdC/2HWDVjSSFw888GSzAZUwuska3+zxNUCDco6rQ=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.59.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.52.0", "", { "os": "none", "cpu": "arm64" }, "sha512-A1JalX4MOaFAAyGgpO7XP5khquv/7xKzLIyLmhNrbiCxWpMlnsTYr8dnsWM7sEeotNmxvSOEL7F65j0HXFcFsw=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.52.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-YQugafP/rH0eOOHGjmNgDURrpYHrIX0yuojOI8bwCyXwxC9ZdTd3vYkmddPX0oHONLXu9Rb1dDmT0VNpjkzGGw=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.52.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-zYdUYhi3Qe2fndujBqL5FjAFzvNeLxtIqfzNEVKD1I7C37/chv1VxhscWSQHTNfjPCrBFQMnynwA3kpZpZ8w4A=="],
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.59.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.52.0", "", { "os": "win32", "cpu": "x64" }, "sha512-fGk03kQylNaCOQ96HDMeT7E2n91EqvCDd3RwvT5k+xNdFCeMGnj5b5hEgTGrQuyidqSsD3zJDQ21QIaxXqTBJw=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.59.0", "", { "os": "none", "cpu": "arm64" }, "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.0", "", { "os": "win32", "cpu": "x64" }, "sha512-6iKDCVSIUQ8jPMoIV0OytRKniaYyy5EbY/RRydmLW8ZR3cEBhxbWl5ro0rkUNe0ef6sScvhbY79HrjRm8i3vDQ=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.59.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.59.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="],
"@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="],
@@ -138,21 +164,23 @@
"@scure/bip39": ["@scure/bip39@1.3.0", "", { "dependencies": { "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" } }, "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ=="],
"@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="],
"@starkware-industries/starkware-crypto-utils": ["@starkware-industries/starkware-crypto-utils@0.2.1", "", { "dependencies": { "assert": "^2.0.0", "bip39": "^3.0.4", "bn.js": "^4.12.0", "brorand": "^1.1.0", "buffer": "^6.0.3", "crypto-browserify": "^3.12.0", "elliptic": "^6.5.4", "enc-utils": "^3.0.0", "ethereumjs-wallet": "^1.0.2", "hash.js": "^1.1.7", "hmac-drbg": "^1.0.1", "inherits": "^2.0.4", "js-sha3": "^0.8.0", "minimalistic-assert": "^1.0.1", "minimalistic-crypto-utils": "^1.0.1", "stream-browserify": "^3.0.0" } }, "sha512-rA5O9b53zaoBOQwQxBd0cbumFbQoBm9NH/vfu+o0Cq3oouEbNPALneLlLjOmFEId2/WOJ5ecC64rFLI/PwuIPQ=="],
"@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="],
"@types/bn.js": ["@types/bn.js@5.2.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q=="],
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
"@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
"@types/node": ["@types/node@25.3.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q=="],
"@types/node": ["@types/node@24.5.2", "", { "dependencies": { "undici-types": "~7.12.0" } }, "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ=="],
"@types/pbkdf2": ["@types/pbkdf2@3.1.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew=="],
"@types/react": ["@types/react@19.1.13", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ=="],
"@types/secp256k1": ["@types/secp256k1@4.0.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-Rcvjl6vARGAKRO6jHeKMatGrvOMGrR/AR11N1x2LqintPCyDZ7NBhrh238Z2VZc7aM7KIwnFpFQ7fnfK4H/9Qw=="],
"@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="],
@@ -168,39 +196,87 @@
"@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
"ansi-escapes": ["ansi-escapes@7.1.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-YdhtCd19sKRKfAAUsrcC1wzm4JuzJoiX4pOJqIoW2qmKj5WzG/dL8uUJ0361zaXtHqK7gEhOwtAtz7t3Yq3X5g=="],
"abitype": ["abitype@1.2.3", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg=="],
"aes-js": ["aes-js@3.1.2", "", {}, "sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ=="],
"ansi-escapes": ["ansi-escapes@7.3.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="],
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
"ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
"asn1.js": ["asn1.js@4.10.1", "", { "dependencies": { "bn.js": "^4.0.0", "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" } }, "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw=="],
"assert": ["assert@2.1.0", "", { "dependencies": { "call-bind": "^1.0.2", "is-nan": "^1.3.2", "object-is": "^1.1.5", "object.assign": "^4.1.4", "util": "^0.12.5" } }, "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw=="],
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
"auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="],
"axios": ["axios@1.12.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw=="],
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
"bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="],
"axios": ["axios@1.13.5", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q=="],
"base-x": ["base-x@3.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA=="],
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
"bip39": ["bip39@3.1.0", "", { "dependencies": { "@noble/hashes": "^1.2.0" } }, "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A=="],
"blakejs": ["blakejs@1.2.1", "", {}, "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ=="],
"bn.js": ["bn.js@4.12.3", "", {}, "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g=="],
"brorand": ["brorand@1.1.0", "", {}, "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w=="],
"browserify-aes": ["browserify-aes@1.2.0", "", { "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" } }, "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA=="],
"browserify-cipher": ["browserify-cipher@1.0.1", "", { "dependencies": { "browserify-aes": "^1.0.4", "browserify-des": "^1.0.0", "evp_bytestokey": "^1.0.0" } }, "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w=="],
"browserify-des": ["browserify-des@1.0.2", "", { "dependencies": { "cipher-base": "^1.0.1", "des.js": "^1.0.0", "inherits": "^2.0.1", "safe-buffer": "^5.1.2" } }, "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A=="],
"browserify-rsa": ["browserify-rsa@4.1.1", "", { "dependencies": { "bn.js": "^5.2.1", "randombytes": "^2.1.0", "safe-buffer": "^5.2.1" } }, "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ=="],
"browserify-sign": ["browserify-sign@4.2.5", "", { "dependencies": { "bn.js": "^5.2.2", "browserify-rsa": "^4.1.1", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", "elliptic": "^6.6.1", "inherits": "^2.0.4", "parse-asn1": "^5.1.9", "readable-stream": "^2.3.8", "safe-buffer": "^5.2.1" } }, "sha512-C2AUdAJg6rlM2W5QMp2Q4KGQMVBwR1lIimTsUnutJ8bMpW5B52pGpR2gEnNBNwijumDo5FojQ0L9JrXA8m4YEw=="],
"bs58": ["bs58@4.0.1", "", { "dependencies": { "base-x": "^3.0.2" } }, "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw=="],
"bs58check": ["bs58check@2.1.2", "", { "dependencies": { "bs58": "^4.0.0", "create-hash": "^1.1.0", "safe-buffer": "^5.1.2" } }, "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA=="],
"buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
"buffer-xor": ["buffer-xor@1.0.3", "", {}, "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ=="],
"bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
"call-bind": ["call-bind@1.0.8", "", { "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" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
"ccxt": ["ccxt@4.5.12", "", { "dependencies": { "ws": "^8.8.1" } }, "sha512-2lfL2TKjq4vBkQUQWJfDqFywhvYCZmk9r0SWC8GqA4AHZ6qozKVUJowxQTvdRsLX9jBwYSE0nc7JVurBrQ6SHg=="],
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
"ccxt": ["ccxt@4.5.40", "", { "dependencies": { "ws": "^8.8.1" } }, "sha512-8nJhhNKft7+fELDNQuCV2AQZBxLJfYQaD2LpqxHrucSszosyVl0u6lCIFfcnDUbeUW925uhrsKevHf2fNbLjKw=="],
"chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
"check-error": ["check-error@2.1.1", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="],
"check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="],
"cipher-base": ["cipher-base@1.0.7", "", { "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1", "to-buffer": "^1.2.2" } }, "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA=="],
"cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
"cli-cursor": ["cli-cursor@4.0.0", "", { "dependencies": { "restore-cursor": "^4.0.0" } }, "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg=="],
"cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="],
"cli-truncate": ["cli-truncate@5.1.1", "", { "dependencies": { "slice-ansi": "^7.1.0", "string-width": "^8.0.0" } }, "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A=="],
"code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="],
@@ -208,19 +284,39 @@
"convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
"create-ecdh": ["create-ecdh@4.0.4", "", { "dependencies": { "bn.js": "^4.1.0", "elliptic": "^6.5.3" } }, "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A=="],
"create-hash": ["create-hash@1.2.0", "", { "dependencies": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", "md5.js": "^1.3.4", "ripemd160": "^2.0.1", "sha.js": "^2.4.0" } }, "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg=="],
"create-hmac": ["create-hmac@1.1.7", "", { "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" } }, "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg=="],
"crypto-browserify": ["crypto-browserify@3.12.1", "", { "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" } }, "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
"define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
"dotenv": ["dotenv@17.2.2", "", {}, "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q=="],
"des.js": ["des.js@1.1.0", "", { "dependencies": { "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" } }, "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg=="],
"diffie-hellman": ["diffie-hellman@5.0.3", "", { "dependencies": { "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", "randombytes": "^2.0.0" } }, "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg=="],
"dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"emoji-regex": ["emoji-regex@10.5.0", "", {}, "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg=="],
"elliptic": ["elliptic@6.6.1", "", { "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" } }, "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g=="],
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
"enc-utils": ["enc-utils@3.0.0", "", { "dependencies": { "is-typedarray": "1.0.0", "typedarray-to-buffer": "3.1.5" } }, "sha512-e57t/Z2HzWOLwOp7DZcV0VMEY8t7ptWwsxyp6kM2b2zrk6JqIpXxzkruHAMiBsy5wg9jp/183GdiRXCvBtzsYg=="],
"environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="],
@@ -234,9 +330,9 @@
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
"es-toolkit": ["es-toolkit@1.39.10", "", {}, "sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w=="],
"es-toolkit": ["es-toolkit@1.44.0", "", {}, "sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg=="],
"esbuild": ["esbuild@0.25.10", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.10", "@esbuild/android-arm": "0.25.10", "@esbuild/android-arm64": "0.25.10", "@esbuild/android-x64": "0.25.10", "@esbuild/darwin-arm64": "0.25.10", "@esbuild/darwin-x64": "0.25.10", "@esbuild/freebsd-arm64": "0.25.10", "@esbuild/freebsd-x64": "0.25.10", "@esbuild/linux-arm": "0.25.10", "@esbuild/linux-arm64": "0.25.10", "@esbuild/linux-ia32": "0.25.10", "@esbuild/linux-loong64": "0.25.10", "@esbuild/linux-mips64el": "0.25.10", "@esbuild/linux-ppc64": "0.25.10", "@esbuild/linux-riscv64": "0.25.10", "@esbuild/linux-s390x": "0.25.10", "@esbuild/linux-x64": "0.25.10", "@esbuild/netbsd-arm64": "0.25.10", "@esbuild/netbsd-x64": "0.25.10", "@esbuild/openbsd-arm64": "0.25.10", "@esbuild/openbsd-x64": "0.25.10", "@esbuild/openharmony-arm64": "0.25.10", "@esbuild/sunos-x64": "0.25.10", "@esbuild/win32-arm64": "0.25.10", "@esbuild/win32-ia32": "0.25.10", "@esbuild/win32-x64": "0.25.10" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ=="],
"esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
"escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
@@ -244,19 +340,31 @@
"ethereum-cryptography": ["ethereum-cryptography@2.2.1", "", { "dependencies": { "@noble/curves": "1.4.2", "@noble/hashes": "1.4.0", "@scure/bip32": "1.4.0", "@scure/bip39": "1.3.0" } }, "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg=="],
"expect-type": ["expect-type@1.2.2", "", {}, "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA=="],
"ethereumjs-util": ["ethereumjs-util@7.1.5", "", { "dependencies": { "@types/bn.js": "^5.1.0", "bn.js": "^5.1.2", "create-hash": "^1.1.2", "ethereum-cryptography": "^0.1.3", "rlp": "^2.2.4" } }, "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg=="],
"ethereumjs-wallet": ["ethereumjs-wallet@1.0.2", "", { "dependencies": { "aes-js": "^3.1.2", "bs58check": "^2.1.2", "ethereum-cryptography": "^0.1.3", "ethereumjs-util": "^7.1.2", "randombytes": "^2.1.0", "scrypt-js": "^3.0.1", "utf8": "^3.0.0", "uuid": "^8.3.2" } }, "sha512-CCWV4RESJgRdHIvFciVQFnCHfqyhXWchTPlkfp28Qc53ufs+doi5I/cV2+xeK9+qEo25XCWfP9MiL+WEPAZfdA=="],
"eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="],
"evp_bytestokey": ["evp_bytestokey@1.0.3", "", { "dependencies": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" } }, "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA=="],
"expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
"form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="],
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="],
"generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="],
"get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="],
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "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" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
@@ -264,69 +372,157 @@
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
"has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
"hash-base": ["hash-base@3.0.5", "", { "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1" } }, "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg=="],
"hash.js": ["hash.js@1.1.7", "", { "dependencies": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" } }, "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA=="],
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"hmac-drbg": ["hmac-drbg@1.0.1", "", { "dependencies": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", "minimalistic-crypto-utils": "^1.0.1" } }, "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg=="],
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
"ink": ["ink@6.3.1", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.0", "ansi-escapes": "^7.0.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^4.0.0", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.32.0", "signal-exit": "^3.0.7", "slice-ansi": "^7.1.0", "stack-utils": "^2.0.6", "string-width": "^7.2.0", "type-fest": "^4.27.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": "^6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-3wGwITGrzL6rkWsi2gEKzgwdafGn4ZYd3u4oRp+sOPvfoxEHlnoB5Vnk9Uy5dMRUhDOqF3hqr4rLQ4lEzBc2sQ=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"ink": ["ink@6.8.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.4", "ansi-escapes": "^7.3.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^5.1.1", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.33.0", "scheduler": "^0.27.0", "signal-exit": "^3.0.7", "slice-ansi": "^8.0.0", "stack-utils": "^2.0.6", "string-width": "^8.1.1", "terminal-size": "^4.0.1", "type-fest": "^5.4.1", "widest-line": "^6.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": ">=6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA=="],
"is-arguments": ["is-arguments@1.2.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA=="],
"is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="],
"is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="],
"is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="],
"is-in-ci": ["is-in-ci@2.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w=="],
"is-nan": ["is-nan@1.3.2", "", { "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" } }, "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w=="],
"is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
"is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="],
"is-typedarray": ["is-typedarray@1.0.0", "", {}, "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="],
"isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
"isows": ["isows@1.0.7", "", { "peerDependencies": { "ws": "*" } }, "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg=="],
"js-sha3": ["js-sha3@0.8.0", "", {}, "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="],
"js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
"keccak": ["keccak@3.0.4", "", { "dependencies": { "node-addon-api": "^2.0.0", "node-gyp-build": "^4.2.0", "readable-stream": "^3.6.0" } }, "sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q=="],
"loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
"magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
"md5.js": ["md5.js@1.3.5", "", { "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1", "safe-buffer": "^5.1.2" } }, "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg=="],
"miller-rabin": ["miller-rabin@4.0.1", "", { "dependencies": { "bn.js": "^4.0.0", "brorand": "^1.0.1" }, "bin": { "miller-rabin": "bin/miller-rabin" } }, "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA=="],
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"minimalistic-assert": ["minimalistic-assert@1.0.1", "", {}, "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="],
"minimalistic-crypto-utils": ["minimalistic-crypto-utils@1.0.1", "", {}, "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"node-addon-api": ["node-addon-api@2.0.2", "", {}, "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA=="],
"node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="],
"object-is": ["object-is@1.1.6", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1" } }, "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q=="],
"object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
"object.assign": ["object.assign@4.1.7", "", { "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" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
"ox": ["ox@0.12.4", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.2.3", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-+P+C7QzuwPV8lu79dOwjBKfB2CbnbEXe/hfyyrff1drrO1nOOj3Hc87svHfcW1yneRr3WXaKr6nz11nq+/DF9Q=="],
"parse-asn1": ["parse-asn1@5.1.9", "", { "dependencies": { "asn1.js": "^4.10.1", "browserify-aes": "^1.2.0", "evp_bytestokey": "^1.0.3", "pbkdf2": "^3.1.5", "safe-buffer": "^5.2.1" } }, "sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg=="],
"patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="],
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
"pbkdf2": ["pbkdf2@3.1.5", "", { "dependencies": { "create-hash": "^1.2.0", "create-hmac": "^1.1.7", "ripemd160": "^2.0.3", "safe-buffer": "^5.2.1", "sha.js": "^2.4.12", "to-buffer": "^1.2.1" } }, "sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
"react": ["react@19.1.1", "", {}, "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ=="],
"public-encrypt": ["public-encrypt@4.0.3", "", { "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" } }, "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q=="],
"react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="],
"randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="],
"randomfill": ["randomfill@1.0.4", "", { "dependencies": { "randombytes": "^2.0.5", "safe-buffer": "^5.1.0" } }, "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw=="],
"react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
"react-reconciler": ["react-reconciler@0.33.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA=="],
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
"restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="],
"rollup": ["rollup@4.52.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.0", "@rollup/rollup-android-arm64": "4.52.0", "@rollup/rollup-darwin-arm64": "4.52.0", "@rollup/rollup-darwin-x64": "4.52.0", "@rollup/rollup-freebsd-arm64": "4.52.0", "@rollup/rollup-freebsd-x64": "4.52.0", "@rollup/rollup-linux-arm-gnueabihf": "4.52.0", "@rollup/rollup-linux-arm-musleabihf": "4.52.0", "@rollup/rollup-linux-arm64-gnu": "4.52.0", "@rollup/rollup-linux-arm64-musl": "4.52.0", "@rollup/rollup-linux-loong64-gnu": "4.52.0", "@rollup/rollup-linux-ppc64-gnu": "4.52.0", "@rollup/rollup-linux-riscv64-gnu": "4.52.0", "@rollup/rollup-linux-riscv64-musl": "4.52.0", "@rollup/rollup-linux-s390x-gnu": "4.52.0", "@rollup/rollup-linux-x64-gnu": "4.52.0", "@rollup/rollup-linux-x64-musl": "4.52.0", "@rollup/rollup-openharmony-arm64": "4.52.0", "@rollup/rollup-win32-arm64-msvc": "4.52.0", "@rollup/rollup-win32-ia32-msvc": "4.52.0", "@rollup/rollup-win32-x64-gnu": "4.52.0", "@rollup/rollup-win32-x64-msvc": "4.52.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-+IuescNkTJQgX7AkIDtITipZdIGcWF0pnVvZTWStiazUmcGA2ag8dfg0urest2XlXUi9kuhfQ+qmdc5Stc3z7g=="],
"ripemd160": ["ripemd160@2.0.3", "", { "dependencies": { "hash-base": "^3.1.2", "inherits": "^2.0.4" } }, "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA=="],
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
"rlp": ["rlp@2.2.7", "", { "dependencies": { "bn.js": "^5.2.0" }, "bin": { "rlp": "bin/rlp" } }, "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ=="],
"rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"scrypt-js": ["scrypt-js@3.0.1", "", {}, "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA=="],
"secp256k1": ["secp256k1@4.0.4", "", { "dependencies": { "elliptic": "^6.5.7", "node-addon-api": "^5.0.0", "node-gyp-build": "^4.2.0" } }, "sha512-6JfvwvjUOn8F/jUoBY2Q1v5WY5XS+rj8qSe0v8Y4ezH4InLgTEeOOPQsRll9OV429Pvo6BCHGavIyJfr3TAhsw=="],
"set-function-length": ["set-function-length@1.2.2", "", { "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" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
"setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="],
"sha.js": ["sha.js@2.4.12", "", { "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1", "to-buffer": "^1.2.0" }, "bin": { "sha.js": "bin.js" } }, "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w=="],
"siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="],
"slice-ansi": ["slice-ansi@8.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "is-fullwidth-code-point": "^5.1.0" } }, "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
@@ -334,13 +530,21 @@
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
"std-env": ["std-env@3.9.0", "", {}, "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw=="],
"std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
"string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
"stream-browserify": ["stream-browserify@3.0.0", "", { "dependencies": { "inherits": "~2.0.4", "readable-stream": "^3.5.0" } }, "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA=="],
"strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
"string-width": ["string-width@8.2.0", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw=="],
"strip-literal": ["strip-literal@3.0.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA=="],
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
"strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
"strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="],
"tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="],
"terminal-size": ["terminal-size@4.0.1", "", {}, "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ=="],
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
@@ -354,30 +558,118 @@
"tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="],
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
"to-buffer": ["to-buffer@1.2.2", "", { "dependencies": { "isarray": "^2.0.5", "safe-buffer": "^5.2.1", "typed-array-buffer": "^1.0.3" } }, "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw=="],
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
"trading-signals": ["trading-signals@7.4.3", "", {}, "sha512-kOyzd85qhuhU7yGkB5z74MVP6j30xBCfZy5+bgBnvfxY1ZdvaQsdD+C5j+CoIRtCAgSOuxB7jmPpBrqO6h17sQ=="],
"undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="],
"ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="],
"vite": ["vite@7.1.7", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA=="],
"type-fest": ["type-fest@5.4.4", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw=="],
"typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
"typedarray-to-buffer": ["typedarray-to-buffer@3.1.5", "", { "dependencies": { "is-typedarray": "^1.0.0" } }, "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"utf8": ["utf8@3.0.0", "", {}, "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ=="],
"util": ["util@0.12.5", "", { "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" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="],
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
"viem": ["viem@2.46.3", "", { "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", "ox": "0.12.4", "ws": "8.18.3" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-2LJS+Hyh2sYjHXQtzfv1kU9pZx9dxFzvoU/ZKIcn0FNtOU0HQuIICuYdWtUDFHaGXbAdVo8J1eCvmjkL9JVGwg=="],
"vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
"vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
"vitest": ["vitest@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="],
"which-typed-array": ["which-typed-array@1.1.20", "", { "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" } }, "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg=="],
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
"widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="],
"widest-line": ["widest-line@6.0.0", "", { "dependencies": { "string-width": "^8.1.0" } }, "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA=="],
"wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
"ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
"yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="],
"cli-truncate/slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="],
"bip39/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
"cli-truncate/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="],
"browserify-rsa/bn.js": ["bn.js@5.2.3", "", {}, "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w=="],
"browserify-sign/bn.js": ["bn.js@5.2.3", "", {}, "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w=="],
"browserify-sign/readable-stream": ["readable-stream@2.3.8", "", { "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" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
"cli-truncate/slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="],
"ethereumjs-util/bn.js": ["bn.js@5.2.3", "", {}, "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w=="],
"ethereumjs-util/ethereum-cryptography": ["ethereum-cryptography@0.1.3", "", { "dependencies": { "@types/pbkdf2": "^3.0.0", "@types/secp256k1": "^4.0.1", "blakejs": "^1.1.0", "browserify-aes": "^1.2.0", "bs58check": "^2.1.2", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", "hash.js": "^1.1.7", "keccak": "^3.0.0", "pbkdf2": "^3.0.17", "randombytes": "^2.1.0", "safe-buffer": "^5.1.2", "scrypt-js": "^3.0.0", "secp256k1": "^4.0.1", "setimmediate": "^1.0.5" } }, "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ=="],
"ethereumjs-wallet/ethereum-cryptography": ["ethereum-cryptography@0.1.3", "", { "dependencies": { "@types/pbkdf2": "^3.0.0", "@types/secp256k1": "^4.0.1", "blakejs": "^1.1.0", "browserify-aes": "^1.2.0", "bs58check": "^2.1.2", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", "hash.js": "^1.1.7", "keccak": "^3.0.0", "pbkdf2": "^3.0.17", "randombytes": "^2.1.0", "safe-buffer": "^5.1.2", "scrypt-js": "^3.0.0", "secp256k1": "^4.0.1", "setimmediate": "^1.0.5" } }, "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ=="],
"md5.js/hash-base": ["hash-base@3.1.2", "", { "dependencies": { "inherits": "^2.0.4", "readable-stream": "^2.3.8", "safe-buffer": "^5.2.1", "to-buffer": "^1.2.1" } }, "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg=="],
"ox/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="],
"ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
"ox/@scure/bip32": ["@scure/bip32@1.7.0", "", { "dependencies": { "@noble/curves": "~1.9.0", "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw=="],
"ox/@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="],
"ripemd160/hash-base": ["hash-base@3.1.2", "", { "dependencies": { "inherits": "^2.0.4", "readable-stream": "^2.3.8", "safe-buffer": "^5.2.1", "to-buffer": "^1.2.1" } }, "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg=="],
"rlp/bn.js": ["bn.js@5.2.3", "", {}, "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w=="],
"secp256k1/node-addon-api": ["node-addon-api@5.1.0", "", {}, "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="],
"to-buffer/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
"viem/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="],
"viem/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
"viem/@scure/bip32": ["@scure/bip32@1.7.0", "", { "dependencies": { "@noble/curves": "~1.9.0", "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw=="],
"viem/@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="],
"viem/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
"wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
"browserify-sign/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
"browserify-sign/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
"md5.js/hash-base/readable-stream": ["readable-stream@2.3.8", "", { "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" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
"ox/@scure/bip32/@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="],
"ox/@scure/bip39/@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="],
"ripemd160/hash-base/readable-stream": ["readable-stream@2.3.8", "", { "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" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
"viem/@scure/bip32/@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="],
"viem/@scure/bip39/@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="],
"md5.js/hash-base/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
"md5.js/hash-base/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
"ripemd160/hash-base/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
"ripemd160/hash-base/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
}
}
+785
View File
@@ -0,0 +1,785 @@
2 Signature
How To Sign Message
How To GET Your L2 Private Key
To sign messages on Layer 2, you need to obtain your L2 private key. This key is used to generate signatures that authorize various actions on the platform.
How To GET Your L2 Private Key
Warning: Keep your private key secure and never share it with anyone. Anyone with access to your private key can sign messages on your behalf.
Signature Algorithm
The signature algorithm used is Ecdsa (Elliptic Curve Digital Signature Algorithm). This algorithm ensures that signatures are secure and verifiable.
L2Signature for Operations (e.g., Order, Transfer, Withdraw): This will use Pedersen hash for signing. However, this hash computation will consume significantly more CPU resources.
<!-- logo -->
<h1 align='center'>StarkWare Crypto Utils</h1>
<!-- tag line -->
<h4 align='center'> Signatures, keys and Pedersen hash on STARK friendly elliptic curve</h4>
<!-- primary badges -->
<p align="center">
<a href="https://www.w3schools.com/js/">
<img src='https://badges.aleen42.com/src/javascript.svg' />
</a>
<a href="https://www.npmjs.com/package/@starkware-industries/starkware-crypto-utils">
<img src='https://img.shields.io/npm/v/@starkware-industries/starkware-crypto-utils?label=npm' />
</a>
<a href="https://starkware.co/">
<img src="https://img.shields.io/badge/powered_by-StarkWare-navy">
</a>
</p>
## Installation
```bash
// using npm
npm i @starkware-industries/starkware-crypto-utils
// using yarn
yarn add @starkware-industries/starkware-crypto-utils
```
## How to use it
```js
const starkwareCrypto = require('@starkware-industries/starkware-crypto-utils');
```
## API
```javascript
{
prime,
ec: starkEc,
constantPoints,
shiftPoint,
maxEcdsaVal, // Data.
pedersen,
getLimitOrderMsgHash,
getTransferMsgHash,
sign,
verify,
assertInRange,
getTransferMsgHashWithFee,
getLimitOrderMsgHashWithFee // Function.
asset: {
getAssetType,
getAssetId // Function.
},
keyDerivation: {
StarkExEc: ec.n, // Data.
getPrivateKeyFromEthSignature,
privateToStarkKey,
getKeyPairFromPath,
getAccountPath,
grindKey // Function.
},
messageUtils: {
assertInRange // Function.
}
}
```
## Usage
### Signing a StarkEx order
```javascript
const starkwareCrypto = require('@starkware-libs/starkware-crypto-utils');
const testData = require('test/config/signature_test_data.json');
const privateKey = testData.meta_data.party_a_order.private_key.substring(2);
const keyPair = starkwareCrypto.ec.keyFromPrivate(privateKey, 'hex');
const publicKey = starkwareCrypto.ec.keyFromPublic(
keyPair.getPublic(true, 'hex'),
'hex'
);
const publicKeyX = publicKey.pub.getX();
assert(
publicKeyX.toString(16) ===
testData.settlement.party_a_order.public_key.substring(2),
`Got: ${publicKeyX.toString(16)}.
Expected: ${testData.settlement.party_a_order.public_key.substring(2)}`
);
const {party_a_order: partyAOrder} = testData.settlement;
const msgHash = starkwareCrypto.getLimitOrderMsgHash(
partyAOrder.vault_id_sell, // - vault_sell (uint31)
partyAOrder.vault_id_buy, // - vault_buy (uint31)
partyAOrder.amount_sell, // - amount_sell (uint63 decimal str)
partyAOrder.amount_buy, // - amount_buy (uint63 decimal str)
partyAOrder.token_sell, // - token_sell (hex str with 0x prefix < prime)
partyAOrder.token_buy, // - token_buy (hex str with 0x prefix < prime)
partyAOrder.nonce, // - nonce (uint31)
partyAOrder.expiration_timestamp // - expiration_timestamp (uint22)
);
assert(
msgHash === testData.meta_data.party_a_order.message_hash.substring(2),
`Got: ${msgHash}. Expected: ` +
testData.meta_data.party_a_order.message_hash.substring(2)
);
const msgSignature = starkwareCrypto.sign(keyPair, msgHash);
const {r, s} = msgSignature;
assert(starkwareCrypto.verify(publicKey, msgHash, msgSignature));
assert(
r.toString(16) === partyAOrder.signature.r.substring(2),
`Got: ${r.toString(16)}. Expected: ${partyAOrder.signature.r.substring(2)}`
);
assert(
s.toString(16) === partyAOrder.signature.s.substring(2),
`Got: ${s.toString(16)}. Expected: ${partyAOrder.signature.s.substring(2)}`
);
// The following is the JSON representation of an order:
console.log('Order JSON representation: ');
console.log(partyAOrder);
console.log('\n');
```
### StarkEx key serialization
```javascript
const starkwareCrypto = require('@starkware-libs/starkware-crypto-utils');
const pubXStr = publicKey.pub.getX().toString('hex');
const pubYStr = publicKey.pub.getY().toString('hex');
// Verify Deserialization.
const pubKeyDeserialized = starkwareCrypto.ec.keyFromPublic(
{x: pubXStr, y: pubYStr},
'hex'
);
assert(starkwareCrypto.verify(pubKeyDeserialized, msgHash, msgSignature));
```
### Signing a StarkEx order with fee
```javascript
const privateKey = testData.meta_data.party_a_order.private_key.substring(2);
const keyPair = starkwareCrypto.ec.keyFromPrivate(privateKey, 'hex');
const publicKey = starkwareCrypto.ec.keyFromPublic(
keyPair.getPublic(true, 'hex'),
'hex'
);
const publicKeyX = publicKey.pub.getX();
assert(
publicKeyX.toString(16) ===
testData.settlement.party_a_order.public_key.substring(2),
`Got: ${publicKeyX.toString(16)}.
Expected: ${testData.settlement.party_a_order.public_key.substring(2)}`
);
const {party_a_order: partyAOrder} = testData.settlement;
const feeInfo = testData.fee_info_user;
const msgHash = starkwareCrypto.getLimitOrderMsgHashWithFee(
partyAOrder.vault_id_sell, // - vault_sell (uint64)
partyAOrder.vault_id_buy, // - vault_buy (uint64)
partyAOrder.amount_sell, // - amount_sell (uint63 decimal str)
partyAOrder.amount_buy, // - amount_buy (uint63 decimal str)
partyAOrder.token_sell, // - token_sell (hex str with 0x prefix < prime)
partyAOrder.token_buy, // - token_buy (hex str with 0x prefix < prime)
partyAOrder.nonce, // - nonce (uint31)
partyAOrder.expiration_timestamp, // - expiration_timestamp (uint22)
feeInfo.token_id, // - token (hex str with 0x prefix < prime)
feeInfo.source_vault_id, // - fee_source_vault_id (uint31)
feeInfo.fee_limit // - amount (uint63 decimal str)
);
assert(
msgHash ===
testData.meta_data.party_a_order_with_fee.message_hash.substring(2),
`Got: ${msgHash}. Expected: ` +
testData.meta_data.party_a_order_with_fee.message_hash.substring(2)
);
// The following is the JSON representation of an order:
console.log('Order With Fee JSON representation: ');
// Fee info is added to the order, and will be also be seen in the JSON of Settlement.
partyAOrder.fee_info = feeInfo; // eslint-disable-line
console.log(partyAOrder);
console.log('\n');
```
### StarkEx transfer
```javascript
const starkwareCrypto = require('@starkware-libs/starkware-crypto-utils');
const testData = require('test/config/signature_test_data.json');
const privateKey = testData.meta_data.transfer_order.private_key.substring(2);
const keyPair = starkwareCrypto.ec.keyFromPrivate(privateKey, 'hex');
const publicKey = starkwareCrypto.ec.keyFromPublic(
keyPair.getPublic(true, 'hex'),
'hex'
);
const publicKeyX = publicKey.pub.getX();
assert(
publicKeyX.toString(16) === testData.transfer_order.public_key.substring(2),
`Got: ${publicKeyX.toString(16)}.
Expected: ${testData.transfer_order.public_key.substring(2)}`
);
const transfer = testData.transfer_order;
const msgHash = starkwareCrypto.getTransferMsgHash(
transfer.amount, // - amount (uint63 decimal str)
transfer.nonce, // - nonce (uint31)
transfer.sender_vault_id, // - sender_vault_id (uint31)
transfer.token, // - token (hex str with 0x prefix < prime)
transfer.target_vault_id, // - target_vault_id (uint31)
transfer.target_public_key, // - target_public_key (hex str with 0x prefix < prime)
transfer.expiration_timestamp // - expiration_timestamp (uint22)
);
assert(
msgHash === testData.meta_data.transfer_order.message_hash.substring(2),
`Got: ${msgHash}. Expected: ` +
testData.meta_data.transfer_order.message_hash.substring(2)
);
// The following is the JSON representation of a transfer:
console.log('Transfer JSON representation: ');
console.log(transfer);
console.log('\n');
```
### StarkEx conditional transfer
```javascript
const starkwareCrypto = require('@starkware-libs/starkware-crypto-utils');
const testData = require('test/config/signature_test_data.json');
const privateKey =
testData.meta_data.conditional_transfer_order.private_key.substring(2);
const keyPair = starkwareCrypto.ec.keyFromPrivate(privateKey, 'hex');
const publicKey = starkwareCrypto.ec.keyFromPublic(
keyPair.getPublic(true, 'hex'),
'hex'
);
const publicKeyX = publicKey.pub.getX();
assert(
publicKeyX.toString(16) ===
testData.conditional_transfer_order.public_key.substring(2),
`Got: ${publicKeyX.toString(16)}.
Expected: ${testData.conditional_transfer_order.public_key.substring(
2
)}`
);
const transfer = testData.conditional_transfer_order;
const msgHash = starkwareCrypto.getTransferMsgHash(
transfer.amount, // - amount (uint63 decimal str)
transfer.nonce, // - nonce (uint31)
transfer.sender_vault_id, // - sender_vault_id (uint31)
transfer.token, // - token (hex str with 0x prefix < prime)
transfer.target_vault_id, // - target_vault_id (uint31)
transfer.target_public_key, // - target_public_key (hex str with 0x prefix < prime)
transfer.expiration_timestamp, // - expiration_timestamp (uint22)
transfer.condition // - condition (hex str with 0x prefix < prime)
);
assert(
msgHash ===
testData.meta_data.conditional_transfer_order.message_hash.substring(2),
`Got: ${msgHash}. Expected: ` +
testData.meta_data.conditional_transfer_order.message_hash.substring(2)
);
// The following is the JSON representation of a transfer:
console.log('Conditional Transfer JSON representation: ');
console.log(transfer);
console.log('\n');
```
### StarkEx transfer with fee
```javascript
const privateKey = testData.meta_data.transfer_order.private_key.substring(2);
const keyPair = starkwareCrypto.ec.keyFromPrivate(privateKey, 'hex');
const publicKey = starkwareCrypto.ec.keyFromPublic(
keyPair.getPublic(true, 'hex'),
'hex'
);
const publicKeyX = publicKey.pub.getX();
assert(
publicKeyX.toString(16) === testData.transfer_order.public_key.substring(2),
`Got: ${publicKeyX.toString(16)}.
Expected: ${testData.transfer_order.public_key.substring(2)}`
);
const transfer = testData.transfer_order;
const feeInfo = testData.fee_info_user;
const msgHash = starkwareCrypto.getTransferMsgHashWithFee(
transfer.amount, // - amount (uint63 decimal str)
transfer.nonce, // - nonce (uint31)
transfer.sender_vault_id, // - sender_vault_id (uint64)
transfer.token, // - token (hex str with 0x prefix < prime)
transfer.target_vault_id, // - target_vault_id (uint64)
transfer.target_public_key, // - target_public_key (hex str with 0x prefix < prime)
transfer.expiration_timestamp, // - expiration_timestamp (uint22)
feeInfo.token_id, // - token (hex str with 0x prefix < prime)
feeInfo.source_vault_id, // - fee_source_vault_id (uint64)
feeInfo.fee_limit // - amount (uint63 decimal str)
);
assert(
msgHash ===
testData.meta_data.transfer_order_with_fee.message_hash.substring(2),
`Got: ${msgHash}. Expected: ` +
testData.meta_data.transfer_order.message_hash.substring(2)
);
// The following is the JSON representation of a transfer:
console.log('Transfer With Fee JSON representation: ');
console.log(transfer);
console.log('\n');
```
### StarkEx conditional Transfer with fee
```javascript
const privateKey =
testData.meta_data.conditional_transfer_order.private_key.substring(2);
const keyPair = starkwareCrypto.ec.keyFromPrivate(privateKey, 'hex');
const publicKey = starkwareCrypto.ec.keyFromPublic(
keyPair.getPublic(true, 'hex'),
'hex'
);
const publicKeyX = publicKey.pub.getX();
assert(
publicKeyX.toString(16) ===
testData.conditional_transfer_order.public_key.substring(2),
`Got: ${publicKeyX.toString(16)}.
Expected: ${testData.conditional_transfer_order.public_key.substring(
2
)}`
);
const transfer = testData.conditional_transfer_order;
const feeInfo = testData.fee_info_user;
const msgHash = starkwareCrypto.getTransferMsgHashWithFee(
transfer.amount, // - amount (uint63 decimal str)
transfer.nonce, // - nonce (uint31)
transfer.sender_vault_id, // - sender_vault_id (uint64)
transfer.token, // - token (hex str with 0x prefix < prime)
transfer.target_vault_id, // - target_vault_id (uint64)
transfer.target_public_key, // - target_public_key (hex str with 0x prefix < prime)
transfer.expiration_timestamp, // - expiration_timestamp (uint22)
feeInfo.token_id, // - token (hex str with 0x prefix < prime)
feeInfo.source_vault_id, // - fee_source_vault_id (uint64)
feeInfo.fee_limit, // - amount (uint63 decimal str)
transfer.condition // - condition (hex str with 0x prefix < prime)
);
assert(
msgHash ===
testData.meta_data.conditional_transfer_order_with_fee.message_hash.substring(
2
),
`Got: ${msgHash}. Expected: ` +
testData.meta_data.conditional_transfer_order.message_hash.substring(2)
);
// The following is the JSON representation of a transfer:
console.log('Conditional Transfer With Fee JSON representation: ');
console.log(transfer);
console.log('\n');
```
### Adding a matching order to create a settlement
```javascript
const starkwareCrypto = require('@starkware-libs/starkware-crypto-utils');
const testData = require('test/config/signature_test_data.json');
const privateKey = testData.meta_data.party_b_order.private_key.substring(2);
const keyPair = starkwareCrypto.ec.keyFromPrivate(privateKey, 'hex');
const publicKey = starkwareCrypto.ec.keyFromPublic(
keyPair.getPublic(true, 'hex'),
'hex'
);
const publicKeyX = publicKey.pub.getX();
assert(
publicKeyX.toString(16) ===
testData.settlement.party_b_order.public_key.substring(2),
`Got: ${publicKeyX.toString(16)}.
Expected: ${testData.settlement.party_b_order.public_key.substring(2)}`
);
const {party_b_order: partyBOrder} = testData.settlement;
const msgHash = starkwareCrypto.getLimitOrderMsgHash(
partyBOrder.vault_id_sell, // - vault_sell (uint31)
partyBOrder.vault_id_buy, // - vault_buy (uint31)
partyBOrder.amount_sell, // - amount_sell (uint63 decimal str)
partyBOrder.amount_buy, // - amount_buy (uint63 decimal str)
partyBOrder.token_sell, // - token_sell (hex str with 0x prefix < prime)
partyBOrder.token_buy, // - token_buy (hex str with 0x prefix < prime)
partyBOrder.nonce, // - nonce (uint31)
partyBOrder.expiration_timestamp // - expiration_timestamp (uint22)
);
assert(
msgHash === testData.meta_data.party_b_order.message_hash.substring(2),
`Got: ${msgHash}. Expected: ` +
testData.meta_data.party_b_order.message_hash.substring(2)
);
const msgSignature = starkwareCrypto.sign(keyPair, msgHash);
const {r, s} = msgSignature;
assert(starkwareCrypto.verify(publicKey, msgHash, msgSignature));
assert(
r.toString(16) === partyBOrder.signature.r.substring(2),
`Got: ${r.toString(16)}. Expected: ${partyBOrder.signature.r.substring(2)}`
);
assert(
s.toString(16) === partyBOrder.signature.s.substring(2),
`Got: ${s.toString(16)}. Expected: ${partyBOrder.signature.s.substring(2)}`
);
// The following is the JSON representation of a settlement:
console.log('Settlement JSON representation: ');
console.log(testData.settlement);
```
## Valid transfer with sender_vault_id=2\*\*63+10
```javascript
const transfer = testData.transfer_order_2nd_valid_range;
const feeInfo = testData.fee_info_user;
const msgHash = starkwareCrypto.getTransferMsgHashWithFee(
transfer.amount, // - amount (uint63 decimal str)
transfer.nonce, // - nonce (uint31)
transfer.sender_vault_id, // - sender_vault_id (uint64)
transfer.token, // - token (hex str with 0x prefix < prime)
transfer.target_vault_id, // - target_vault_id (uint64)
transfer.target_public_key, // - target_public_key (hex str with 0x prefix < prime)
transfer.expiration_timestamp, // - expiration_timestamp (uint22)
feeInfo.token_id, // - token (hex str with 0x prefix < prime)
feeInfo.source_vault_id, // - fee_source_vault_id (uint64)
feeInfo.fee_limit, // - amount (uint63 decimal str)
transfer.condition // - condition (hex str with 0x prefix < prime)
);
assert(
msgHash ===
testData.meta_data.transfer_order_2nd_valid_range.message_hash.substring(2),
`Got: ${msgHash}. Expected: ` +
testData.meta_data.transfer_order_2nd_valid_range.message_hash.substring(2)
);
// The following is the JSON representation of a transfer with sender_vault_id in the second
// valid range:
console.log('Transfer JSON representation: ');
console.log(transfer);
console.log('\n');
```
## License
[Apache License 2.0](LICENSE.md)
Java L2Signature Demo
Below is a Java implementation of the Ecdsa signature algorithm. This example demonstrates how to sign a message using a private key.
Copy
public static CreateOrderRequest signOrder(
CreateOrderRequest request,
Contract contract,
Coin quotelCoin,
PrivateKey privateKey) {
BigInteger msgHash = L2SignUtil.hashLimitOrder(
request.getSide() == OrderSide.BUY,
BigIntUtil.toBigInt(quotelCoin.getStarkExAssetId()),
BigIntUtil.toBigInt(contract.getStarkExSyntheticAssetId()),
BigIntUtil.toBigInt(quotelCoin.getStarkExAssetId()),
UnsignedLong.valueOf(new BigDecimal(request.getL2Value())
.multiply(new BigDecimal(BigIntUtil.toBigInt(quotelCoin.getStarkExResolution())))
.toBigIntegerExact()),
UnsignedLong.valueOf(new BigDecimal(request.getL2Size())
.multiply(new BigDecimal(BigIntUtil.toBigInt(contract.getStarkExResolution())))
.toBigIntegerExact()),
UnsignedLong.valueOf(new BigDecimal(request.getL2LimitFee())
.multiply(new BigDecimal(BigIntUtil.toBigInt(quotelCoin.getStarkExResolution())))
.toBigIntegerExact()),
UnsignedLong.fromLongBits(request.getAccountId()),
UnsignedInteger.valueOf(request.getL2Nonce()),
UnsignedInteger.valueOf(request.getL2ExpireTime() / (60 * 60 * 1000L)));
Signature signature = Ecdsa.sign(msgHash, privateKey);
return request.toBuilder()
.setL2Signature(L2Signature.newBuilder()
.setR(BigIntUtil.toHexStr(signature.r))
.setS(BigIntUtil.toHexStr(signature.s))
.build())
.build();
}
public static BigInteger hashLimitOrder(
boolean isBuyingSynthetic,
BigInteger assetIdCollateral,
BigInteger assetIdSynthetic,
BigInteger assetIdFee,
UnsignedLong amountCollateral,
UnsignedLong amountSynthetic,
UnsignedLong maxAmountFee,
UnsignedLong positionId,
UnsignedInteger nonce,
UnsignedInteger expirationTimestamp) {
BigInteger assetIdSell;
BigInteger assetIdBuy;
UnsignedLong amountSell;
UnsignedLong amountBuy;
if (isBuyingSynthetic) {
assetIdSell = assetIdCollateral;
assetIdBuy = assetIdSynthetic;
amountSell = amountCollateral;
amountBuy = amountSynthetic;
} else {
assetIdSell = assetIdSynthetic;
assetIdBuy = assetIdCollateral;
amountSell = amountSynthetic;
amountBuy = amountCollateral;
}
BigInteger packedMessage0 = amountSell.bigIntegerValue();
packedMessage0 = packedMessage0.shiftLeft(64).add(amountBuy.bigIntegerValue());
packedMessage0 = packedMessage0.shiftLeft(64).add(maxAmountFee.bigIntegerValue());
packedMessage0 = packedMessage0.shiftLeft(32).add(nonce.bigIntegerValue());
BigInteger packedMessage1 = BigInteger.valueOf(3);
packedMessage1 = packedMessage1.shiftLeft(64).add(positionId.bigIntegerValue());
packedMessage1 = packedMessage1.shiftLeft(64).add(positionId.bigIntegerValue());
packedMessage1 = packedMessage1.shiftLeft(64).add(positionId.bigIntegerValue());
packedMessage1 = packedMessage1.shiftLeft(32).add(expirationTimestamp.bigIntegerValue());
packedMessage1 = packedMessage1.shiftLeft(17);
BigInteger msg = pedersenHash(assetIdSell, assetIdBuy);
msg = pedersenHash(msg, assetIdFee);
msg = pedersenHash(msg, packedMessage0);
msg = pedersenHash(msg, packedMessage1);
return msg;
}
public static BigInteger pedersenHash(BigInteger... input) {
BigInteger[][] points = PEDERSEN_POINTS;
Point shiftPoint = new Point(points[0][0], points[0][1]);
for (int i = 0; i < input.length; i++) {
BigInteger x = input[i];
for (int j = 0; j < 252; j++) {
int pos = 2 + i * 252 + j;
Point pt = new Point(points[pos][0], points[pos][1]);
if (x.and(BigInteger.ONE).intValue() != 0) {
shiftPoint = EcMath.add(shiftPoint, pt, Curve.secp256k1.A, Curve.secp256k1.P);
}
x = x.shiftRight(1);
}
}
return shiftPoint.x;
}
public static Signature sign(BigInteger msgHash, PrivateKey privateKey) {
Curve curve = privateKey.curve;
BigInteger randNum = new BigInteger(curve.N.toByteArray().length * 8 - 1, new SecureRandom()).abs().add(BigInteger.ONE);
Point randomSignPoint = EcMath.multiply(curve.G, randNum, curve.N, curve.A, curve.P);
BigInteger r = randomSignPoint.x.mod(curve.N);
BigInteger s = ((msgHash.add(r.multiply(privateKey.secret))).multiply(EcMath.inv(randNum, curve.N))).mod(curve.N);
return Signature.create(r, s);
}
Signature Construction Guide
This section provides detailed instructions on constructing signatures for various actions on the platform.
Withdrawal Signature
Used to authorize withdrawing assets from Layer 2 to an Ethereum address.
Parameters
assetIdCollateral - Asset ID for the collateral token from meta_data.coinList.starkExAssetId
positionId - User's account ID in Layer 2
ethAddress - Destination Ethereum address for withdrawal
nonce - Unique transaction identifier to prevent replay attacks
expirationTimestamp - Unix timestamp when signature expires
amount - Amount to withdraw in base units
Calculation
The following TypeScript function constructs the withdrawal message for signing:
Copy
// Construct withdrawal message for signing
function getWithdrawalToAddressMsg({
assetIdCollateral,
positionId,
ethAddress,
nonce,
expirationTimestamp,
amount
}) {
// Pack parameters into 256-bit words
const w1 = assetIdCollateral;
let w5 = BigInt(withdrawalToAddress); // Constant identifier
w5 = (w5 << 64) + BigInt(positionId);
w5 = (w5 << 32) + BigInt(nonce);
w5 = (w5 << 64) + BigInt(amount);
w5 = (w5 << 32) + BigInt(expirationTimestamp);
w5 = w5 << 49;
// Calculate Pedersen hash
return pedersen([
pedersen([w1, ethAddress]),
w5.toString(16)
]);
}
Limit Order Signature
Used to authorize a limit order for perpetual trading.
Parameters
assetIdSynthetic - Synthetic asset ID from meta_data.contractList.starkExSyntheticAssetId
assetIdCollateral - Collateral asset ID from meta_data.coinList.starkExAssetId
isBuyingSynthetic - true for buy orders, false for sell orders
assetIdFee - Fee token asset ID from meta_data.coinList.starkExAssetId
amountSynthetic - Amount of synthetic asset
amountCollateral - Amount of collateral asset
maxAmountFee - Maximum fee amount allowed
nonce - Unique order identifier
positionId - User's position ID
expirationTimestamp - Unix timestamp when order expires
Calculation
The following TypeScript function constructs the limit order message for signing:
Copy
function getLimitOrderMsg({
assetIdSynthetic,
assetIdCollateral,
isBuyingSynthetic,
assetIdFee,
amountSynthetic,
amountCollateral,
maxAmountFee,
nonce,
positionId,
expirationTimestamp
}) {
// Determine sell/buy assets based on order side
const [assetIdSell, assetIdBuy] = isBuyingSynthetic
? [assetIdCollateral, assetIdSynthetic]
: [assetIdSynthetic, assetIdCollateral];
const [amountSell, amountBuy] = isBuyingSynthetic
? [amountCollateral, amountSynthetic]
: [amountSynthetic, amountCollateral];
// Pack order data into 256-bit words
const w1 = assetIdSell;
const w2 = assetIdBuy;
const w3 = assetIdFee;
// Calculate message hash
let msg = pedersen([w1, w2]);
msg = pedersen([msg, w3]);
let w4 = BigInt(amountSell);
w4 = (w4 << 64) + BigInt(amountBuy);
w4 = (w4 << 64) + BigInt(maxAmountFee);
w4 = (w4 << 32) + BigInt(nonce);
msg = pedersen([msg, w4.toString(16)]);
let w5 = BigInt(limitOrderWithFees); // Constant identifier
w5 = (w5 << 64) + BigInt(positionId);
w5 = (w5 << 64) + BigInt(positionId);
w5 = (w5 << 64) + BigInt(positionId);
w5 = (w5 << 32) + BigInt(expirationTimestamp);
w5 = w5 << 17;
return pedersen([msg, w5.toString(16)]);
}
Transfer Signature
Used to authorize transfers between Layer 2 accounts.
Parameters
assetId - Asset ID being transferred
receiverPublicKey - Recipient's public key
senderPositionId - Sender's position ID
receiverPositionId - Recipient's position ID
srcFeePositionId - Fee source position ID
nonce - Unique transfer identifier
amount - Transfer amount
expirationTimestamp - Unix timestamp when transfer expires
assetIdFee - Fee token asset ID (optional, default '0')
maxAmountFee - Maximum fee amount (optional, default '0')
Calculation
The following TypeScript function constructs the transfer message for signing:
Copy
function getTransferMsg({
assetId,
receiverPublicKey,
senderPositionId,
receiverPositionId,
srcFeePositionId,
nonce,
amount,
expirationTimestamp,
assetIdFee = '0',
maxAmountFee = '0'
}) {
// Pack transfer data into 256-bit words
const w1 = assetId;
const w2 = assetIdFee;
const w3 = receiverPublicKey;
let w4 = BigInt(senderPositionId);
w4 = (w4 << 64) + BigInt(receiverPositionId);
w4 = (w4 << 64) + BigInt(srcFeePositionId);
w4 = (w4 << 32) + BigInt(nonce);
let w5 = BigInt(transfer); // Constant identifier
w5 = (w5 << 64) + BigInt(amount);
w5 = (w5 << 64) + BigInt(maxAmountFee);
w5 = (w5 << 32) + BigInt(expirationTimestamp);
w5 = w5 << 81;
// Calculate message hash
let msg = pedersen([w1, w2]);
msg = pedersen([msg, w3]);
msg = pedersen([msg, w4.toString(16)]);
return pedersen([msg, w5.toString(16)]);
}
For more details on the signature construction, see the StarkEx documentation.
+172
View File
@@ -0,0 +1,172 @@
Authentication
Authentication is crucial for ensuring that only authorized users can access private APIs. This document outlines the authentication mechanisms used for public and private APIs.
Public API
Public APIs do not require authentication. These interfaces are accessible to anyone without the need for any credentials.
Copy
No authentication is required for public interfaces.
Private API
Private APIs require authentication to ensure that only authorized users can access them. Authentication is achieved using custom headers that include a timestamp and a signature.
Auth Header
The following headers must be included in the request to authenticate access to private APIs:
Name
Location
Type
Required
Description
X-edgeX-Api-Timestamp
header
string
must
The timestamp when the request was made. This helps prevent replay attacks.
X-edgeX-Api-Signature
header
string
must
The signature generated using the private key and request details.
CURL Examble
Copy
curl --location --request GET 'https://pro.edgex.exchange/api/v1/private/account/getPositionTransactionPage?filterTypeList=SETTLE_FUNDING_FEE&size=10&accountId=544159487963955214' \
--header 'X-edgeX-Api-Signature: 06d28020763542c0afc296dc8743797c6fda8ea9727745b57b671f70326dfed6077cd******************************aff3162e39d05d9df1c3ddf9648650382d6e62ff1076b14c0e6c687088d3917d8490e5412a080a6e9ea940c720ddd' \
--header 'X-edgeX-Api-Timestamp: 1736313025024'
Signature Elements
The signature is generated using the following elements:
Signature Element
Description
X-edgeX-Api-Timestamp
The timestamp when the request was made. This is retrieved from the request header.
Request Method (Uppercase)
The HTTP method of the request, converted to uppercase (e.g., GET, POST).
Request Path
The URI path of the request (e.g., /api/v1/resource).
Request Parameter/Body
The query parameters or request body, sorted alphabetically.
Request Parameter To Signature Content
The request parameters are concatenated into a single string that forms the signature content. This string includes the timestamp, HTTP method, request path, and sorted query parameters or request body, ensuring the integrity and authenticity of the request.
For example, the following request parameters are concatenated into a single string:
1735542383256GET/api/v1/private/account/getPositionTransactionPageaccountId=543429922991899150&filterTypeList=SETTLE_FUNDING_FEE&size=10
Generate Signature Java Example
Below is a Java implementation of the Ecdsa signature algorithm. This example demonstrates how to sign a message using a private key.
Private API Auth Signature: This is used for authentication. We do not want the hash computation to consume excessive CPU resources. Therefore, this will use SHA3 to hash the request body string before signing.
Copy
import java.math.BigInteger;
import org.web3j.abi.TypeEncoder;
import org.web3j.abi.datatypes.Utf8String;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.crypto.Hash;
import org.web3j.utils.Numeric;
public class EcdsaSignatureDemo {
public static final BigInteger K_MODULUS = Numeric
.toBigInt("0x0800000000000010ffffffffffffffffb781126dcae7b2321e66a241adc64d2f");
public static void main(String[] args) {
String privateKeyHex = "0463ac809cc7d7c1baf*********************baff9fc6e3d8e5b160ea3fc";
// Ensure that the private key is a hexadecimal string without the "0x" prefix.
if (privateKeyHex.startsWith("0x")) {
privateKeyHex = privateKeyHex.substring(2);
}
BigInteger mySecretKey = new BigInteger(privateKeyHex, 16);
PrivateKey privateKey = PrivateKey.create(mySecretKey);
String message = "1735542383256GET/api/v1/private/account/getPositionTransactionPageaccountId=543429922991899150&filterTypeList=SETTLE_FUNDING_FEE&size=10";
String msg = TypeEncoder.encodePacked(new Utf8String(message));
BigInteger msgHash = Numeric.toBigInt(Hash.sha3(Numeric.hexStringToByteArray(msg)));
msgHash = msgHash.mod(K_MODULUS);
Signature signature = Ecdsa.sign(msgHash, privateKey);
String starkSignature = TypeEncoder.encodePacked(new Uint256(signature.r)) +
TypeEncoder.encodePacked(new Uint256(signature.s)) +
TypeEncoder.encodePacked(new Uint256(privateKey.publicKey().point.y));
System.out.println(starkSignature);
}
public static Signature sign(BigInteger msgHash, PrivateKey privateKey) {
Curve curve = privateKey.curve;
BigInteger randNum = new BigInteger(curve.N.toByteArray().length * 8 - 1, new SecureRandom()).abs().add(BigInteger.ONE);
Point randomSignPoint = EcMath.multiply(curve.G, randNum, curve.N, curve.A, curve.P);
BigInteger r = randomSignPoint.x.mod(curve.N);
BigInteger s = ((msgHash.add(r.multiply(privateKey.secret))).multiply(EcMath.inv(randNum, curve.N))).mod(curve.N);
return Signature.create(r, s);
}
}
Request Body To Body String Code Example
The following Java code example demonstrates how to convert a JSON request body into a sorted string format suitable for signature generation:
Copy
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import java.util.stream.Collectors;
public class RequestBodyToString {
private static final String EMPTY_STRING = "";
private static String getValue(JsonElement valueJson) {
if (valueJson.isJsonNull()) {
return EMPTY_STRING;
} else if (valueJson.isJsonPrimitive()) {
return valueJson.getAsString();
} else if (valueJson.isJsonArray()) {
JsonArray valueArray = valueJson.getAsJsonArray();
if (valueArray.isEmpty()) {
return EMPTY_STRING;
}
List<String> values = new ArrayList<>();
for (JsonElement itemValue : valueArray) {
values.add(getValue(itemValue));
}
return String.join("&", values);
} else if (valueJson.isJsonObject()) {
TreeMap<String, String> sortedDataMap = new TreeMap<>();
JsonObject valueJsonObj = valueJson.getAsJsonObject();
for (String key : valueJsonObj.keySet()) {
sortedDataMap.put(key, getValue(valueJsonObj.get(key)));
}
return sortedDataMap.keySet().stream()
.map(key -> key + "=" + sortedDataMap.get(key))
.collect(Collectors.joining("&"));
}
return EMPTY_STRING;
}
}
Signature Algorithm
The signature algorithm used is Ecdsa (Elliptic Curve Digital Signature Algorithm).
@@ -0,0 +1,45 @@
name: Publish to PyPI
on:
release:
types: [published]
workflow_dispatch: # Allow manual triggering
jobs:
build-and-publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install build dependencies
run: |
python -m pip install --upgrade pip
pip install build twine
- name: Build package
run: python -m build
- name: Check package
run: twine check dist/*
- name: Publish to Test PyPI
if: github.event_name == 'workflow_dispatch'
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }}
run: |
twine upload --repository testpypi dist/*
- name: Publish to PyPI
if: github.event_name == 'release'
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: |
twine upload dist/*
@@ -0,0 +1,31 @@
# Logs
logs
*.log
# IDE files
.idea/
.vscode/
*.swp
*.swo
# Environment variables
.env
# Python bytecode files
__pycache__/
*.py[cod]
*$py.class
# Distribution / packaging
dist/
build/
*.egg-info/
# Virtual environments
venv/
env/
ENV/
# OS specific files
.DS_Store
Thumbs.db
+516
View File
@@ -0,0 +1,516 @@
# EdgeX Python SDK
A Python SDK for interacting with the EdgeX Exchange API. This SDK provides a comprehensive interface to the EdgeX API, allowing you to easily integrate EdgeX functionality into your Python applications.
## Features
- **Complete API Coverage**: Access all EdgeX API endpoints
- **WebSocket Support**: Real-time data streaming
- **Async/Await**: Modern Python async interface
- **Type Hints**: Comprehensive type annotations for better IDE support
- **Error Handling**: Proper error handling and validation
- **Pagination**: Support for paginated API endpoints
- **Authentication**: Automatic request signing
## Installation
### From PyPI
```bash
pip install edgex-python-sdk
```
### From Source
```bash
git clone https://github.com/edgex-Tech/edgex-python-sdk.git
cd edgex-python-sdk
pip install -e .
```
### Using Requirements Files
For production use:
```bash
pip install -r requirements.txt
```
For development (includes testing and linting tools):
```bash
pip install -r requirements-dev.txt
```
### Virtual Environment (Recommended)
It's recommended to use a virtual environment:
```bash
# Create virtual environment
python3 -m venv venv
# Activate virtual environment
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
# Or install in development mode
pip install -e .
```
## Quick Start
```python
import asyncio
import os
from edgex_sdk import Client, OrderSide
async def main():
# Create a new client
client = Client(
base_url="https://pro.edgex.exchange", # Use https://testnet.edgex.exchange for testnet
account_id=12345, # Your account ID
stark_private_key="your-stark-private-key" # Your private key
)
# Get server time
server_time = await client.get_server_time()
print(f"Server Time: {server_time}")
# Get exchange metadata
metadata = await client.get_metadata()
print(f"Available contracts: {len(metadata.get('data', {}).get('contractList', []))}")
# Get account assets
assets = await client.get_account_asset()
print(f"Account Assets: {assets}")
# Get account positions
positions = await client.get_account_positions()
print(f"Account Positions: {positions}")
# Get 24-hour market data for BNB2USDT (contract ID: 10000004)
quote = await client.get_24_hour_quote("10000004")
print(f"BNB2USDT Price: {quote}")
# Create a limit order (uncomment to place real order)
# order = await client.create_limit_order(
# contract_id="10000004", # BNB2USDT
# size="0.01",
# price="600.00",
# side=OrderSide.BUY
# )
# print(f"Order created: {order}")
# Run the async function
asyncio.run(main())
```
## Architecture
The SDK is organized into modules that correspond to the EdgeX API structure:
```
edgex_sdk/
├── __init__.py
├── client.py # Main client
├── account/ # Account API
├── asset/ # Asset API
├── funding/ # Funding API
├── internal/ # Internal utilities
├── metadata/ # Metadata API
├── order/ # Order API
├── quote/ # Quote API
├── transfer/ # Transfer API
└── ws/ # WebSocket API
```
## Available APIs
The SDK currently supports the following API modules:
- **Account API**: Manage account positions, retrieve position transactions, and handle collateral transactions
- Get account positions
- Get position by contract ID
- Get position transaction history
- Get collateral transaction details
- Update leverage settings
- **Asset API**: Handle asset management and withdrawals
- Get asset orders with pagination
- Get coin rates
- Manage withdrawals (normal, cross-chain, and fast)
- Get withdrawal records and sign information
- Check withdrawable amounts
- **Funding API**: Manage funding operations and account balance
- Handle funding transactions
- Manage funding accounts
- Get funding transaction history
- **Metadata API**: Access exchange system information
- Get server time
- Get exchange metadata (trading pairs, contracts, etc.)
- **Order API**: Comprehensive order management
- Create and cancel orders
- Get active orders
- Get order fill transactions
- Calculate maximum order sizes
- Manage order history
- **Quote API**: Access market data and pricing
- Get multi-contract K-line data
- Get order book depth
- Access real-time market quotes
- Get 24-hour ticker data
- **Transfer API**: Handle asset transfers
- Create transfer out orders
- Get transfer records (in/out)
- Check available withdrawal amounts
- Manage transfer history
- **WebSocket API**: Real-time data streaming
- Market data (tickers, K-lines, order book, trades)
- Account updates
- Order updates
- Position updates
## WebSocket Support
The SDK provides a WebSocket manager for handling real-time data:
```python
import asyncio
from edgex_sdk import WebSocketManager
async def main():
# Create a WebSocket manager
ws_manager = WebSocketManager(
base_url="wss://quote.edgex.exchange", # Use wss://quote-testnet.edgex.exchange for testnet
account_id=12345,
stark_pri_key="your-stark-private-key"
)
# Define message handlers
def ticker_handler(message):
print(f"Ticker Update: {message}")
def kline_handler(message):
print(f"K-line Update: {message}")
# Connect to public WebSocket for market data
ws_manager.connect_public()
# Subscribe to real-time updates for BNB2USDT (contract ID: 10000004)
ws_manager.subscribe_ticker("10000004", ticker_handler)
ws_manager.subscribe_kline("10000004", "1m", kline_handler)
# Connect to private WebSocket for account updates
ws_manager.connect_private()
# Wait for updates
await asyncio.sleep(30)
# Disconnect all connections
ws_manager.disconnect_all()
asyncio.run(main())
```
## Signing Adapters
The SDK provides a flexible signing mechanism through signing adapters. **StarkExSigningAdapter is used by default**, so you don't need to explicitly create one:
```python
from edgex_sdk import Client
# Create a client (uses StarkExSigningAdapter by default)
client = Client(
base_url="https://pro.edgex.exchange", # Use https://testnet.edgex.exchange for testnet
account_id=12345,
stark_private_key="your-stark-private-key"
)
```
If you need to use a custom signing adapter, you can still provide one:
```python
from edgex_sdk import Client, StarkExSigningAdapter
# Create a custom signing adapter (optional)
signing_adapter = StarkExSigningAdapter()
# Create a client with a custom signing adapter
client = Client(
base_url="https://pro.edgex.exchange", # Use https://testnet.edgex.exchange for testnet
account_id=12345,
stark_private_key="your-stark-private-key",
signing_adapter=signing_adapter
)
```
The SDK includes the following signing adapters:
- **StarkExSigningAdapter** (default): Full implementation using StarkWare cryptographic operations for production use
You can also create your own signing adapter by implementing the `SigningAdapter` interface if you need custom cryptographic operations.
## Error Handling
The SDK provides proper error handling for API requests:
```python
import asyncio
from edgex_sdk import Client, OrderSide
async def main():
client = Client(
base_url="https://pro.edgex.exchange", # Use https://testnet.edgex.exchange for testnet
account_id=12345,
stark_private_key="your-stark-private-key"
)
try:
# Create a limit order for BNB2USDT
order = await client.create_limit_order(
contract_id="10000004", # BNB2USDT
size="0.01",
price="600.00",
side=OrderSide.BUY
)
print(f"Order created: {order}")
# Cancel the order
from edgex_sdk import CancelOrderParams
cancel_params = CancelOrderParams(
order_id=order.get("data", {}).get("orderId")
)
cancel_result = await client.cancel_order(cancel_params)
print(f"Order cancelled: {cancel_result}")
except ValueError as e:
print(f"Failed to create/cancel order: {str(e)}")
except Exception as e:
print(f"Unexpected error: {str(e)}")
asyncio.run(main())
```
## Pagination
Many API endpoints support pagination:
```python
import asyncio
from edgex_sdk import Client, GetActiveOrderParams
async def main():
client = Client(
base_url="https://pro.edgex.exchange", # Use https://testnet.edgex.exchange for testnet
account_id=12345,
stark_private_key="your-stark-private-key"
)
# Create pagination parameters
params = GetActiveOrderParams(
size="10",
offset_data=""
)
# Get active orders
orders = await client.get_active_orders(params)
print(f"Active orders: {orders}")
# Get next page if available
if orders.get("data", {}).get("hasNext"):
params.offset_data = orders.get("data", {}).get("offsetData")
next_page = await client.get_active_orders(params)
print(f"Next page: {next_page}")
asyncio.run(main())
```
## API Examples
### Market Data
```python
from edgex_sdk import Client, GetKLineParams, GetOrderBookDepthParams
# Get 24-hour market quotes for BNB2USDT (contract ID: 10000004)
quote = await client.get_24_hour_quote("10000004")
print(f"Current price: {quote}")
# Get K-line data for BTCUSDT (contract ID: 10000001)
kline_params = GetKLineParams(
contract_id="10000001", # BTCUSDT
interval="1m",
size="10"
)
klines = await client.quote.get_k_line(kline_params)
print(f"K-lines: {klines}")
# Get order book depth for ETHUSDT (contract ID: 10000002)
depth_params = GetOrderBookDepthParams(
contract_id="10000002", # ETHUSDT
limit=10
)
depth = await client.quote.get_order_book_depth(depth_params)
print(f"Order book: {depth}")
```
### Account Management
```python
# Get account assets
assets = await client.get_account_asset()
print(f"Account assets: {assets}")
# Get account positions
positions = await client.get_account_positions()
print(f"Positions: {positions}")
# Get position transactions
from edgex_sdk import GetPositionTransactionPageParams
tx_params = GetPositionTransactionPageParams(
size="10",
offset_data=""
)
transactions = await client.account.get_position_transaction_page(tx_params)
print(f"Transactions: {transactions}")
```
### Order Management
```python
from edgex_sdk import OrderSide, CreateOrderParams, CancelOrderParams
# Create a limit order for BNBUSDT
order = await client.create_limit_order(
contract_id="10000004", # BNBUSDT
size="0.01",
price="600.00",
side=OrderSide.BUY
)
print(f"Order created: {order}")
# Get maximum order size for BNBUSDT
max_size = await client.get_max_order_size("10000004", 600.00)
print(f"Max order size: {max_size}")
# Cancel an order
cancel_params = CancelOrderParams(
order_id=order.get("data", {}).get("orderId")
)
cancel_result = await client.cancel_order(cancel_params)
print(f"Order cancelled: {cancel_result}")
```
### Contract IDs
EdgeX uses numeric contract IDs instead of symbol-based identifiers. Here are some common contract mappings:
| Contract ID | Symbol | Tick Size |
|-------------|---------------|-----------|
| 10000001 | BTCUSDT | 0.1 |
| 10000002 | ETHUSDT | 0.01 |
| 10000003 | SOLUSDT | 0.01 |
To get the complete list of available contracts:
```python
metadata = await client.get_metadata()
contracts = metadata.get("data", {}).get("contractList", [])
for contract in contracts:
print(f"ID: {contract['contractId']} - {contract['contractName']}")
```
For more detailed examples, please refer to the [examples](examples) directory.
## Testing
The SDK includes comprehensive test coverage with multiple test suites:
### Unit Tests
```bash
# Run unit tests (no API credentials required)
python -m pytest tests/test_client.py tests/test_starkex_signing_adapter.py -v
```
### Public API Tests
```bash
# Run public endpoint tests (no authentication required)
python run_public_tests.py
```
### Mock Integration Tests
```bash
# Run mock tests (test structure without real API calls)
python run_mock_tests.py
```
### Full Integration Tests
```bash
# Run full integration tests (requires real API credentials)
python run_integration_tests.py
```
### All Tests
```bash
# Run all available tests
python run_tests.py
```
For more testing information, see [TESTING.md](TESTING.md).
## Environment Variables
For testing and development, you can set the following environment variables or create a `.env` file:
```bash
# API Configuration
EDGEX_BASE_URL=https://pro.edgex.exchange # Use https://testnet.edgex.exchange for testnet
EDGEX_WS_URL=wss://quote.edgex.exchange # Use wss://quote-testnet.edgex.exchange for testnet
# Account Credentials
EDGEX_ACCOUNT_ID=12345
EDGEX_STARK_PRIVATE_KEY=your-stark-private-key
# Signing Configuration
EDGEX_SIGNING_ADAPTER=starkex
```
Then load them in your code:
```python
import os
from dotenv import load_dotenv
from edgex_sdk import Client
# Load environment variables from .env file
load_dotenv()
client = Client(
base_url=os.getenv("EDGEX_BASE_URL"),
account_id=int(os.getenv("EDGEX_ACCOUNT_ID")),
stark_private_key=os.getenv("EDGEX_STARK_PRIVATE_KEY")
)
```
## Documentation
For detailed API documentation, please refer to the [EdgeX API documentation](https://docs.edgex.exchange).
## Contributing
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin feature/my-new-feature`)
5. Create a new Pull Request
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,72 @@
"""
EdgeX Python SDK - A Python SDK for interacting with the EdgeX Exchange API.
"""
from .client import Client
from .internal.signing_adapter import SigningAdapter
from .internal.starkex_signing_adapter import StarkExSigningAdapter
from .order.types import (
OrderType,
OrderSide,
TimeInForce,
CreateOrderParams,
CancelOrderParams,
GetActiveOrderParams,
OrderFillTransactionParams
)
from .account.client import (
GetPositionTransactionPageParams,
GetCollateralTransactionPageParams,
GetPositionTermPageParams,
GetAccountAssetSnapshotPageParams
)
from .quote.client import (
GetKLineParams,
GetOrderBookDepthParams,
GetMultiContractKLineParams
)
from .transfer.client import (
GetTransferOutByIdParams,
GetTransferInByIdParams,
GetWithdrawAvailableAmountParams,
CreateTransferOutParams,
GetTransferOutPageParams,
GetTransferInPageParams
)
from .asset.client import (
GetAssetOrdersParams,
CreateWithdrawalParams,
GetWithdrawalRecordsParams
)
from .ws.manager import Manager as WebSocketManager
__version__ = "0.2.0"
__all__ = [
"Client",
"OrderType",
"OrderSide",
"TimeInForce",
"CreateOrderParams",
"CancelOrderParams",
"GetActiveOrderParams",
"OrderFillTransactionParams",
"GetPositionTransactionPageParams",
"GetCollateralTransactionPageParams",
"GetPositionTermPageParams",
"GetAccountAssetSnapshotPageParams",
"GetKLineParams",
"GetOrderBookDepthParams",
"GetMultiContractKLineParams",
"GetTransferOutByIdParams",
"GetTransferInByIdParams",
"GetWithdrawAvailableAmountParams",
"CreateTransferOutParams",
"GetTransferOutPageParams",
"GetTransferInPageParams",
"GetAssetOrdersParams",
"CreateWithdrawalParams",
"GetWithdrawalRecordsParams",
"WebSocketManager",
"SigningAdapter",
"StarkExSigningAdapter"
]
@@ -0,0 +1,437 @@
from typing import Dict, Any, List, Optional
from ..internal.async_client import AsyncClient
class GetPositionTransactionPageParams:
"""Parameters for getting position transactions with pagination."""
def __init__(
self,
size: str = "",
offset_data: str = "",
filter_contract_id_list: List[str] = None,
filter_start_created_time_inclusive: int = 0,
filter_end_created_time_exclusive: int = 0
):
self.size = size
self.offset_data = offset_data
self.filter_contract_id_list = filter_contract_id_list or []
self.filter_start_created_time_inclusive = filter_start_created_time_inclusive
self.filter_end_created_time_exclusive = filter_end_created_time_exclusive
class GetCollateralTransactionPageParams:
"""Parameters for getting collateral transactions with pagination."""
def __init__(
self,
size: str = "",
offset_data: str = "",
filter_start_created_time_inclusive: int = 0,
filter_end_created_time_exclusive: int = 0
):
self.size = size
self.offset_data = offset_data
self.filter_start_created_time_inclusive = filter_start_created_time_inclusive
self.filter_end_created_time_exclusive = filter_end_created_time_exclusive
class GetPositionTermPageParams:
"""Parameters for getting position terms with pagination."""
def __init__(
self,
size: str = "",
offset_data: str = "",
filter_contract_id_list: List[str] = None,
filter_start_created_time_inclusive: int = 0,
filter_end_created_time_exclusive: int = 0
):
self.size = size
self.offset_data = offset_data
self.filter_contract_id_list = filter_contract_id_list or []
self.filter_start_created_time_inclusive = filter_start_created_time_inclusive
self.filter_end_created_time_exclusive = filter_end_created_time_exclusive
class GetAccountAssetSnapshotPageParams:
"""Parameters for getting account asset snapshots with pagination."""
def __init__(
self,
size: str = "",
offset_data: str = "",
filter_start_created_time_inclusive: int = 0,
filter_end_created_time_exclusive: int = 0
):
self.size = size
self.offset_data = offset_data
self.filter_start_created_time_inclusive = filter_start_created_time_inclusive
self.filter_end_created_time_exclusive = filter_end_created_time_exclusive
class Client:
"""Client for account-related API endpoints."""
def __init__(self, async_client: AsyncClient):
"""
Initialize the account client.
Args:
async_client: The async client for common functionality
"""
self.async_client = async_client
async def get_account_asset(self) -> Dict[str, Any]:
"""
Get the account asset information.
Returns:
Dict[str, Any]: The account asset information
Raises:
ValueError: If the request fails
"""
params = {
"accountId": str(self.async_client.get_account_id())
}
return await self.async_client.make_authenticated_request(
method="GET",
path="/api/v1/private/account/getAccountAsset",
params=params
)
async def get_account_positions(self) -> Dict[str, Any]:
"""
Get the account positions.
Note: This calls the same endpoint as get_account_asset, which returns both
collateral and position data. The position data is in the 'positionAssetList' field.
Returns:
Dict[str, Any]: The account positions (same as account asset response)
Raises:
ValueError: If the request fails
"""
# Use the same endpoint as get_account_asset (matching Go SDK behavior)
return await self.get_account_asset()
async def get_position_transaction_page(self, params: GetPositionTransactionPageParams) -> Dict[str, Any]:
"""
Get the position transactions with pagination.
Args:
params: Position transaction query parameters
Returns:
Dict[str, Any]: The position transactions
Raises:
ValueError: If the request fails
"""
query_params = {
"accountId": str(self.async_client.get_account_id())
}
# Add pagination parameters
if params.size:
query_params["size"] = params.size
if params.offset_data:
query_params["offsetData"] = params.offset_data
# Add filter parameters
if params.filter_contract_id_list:
query_params["filterContractIdList"] = ",".join(params.filter_contract_id_list)
# Add time filters
if params.filter_start_created_time_inclusive > 0:
query_params["filterStartCreatedTimeInclusive"] = str(params.filter_start_created_time_inclusive)
if params.filter_end_created_time_exclusive > 0:
query_params["filterEndCreatedTimeExclusive"] = str(params.filter_end_created_time_exclusive)
return await self.async_client.make_authenticated_request(
method="GET",
path="/api/v1/private/account/getPositionTransactionPage",
params=query_params
)
async def get_collateral_transaction_page(self, params: GetCollateralTransactionPageParams) -> Dict[str, Any]:
"""
Get the collateral transactions with pagination.
Args:
params: Collateral transaction query parameters
Returns:
Dict[str, Any]: The collateral transactions
Raises:
ValueError: If the request fails
"""
query_params = {
"accountId": str(self.async_client.get_account_id())
}
# Add pagination parameters
if params.size:
query_params["size"] = params.size
if params.offset_data:
query_params["offsetData"] = params.offset_data
# Add time filters
if params.filter_start_created_time_inclusive > 0:
query_params["filterStartCreatedTimeInclusive"] = str(params.filter_start_created_time_inclusive)
if params.filter_end_created_time_exclusive > 0:
query_params["filterEndCreatedTimeExclusive"] = str(params.filter_end_created_time_exclusive)
return await self.async_client.make_authenticated_request(
method="GET",
path="/api/v1/private/account/getCollateralTransactionPage",
params=query_params
)
async def get_position_term_page(self, params: GetPositionTermPageParams) -> Dict[str, Any]:
"""
Get the position terms with pagination.
Args:
params: Position term query parameters
Returns:
Dict[str, Any]: The position terms
Raises:
ValueError: If the request fails
"""
url = f"{self.base_url}/api/v1/private/account/getPositionTermPage"
query_params = {
"accountId": str(self.internal_client.get_account_id())
}
# Add pagination parameters
if params.size:
query_params["size"] = params.size
if params.offset_data:
query_params["offsetData"] = params.offset_data
# Add filter parameters
if params.filter_contract_id_list:
query_params["filterContractIdList"] = ",".join(params.filter_contract_id_list)
# Add time filters
if params.filter_start_created_time_inclusive > 0:
query_params["filterStartCreatedTimeInclusive"] = str(params.filter_start_created_time_inclusive)
if params.filter_end_created_time_exclusive > 0:
query_params["filterEndCreatedTimeExclusive"] = str(params.filter_end_created_time_exclusive)
response = self.session.get(url, params=query_params)
if response.status_code != 200:
raise ValueError(f"request failed with status code: {response.status_code}")
resp_data = response.json()
if resp_data.get("code") != ResponseCode.SUCCESS:
error_param = resp_data.get("errorParam")
if error_param:
raise ValueError(f"request failed with error params: {error_param}")
raise ValueError(f"request failed with code: {resp_data.get('code')}")
return resp_data
async def get_account_by_id(self) -> Dict[str, Any]:
"""
Get account information by ID.
Returns:
Dict[str, Any]: The account information
Raises:
ValueError: If the request fails
"""
params = {
"accountId": str(self.async_client.get_account_id())
}
return await self.async_client.make_authenticated_request(
method="GET",
path="/api/v1/private/account/getAccountById",
params=params
)
async def get_account_deleverage_light(self) -> Dict[str, Any]:
"""
Get account deleverage light information.
Returns:
Dict[str, Any]: The account deleverage light information
Raises:
ValueError: If the request fails
"""
url = f"{self.base_url}/api/v1/private/account/getAccountDeleverageLight"
params = {
"accountId": str(self.internal_client.get_account_id())
}
response = self.session.get(url, params=params)
if response.status_code != 200:
raise ValueError(f"request failed with status code: {response.status_code}")
resp_data = response.json()
if resp_data.get("code") != ResponseCode.SUCCESS:
error_param = resp_data.get("errorParam")
if error_param:
raise ValueError(f"request failed with error params: {error_param}")
raise ValueError(f"request failed with code: {resp_data.get('code')}")
return resp_data
async def get_account_asset_snapshot_page(self, params: GetAccountAssetSnapshotPageParams) -> Dict[str, Any]:
"""
Get account asset snapshots with pagination.
Args:
params: Account asset snapshot query parameters
Returns:
Dict[str, Any]: The account asset snapshots
Raises:
ValueError: If the request fails
"""
url = f"{self.base_url}/api/v1/private/account/getAccountAssetSnapshotPage"
query_params = {
"accountId": str(self.internal_client.get_account_id())
}
# Add pagination parameters
if params.size:
query_params["size"] = params.size
if params.offset_data:
query_params["offsetData"] = params.offset_data
# Add time filters
if params.filter_start_created_time_inclusive > 0:
query_params["filterStartCreatedTimeInclusive"] = str(params.filter_start_created_time_inclusive)
if params.filter_end_created_time_exclusive > 0:
query_params["filterEndCreatedTimeExclusive"] = str(params.filter_end_created_time_exclusive)
response = self.session.get(url, params=query_params)
if response.status_code != 200:
raise ValueError(f"request failed with status code: {response.status_code}")
resp_data = response.json()
if resp_data.get("code") != ResponseCode.SUCCESS:
error_param = resp_data.get("errorParam")
if error_param:
raise ValueError(f"request failed with error params: {error_param}")
raise ValueError(f"request failed with code: {resp_data.get('code')}")
return resp_data
async def get_position_transaction_by_id(self, transaction_ids: List[str]) -> Dict[str, Any]:
"""
Get position transactions by IDs.
Args:
transaction_ids: List of transaction IDs
Returns:
Dict[str, Any]: The position transactions
Raises:
ValueError: If the request fails
"""
url = f"{self.base_url}/api/v1/private/account/getPositionTransactionById"
query_params = {
"accountId": str(self.internal_client.get_account_id()),
"transactionIdList": ",".join(transaction_ids)
}
response = self.session.get(url, params=query_params)
if response.status_code != 200:
raise ValueError(f"request failed with status code: {response.status_code}")
resp_data = response.json()
if resp_data.get("code") != ResponseCode.SUCCESS:
error_param = resp_data.get("errorParam")
if error_param:
raise ValueError(f"request failed with error params: {error_param}")
raise ValueError(f"request failed with code: {resp_data.get('code')}")
return resp_data
async def get_collateral_transaction_by_id(self, transaction_ids: List[str]) -> Dict[str, Any]:
"""
Get collateral transactions by IDs.
Args:
transaction_ids: List of transaction IDs
Returns:
Dict[str, Any]: The collateral transactions
Raises:
ValueError: If the request fails
"""
url = f"{self.base_url}/api/v1/private/account/getCollateralTransactionById"
query_params = {
"accountId": str(self.internal_client.get_account_id()),
"transactionIdList": ",".join(transaction_ids)
}
response = self.session.get(url, params=query_params)
if response.status_code != 200:
raise ValueError(f"request failed with status code: {response.status_code}")
resp_data = response.json()
if resp_data.get("code") != ResponseCode.SUCCESS:
error_param = resp_data.get("errorParam")
if error_param:
raise ValueError(f"request failed with error params: {error_param}")
raise ValueError(f"request failed with code: {resp_data.get('code')}")
return resp_data
async def update_leverage_setting(self, contract_id: str, leverage: str) -> None:
"""
Update the account leverage settings.
Args:
contract_id: The contract ID
leverage: The leverage value
Raises:
ValueError: If the request fails
"""
url = f"{self.base_url}/api/v1/private/account/updateLeverageSetting"
data = {
"accountId": str(self.internal_client.get_account_id()),
"contractId": contract_id,
"leverage": leverage
}
response = self.session.post(url, json=data)
if response.status_code != 200:
raise ValueError(f"request failed with status code: {response.status_code}")
resp_data = response.json()
if resp_data.get("code") != ResponseCode.SUCCESS:
error_param = resp_data.get("errorParam")
if error_param:
raise ValueError(f"request failed with error params: {error_param}")
raise ValueError(f"request failed with code: {resp_data.get('code')}")
@@ -0,0 +1,300 @@
from typing import Dict, Any, List
from ..internal.async_client import AsyncClient
class GetAssetOrdersParams:
"""Parameters for getting asset orders."""
def __init__(self, size: str = "10", offset_data: str = "", filter_coin_id_list: List[str] = None,
filter_start_created_time_inclusive: int = 0, filter_end_created_time_exclusive: int = 0):
self.size = size
self.offset_data = offset_data
self.filter_coin_id_list = filter_coin_id_list or []
self.filter_start_created_time_inclusive = filter_start_created_time_inclusive
self.filter_end_created_time_exclusive = filter_end_created_time_exclusive
class CreateWithdrawalParams:
"""Parameters for creating a withdrawal."""
def __init__(self, coin_id: str, amount: str, address: str, tag: str = ""):
self.coin_id = coin_id
self.amount = amount
self.address = address
self.tag = tag
class GetWithdrawalRecordsParams:
"""Parameters for getting withdrawal records."""
def __init__(self, size: str = "10", offset_data: str = "", filter_coin_id_list: List[str] = None,
filter_status_list: List[str] = None, filter_start_created_time_inclusive: int = 0,
filter_end_created_time_exclusive: int = 0):
self.size = size
self.offset_data = offset_data
self.filter_coin_id_list = filter_coin_id_list or []
self.filter_status_list = filter_status_list or []
self.filter_start_created_time_inclusive = filter_start_created_time_inclusive
self.filter_end_created_time_exclusive = filter_end_created_time_exclusive
class Client:
"""Client for asset-related API endpoints."""
def __init__(self, async_client: AsyncClient):
"""
Initialize the asset client.
Args:
async_client: The async client for common functionality
"""
self.async_client = async_client
async def get_account_asset(self) -> Dict[str, Any]:
"""
Get the account asset information.
Note: This method delegates to the account client since it's an account endpoint.
Returns:
Dict[str, Any]: The account asset information
Raises:
ValueError: If the request fails
"""
# This is actually an account endpoint, not an asset endpoint
# We should delegate to the account client
raise NotImplementedError("This method should be called from the account client: client.account.get_account_asset()")
async def get_asset_orders(
self,
params: GetAssetOrdersParams
) -> Dict[str, Any]:
"""
Get asset orders with pagination.
Args:
params: Parameters for the request
Returns:
Dict[str, Any]: The asset orders
Raises:
ValueError: If the request fails
"""
query_params = {
"accountId": str(self.async_client.get_account_id())
}
# Add pagination parameters
if params.size:
query_params["size"] = params.size
if params.offset_data:
query_params["offsetData"] = params.offset_data
# Add filter parameters
if params.filter_coin_id_list:
query_params["filterCoinIdList"] = ",".join(params.filter_coin_id_list)
# Add time filters
if params.filter_start_created_time_inclusive > 0:
query_params["filterStartCreatedTimeInclusive"] = str(params.filter_start_created_time_inclusive)
if params.filter_end_created_time_exclusive > 0:
query_params["filterEndCreatedTimeExclusive"] = str(params.filter_end_created_time_exclusive)
return await self.async_client.make_authenticated_request(
method="GET",
path="/api/v1/private/assets/getAllOrdersPage",
params=query_params
)
async def get_coin_rates(self, chain_id: str = "1", coin: str = "0xdac17f958d2ee523a2206206994597c13d831ec7") -> Dict[str, Any]:
"""
Get coin rates.
Args:
chain_id: Chain ID (default: "1" for Ethereum mainnet)
coin: Coin contract address (default: USDT)
Returns:
Dict[str, Any]: The coin rates
Raises:
ValueError: If the request fails
"""
params = {
"chainId": chain_id,
"coin": coin
}
return await self.async_client.make_authenticated_request(
method="GET",
path="/api/v1/private/assets/getCoinRate",
params=params
)
async def create_withdrawal(
self,
coin_id: str,
amount: str,
address: str,
network: str,
memo: str = "",
client_order_id: str = None
) -> Dict[str, Any]:
"""
Create a withdrawal request.
Args:
coin_id: The coin ID
amount: The withdrawal amount
address: The withdrawal address
network: The network
memo: Optional memo
client_order_id: Optional client order ID
Returns:
Dict[str, Any]: The withdrawal result
Raises:
ValueError: If the request fails
"""
data = {
"accountId": str(self.async_client.get_account_id()),
"coinId": coin_id,
"amount": amount,
"address": address,
"network": network
}
if memo:
data["memo"] = memo
if client_order_id:
data["clientOrderId"] = client_order_id
else:
data["clientOrderId"] = self.async_client.generate_uuid()
return await self.async_client.make_authenticated_request(
method="POST",
path="/api/v1/private/assets/createNormalWithdraw",
data=data
)
async def get_withdrawal_records(
self,
size: str = "",
offset_data: str = "",
filter_coin_id_list: List[str] = None,
filter_status_list: List[str] = None,
filter_start_created_time_inclusive: int = 0,
filter_end_created_time_exclusive: int = 0
) -> Dict[str, Any]:
"""
Get withdrawal records with pagination.
Args:
size: Size of the page
offset_data: Offset data for pagination
filter_coin_id_list: Filter by coin IDs
filter_status_list: Filter by status
filter_start_created_time_inclusive: Filter start time (inclusive)
filter_end_created_time_exclusive: Filter end time (exclusive)
Returns:
Dict[str, Any]: The withdrawal records
Raises:
ValueError: If the request fails
"""
query_params = {
"accountId": str(self.async_client.get_account_id())
}
# Add pagination parameters
if size:
query_params["size"] = size
if offset_data:
query_params["offsetData"] = offset_data
# Add filter parameters
if filter_coin_id_list:
query_params["filterCoinIdList"] = ",".join(filter_coin_id_list)
if filter_status_list:
query_params["filterStatusList"] = ",".join(filter_status_list)
# Add time filters
if filter_start_created_time_inclusive > 0:
query_params["filterStartCreatedTimeInclusive"] = str(filter_start_created_time_inclusive)
if filter_end_created_time_exclusive > 0:
query_params["filterEndCreatedTimeExclusive"] = str(filter_end_created_time_exclusive)
return await self.async_client.make_authenticated_request(
method="GET",
path="/api/v1/private/assets/getNormalWithdrawById",
params=query_params
)
async def get_withdrawable_amount(self, address: str) -> Dict[str, Any]:
"""
Get the withdrawable amount for a coin.
Args:
address: The coin contract address
Returns:
Dict[str, Any]: The withdrawable amount information
Raises:
ValueError: If the request fails
"""
query_params = {
"address": address
}
return await self.async_client.make_authenticated_request(
method="GET",
path="/api/v1/private/assets/getNormalWithdrawableAmount",
params=query_params
)
async def get_withdrawal_records(self, params: GetWithdrawalRecordsParams) -> Dict[str, Any]:
"""
Get withdrawal records with pagination.
Args:
params: Parameters for the request
Returns:
Dict[str, Any]: The withdrawal records
Raises:
ValueError: If the request fails
"""
query_params = {
"accountId": str(self.async_client.get_account_id())
}
# Add pagination parameters
if params.size:
query_params["size"] = params.size
if params.offset_data:
query_params["offsetData"] = params.offset_data
# Add filter parameters
if params.filter_coin_id_list:
query_params["filterCoinIdList"] = ",".join(params.filter_coin_id_list)
if params.filter_status_list:
query_params["filterStatusList"] = ",".join(params.filter_status_list)
# Add time filters
if params.filter_start_created_time_inclusive > 0:
query_params["filterStartCreatedTimeInclusive"] = str(params.filter_start_created_time_inclusive)
if params.filter_end_created_time_exclusive > 0:
query_params["filterEndCreatedTimeExclusive"] = str(params.filter_end_created_time_exclusive)
return await self.async_client.make_authenticated_request(
method="GET",
path="/api/v1/private/assets/getNormalWithdrawById",
params=query_params
)
@@ -0,0 +1,264 @@
import json
import time
from typing import Dict, Any, Optional, List, Union
from decimal import Decimal
from .internal.async_client import AsyncClient
from .internal.signing_adapter import SigningAdapter
from .internal.starkex_signing_adapter import StarkExSigningAdapter
from .account.client import Client as AccountClient
from .asset.client import Client as AssetClient
from .funding.client import Client as FundingClient
from .metadata.client import Client as MetadataClient
from .order.client import Client as OrderClient
from .quote.client import Client as QuoteClient
from .transfer.client import Client as TransferClient
from .order.types import CreateOrderParams, CancelOrderParams, GetActiveOrderParams, OrderFillTransactionParams
class Client:
"""Main EdgeX SDK client."""
def __init__(self, base_url: str, account_id: int, stark_private_key: str,
signing_adapter: Optional[SigningAdapter] = None, timeout: float = 30.0):
"""
Initialize the EdgeX SDK client.
Args:
base_url: Base URL for API endpoints
account_id: Account ID for authentication
stark_private_key: Stark private key for signing
signing_adapter: Optional signing adapter (defaults to StarkExSigningAdapter)
timeout: Request timeout in seconds
"""
# Use StarkExSigningAdapter as default if none provided
if signing_adapter is None:
signing_adapter = StarkExSigningAdapter()
# Create async client
self.async_client = AsyncClient(
base_url=base_url,
account_id=account_id,
stark_pri_key=stark_private_key,
signing_adapter=signing_adapter,
timeout=timeout
)
# Initialize API clients
self.metadata = MetadataClient(self.async_client)
self.account = AccountClient(self.async_client)
self.order = OrderClient(self.async_client)
self.quote = QuoteClient(self.async_client)
self.funding = FundingClient(self.async_client)
self.transfer = TransferClient(self.async_client)
self.asset = AssetClient(self.async_client)
async def __aenter__(self):
"""Async context manager entry."""
await self.async_client._ensure_session()
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
"""Async context manager exit."""
await self.close()
async def close(self):
"""Close the client and cleanup resources."""
await self.async_client.close()
@property
def internal_client(self):
"""Backward compatibility property for accessing internal client."""
return self.async_client
async def get_metadata(self) -> Dict[str, Any]:
"""Get the exchange metadata."""
return await self.metadata.get_metadata()
async def get_server_time(self) -> Dict[str, Any]:
"""Get the current server time."""
return await self.metadata.get_server_time()
async def create_order(self, params: CreateOrderParams) -> Dict[str, Any]:
"""
Create a new order with the given parameters.
Args:
params: Order parameters
Returns:
Dict[str, Any]: The created order
"""
# Get metadata first
metadata = await self.get_metadata()
if not metadata:
raise ValueError("failed to get metadata")
return await self.order.create_order(params, metadata.get("data", {}))
async def get_max_order_size(self, contract_id: str, price: Decimal) -> Dict[str, Any]:
"""
Get the maximum order size for a given contract and price.
Args:
contract_id: The contract ID
price: The price
Returns:
Dict[str, Any]: The maximum order size information
"""
return await self.order.get_max_order_size(contract_id, float(price))
async def cancel_order(self, params: CancelOrderParams) -> Dict[str, Any]:
"""
Cancel a specific order.
Args:
params: Cancel order parameters
Returns:
Dict[str, Any]: The cancellation result
"""
return await self.order.cancel_order(params)
async def get_active_orders(self, params: GetActiveOrderParams) -> Dict[str, Any]:
"""
Get active orders with pagination and filters.
Args:
params: Active order query parameters
Returns:
Dict[str, Any]: The active orders
"""
return await self.order.get_active_orders(params)
async def get_order_fill_transactions(self, params: OrderFillTransactionParams) -> Dict[str, Any]:
"""
Get order fill transactions with pagination and filters.
Args:
params: Order fill transaction query parameters
Returns:
Dict[str, Any]: The order fill transactions
"""
return await self.order.get_order_fill_transactions(params)
async def get_account_asset(self) -> Dict[str, Any]:
"""Get the account asset information."""
return await self.account.get_account_asset()
async def get_account_positions(self) -> Dict[str, Any]:
"""Get the account positions."""
return await self.account.get_account_positions()
async def create_limit_order(
self,
contract_id: str,
size: str,
price: str,
side: str,
client_order_id: Optional[str] = None
) -> Dict[str, Any]:
"""
Create a new limit order with the given parameters.
Args:
contract_id: The contract ID
size: The order size
price: The order price
side: The order side (BUY or SELL)
client_order_id: Optional client order ID
Returns:
Dict[str, Any]: The created order
"""
from .order.types import OrderType
params = CreateOrderParams(
contract_id=contract_id,
size=size,
price=price,
side=side,
type=OrderType.LIMIT,
client_order_id=client_order_id
)
return await self.create_order(params)
async def create_market_order(
self,
contract_id: str,
size: str,
side: str,
client_order_id: Optional[str] = None
) -> Dict[str, Any]:
"""
Create a new market order with the given parameters.
Args:
contract_id: The contract ID
size: The order size
side: The order side (BUY or SELL)
client_order_id: Optional client order ID
Returns:
Dict[str, Any]: The created order
"""
# Get metadata for contract info
metadata = await self.get_metadata()
if not metadata:
raise ValueError("failed to get metadata")
# Find the contract
contract = None
contract_list = metadata.get("data", {}).get("contractList", [])
for c in contract_list:
if c.get("contractId") == contract_id:
contract = c
break
if not contract:
raise ValueError(f"contract not found: {contract_id}")
# Calculate price based on side
from .order.types import OrderSide, OrderType
if side == OrderSide.BUY:
# For buy orders: oracle_price * 10, rounded to price precision
quote = await self.get_24_hour_quote(contract_id)
if not quote:
raise ValueError("failed to get 24-hour quotes")
oracle_price = Decimal(quote.get("data", [])[0].get("oraclePrice", "0"))
multiplier = Decimal("10")
tick_size = Decimal(contract.get("tickSize", "0"))
precision = abs(tick_size.as_tuple().exponent)
price = str(round(oracle_price * multiplier, precision))
else:
# For sell orders: use tick size
price = contract.get("tickSize", "0")
params = CreateOrderParams(
contract_id=contract_id,
size=size,
price=price,
side=side,
type=OrderType.MARKET,
client_order_id=client_order_id
)
return await self.create_order(params)
async def get_24_hour_quote(self, contract_id: str) -> Dict[str, Any]:
"""
Get the 24-hour quotes for a given contract.
Args:
contract_id: The contract ID
Returns:
Dict[str, Any]: The 24-hour quotes
"""
return await self.quote.get_24_hour_quote(contract_id)
@@ -0,0 +1,13 @@
"""
Cryptographic utilities for the EdgeX Python SDK.
This module provides cryptographic functions including Pedersen hash
implementation compatible with StarkWare's specifications.
"""
from .pedersen_hash import pedersen_hash, pedersen_hash_as_point
__all__ = [
'pedersen_hash',
'pedersen_hash_as_point',
]
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,209 @@
"""
Pedersen hash implementation for StarkWare cryptography.
This module provides a full implementation of the Pedersen hash function
as specified by StarkWare, compatible with the reference implementation.
"""
from typing import List, Tuple, Union
# Handle both relative and absolute imports
try:
from .constants import (
FIELD_PRIME, ALPHA, BETA, N_ELEMENT_BITS_HASH,
SHIFT_POINT, CONSTANT_POINTS
)
except ImportError:
from constants import (
FIELD_PRIME, ALPHA, BETA, N_ELEMENT_BITS_HASH,
SHIFT_POINT, CONSTANT_POINTS
)
def _div_mod(n: int, m: int, p: int) -> int:
"""
Calculate (n / m) mod p.
Args:
n: The numerator
m: The denominator
p: The modulus
Returns:
int: The result of the division modulo p
"""
return (n * pow(m, -1, p)) % p
def _ec_add(p1: Tuple[int, int], p2: Tuple[int, int]) -> Tuple[int, int]:
"""
Add two points on the elliptic curve.
Args:
p1: The first point as (x, y) coordinates
p2: The second point as (x, y) coordinates
Returns:
Tuple[int, int]: The resulting point as (x, y) coordinates
"""
if p1[0] == p2[0]:
if (p1[1] + p2[1]) % FIELD_PRIME == 0:
# The points are negatives of each other, return the point at infinity
# We represent the point at infinity as None, but this should never happen
# in our use case, so we raise an exception instead
raise ValueError("Points are negatives of each other")
# The points are the same, so we're doubling
return _ec_double(p1)
# Calculate the slope
slope = _div_mod(p2[1] - p1[1], p2[0] - p1[0], FIELD_PRIME)
# Calculate the new point
x3 = (slope * slope - p1[0] - p2[0]) % FIELD_PRIME
y3 = (slope * (p1[0] - x3) - p1[1]) % FIELD_PRIME
return (x3, y3)
def _ec_double(p: Tuple[int, int]) -> Tuple[int, int]:
"""
Double a point on the elliptic curve.
Args:
p: The point to double as (x, y) coordinates
Returns:
Tuple[int, int]: The resulting point as (x, y) coordinates
"""
# Calculate the slope
slope = _div_mod(3 * p[0] * p[0] + ALPHA, 2 * p[1], FIELD_PRIME)
# Calculate the new point
x3 = (slope * slope - 2 * p[0]) % FIELD_PRIME
y3 = (slope * (p[0] - x3) - p[1]) % FIELD_PRIME
return (x3, y3)
def _ec_mult(m: int, p: Tuple[int, int]) -> Tuple[int, int]:
"""
Multiply a point on the elliptic curve by a scalar.
Args:
m: The scalar
p: The point as (x, y) coordinates
Returns:
Tuple[int, int]: The resulting point as (x, y) coordinates
"""
if m == 0:
raise ValueError("Cannot multiply by 0")
if m == 1:
return p
if m % 2 == 0:
return _ec_mult(m // 2, _ec_double(p))
else:
return _ec_add(p, _ec_mult(m - 1, p))
def pedersen_hash_as_point(*elements: int) -> Tuple[int, int]:
"""
Calculate the Pedersen hash of a list of integers and return the full EC point.
This is the full implementation following StarkWare's specification:
For each element, iterate through its 252 bits and add corresponding
constant points based on the bit values.
Args:
*elements: Variable number of integers to hash
Returns:
Tuple[int, int]: The resulting EC point as (x, y) coordinates
Raises:
ValueError: If any element is out of range or if there are insufficient constant points
"""
# Start with the shift point
point = tuple(SHIFT_POINT)
for i, element in enumerate(elements):
# Validate element is in valid range
if not (0 <= element < FIELD_PRIME):
raise ValueError(f"Element {element} is out of range [0, {FIELD_PRIME})")
# Calculate the starting index for this element's constant points
start_idx = 2 + i * N_ELEMENT_BITS_HASH
# Check if we have enough constant points
if start_idx + N_ELEMENT_BITS_HASH > len(CONSTANT_POINTS):
raise ValueError(f"Insufficient constant points for element {i}. Need {start_idx + N_ELEMENT_BITS_HASH}, have {len(CONSTANT_POINTS)}")
# Full implementation using all 252 bits
for j in range(N_ELEMENT_BITS_HASH):
pt = tuple(CONSTANT_POINTS[start_idx + j])
# Check for unhashable input (same x coordinate)
if point[0] == pt[0]:
raise ValueError('Unhashable input: point collision detected')
if element & 1:
point = _ec_add(point, pt)
element >>= 1
# Ensure all bits have been processed
if element != 0:
raise ValueError(f"Element too large: remaining bits {element}")
return point
def pedersen_hash(*elements: int) -> int:
"""
Calculate the Pedersen hash of a list of integers.
This function returns only the x-coordinate of the resulting EC point,
which is the standard Pedersen hash value.
Args:
*elements: Variable number of integers to hash
Returns:
int: The Pedersen hash as an integer (x-coordinate of the EC point)
Raises:
ValueError: If any element is out of range
"""
point = pedersen_hash_as_point(*elements)
return point[0]
def pedersen_hash_bytes(*elements: Union[int, bytes]) -> bytes:
"""
Calculate the Pedersen hash and return as bytes.
Args:
*elements: Variable number of integers or bytes to hash
Returns:
bytes: The hash result as 32 bytes (big-endian)
Raises:
ValueError: If any element is invalid
"""
# Convert bytes to integers if needed
int_elements = []
for element in elements:
if isinstance(element, bytes):
if len(element) > 32:
raise ValueError(f"Bytes element too long: {len(element)} > 32")
int_elements.append(int.from_bytes(element, byteorder='big'))
elif isinstance(element, int):
int_elements.append(element)
else:
raise ValueError(f"Invalid element type: {type(element)}")
hash_result = pedersen_hash(*int_elements)
return hash_result.to_bytes(32, byteorder='big')
@@ -0,0 +1,114 @@
from typing import Dict, Any, List
from ..internal.async_client import AsyncClient
class Client:
"""Client for funding-related API endpoints."""
def __init__(self, async_client: AsyncClient):
"""
Initialize the funding client.
Args:
async_client: The async client for common functionality
"""
self.async_client = async_client
async def get_funding_transactions(
self,
size: str = "",
offset_data: str = "",
filter_coin_id_list: List[str] = None,
filter_type_list: List[str] = None,
filter_start_created_time_inclusive: int = 0,
filter_end_created_time_exclusive: int = 0
) -> Dict[str, Any]:
"""
Get funding transactions with pagination.
Args:
size: Size of the page
offset_data: Offset data for pagination
filter_coin_id_list: Filter by coin IDs
filter_type_list: Filter by transaction types
filter_start_created_time_inclusive: Filter start time (inclusive)
filter_end_created_time_exclusive: Filter end time (exclusive)
Returns:
Dict[str, Any]: The funding transactions
Raises:
ValueError: If the request fails
"""
query_params = {
"accountId": str(self.async_client.get_account_id())
}
# Add pagination parameters
if size:
query_params["size"] = size
if offset_data:
query_params["offsetData"] = offset_data
# Add filter parameters
if filter_coin_id_list:
query_params["filterCoinIdList"] = ",".join(filter_coin_id_list)
if filter_type_list:
query_params["filterTypeList"] = ",".join(filter_type_list)
# Add time filters
if filter_start_created_time_inclusive > 0:
query_params["filterStartCreatedTimeInclusive"] = str(filter_start_created_time_inclusive)
if filter_end_created_time_exclusive > 0:
query_params["filterEndCreatedTimeExclusive"] = str(filter_end_created_time_exclusive)
return await self.async_client.make_authenticated_request(
method="GET",
path="/api/v1/public/funding/getFundingRatePage",
params=query_params
)
async def get_funding_account(self) -> Dict[str, Any]:
"""
Get funding account information.
Returns:
Dict[str, Any]: The funding account information
Raises:
ValueError: If the request fails
"""
params = {
"accountId": str(self.async_client.get_account_id())
}
return await self.async_client.make_authenticated_request(
method="GET",
path="/api/v1/private/account/getAccountAsset",
params=params
)
async def get_funding_transaction_by_id(self, transaction_ids: List[str]) -> Dict[str, Any]:
"""
Get funding transactions by IDs.
Args:
transaction_ids: List of transaction IDs
Returns:
Dict[str, Any]: The funding transactions
Raises:
ValueError: If the request fails
"""
query_params = {
"accountId": str(self.async_client.get_account_id()),
"transactionIdList": ",".join(transaction_ids)
}
return await self.async_client.make_authenticated_request(
method="GET",
path="/api/v1/public/funding/getLatestFundingRate",
params=query_params
)
@@ -0,0 +1,466 @@
import asyncio
import binascii
import hashlib
import time
import uuid
from typing import Dict, Any, Optional, Tuple, List, Union
import json
import aiohttp
from Crypto.Hash import keccak
from .signing_adapter import SigningAdapter
# Import field prime for modular arithmetic
try:
from ..crypto.constants import FIELD_PRIME
except ImportError:
# Fallback if crypto module is not available
FIELD_PRIME = 0x800000000000011000000000000000000000000000000000000000000000001
# Constants
LIMIT_ORDER_WITH_FEE_TYPE = 3
class L2Signature:
"""Represents a signature for L2 operations."""
def __init__(self, r: str, s: str, v: str = ""):
self.r = r
self.s = s
self.v = v
class AsyncClient:
"""Async base client with common functionality."""
def __init__(self, base_url: str, account_id: int, stark_pri_key: str,
signing_adapter: Optional[SigningAdapter] = None,
timeout: float = 30.0, connector_limit: int = 100):
"""
Initialize the async internal client.
Args:
base_url: Base URL for API endpoints
account_id: Account ID for authentication
stark_pri_key: Stark private key for signing
signing_adapter: Optional signing adapter to use for cryptographic operations
timeout: Request timeout in seconds
connector_limit: Maximum number of connections in the pool
"""
self.base_url = base_url
self.account_id = account_id
self.stark_pri_key = stark_pri_key
# Use the provided signing adapter (required)
if signing_adapter is None:
raise ValueError("signing_adapter is required")
self.signing_adapter = signing_adapter
# Store configuration for later session creation
self._session = None
self._timeout = timeout
self._connector_limit = connector_limit
self._closed = False
async def __aenter__(self):
"""Async context manager entry."""
await self._ensure_session()
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
"""Async context manager exit."""
await self.close()
async def _ensure_session(self):
"""Ensure the aiohttp session is created."""
if self._session is None or self._session.closed:
# Create connector and session when needed (inside event loop)
timeout_config = aiohttp.ClientTimeout(total=self._timeout)
connector = aiohttp.TCPConnector(
limit=self._connector_limit,
limit_per_host=30,
keepalive_timeout=30,
enable_cleanup_closed=True
)
self._session = aiohttp.ClientSession(
timeout=timeout_config,
connector=connector,
headers={
"Content-Type": "application/json",
"Accept": "application/json"
}
)
async def close(self):
"""Close the HTTP session and cleanup resources."""
if self._session and not self._session.closed:
await self._session.close()
self._closed = True
@property
def session(self) -> aiohttp.ClientSession:
"""Get the HTTP session, ensuring it's created."""
if self._session is None or self._session.closed:
raise RuntimeError("Session not initialized. Use 'async with client:' or call '_ensure_session()'")
return self._session
def get_account_id(self) -> int:
"""Get the account ID."""
return self.account_id
def get_stark_pri_key(self) -> str:
"""Get the stark private key."""
return self.stark_pri_key
def sign(self, message_hash: bytes) -> L2Signature:
"""
Sign a message hash using the client's Stark private key.
Args:
message_hash: The hash of the message to sign
Returns:
L2Signature: The signature components
Raises:
ValueError: If the stark private key is not set or invalid
"""
private_key = self.get_stark_pri_key()
if not private_key:
raise ValueError("stark private key not set")
# Sign the message using the signing adapter
try:
r, s = self.signing_adapter.sign(message_hash, private_key)
return L2Signature(r=r, s=s, v="")
except Exception as e:
raise ValueError(f"failed to sign message: {str(e)}")
def generate_uuid(self) -> str:
"""Generate a UUID for client order IDs."""
return str(uuid.uuid4())
def calc_nonce(self, client_order_id: str) -> int:
"""
Calculate a nonce from a client order ID.
Args:
client_order_id: The client order ID
Returns:
int: The calculated nonce
"""
# Use SHA256 like the Go SDK (not Keccak256)
h = hashlib.sha256()
h.update(client_order_id.encode())
hash_hex = h.hexdigest()
return int(hash_hex[:8], 16)
async def make_authenticated_request(
self,
method: str,
path: str,
data: Optional[Dict[str, Any]] = None,
params: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""
Make an authenticated HTTP request.
Args:
method: HTTP method (GET, POST, etc.)
path: API path (e.g., '/api/v1/private/order/createOrder')
data: JSON data for POST requests
params: Query parameters for GET requests
Returns:
Dict[str, Any]: Response JSON data
Raises:
ValueError: If the request fails
"""
await self._ensure_session()
# Generate timestamp
timestamp = int(time.time() * 1000)
# Build full URL
url = f"{self.base_url}{path}"
# Generate signature content
sign_content = self._build_signature_content(timestamp, method, path, data, params)
# Sign the content
keccak_hash = keccak.new(digest_bits=256)
keccak_hash.update(sign_content.encode())
content_hash = keccak_hash.digest()
sig = self.sign(content_hash)
# Prepare headers
headers = {
"X-edgeX-Api-Timestamp": str(timestamp),
"X-edgeX-Api-Signature": f"{sig.r}{sig.s}"
}
# Make the request
try:
async with self.session.request(
method=method,
url=url,
json=data,
params=params,
headers=headers
) as response:
if response.status != 200:
try:
error_detail = await response.json()
raise ValueError(f"request failed with status code: {response.status}, response: {error_detail}")
except (aiohttp.ContentTypeError, json.JSONDecodeError):
text = await response.text()
raise ValueError(f"request failed with status code: {response.status}, response: {text}")
resp_data = await response.json()
# Check response code
if resp_data.get("code") != "SUCCESS":
error_param = resp_data.get("errorParam")
if error_param:
raise ValueError(f"request failed with error params: {error_param}")
raise ValueError(f"request failed with code: {resp_data.get('code')}")
return resp_data
except aiohttp.ClientError as e:
raise ValueError(f"HTTP request failed: {str(e)}")
def _build_signature_content(
self,
timestamp: int,
method: str,
path: str,
data: Optional[Dict[str, Any]],
params: Optional[Dict[str, Any]]
) -> str:
"""Build the content string for signature generation."""
if data:
# Convert body to sorted string format
body_str = self.get_value(data)
sign_content = f"{timestamp}{method}{path}{body_str}"
else:
# For requests without body, use query parameters if present
if params:
# Sort query parameters as strings (matching Go SDK exactly)
param_pairs = []
for key, value in sorted(params.items()):
param_pairs.append(f"{key}={value}")
query_string = "&".join(param_pairs)
sign_content = f"{timestamp}{method}{path}{query_string}"
else:
sign_content = f"{timestamp}{method}{path}"
return sign_content
def get_value(self, data: Union[Dict[str, Any], List[Any], str, int, float, None]) -> str:
"""
Convert a value to a string representation for signing.
This function recursively processes dictionaries, lists, and primitive types.
Args:
data: The value to convert
Returns:
str: The string representation
"""
if data is None:
return ""
if isinstance(data, str):
return data
if isinstance(data, bool):
# Convert boolean to lowercase string to match Go SDK
return str(data).lower()
if isinstance(data, (int, float)):
return str(data)
if isinstance(data, list):
if len(data) == 0:
return ""
values = [self.get_value(item) for item in data]
return "&".join(values)
if isinstance(data, dict):
# Convert all values to strings and sort by keys
sorted_map = {}
for key, val in data.items():
sorted_map[key] = self.get_value(val)
# Get sorted keys
keys = sorted(sorted_map.keys())
# Build key=value pairs
pairs = [f"{key}={sorted_map[key]}" for key in keys]
return "&".join(pairs)
# Handle other types by converting to string
return str(data)
def calc_limit_order_hash(
self,
synthetic_asset_id: str,
collateral_asset_id: str,
fee_asset_id: str,
is_buy: bool,
amount_synthetic: int,
amount_collateral: int,
amount_fee: int,
nonce: int,
account_id: int,
expire_time: int
) -> bytes:
"""
Calculate the hash for a limit order using StarkEx protocol.
Args:
synthetic_asset_id: The synthetic asset ID (hex string)
collateral_asset_id: The collateral asset ID (hex string)
fee_asset_id: The fee asset ID (hex string)
is_buy: Whether the order is a buy order
amount_synthetic: The synthetic amount
amount_collateral: The collateral amount
amount_fee: The fee amount
nonce: The nonce
account_id: The account ID (position ID)
expire_time: The expiration time
Returns:
bytes: The calculated hash
"""
# Remove 0x prefix if present
if synthetic_asset_id.startswith('0x'):
synthetic_asset_id = synthetic_asset_id[2:]
if collateral_asset_id.startswith('0x'):
collateral_asset_id = collateral_asset_id[2:]
if fee_asset_id.startswith('0x'):
fee_asset_id = fee_asset_id[2:]
# Convert hex strings to integers and ensure they're within the field
asset_id_synthetic = int(synthetic_asset_id, 16) % FIELD_PRIME
asset_id_collateral = int(collateral_asset_id, 16) % FIELD_PRIME
asset_id_fee = int(fee_asset_id, 16) % FIELD_PRIME
# Determine buy/sell assets based on order direction
if is_buy:
asset_id_sell = asset_id_collateral
asset_id_buy = asset_id_synthetic
amount_sell = amount_collateral
amount_buy = amount_synthetic
else:
asset_id_sell = asset_id_synthetic
asset_id_buy = asset_id_collateral
amount_sell = amount_synthetic
amount_buy = amount_collateral
# Use the signing adapter to calculate the Pedersen hash
# First hash: hash(asset_id_sell, asset_id_buy)
msg = self.signing_adapter.pedersen_hash([asset_id_sell, asset_id_buy])
msg_int = int.from_bytes(msg, byteorder='big')
# Second hash: hash(msg, asset_id_fee)
msg = self.signing_adapter.pedersen_hash([msg_int, asset_id_fee])
msg_int = int.from_bytes(msg, byteorder='big')
# Pack message 0
# packed_message0 = amount_sell * 2^64 + amount_buy * 2^64 + max_amount_fee * 2^32 + nonce
packed_message0 = amount_sell
packed_message0 = (packed_message0 << 64) + amount_buy
packed_message0 = (packed_message0 << 64) + amount_fee
packed_message0 = (packed_message0 << 32) + nonce
packed_message0 = packed_message0 % FIELD_PRIME # Ensure within field
# Third hash: hash(msg, packed_message0)
msg = self.signing_adapter.pedersen_hash([msg_int, packed_message0])
msg_int = int.from_bytes(msg, byteorder='big')
# Pack message 1
# packed_message1 = LIMIT_ORDER_WITH_FEES * 2^64 + position_id * 2^64 + position_id * 2^64 + position_id * 2^32 + expiration_timestamp * 2^17
packed_message1 = LIMIT_ORDER_WITH_FEE_TYPE
packed_message1 = (packed_message1 << 64) + account_id
packed_message1 = (packed_message1 << 64) + account_id
packed_message1 = (packed_message1 << 64) + account_id
packed_message1 = (packed_message1 << 32) + expire_time
packed_message1 = packed_message1 << 17 # Padding
packed_message1 = packed_message1 % FIELD_PRIME # Ensure within field
# Final hash: hash(msg, packed_message1)
msg = self.signing_adapter.pedersen_hash([msg_int, packed_message1])
return msg
def calc_transfer_hash(
self,
asset_id: int,
asset_id_fee: int,
receiver_public_key: int,
sender_position_id: int,
receiver_position_id: int,
fee_position_id: int,
nonce: int,
amount: int,
max_amount_fee: int,
expiration_timestamp: int
) -> bytes:
"""
Calculate the hash for a transfer using StarkEx protocol.
Args:
asset_id: The asset ID
asset_id_fee: The fee asset ID
receiver_public_key: The receiver's public key
sender_position_id: The sender's position ID
receiver_position_id: The receiver's position ID
fee_position_id: The fee position ID
nonce: The nonce
amount: The transfer amount
max_amount_fee: The maximum fee amount
expiration_timestamp: The expiration timestamp
Returns:
bytes: The calculated hash
"""
# First hash: hash(asset_id, asset_id_fee)
msg = self.signing_adapter.pedersen_hash([asset_id, asset_id_fee])
msg_int = int.from_bytes(msg, byteorder='big')
# Second hash: hash(msg, receiver_public_key)
msg = self.signing_adapter.pedersen_hash([msg_int, receiver_public_key])
msg_int = int.from_bytes(msg, byteorder='big')
# Pack message 0
# packed_msg0 = sender_position_id * 2^64 + receiver_position_id * 2^64 + fee_position_id * 2^32 + nonce
packed_msg0 = sender_position_id
packed_msg0 = (packed_msg0 << 64) + receiver_position_id
packed_msg0 = (packed_msg0 << 64) + fee_position_id
packed_msg0 = (packed_msg0 << 32) + nonce
packed_msg0 = packed_msg0 % FIELD_PRIME # Ensure within field
# Third hash: hash(msg, packed_msg0)
msg = self.signing_adapter.pedersen_hash([msg_int, packed_msg0])
msg_int = int.from_bytes(msg, byteorder='big')
# Pack message 1
# packed_msg1 = 4 * 2^64 + amount * 2^64 + max_amount_fee * 2^32 + expiration_timestamp * 2^81
packed_msg1 = 4 # Transfer type
packed_msg1 = (packed_msg1 << 64) + amount
packed_msg1 = (packed_msg1 << 64) + max_amount_fee
packed_msg1 = (packed_msg1 << 32) + expiration_timestamp
packed_msg1 = packed_msg1 << 81 # Padding
packed_msg1 = packed_msg1 % FIELD_PRIME # Ensure within field
# Final hash: hash(msg, packed_msg1)
msg = self.signing_adapter.pedersen_hash([msg_int, packed_msg1])
return msg
@@ -0,0 +1,312 @@
import binascii
import hashlib
import time
import uuid
from typing import Dict, Any, Optional, Tuple, List, Union
import requests
from Crypto.Hash import keccak
from .signing_adapter import SigningAdapter
# Import field prime for modular arithmetic
try:
from ..crypto.constants import FIELD_PRIME
except ImportError:
# Fallback if crypto module is not available
FIELD_PRIME = 0x800000000000011000000000000000000000000000000000000000000000001
# Constants
LIMIT_ORDER_WITH_FEE_TYPE = 3
class L2Signature:
"""Represents a signature for L2 operations."""
def __init__(self, r: str, s: str, v: str = ""):
self.r = r
self.s = s
self.v = v
class Client:
"""Base client with common functionality."""
def __init__(self, base_url: str, account_id: int, stark_pri_key: str, signing_adapter: Optional[SigningAdapter] = None):
"""
Initialize the internal client.
Args:
base_url: Base URL for API endpoints
account_id: Account ID for authentication
stark_pri_key: Stark private key for signing
signing_adapter: Optional signing adapter to use for cryptographic operations
"""
self.http_client = requests.Session()
self.http_client.headers.update({
"Content-Type": "application/json",
"Accept": "application/json"
})
self.base_url = base_url
self.account_id = account_id
self.stark_pri_key = stark_pri_key
# Use the provided signing adapter (required)
if signing_adapter is None:
raise ValueError("signing_adapter is required")
self.signing_adapter = signing_adapter
def get_account_id(self) -> int:
"""Get the account ID."""
return self.account_id
def get_stark_pri_key(self) -> str:
"""Get the stark private key."""
return self.stark_pri_key
def sign(self, message_hash: bytes) -> L2Signature:
"""
Sign a message hash using the client's Stark private key.
Args:
message_hash: The hash of the message to sign
Returns:
L2Signature: The signature components
Raises:
ValueError: If the stark private key is not set or invalid
"""
private_key = self.get_stark_pri_key()
if not private_key:
raise ValueError("stark private key not set")
# Sign the message using the signing adapter
try:
r, s = self.signing_adapter.sign(message_hash, private_key)
return L2Signature(r=r, s=s, v="")
except Exception as e:
raise ValueError(f"failed to sign message: {str(e)}")
def generate_uuid(self) -> str:
"""Generate a UUID for client order IDs."""
return str(uuid.uuid4())
def calc_nonce(self, client_order_id: str) -> int:
"""
Calculate a nonce from a client order ID.
Args:
client_order_id: The client order ID
Returns:
int: The calculated nonce
"""
# Use SHA256 like the Go SDK (not Keccak256)
h = hashlib.sha256()
h.update(client_order_id.encode())
hash_hex = h.hexdigest()
return int(hash_hex[:8], 16)
def calc_limit_order_hash(
self,
synthetic_asset_id: str,
collateral_asset_id: str,
fee_asset_id: str,
is_buy: bool,
amount_synthetic: int,
amount_collateral: int,
amount_fee: int,
nonce: int,
account_id: int,
expire_time: int
) -> bytes:
"""
Calculate the hash for a limit order using StarkEx protocol.
Args:
synthetic_asset_id: The synthetic asset ID (hex string)
collateral_asset_id: The collateral asset ID (hex string)
fee_asset_id: The fee asset ID (hex string)
is_buy: Whether the order is a buy order
amount_synthetic: The synthetic amount
amount_collateral: The collateral amount
amount_fee: The fee amount
nonce: The nonce
account_id: The account ID (position ID)
expire_time: The expiration time
Returns:
bytes: The calculated hash
"""
# Remove 0x prefix if present
if synthetic_asset_id.startswith('0x'):
synthetic_asset_id = synthetic_asset_id[2:]
if collateral_asset_id.startswith('0x'):
collateral_asset_id = collateral_asset_id[2:]
if fee_asset_id.startswith('0x'):
fee_asset_id = fee_asset_id[2:]
# Convert hex strings to integers and ensure they're within the field
asset_id_synthetic = int(synthetic_asset_id, 16) % FIELD_PRIME
asset_id_collateral = int(collateral_asset_id, 16) % FIELD_PRIME
asset_id_fee = int(fee_asset_id, 16) % FIELD_PRIME
# Determine buy/sell assets based on order direction
if is_buy:
asset_id_sell = asset_id_collateral
asset_id_buy = asset_id_synthetic
amount_sell = amount_collateral
amount_buy = amount_synthetic
else:
asset_id_sell = asset_id_synthetic
asset_id_buy = asset_id_collateral
amount_sell = amount_synthetic
amount_buy = amount_collateral
# Use the signing adapter to calculate the Pedersen hash
# First hash: hash(asset_id_sell, asset_id_buy)
msg = self.signing_adapter.pedersen_hash([asset_id_sell, asset_id_buy])
msg_int = int.from_bytes(msg, byteorder='big')
# Second hash: hash(msg, asset_id_fee)
msg = self.signing_adapter.pedersen_hash([msg_int, asset_id_fee])
msg_int = int.from_bytes(msg, byteorder='big')
# Pack message 0
# packed_message0 = amount_sell * 2^64 + amount_buy * 2^64 + max_amount_fee * 2^32 + nonce
packed_message0 = amount_sell
packed_message0 = (packed_message0 << 64) + amount_buy
packed_message0 = (packed_message0 << 64) + amount_fee
packed_message0 = (packed_message0 << 32) + nonce
packed_message0 = packed_message0 % FIELD_PRIME # Ensure within field
# Third hash: hash(msg, packed_message0)
msg = self.signing_adapter.pedersen_hash([msg_int, packed_message0])
msg_int = int.from_bytes(msg, byteorder='big')
# Pack message 1
# packed_message1 = LIMIT_ORDER_WITH_FEES * 2^64 + position_id * 2^64 + position_id * 2^64 + position_id * 2^32 + expiration_timestamp * 2^17
packed_message1 = LIMIT_ORDER_WITH_FEE_TYPE
packed_message1 = (packed_message1 << 64) + account_id
packed_message1 = (packed_message1 << 64) + account_id
packed_message1 = (packed_message1 << 64) + account_id
packed_message1 = (packed_message1 << 32) + expire_time
packed_message1 = packed_message1 << 17 # Padding
packed_message1 = packed_message1 % FIELD_PRIME # Ensure within field
# Final hash: hash(msg, packed_message1)
msg = self.signing_adapter.pedersen_hash([msg_int, packed_message1])
return msg
def calc_transfer_hash(
self,
asset_id: int,
asset_id_fee: int,
receiver_public_key: int,
sender_position_id: int,
receiver_position_id: int,
fee_position_id: int,
nonce: int,
amount: int,
max_amount_fee: int,
expiration_timestamp: int
) -> bytes:
"""
Calculate the hash for a transfer using StarkEx protocol.
Args:
asset_id: The asset ID
asset_id_fee: The fee asset ID
receiver_public_key: The receiver's public key
sender_position_id: The sender's position ID
receiver_position_id: The receiver's position ID
fee_position_id: The fee position ID
nonce: The nonce
amount: The transfer amount
max_amount_fee: The maximum fee amount
expiration_timestamp: The expiration timestamp
Returns:
bytes: The calculated hash
"""
# First hash: hash(asset_id, asset_id_fee)
msg = self.signing_adapter.pedersen_hash([asset_id, asset_id_fee])
msg_int = int.from_bytes(msg, byteorder='big')
# Second hash: hash(msg, receiver_public_key)
msg = self.signing_adapter.pedersen_hash([msg_int, receiver_public_key])
msg_int = int.from_bytes(msg, byteorder='big')
# Pack message 0
# packed_msg0 = sender_position_id * 2^64 + receiver_position_id * 2^64 + fee_position_id * 2^32 + nonce
packed_msg0 = sender_position_id
packed_msg0 = (packed_msg0 << 64) + receiver_position_id
packed_msg0 = (packed_msg0 << 64) + fee_position_id
packed_msg0 = (packed_msg0 << 32) + nonce
packed_msg0 = packed_msg0 % FIELD_PRIME # Ensure within field
# Third hash: hash(msg, packed_msg0)
msg = self.signing_adapter.pedersen_hash([msg_int, packed_msg0])
msg_int = int.from_bytes(msg, byteorder='big')
# Pack message 1
# packed_msg1 = 4 * 2^64 + amount * 2^64 + max_amount_fee * 2^32 + expiration_timestamp * 2^81
packed_msg1 = 4 # Transfer type
packed_msg1 = (packed_msg1 << 64) + amount
packed_msg1 = (packed_msg1 << 64) + max_amount_fee
packed_msg1 = (packed_msg1 << 32) + expiration_timestamp
packed_msg1 = packed_msg1 << 81 # Padding
packed_msg1 = packed_msg1 % FIELD_PRIME # Ensure within field
# Final hash: hash(msg, packed_msg1)
msg = self.signing_adapter.pedersen_hash([msg_int, packed_msg1])
return msg
def get_value(self, data: Union[Dict[str, Any], List[Any], str, int, float, None]) -> str:
"""
Convert a value to a string representation for signing.
This function recursively processes dictionaries, lists, and primitive types.
Args:
data: The value to convert
Returns:
str: The string representation
"""
if data is None:
return ""
if isinstance(data, str):
return data
if isinstance(data, bool):
# Convert boolean to lowercase string to match Go SDK
return str(data).lower()
if isinstance(data, (int, float)):
return str(data)
if isinstance(data, list):
if len(data) == 0:
return ""
values = [self.get_value(item) for item in data]
return "&".join(values)
if isinstance(data, dict):
# Convert all values to strings and sort by keys
sorted_map = {}
for key, val in data.items():
sorted_map[key] = self.get_value(val)
# Get sorted keys
keys = sorted(sorted_map.keys())
# Build key=value pairs
pairs = [f"{key}={sorted_map[key]}" for key in keys]
return "&".join(pairs)
# Handle other types by converting to string
return str(data)
@@ -0,0 +1,77 @@
"""
Signing adapter interface for the EdgeX Python SDK.
This module defines the interface for signing adapters that can be used with the SDK.
Different implementations can be provided for different environments (development, testing, production).
"""
from abc import ABC, abstractmethod
from typing import Tuple, List
class SigningAdapter(ABC):
"""Interface for signing adapters."""
@abstractmethod
def sign(self, message_hash: bytes, private_key: str) -> Tuple[str, str]:
"""
Sign a message hash using a private key.
Args:
message_hash: The hash of the message to sign
private_key: The private key as a hex string
Returns:
Tuple[str, str]: The signature as (r, s) hex strings
Raises:
ValueError: If the private key is invalid or the signing fails
"""
pass
@abstractmethod
def get_public_key(self, private_key: str) -> str:
"""
Get the public key from a private key.
Args:
private_key: The private key as a hex string
Returns:
str: The public key as a hex string
Raises:
ValueError: If the private key is invalid
"""
pass
@abstractmethod
def verify(self, message_hash: bytes, signature: Tuple[str, str], public_key: str) -> bool:
"""
Verify a signature using a public key.
Args:
message_hash: The hash of the message
signature: The signature as (r, s) hex strings
public_key: The public key as a hex string
Returns:
bool: Whether the signature is valid
"""
pass
@abstractmethod
def pedersen_hash(self, elements: List[int]) -> bytes:
"""
Calculate the Pedersen hash of a list of integers.
Args:
elements: List of integers to hash
Returns:
bytes: The hash result
Raises:
ValueError: If the calculation fails
"""
pass
@@ -0,0 +1,496 @@
"""
StarkEx signing adapter for the EdgeX Python SDK.
This module provides an implementation of the signing adapter interface
that uses the StarkWare cryptographic primitives for signing operations.
"""
import binascii
import math
import secrets
from typing import List, Tuple
from .signing_adapter import SigningAdapter
from ..crypto.pedersen_hash import pedersen_hash_bytes
# StarkEx curve parameters
FIELD_PRIME = 0x800000000000011000000000000000000000000000000000000000000000001
ALPHA = 1
BETA = 0x6f21413efbe40de150e596d72f7a8c5609ad26c15c915c1f4cdfcb99cee9e89
EC_ORDER = 0x800000000000010ffffffffffffffffb781126dcae7b2321e66a241adc64d2f
N_ELEMENT_BITS_ECDSA = math.floor(math.log(FIELD_PRIME, 2))
assert N_ELEMENT_BITS_ECDSA == 251
# Generator point for the Stark curve
EC_GEN = (
0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca,
0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f
)
class StarkExSigningAdapter(SigningAdapter):
"""StarkEx implementation of the signing adapter interface."""
def sign(self, message_hash: bytes, private_key: str) -> Tuple[str, str]:
"""
Sign a message hash using a private key.
Args:
message_hash: The hash of the message to sign
private_key: The private key as a hex string
Returns:
Tuple[str, str]: The signature as (r, s) hex strings
Raises:
ValueError: If the private key is invalid or the signing fails
"""
try:
# Validate private key format
binascii.unhexlify(private_key)
except binascii.Error:
raise ValueError("Invalid private key hex string")
# Convert message hash to integer
msg_hash_int = int.from_bytes(message_hash, byteorder='big')
# Ensure the message hash is in the valid range
# Use the same modulus as the Golang SDK (EC_ORDER, which is starkcurve.N)
msg_hash_int = msg_hash_int % EC_ORDER
# Convert private key to integer
priv_key_int = int(private_key, 16)
# Ensure the private key is in the valid range
# For testing purposes, we'll just take the modulus
priv_key_int = priv_key_int % EC_ORDER
if priv_key_int == 0:
priv_key_int = 1
# Sign the message
r, s = self._sign(msg_hash_int, priv_key_int)
# Convert r and s to hex strings
r_hex = format(r, '064x')
s_hex = format(s, '064x')
return r_hex, s_hex
def get_public_key(self, private_key: str) -> str:
"""
Get the public key from a private key.
Args:
private_key: The private key as a hex string
Returns:
str: The public key as a hex string
Raises:
ValueError: If the private key is invalid
"""
try:
# Validate private key format
binascii.unhexlify(private_key)
except binascii.Error:
raise ValueError("Invalid private key hex string")
# Convert private key to integer
priv_key_int = int(private_key, 16)
# Ensure the private key is in the valid range
# For testing purposes, we'll just take the modulus
priv_key_int = priv_key_int % EC_ORDER
if priv_key_int == 0:
priv_key_int = 1
# Get the public key
public_key = self._private_to_stark_key(priv_key_int)
# Convert public key to hex string
public_key_hex = format(public_key, '064x')
return public_key_hex
def verify(self, message_hash: bytes, signature: Tuple[str, str], public_key: str) -> bool:
"""
Verify a signature using a public key.
Args:
message_hash: The hash of the message
signature: The signature as (r, s) hex strings
public_key: The public key as a hex string
Returns:
bool: Whether the signature is valid
"""
try:
# Convert message hash to integer
msg_hash_int = int.from_bytes(message_hash, byteorder='big')
# Ensure the message hash is in the valid range
# Use the same modulus as the sign method (EC_ORDER)
msg_hash_int = msg_hash_int % EC_ORDER
# Convert signature components to integers
r_int = int(signature[0], 16)
s_int = int(signature[1], 16)
# Ensure r and s are in the valid range
if not (1 <= r_int < 2**N_ELEMENT_BITS_ECDSA and 1 <= s_int < EC_ORDER):
return False
# Convert public key to integer
pub_key_int = int(public_key, 16)
# Verify the signature
return self._verify(msg_hash_int, r_int, s_int, pub_key_int)
except Exception:
return False
def pedersen_hash(self, elements: List[int]) -> bytes:
"""
Calculate the Pedersen hash of a list of integers.
This method now uses the full Pedersen hash implementation
that follows StarkWare's specification.
Args:
elements: List of integers to hash
Returns:
bytes: The hash result
Raises:
ValueError: If the calculation fails
"""
try:
# Use the full Pedersen hash implementation
return pedersen_hash_bytes(*elements)
except Exception as e:
raise ValueError(f"Failed to calculate Pedersen hash: {str(e)}")
def _sign(self, msg_hash: int, priv_key: int) -> Tuple[int, int]:
"""
Sign a message hash using a private key.
Args:
msg_hash: The hash of the message to sign as an integer
priv_key: The private key as an integer
Returns:
Tuple[int, int]: The signature as (r, s) integers
"""
# Choose a valid k. In our version of ECDSA not every k value is valid,
# and there is a negligible probability a drawn k cannot be used for signing.
# This is why we have this loop.
while True:
# Use random nonce generation like the Go SDK
k = self._generate_random_k()
# Cannot fail because 0 < k < EC_ORDER and EC_ORDER is prime.
x = self._ec_mult(k, EC_GEN)[0]
# DIFF: in classic ECDSA, we take int(x) % n.
r = int(x)
if not (1 <= r < 2**N_ELEMENT_BITS_ECDSA):
# Bad value. This fails with negligible probability.
continue
if (msg_hash + r * priv_key) % EC_ORDER == 0:
# Bad value. This fails with negligible probability.
continue
w = self._div_mod(k, msg_hash + r * priv_key, EC_ORDER)
if not (1 <= w < 2**N_ELEMENT_BITS_ECDSA):
# Bad value. This fails with negligible probability.
continue
s = self._inv_mod_curve_size(w)
return r, s
def _verify(self, msg_hash: int, r: int, s: int, public_key: int) -> bool:
"""
Verify a signature using a public key.
Args:
msg_hash: The hash of the message as an integer
r: The r component of the signature as an integer
s: The s component of the signature as an integer
public_key: The public key as an integer
Returns:
bool: Whether the signature is valid
"""
# Compute w = s^-1 (mod EC_ORDER).
if not (1 <= s < EC_ORDER):
return False
w = self._inv_mod_curve_size(s)
# Preassumptions:
# DIFF: in classic ECDSA, we assert 1 <= r, w <= EC_ORDER-1.
# Since r, w < 2**N_ELEMENT_BITS_ECDSA < EC_ORDER, we only need to verify r, w != 0.
if not (1 <= r < 2**N_ELEMENT_BITS_ECDSA and 1 <= w < 2**N_ELEMENT_BITS_ECDSA):
return False
if not (0 <= msg_hash < 2**N_ELEMENT_BITS_ECDSA):
return False
# Only the x coordinate of the point is given, check the two possibilities for the y
# coordinate.
try:
y = self._get_y_coordinate(public_key)
except ValueError:
return False
# Verify it is on the curve.
if (y**2 - (public_key**3 + ALPHA * public_key + BETA)) % FIELD_PRIME != 0:
return False
# Try both possible y coordinates.
for y_candidate in [y, (-y) % FIELD_PRIME]:
public_key_point = (public_key, y_candidate)
# Signature validation.
try:
# Calculate u1 = msg_hash * w mod n
u1 = (msg_hash * w) % EC_ORDER
# Calculate u2 = r * w mod n
u2 = (r * w) % EC_ORDER
# Calculate u1*G + u2*Q
point1 = self._ec_mult(u1, EC_GEN)
point2 = self._ec_mult(u2, public_key_point)
point = self._ec_add(point1, point2)
# The signature is valid if the x-coordinate of the resulting point equals r
if point[0] == r:
return True
except Exception:
continue
return False
def _generate_random_k(self) -> int:
"""
Generate a cryptographically secure random k value.
Returns:
int: The generated k value in range [1, EC_ORDER)
"""
# Generate a cryptographically secure random number in the range [1, EC_ORDER)
# This matches the Go implementation's approach of using random nonces
return secrets.randbelow(EC_ORDER - 1) + 1
def _private_to_stark_key(self, priv_key: int) -> int:
"""
Convert a private key to a Stark public key.
Args:
priv_key: The private key as an integer
Returns:
int: The public key as an integer
"""
return self._private_key_to_ec_point_on_stark_curve(priv_key)[0]
def _private_key_to_ec_point_on_stark_curve(self, priv_key: int) -> Tuple[int, int]:
"""
Convert a private key to an EC point on the Stark curve.
Args:
priv_key: The private key as an integer
Returns:
Tuple[int, int]: The EC point as (x, y) coordinates
"""
# Ensure the private key is in the valid range
# For testing purposes, we'll just take the modulus
priv_key = priv_key % EC_ORDER
if priv_key == 0:
priv_key = 1
return self._ec_mult(priv_key, EC_GEN)
def _inv_mod_curve_size(self, x: int) -> int:
"""
Calculate the modular inverse of x modulo the curve order.
Args:
x: The value to invert
Returns:
int: The modular inverse
"""
return self._div_mod(1, x, EC_ORDER)
def _div_mod(self, n: int, m: int, p: int) -> int:
"""
Calculate (n / m) mod p.
Args:
n: The numerator
m: The denominator
p: The modulus
Returns:
int: The result of the division modulo p
"""
return (n * pow(m, -1, p)) % p
def _is_quad_residue(self, n: int, p: int) -> bool:
"""
Check if n is a quadratic residue modulo p.
Args:
n: The number to check
p: The modulus
Returns:
bool: True if n is a quadratic residue modulo p, False otherwise
"""
return pow(n, (p - 1) // 2, p) == 1
def _sqrt_mod(self, n: int, p: int) -> int:
"""
Calculate the square root of n modulo p.
Args:
n: The number to take the square root of
p: The modulus
Returns:
int: The square root of n modulo p
"""
# Handle the case where p = 3 mod 4
if p % 4 == 3:
return pow(n, (p + 1) // 4, p)
# Handle the general case using the Tonelli-Shanks algorithm
q = p - 1
s = 0
while q % 2 == 0:
q //= 2
s += 1
# Find a non-residue
z = 2
while self._is_quad_residue(z, p):
z += 1
m = s
c = pow(z, q, p)
t = pow(n, q, p)
r = pow(n, (q + 1) // 2, p)
while t != 1:
# Find the least i, 0 < i < m, such that t^(2^i) = 1
i = 0
t_sq = t
while t_sq != 1 and i < m - 1:
t_sq = (t_sq * t_sq) % p
i += 1
# Calculate b = c^(2^(m-i-1))
b = pow(c, 2**(m - i - 1), p)
m = i
c = (b * b) % p
t = (t * b * b) % p
r = (r * b) % p
return r
def _get_y_coordinate(self, x: int) -> int:
"""
Given the x coordinate of a point, returns a possible y coordinate such that
together the point (x,y) is on the curve.
Args:
x: The x coordinate
Returns:
int: A possible y coordinate
Raises:
ValueError: If x is not a valid x coordinate on the curve
"""
y_squared = (x * x * x + ALPHA * x + BETA) % FIELD_PRIME
if not self._is_quad_residue(y_squared, FIELD_PRIME):
raise ValueError("Given x coordinate does not represent any point on the elliptic curve.")
return self._sqrt_mod(y_squared, FIELD_PRIME)
def _ec_add(self, p1: Tuple[int, int], p2: Tuple[int, int]) -> Tuple[int, int]:
"""
Add two points on the elliptic curve.
Args:
p1: The first point as (x, y) coordinates
p2: The second point as (x, y) coordinates
Returns:
Tuple[int, int]: The resulting point as (x, y) coordinates
"""
if p1[0] == p2[0]:
if (p1[1] + p2[1]) % FIELD_PRIME == 0:
# The points are negatives of each other, return the point at infinity
# We represent the point at infinity as None, but this should never happen
# in our use case, so we raise an exception instead
raise ValueError("Points are negatives of each other")
# The points are the same, so we're doubling
return self._ec_double(p1)
# Calculate the slope
slope = self._div_mod(p2[1] - p1[1], p2[0] - p1[0], FIELD_PRIME)
# Calculate the new point
x3 = (slope * slope - p1[0] - p2[0]) % FIELD_PRIME
y3 = (slope * (p1[0] - x3) - p1[1]) % FIELD_PRIME
return (x3, y3)
def _ec_double(self, p: Tuple[int, int]) -> Tuple[int, int]:
"""
Double a point on the elliptic curve.
Args:
p: The point to double as (x, y) coordinates
Returns:
Tuple[int, int]: The resulting point as (x, y) coordinates
"""
# Calculate the slope
slope = self._div_mod(3 * p[0] * p[0] + ALPHA, 2 * p[1], FIELD_PRIME)
# Calculate the new point
x3 = (slope * slope - 2 * p[0]) % FIELD_PRIME
y3 = (slope * (p[0] - x3) - p[1]) % FIELD_PRIME
return (x3, y3)
def _ec_mult(self, m: int, p: Tuple[int, int]) -> Tuple[int, int]:
"""
Multiply a point on the elliptic curve by a scalar.
Args:
m: The scalar
p: The point as (x, y) coordinates
Returns:
Tuple[int, int]: The resulting point as (x, y) coordinates
"""
if m == 0:
raise ValueError("Cannot multiply by 0")
if m == 1:
return p
if m % 2 == 0:
return self._ec_mult(m // 2, self._ec_double(p))
else:
return self._ec_add(p, self._ec_mult(m - 1, p))
@@ -0,0 +1,96 @@
from typing import Dict, Any
from ..internal.async_client import AsyncClient
class Client:
"""Client for metadata-related API endpoints."""
def __init__(self, async_client: AsyncClient):
"""
Initialize the metadata client.
Args:
async_client: The async client for common functionality
"""
self.async_client = async_client
async def get_metadata(self) -> Dict[str, Any]:
"""
Get the exchange metadata.
Returns:
Dict[str, Any]: The exchange metadata
Raises:
ValueError: If the request fails
"""
# Public endpoint - use simple GET request
await self.async_client._ensure_session()
url = f"{self.async_client.base_url}/api/v1/public/meta/getMetaData"
try:
async with self.async_client.session.get(url) as response:
if response.status != 200:
try:
error_detail = await response.json()
raise ValueError(f"request failed with status code: {response.status}, response: {error_detail}")
except:
text = await response.text()
raise ValueError(f"request failed with status code: {response.status}, response: {text}")
resp_data = await response.json()
if resp_data.get("code") != "SUCCESS":
error_param = resp_data.get("errorParam")
if error_param:
raise ValueError(f"request failed with error params: {error_param}")
raise ValueError(f"request failed with code: {resp_data.get('code')}")
return resp_data
except Exception as e:
if isinstance(e, ValueError):
raise
raise ValueError(f"request failed: {str(e)}")
async def get_server_time(self) -> Dict[str, Any]:
"""
Get the current server time.
Returns:
Dict[str, Any]: The server time information
Raises:
ValueError: If the request fails
"""
# Public endpoint - use simple GET request
await self.async_client._ensure_session()
url = f"{self.async_client.base_url}/api/v1/public/meta/getServerTime"
try:
async with self.async_client.session.get(url) as response:
if response.status != 200:
try:
error_detail = await response.json()
raise ValueError(f"request failed with status code: {response.status}, response: {error_detail}")
except:
text = await response.text()
raise ValueError(f"request failed with status code: {response.status}, response: {text}")
resp_data = await response.json()
if resp_data.get("code") != "SUCCESS":
error_param = resp_data.get("errorParam")
if error_param:
raise ValueError(f"request failed with error params: {error_param}")
raise ValueError(f"request failed with code: {resp_data.get('code')}")
return resp_data
except Exception as e:
if isinstance(e, ValueError):
raise
raise ValueError(f"request failed: {str(e)}")
@@ -0,0 +1,343 @@
import math
import time
from decimal import Decimal
from typing import Dict, Any, Optional, List
from ..internal.async_client import AsyncClient
from .types import (
CreateOrderParams,
CancelOrderParams,
GetActiveOrderParams,
OrderFillTransactionParams,
TimeInForce,
OrderType
)
class Client:
"""Client for order-related API endpoints."""
def __init__(self, async_client: AsyncClient):
"""
Initialize the order client.
Args:
async_client: The async client for common functionality
"""
self.async_client = async_client
async def create_order(self, params: CreateOrderParams, metadata: Dict[str, Any]) -> Dict[str, Any]:
"""
Create a new order with the given parameters.
Args:
params: Order parameters
metadata: Exchange metadata
Returns:
Dict[str, Any]: The created order
Raises:
ValueError: If required parameters are missing or invalid
"""
# Set default TimeInForce based on order type if not specified
if not params.time_in_force:
if params.type == OrderType.MARKET:
params.time_in_force = TimeInForce.IMMEDIATE_OR_CANCEL
elif params.type == OrderType.LIMIT:
params.time_in_force = TimeInForce.GOOD_TIL_CANCEL
# Find the contract from metadata
contract = None
contract_list = metadata.get("contractList", [])
for c in contract_list:
if c.get("contractId") == params.contract_id:
contract = c
break
if not contract:
raise ValueError(f"contract not found: {params.contract_id}")
# Get collateral coin from metadata
global_data = metadata.get("global", {})
collateral_coin = global_data.get("starkExCollateralCoin", {})
# Parse decimal values
try:
size = Decimal(params.size)
price = Decimal(params.price)
except (ValueError, TypeError):
raise ValueError("failed to parse size or price")
# Convert hex resolution to decimal
hex_resolution = contract.get("starkExResolution", "0x0")
# Remove "0x" prefix if present
hex_resolution = hex_resolution.replace("0x", "")
# Parse hex string to int
try:
resolution_int = int(hex_resolution, 16)
resolution = Decimal(resolution_int)
except (ValueError, TypeError):
raise ValueError("failed to parse hex resolution")
client_order_id = params.client_order_id or self.async_client.generate_uuid()
# Calculate values
value_dm = price * size
amount_synthetic = int(size * resolution)
amount_collateral = int(value_dm * Decimal("1000000")) # Shift 6 decimal places
# Calculate fee based on order type (maker/taker)
try:
fee_rate = Decimal(contract.get("defaultTakerFeeRate", "0"))
except (ValueError, TypeError):
raise ValueError("failed to parse fee rate")
# Calculate fee amount in decimal with ceiling to integer
amount_fee_dm = Decimal(str(math.ceil(float(value_dm * fee_rate))))
amount_fee_str = str(amount_fee_dm)
# Convert to the required integer format for the protocol
amount_fee = int(amount_fee_dm * Decimal("1000000")) # Shift 6 decimal places
nonce = self.async_client.calc_nonce(client_order_id)
l2_expire_time = int(time.time() * 1000) + (14 * 24 * 60 * 60 * 1000) # 14 days
# Calculate signature using asset IDs from metadata
expire_time_unix = l2_expire_time // (60 * 60 * 1000)
sig_hash = self.async_client.calc_limit_order_hash(
contract.get("starkExSyntheticAssetId", ""),
collateral_coin.get("starkExAssetId", ""),
collateral_coin.get("starkExAssetId", ""),
params.side.value == "BUY",
amount_synthetic,
amount_collateral,
amount_fee,
nonce,
self.async_client.get_account_id(),
expire_time_unix
)
# Sign the order
sig = self.async_client.sign(sig_hash)
# Convert signature to string (include v component like Go SDK, even though it's empty)
sig_str = f"{sig.r}{sig.s}{sig.v if hasattr(sig, 'v') and sig.v else ''}"
# Create order request
account_id = str(self.async_client.get_account_id())
nonce_str = str(nonce)
l2_expire_time_str = str(l2_expire_time)
expire_time_str = str(l2_expire_time - 864000000) # 10 days earlier
value_str = str(value_dm)
price_str = params.price if params.type == OrderType.LIMIT else "0"
# Prepare request data
request_data = {
"accountId": account_id,
"contractId": params.contract_id,
"price": price_str,
"size": params.size,
"type": params.type.value, # Use .value to get the string value
"timeInForce": params.time_in_force.value, # Use .value to get the string value
"side": params.side.value, # Use .value to get the string value
"l2Signature": sig_str,
"l2Nonce": nonce_str,
"l2ExpireTime": l2_expire_time_str,
"l2Value": value_str,
"l2Size": params.size,
"l2LimitFee": amount_fee_str,
"clientOrderId": client_order_id,
"expireTime": expire_time_str,
"reduceOnly": params.reduce_only
}
# Execute request using async client
return await self.async_client.make_authenticated_request(
method="POST",
path="/api/v1/private/order/createOrder",
data=request_data
)
async def cancel_order(self, params: CancelOrderParams) -> Dict[str, Any]:
"""
Cancel a specific order.
Args:
params: Cancel order parameters
Returns:
Dict[str, Any]: The cancellation result
Raises:
ValueError: If required parameters are missing or invalid
"""
account_id = str(self.async_client.get_account_id())
if params.order_id:
path = "/api/v1/private/order/cancelOrderById"
request_data = {
"accountId": account_id,
"orderIdList": [params.order_id]
}
elif params.client_id:
path = "/api/v1/private/order/cancelOrderByClientOrderId"
request_data = {
"accountId": account_id,
"clientOrderIdList": [params.client_id]
}
elif params.contract_id:
path = "/api/v1/private/order/cancelAllOrder"
request_data = {
"accountId": account_id,
"filterContractIdList": [params.contract_id]
}
else:
raise ValueError("must provide either order_id, client_id, or contract_id")
# Execute request using async client
return await self.async_client.make_authenticated_request(
method="POST",
path=path,
data=request_data
)
async def get_active_orders(self, params: GetActiveOrderParams) -> Dict[str, Any]:
"""
Get active orders with pagination and filters.
Args:
params: Active order query parameters
Returns:
Dict[str, Any]: The active orders
Raises:
ValueError: If the request fails
"""
# Build query parameters
query_params = {
"accountId": str(self.async_client.get_account_id())
}
# Add pagination parameters
if params.size:
query_params["size"] = params.size
if params.offset_data:
query_params["offsetData"] = params.offset_data
# Add filter parameters
if params.filter_coin_id_list:
query_params["filterCoinIdList"] = ",".join(params.filter_coin_id_list)
if params.filter_contract_id_list:
query_params["filterContractIdList"] = ",".join(params.filter_contract_id_list)
if params.filter_type_list:
query_params["filterTypeList"] = ",".join(params.filter_type_list)
if params.filter_status_list:
query_params["filterStatusList"] = ",".join(params.filter_status_list)
# Add boolean filters
if params.filter_is_liquidate is not None:
query_params["filterIsLiquidateList"] = str(params.filter_is_liquidate).lower()
if params.filter_is_deleverage is not None:
query_params["filterIsDeleverageList"] = str(params.filter_is_deleverage).lower()
if params.filter_is_position_tpsl is not None:
query_params["filterIsPositionTpslList"] = str(params.filter_is_position_tpsl).lower()
# Add time filters
if params.filter_start_created_time_inclusive > 0:
query_params["filterStartCreatedTimeInclusive"] = str(params.filter_start_created_time_inclusive)
if params.filter_end_created_time_exclusive > 0:
query_params["filterEndCreatedTimeExclusive"] = str(params.filter_end_created_time_exclusive)
# Execute request using async client
return await self.async_client.make_authenticated_request(
method="GET",
path="/api/v1/private/order/getActiveOrderPage",
params=query_params
)
async def get_order_fill_transactions(self, params: OrderFillTransactionParams) -> Dict[str, Any]:
"""
Get order fill transactions with pagination and filters.
Args:
params: Order fill transaction query parameters
Returns:
Dict[str, Any]: The order fill transactions
Raises:
ValueError: If the request fails
"""
# Build query parameters
query_params = {
"accountId": str(self.async_client.get_account_id())
}
# Add pagination parameters
if params.size:
query_params["size"] = params.size
if params.offset_data:
query_params["offsetData"] = params.offset_data
# Add filter parameters
if params.filter_coin_id_list:
query_params["filterCoinIdList"] = ",".join(params.filter_coin_id_list)
if params.filter_contract_id_list:
query_params["filterContractIdList"] = ",".join(params.filter_contract_id_list)
if params.filter_order_id_list:
query_params["filterOrderIdList"] = ",".join(params.filter_order_id_list)
# Add boolean filters
if params.filter_is_liquidate is not None:
query_params["filterIsLiquidateList"] = str(params.filter_is_liquidate).lower()
if params.filter_is_deleverage is not None:
query_params["filterIsDeleverageList"] = str(params.filter_is_deleverage).lower()
if params.filter_is_position_tpsl is not None:
query_params["filterIsPositionTpslList"] = str(params.filter_is_position_tpsl).lower()
# Add time filters
if params.filter_start_created_time_inclusive > 0:
query_params["filterStartCreatedTimeInclusive"] = str(params.filter_start_created_time_inclusive)
if params.filter_end_created_time_exclusive > 0:
query_params["filterEndCreatedTimeExclusive"] = str(params.filter_end_created_time_exclusive)
# Execute request using async client
return await self.async_client.make_authenticated_request(
method="GET",
path="/api/v1/private/order/getHistoryOrderFillTransactionPage",
params=query_params
)
async def get_max_order_size(self, contract_id: str, price: float) -> Dict[str, Any]:
"""
Get the maximum order size for a given contract and price.
Args:
contract_id: The contract ID
price: The price
Returns:
Dict[str, Any]: The maximum order size information
Raises:
ValueError: If the request fails
"""
# Build request body (API expects POST with JSON body)
data = {
"accountId": str(self.async_client.get_account_id()),
"contractId": contract_id,
"price": str(price)
}
# Execute request using async client
return await self.async_client.make_authenticated_request(
method="POST",
path="/api/v1/private/order/getMaxCreateOrderSize",
data=data
)
@@ -0,0 +1,165 @@
from dataclasses import dataclass
from enum import Enum
from typing import List, Optional, Dict, Any
class TimeInForce(str, Enum):
"""Time in force options for orders."""
UNKNOWN_TIME_IN_FORCE = "UNKNOWN_TIME_IN_FORCE"
GOOD_TIL_CANCEL = "GOOD_TIL_CANCEL"
FILL_OR_KILL = "FILL_OR_KILL"
IMMEDIATE_OR_CANCEL = "IMMEDIATE_OR_CANCEL"
POST_ONLY = "POST_ONLY"
class OrderSide(str, Enum):
"""Order side options."""
BUY = "BUY"
SELL = "SELL"
class ResponseCode(str, Enum):
"""API response codes."""
SUCCESS = "SUCCESS"
class OrderType(str, Enum):
"""Order type options."""
UNKNOWN = "UNKNOWN_ORDER_TYPE"
LIMIT = "LIMIT"
MARKET = "MARKET"
STOP_LIMIT = "STOP_LIMIT"
STOP_MARKET = "STOP_MARKET"
TAKE_PROFIT_LIMIT = "TAKE_PROFIT_LIMIT"
TAKE_PROFIT_MARKET = "TAKE_PROFIT_MARKET"
@dataclass
class OrderFilterParams:
"""Common filter types used across different order APIs."""
filter_coin_id_list: List[str] = None # Filter by coin IDs, empty means all coins
filter_contract_id_list: List[str] = None # Filter by contract IDs, empty means all contracts
filter_type_list: List[str] = None # Filter by order types
filter_status_list: List[str] = None # Filter by order statuses
filter_is_liquidate: Optional[bool] = None # Filter by liquidation status
filter_is_deleverage: Optional[bool] = None # Filter by deleverage status
filter_is_position_tpsl: Optional[bool] = None # Filter by position take-profit/stop-loss status
def __post_init__(self):
"""Initialize empty lists."""
if self.filter_coin_id_list is None:
self.filter_coin_id_list = []
if self.filter_contract_id_list is None:
self.filter_contract_id_list = []
if self.filter_type_list is None:
self.filter_type_list = []
if self.filter_status_list is None:
self.filter_status_list = []
@dataclass
class PaginationParams:
"""Common pagination parameters."""
size: str = "" # Size of the page, must be greater than 0 and less than or equal to 100/200
offset_data: str = "" # Offset data for pagination. Empty string gets the first page
@dataclass
class OrderFillTransactionParams(PaginationParams, OrderFilterParams):
"""Parameters for getting order fill transactions."""
filter_order_id_list: List[str] = None # Filter by order IDs, empty means all orders
filter_start_created_time_inclusive: int = 0 # Filter start time (inclusive), 0 means from earliest
filter_end_created_time_exclusive: int = 0 # Filter end time (exclusive), 0 means until latest
def __post_init__(self):
"""Initialize empty lists."""
super().__post_init__()
if self.filter_order_id_list is None:
self.filter_order_id_list = []
@dataclass
class GetActiveOrderParams(PaginationParams, OrderFilterParams):
"""Parameters for getting active orders."""
filter_start_created_time_inclusive: int = 0 # Filter start time (inclusive), 0 means from earliest
filter_end_created_time_exclusive: int = 0 # Filter end time (exclusive), 0 means until latest
@dataclass
class GetHistoryOrderParams(PaginationParams, OrderFilterParams):
"""Parameters for getting historical orders."""
filter_start_created_time_inclusive: int = 0 # Filter start time (inclusive), 0 means from earliest
filter_end_created_time_exclusive: int = 0 # Filter end time (exclusive), 0 means until latest
@dataclass
class CreateOrderParams:
"""Parameters for creating an order."""
contract_id: str
price: str
size: str
type: OrderType
side: str
client_order_id: Optional[str] = None
l2_expire_time: Optional[int] = None
time_in_force: Optional[str] = None
reduce_only: bool = False
@dataclass
class CancelOrderParams:
"""Parameters for canceling orders."""
order_id: str = "" # Order ID to cancel
client_id: str = "" # Client order ID to cancel
contract_id: str = "" # Contract ID for canceling all orders
class OrderResponse:
"""Response from creating an order."""
code: str
data: Dict[str, Any]
error_param: Optional[Dict[str, Any]]
request_time: str
response_time: str
trace_id: str
def __init__(self, response_data: Dict[str, Any]):
"""Initialize from response data."""
self.code = response_data.get("code", "")
self.data = response_data.get("data", {})
self.error_param = response_data.get("errorParam")
self.request_time = response_data.get("requestTime", "")
self.response_time = response_data.get("responseTime", "")
self.trace_id = response_data.get("traceId", "")
class MaxOrderSizeResponse(OrderResponse):
"""Response from getting max order size."""
pass
class OrderListResponse(OrderResponse):
"""Response from getting a list of orders."""
pass
class OrderPageResponse(OrderResponse):
"""Response from getting paginated orders."""
pass
class OrderFillTransactionResponse(OrderResponse):
"""Response from getting order fill transactions."""
pass
@dataclass
class OrderFillFilterParams(OrderFilterParams):
"""Parameters for filtering order fill transactions."""
filter_order_id_list: List[str] = None # Filter by order IDs, empty means all orders
def __post_init__(self):
"""Initialize empty lists."""
super().__post_init__()
if self.filter_order_id_list is None:
self.filter_order_id_list = []
@@ -0,0 +1,312 @@
from typing import Dict, Any, List
from ..internal.async_client import AsyncClient
class GetKLineParams:
"""Parameters for getting K-line data."""
def __init__(
self,
contract_id: str,
interval: str,
size: str = "",
offset_data: str = "",
filter_start_time_inclusive: int = 0,
filter_end_time_exclusive: int = 0
):
self.contract_id = contract_id
self.interval = interval
self.size = size
self.offset_data = offset_data
self.filter_start_time_inclusive = filter_start_time_inclusive
self.filter_end_time_exclusive = filter_end_time_exclusive
class GetOrderBookDepthParams:
"""Parameters for getting order book depth."""
def __init__(
self,
contract_id: str,
limit: int = 50
):
self.contract_id = contract_id
self.limit = limit
class GetMultiContractKLineParams:
"""Parameters for getting K-line data for multiple contracts."""
def __init__(
self,
contract_id_list: List[str],
interval: str,
limit: int = 1
):
self.contract_id_list = contract_id_list
self.interval = interval
self.limit = limit
class Client:
"""Client for quote-related API endpoints."""
def __init__(self, async_client: AsyncClient):
"""
Initialize the quote client.
Args:
async_client: The async client for common functionality
"""
self.async_client = async_client
async def get_quote_summary(self, contract_id: str) -> Dict[str, Any]:
"""
Get the quote summary for a given contract.
Args:
contract_id: The contract ID
Returns:
Dict[str, Any]: The quote summary
Raises:
ValueError: If the request fails
"""
# Public endpoint - use simple GET request
await self.async_client._ensure_session()
url = f"{self.async_client.base_url}/api/v1/public/quote/getTicketSummary"
params = {
"contractId": contract_id
}
try:
async with self.async_client.session.get(url, params=params) as response:
if response.status != 200:
try:
error_detail = await response.json()
raise ValueError(f"request failed with status code: {response.status}, response: {error_detail}")
except:
text = await response.text()
raise ValueError(f"request failed with status code: {response.status}, response: {text}")
resp_data = await response.json()
if resp_data.get("code") != "SUCCESS":
error_param = resp_data.get("errorParam")
if error_param:
raise ValueError(f"request failed with error params: {error_param}")
raise ValueError(f"request failed with code: {resp_data.get('code')}")
return resp_data
except Exception as e:
if isinstance(e, ValueError):
raise
raise ValueError(f"request failed: {str(e)}")
async def get_24_hour_quote(self, contract_id: str) -> Dict[str, Any]:
"""
Get the 24-hour quotes for a given contract.
Args:
contract_id: The contract ID
Returns:
Dict[str, Any]: The 24-hour quotes
Raises:
ValueError: If the request fails
"""
# Public endpoint - use simple GET request
await self.async_client._ensure_session()
url = f"{self.async_client.base_url}/api/v1/public/quote/getTicker"
params = {
"contractId": contract_id
}
try:
async with self.async_client.session.get(url, params=params) as response:
if response.status != 200:
try:
error_detail = await response.json()
raise ValueError(f"request failed with status code: {response.status}, response: {error_detail}")
except:
text = await response.text()
raise ValueError(f"request failed with status code: {response.status}, response: {text}")
resp_data = await response.json()
if resp_data.get("code") != "SUCCESS":
error_param = resp_data.get("errorParam")
if error_param:
raise ValueError(f"request failed with error params: {error_param}")
raise ValueError(f"request failed with code: {resp_data.get('code')}")
return resp_data
except Exception as e:
if isinstance(e, ValueError):
raise
raise ValueError(f"request failed: {str(e)}")
async def get_k_line(self, params: GetKLineParams) -> Dict[str, Any]:
"""
Get the K-line data for a contract.
Args:
params: K-line query parameters
Returns:
Dict[str, Any]: The K-line data
Raises:
ValueError: If the request fails
"""
url = f"{self.async_client.base_url}/api/v1/public/quote/getKline"
query_params = {
"contractId": params.contract_id,
"interval": params.interval
}
# Add pagination parameters
if params.size:
query_params["size"] = params.size
if params.offset_data:
query_params["offsetData"] = params.offset_data
# Add time filters
if params.filter_start_time_inclusive > 0:
query_params["filterStartTimeInclusive"] = str(params.filter_start_time_inclusive)
if params.filter_end_time_exclusive > 0:
query_params["filterEndTimeExclusive"] = str(params.filter_end_time_exclusive)
# Public endpoint - use simple GET request
await self.async_client._ensure_session()
url = f"{self.async_client.base_url}/api/v1/public/quote/getKline"
try:
async with self.async_client.session.get(url, params=query_params) as response:
if response.status != 200:
try:
error_detail = await response.json()
raise ValueError(f"request failed with status code: {response.status}, response: {error_detail}")
except:
text = await response.text()
raise ValueError(f"request failed with status code: {response.status}, response: {text}")
resp_data = await response.json()
if resp_data.get("code") != "SUCCESS":
error_param = resp_data.get("errorParam")
if error_param:
raise ValueError(f"request failed with error params: {error_param}")
raise ValueError(f"request failed with code: {resp_data.get('code')}")
return resp_data
except Exception as e:
if isinstance(e, ValueError):
raise
raise ValueError(f"request failed: {str(e)}")
async def get_order_book_depth(self, params: GetOrderBookDepthParams) -> Dict[str, Any]:
"""
Get the order book depth for a contract.
Args:
params: Order book depth query parameters
Returns:
Dict[str, Any]: The order book depth
Raises:
ValueError: If the request fails
"""
url = f"{self.async_client.base_url}/api/v1/public/quote/getDepth"
query_params = {
"contractId": params.contract_id,
"level": str(params.limit) # The API expects 'level', not 'limit'
}
# Public endpoint - use simple GET request
await self.async_client._ensure_session()
url = f"{self.async_client.base_url}/api/v1/public/quote/getDepth"
try:
async with self.async_client.session.get(url, params=query_params) as response:
if response.status != 200:
try:
error_detail = await response.json()
raise ValueError(f"request failed with status code: {response.status}, response: {error_detail}")
except:
text = await response.text()
raise ValueError(f"request failed with status code: {response.status}, response: {text}")
resp_data = await response.json()
if resp_data.get("code") != "SUCCESS":
error_param = resp_data.get("errorParam")
if error_param:
raise ValueError(f"request failed with error params: {error_param}")
raise ValueError(f"request failed with code: {resp_data.get('code')}")
return resp_data
except Exception as e:
if isinstance(e, ValueError):
raise
raise ValueError(f"request failed: {str(e)}")
async def get_multi_contract_k_line(self, params: GetMultiContractKLineParams) -> Dict[str, Any]:
"""
Get the K-line data for multiple contracts.
Args:
params: Multi-contract K-line query parameters
Returns:
Dict[str, Any]: The K-line data for multiple contracts
Raises:
ValueError: If the request fails
"""
# Public endpoint - use simple GET request
await self.async_client._ensure_session()
url = f"{self.async_client.base_url}/api/v1/public/quote/getMultiContractKline"
query_params = {
"contractIdList": ",".join(params.contract_id_list),
"interval": params.interval,
"limit": str(params.limit)
}
try:
async with self.async_client.session.get(url, params=query_params) as response:
if response.status != 200:
try:
error_detail = await response.json()
raise ValueError(f"request failed with status code: {response.status}, response: {error_detail}")
except:
text = await response.text()
raise ValueError(f"request failed with status code: {response.status}, response: {text}")
resp_data = await response.json()
if resp_data.get("code") != "SUCCESS":
error_param = resp_data.get("errorParam")
if error_param:
raise ValueError(f"request failed with error params: {error_param}")
raise ValueError(f"request failed with code: {resp_data.get('code')}")
return resp_data
except Exception as e:
if isinstance(e, ValueError):
raise
raise ValueError(f"request failed: {str(e)}")
@@ -0,0 +1,288 @@
from typing import Dict, Any, List
from ..internal.async_client import AsyncClient
class GetTransferOutByIdParams:
"""Parameters for getting transfer out records by ID."""
def __init__(self, transfer_id_list: List[str]):
self.transfer_id_list = transfer_id_list
class GetTransferInByIdParams:
"""Parameters for getting transfer in records by ID."""
def __init__(self, transfer_id_list: List[str]):
self.transfer_id_list = transfer_id_list
class GetWithdrawAvailableAmountParams:
"""Parameters for getting available withdrawal amount."""
def __init__(self, coin_id: str):
self.coin_id = coin_id
class CreateTransferOutParams:
"""Parameters for creating a transfer out order."""
def __init__(
self,
coin_id: str,
amount: str,
address: str,
network: str,
memo: str = "",
client_order_id: str = None
):
self.coin_id = coin_id
self.amount = amount
self.address = address
self.network = network
self.memo = memo
self.client_order_id = client_order_id
class GetTransferOutPageParams:
"""Parameters for getting transfer out page."""
def __init__(self, size: str = "10", offset_data: str = "", filter_coin_id_list: List[str] = None,
filter_status_list: List[str] = None, filter_start_created_time_inclusive: int = 0,
filter_end_created_time_exclusive: int = 0):
self.size = size
self.offset_data = offset_data
self.filter_coin_id_list = filter_coin_id_list or []
self.filter_status_list = filter_status_list or []
self.filter_start_created_time_inclusive = filter_start_created_time_inclusive
self.filter_end_created_time_exclusive = filter_end_created_time_exclusive
class GetTransferInPageParams:
"""Parameters for getting transfer in page."""
def __init__(self, size: str = "10", offset_data: str = "", filter_coin_id_list: List[str] = None,
filter_status_list: List[str] = None, filter_start_created_time_inclusive: int = 0,
filter_end_created_time_exclusive: int = 0):
self.size = size
self.offset_data = offset_data
self.filter_coin_id_list = filter_coin_id_list or []
self.filter_status_list = filter_status_list or []
self.filter_start_created_time_inclusive = filter_start_created_time_inclusive
self.filter_end_created_time_exclusive = filter_end_created_time_exclusive
class Client:
"""Client for transfer-related API endpoints."""
def __init__(self, async_client: AsyncClient):
"""
Initialize the transfer client.
Args:
async_client: The async client for common functionality
"""
self.async_client = async_client
async def get_transfer_out_by_id(self, params: GetTransferOutByIdParams) -> Dict[str, Any]:
"""
Get transfer out records by ID.
Args:
params: Transfer out query parameters
Returns:
Dict[str, Any]: The transfer out records
Raises:
ValueError: If the request fails
"""
query_params = {
"accountId": str(self.async_client.get_account_id()),
"transferIdList": ",".join(params.transfer_id_list)
}
return await self.async_client.make_authenticated_request(
method="GET",
path="/api/v1/private/transfer/getTransferOutById",
params=query_params
)
async def get_transfer_in_by_id(self, params: GetTransferInByIdParams) -> Dict[str, Any]:
"""
Get transfer in records by ID.
Args:
params: Transfer in query parameters
Returns:
Dict[str, Any]: The transfer in records
Raises:
ValueError: If the request fails
"""
query_params = {
"accountId": str(self.async_client.get_account_id()),
"transferIdList": ",".join(params.transfer_id_list)
}
return await self.async_client.make_authenticated_request(
method="GET",
path="/api/v1/private/transfer/getTransferInById",
params=query_params
)
async def get_withdraw_available_amount(self, params: GetWithdrawAvailableAmountParams) -> Dict[str, Any]:
"""
Get the available withdrawal amount.
Args:
params: Withdrawal available amount query parameters
Returns:
Dict[str, Any]: The available withdrawal amount
Raises:
ValueError: If the request fails
"""
query_params = {
"accountId": str(self.async_client.get_account_id()),
"coinId": params.coin_id
}
return await self.async_client.make_authenticated_request(
method="GET",
path="/api/v1/private/transfer/getTransferOutAvailableAmount",
params=query_params
)
async def create_transfer_out(self, params: CreateTransferOutParams, metadata: Dict[str, Any] = None) -> Dict[str, Any]:
"""
Create a new transfer out order.
Args:
params: Transfer out parameters
metadata: Exchange metadata (optional, not used in current implementation)
Returns:
Dict[str, Any]: The created transfer out order
Raises:
ValueError: If the request fails
"""
client_order_id = params.client_order_id or self.async_client.generate_uuid()
data = {
"accountId": str(self.async_client.get_account_id()),
"coinId": params.coin_id,
"amount": params.amount,
"address": params.address,
"network": params.network,
"clientOrderId": client_order_id
}
if params.memo:
data["memo"] = params.memo
# TODO: Implement signature calculation for transfer out
# This would require:
# 1. Asset ID from metadata based on coin_id
# 2. Receiver public key from address
# 3. Position IDs for sender, receiver, and fee
# 4. Proper expiration time calculation
# 5. Call to calc_transfer_hash and sign the result
# For now, the API call is made without signature (may fail on actual server)
return await self.async_client.make_authenticated_request(
method="POST",
path="/api/v1/private/transfer/createTransferOut",
data=data
)
async def get_transfer_out_page(
self,
params: GetTransferOutPageParams
) -> Dict[str, Any]:
"""
Get transfer out records with pagination.
Args:
params: Parameters for the request
Returns:
Dict[str, Any]: The transfer out records
Raises:
ValueError: If the request fails
"""
query_params = {
"accountId": str(self.async_client.get_account_id())
}
# Add pagination parameters
if params.size:
query_params["size"] = params.size
if params.offset_data:
query_params["offsetData"] = params.offset_data
# Add filter parameters
if params.filter_coin_id_list:
query_params["filterCoinIdList"] = ",".join(params.filter_coin_id_list)
if params.filter_status_list:
query_params["filterStatusList"] = ",".join(params.filter_status_list)
# Add time filters
if params.filter_start_created_time_inclusive > 0:
query_params["filterStartCreatedTimeInclusive"] = str(params.filter_start_created_time_inclusive)
if params.filter_end_created_time_exclusive > 0:
query_params["filterEndCreatedTimeExclusive"] = str(params.filter_end_created_time_exclusive)
return await self.async_client.make_authenticated_request(
method="GET",
path="/api/v1/private/transfer/getActiveTransferOut",
params=query_params
)
async def get_transfer_in_page(
self,
params: GetTransferInPageParams
) -> Dict[str, Any]:
"""
Get transfer in records with pagination.
Args:
params: Parameters for the request
Returns:
Dict[str, Any]: The transfer in records
Raises:
ValueError: If the request fails
"""
query_params = {
"accountId": str(self.async_client.get_account_id())
}
# Add pagination parameters
if params.size:
query_params["size"] = params.size
if params.offset_data:
query_params["offsetData"] = params.offset_data
# Add filter parameters
if params.filter_coin_id_list:
query_params["filterCoinIdList"] = ",".join(params.filter_coin_id_list)
if params.filter_status_list:
query_params["filterStatusList"] = ",".join(params.filter_status_list)
# Add time filters
if params.filter_start_created_time_inclusive > 0:
query_params["filterStartCreatedTimeInclusive"] = str(params.filter_start_created_time_inclusive)
if params.filter_end_created_time_exclusive > 0:
query_params["filterEndCreatedTimeExclusive"] = str(params.filter_end_created_time_exclusive)
return await self.async_client.make_authenticated_request(
method="GET",
path="/api/v1/private/transfer/getActiveTransferIn",
params=query_params
)
@@ -0,0 +1,302 @@
import asyncio
import binascii
import json
import logging
import threading
import time
from typing import Dict, Any, List, Optional, Callable, Union
import websocket
from Crypto.Hash import keccak
from ..internal.signing_adapter import SigningAdapter
from ..internal.client import Client as InternalClient
class Client:
"""WebSocket client for real-time data."""
def __init__(self, url: str, is_private: bool, account_id: int, stark_pri_key: str, signing_adapter: Optional[SigningAdapter] = None):
"""
Initialize the WebSocket client.
Args:
url: WebSocket URL
is_private: Whether this is a private WebSocket connection
account_id: Account ID for authentication
stark_pri_key: Stark private key for signing
"""
self.url = url
self.is_private = is_private
self.account_id = account_id
self.stark_pri_key = stark_pri_key
# Use the provided signing adapter (required)
if signing_adapter is None:
raise ValueError("signing_adapter is required")
self.signing_adapter = signing_adapter
self.conn = None
self.handlers = {}
self.done = threading.Event()
self.ping_thread = None
self.subscriptions = set()
self.on_connect_hooks = []
self.on_message_hooks = []
self.on_disconnect_hooks = []
self.logger = logging.getLogger(__name__)
def connect(self):
"""
Establish a WebSocket connection.
Raises:
ValueError: If the connection fails
"""
headers = {}
url = self.url
# Add timestamp parameter for both public and private connections
timestamp = int(time.time() * 1000)
if self.is_private:
# Add timestamp header
headers["X-edgeX-Api-Timestamp"] = str(timestamp)
# Generate signature content (no ? separator, matching Go SDK)
path = f"/api/v1/private/wsaccountId={self.account_id}"
sign_content = f"{timestamp}GET{path}"
# Hash the content
keccak_hash = keccak.new(digest_bits=256)
keccak_hash.update(sign_content.encode())
message_hash = keccak_hash.digest()
# Sign the message using the signing adapter
try:
r, s = self.signing_adapter.sign(message_hash, self.stark_pri_key)
except Exception as e:
raise ValueError(f"failed to sign message: {str(e)}")
# Set signature header
headers["X-edgeX-Api-Signature"] = f"{r}{s}"
else:
# For public connections, add timestamp as URL parameter
separator = "&" if "?" in url else "?"
url = f"{url}{separator}timestamp={timestamp}"
# Create WebSocket connection
try:
self.conn = websocket.create_connection(url, header=headers)
except Exception as e:
raise ValueError(f"failed to connect to WebSocket: {str(e)}")
# Start ping thread
self.done.clear()
self.ping_thread = threading.Thread(target=self._ping_loop)
self.ping_thread.daemon = True
self.ping_thread.start()
# Start message handling thread
self.message_thread = threading.Thread(target=self._handle_messages)
self.message_thread.daemon = True
self.message_thread.start()
# Call connect hooks
for hook in self.on_connect_hooks:
hook()
def close(self):
"""Close the WebSocket connection."""
self.done.set()
if self.conn:
self.conn.close()
self.conn = None
def _ping_loop(self):
"""Send periodic ping messages."""
while not self.done.is_set():
if self.conn:
ping_msg = {
"type": "ping",
"time": str(int(time.time() * 1000))
}
try:
self.conn.send(json.dumps(ping_msg))
except Exception as e:
self.logger.error(f"Failed to send ping: {str(e)}")
break
# Wait for 30 seconds or until done
self.done.wait(30)
def _handle_messages(self):
"""Process incoming WebSocket messages."""
while not self.done.is_set():
if not self.conn:
break
try:
message = self.conn.recv()
# Call message hooks
for hook in self.on_message_hooks:
hook(message)
# Parse message
try:
msg = json.loads(message)
except json.JSONDecodeError:
continue
# Handle ping messages
if msg.get("type") == "ping":
self._handle_pong(msg.get("time", ""))
continue
# Handle quote events
if msg.get("type") == "quote-event":
channel = msg.get("channel", "")
channel_type = channel.split(".")[0] if "." in channel else channel
if channel_type in self.handlers:
self.handlers[channel_type](message)
continue
# Call registered handlers for other message types
msg_type = msg.get("type", "")
if msg_type in self.handlers:
self.handlers[msg_type](message)
except Exception as e:
self.logger.error(f"Error handling message: {str(e)}")
# Call disconnect hooks
for hook in self.on_disconnect_hooks:
hook(e)
break
def _handle_pong(self, timestamp: str):
"""
Send pong response to server ping.
Args:
timestamp: The timestamp from the ping message
"""
pong_msg = {
"type": "pong",
"time": timestamp
}
try:
self.conn.send(json.dumps(pong_msg))
except Exception as e:
self.logger.error(f"Failed to send pong: {str(e)}")
def subscribe(self, topic: str, params: Dict[str, Any] = None) -> bool:
"""
Subscribe to a topic (for public WebSocket).
Args:
topic: The topic to subscribe to
params: Optional parameters for the subscription
Returns:
bool: Whether the subscription was successful
Raises:
ValueError: If the subscription fails
"""
if self.is_private:
raise ValueError("cannot subscribe on private WebSocket connection")
if not self.conn:
raise ValueError("WebSocket connection is not established")
sub_msg = {
"type": "subscribe",
"channel": topic
}
if params:
sub_msg.update(params)
try:
self.conn.send(json.dumps(sub_msg))
self.subscriptions.add(topic)
return True
except Exception as e:
raise ValueError(f"failed to subscribe: {str(e)}")
def unsubscribe(self, topic: str) -> bool:
"""
Unsubscribe from a topic (for public WebSocket).
Args:
topic: The topic to unsubscribe from
Returns:
bool: Whether the unsubscription was successful
Raises:
ValueError: If the unsubscription fails
"""
if self.is_private:
raise ValueError("cannot unsubscribe on private WebSocket connection")
if not self.conn:
raise ValueError("WebSocket connection is not established")
unsub_msg = {
"type": "unsubscribe",
"channel": topic
}
try:
self.conn.send(json.dumps(unsub_msg))
self.subscriptions.discard(topic)
return True
except Exception as e:
raise ValueError(f"failed to unsubscribe: {str(e)}")
def on_message(self, msg_type: str, handler: Callable[[str], None]):
"""
Register a handler for a specific message type.
Args:
msg_type: The message type to handle
handler: The handler function
"""
self.handlers[msg_type] = handler
def on_message_hook(self, hook: Callable[[str], None]):
"""
Register a hook that will be called for all messages.
Args:
hook: The hook function
"""
self.on_message_hooks.append(hook)
def on_connect(self, hook: Callable[[], None]):
"""
Register a hook that will be called when connection is established.
Args:
hook: The hook function
"""
self.on_connect_hooks.append(hook)
def on_disconnect(self, hook: Callable[[Exception], None]):
"""
Register a hook that will be called when connection is closed.
Args:
hook: The hook function
"""
self.on_disconnect_hooks.append(hook)
@@ -0,0 +1,231 @@
import logging
from typing import Dict, Any, List, Optional, Callable
from ..internal.signing_adapter import SigningAdapter
from ..internal.starkex_signing_adapter import StarkExSigningAdapter
from .client import Client
class Manager:
"""Manager for WebSocket connections."""
def __init__(self, base_url: str, account_id: int, stark_pri_key: str, signing_adapter: Optional[SigningAdapter] = None):
"""
Initialize the WebSocket manager.
Args:
base_url: Base WebSocket URL
account_id: Account ID for authentication
stark_pri_key: Stark private key for signing
signing_adapter: Optional signing adapter (defaults to StarkExSigningAdapter)
"""
self.base_url = base_url
self.account_id = account_id
self.stark_pri_key = stark_pri_key
# Use StarkExSigningAdapter as default if none provided
if signing_adapter is None:
signing_adapter = StarkExSigningAdapter()
self.signing_adapter = signing_adapter
self.public_client = None
self.private_client = None
self.logger = logging.getLogger(__name__)
def get_public_client(self) -> Client:
"""
Get the public WebSocket client.
Returns:
Client: The public WebSocket client
"""
if not self.public_client:
self.public_client = Client(
url=f"{self.base_url}/api/v1/public/ws",
is_private=False,
account_id=self.account_id,
stark_pri_key=self.stark_pri_key,
signing_adapter=self.signing_adapter
)
return self.public_client
def get_private_client(self) -> Client:
"""
Get the private WebSocket client.
Returns:
Client: The private WebSocket client
"""
if not self.private_client:
self.private_client = Client(
url=f"{self.base_url}/api/v1/private/ws?accountId={self.account_id}",
is_private=True,
account_id=self.account_id,
stark_pri_key=self.stark_pri_key,
signing_adapter=self.signing_adapter
)
return self.private_client
def connect_public(self):
"""
Connect to the public WebSocket.
Raises:
ValueError: If the connection fails
"""
client = self.get_public_client()
client.connect()
def connect_private(self):
"""
Connect to the private WebSocket.
Raises:
ValueError: If the connection fails
"""
client = self.get_private_client()
client.connect()
def disconnect_public(self):
"""Disconnect from the public WebSocket."""
if self.public_client:
self.public_client.close()
def disconnect_private(self):
"""Disconnect from the private WebSocket."""
if self.private_client:
self.private_client.close()
def disconnect_all(self):
"""Disconnect from all WebSockets."""
self.disconnect_public()
self.disconnect_private()
def subscribe_ticker(self, contract_id: str, handler: Callable[[str], None]):
"""
Subscribe to ticker updates for a contract.
Args:
contract_id: The contract ID
handler: The handler function
Raises:
ValueError: If the subscription fails
"""
client = self.get_public_client()
# Register handler
client.on_message("ticker", handler)
# Subscribe to ticker channel
channel = f"ticker.{contract_id}"
client.subscribe(channel)
def subscribe_kline(self, contract_id: str, interval: str, handler: Callable[[str], None]):
"""
Subscribe to K-line updates for a contract.
Args:
contract_id: The contract ID
interval: The K-line interval
handler: The handler function
Raises:
ValueError: If the subscription fails
"""
client = self.get_public_client()
# Register handler
client.on_message("kline", handler)
# Subscribe to kline channel
channel = f"kline.{contract_id}.{interval}"
client.subscribe(channel)
def subscribe_depth(self, contract_id: str, handler: Callable[[str], None]):
"""
Subscribe to depth updates for a contract.
Args:
contract_id: The contract ID
handler: The handler function
Raises:
ValueError: If the subscription fails
"""
client = self.get_public_client()
# Register handler
client.on_message("depth", handler)
# Subscribe to depth channel
channel = f"depth.{contract_id}"
client.subscribe(channel)
def subscribe_trade(self, contract_id: str, handler: Callable[[str], None]):
"""
Subscribe to trade updates for a contract.
Args:
contract_id: The contract ID
handler: The handler function
Raises:
ValueError: If the subscription fails
"""
client = self.get_public_client()
# Register handler
client.on_message("trade", handler)
# Subscribe to trade channel
channel = f"trade.{contract_id}"
client.subscribe(channel)
def subscribe_account_update(self, handler: Callable[[str], None]):
"""
Subscribe to account updates.
Args:
handler: The handler function
Raises:
ValueError: If the subscription fails
"""
client = self.get_private_client()
# Register handler
client.on_message("account", handler)
def subscribe_order_update(self, handler: Callable[[str], None]):
"""
Subscribe to order updates.
Args:
handler: The handler function
Raises:
ValueError: If the subscription fails
"""
client = self.get_private_client()
# Register handler
client.on_message("order", handler)
def subscribe_position_update(self, handler: Callable[[str], None]):
"""
Subscribe to position updates.
Args:
handler: The handler function
Raises:
ValueError: If the subscription fails
"""
client = self.get_private_client()
# Register handler
client.on_message("position", handler)
@@ -0,0 +1,120 @@
# EdgeX Python SDK Examples
This directory contains examples demonstrating how to use the EdgeX Python SDK.
## Prerequisites
Before running the examples, make sure you have installed the EdgeX Python SDK:
```bash
pip install edgex-python-sdk
```
Or, if you're working with the source code:
```bash
cd edgex-python-sdk
pip install -e .
```
## Environment Variables
The examples use the following environment variables:
- `EDGEX_BASE_URL`: Base URL for HTTP API endpoints (e.g., "https://pro.edgex.exchange" for production, "https://testnet.edgex.exchange" for testnet)
- `EDGEX_WS_URL`: Base URL for WebSocket endpoints (e.g., "wss://quote.edgex.exchange" for production, "wss://quote-testnet.edgex.exchange" for testnet)
- `EDGEX_ACCOUNT_ID`: Your account ID
- `EDGEX_STARK_PRIVATE_KEY`: Your stark private key
You can set these variables in your environment or create a `.env` file in the examples directory:
```
EDGEX_BASE_URL=https://pro.edgex.exchange # Use https://testnet.edgex.exchange for testnet
EDGEX_WS_URL=wss://quote.edgex.exchange # Use wss://quote-testnet.edgex.exchange for testnet
EDGEX_ACCOUNT_ID=12345
EDGEX_STARK_PRIVATE_KEY=your-stark-private-key
```
## Examples
### Basic Usage
The `basic_usage.py` example demonstrates the basic functionality of the SDK:
- Creating a client
- Getting server time and metadata
- Getting account assets and positions
- Getting market data (K-lines, order book depth)
- Creating orders (commented out to avoid actual order creation)
- Using WebSockets for real-time data
To run the example:
```bash
python basic_usage.py
```
### Advanced Usage
The `advanced_usage.py` example demonstrates more advanced features of the SDK:
- Order management (creating and canceling orders)
- WebSocket integration with proper handlers
- Error handling
- Pagination
- Using a trader class to encapsulate functionality
To run the example:
```bash
python advanced_usage.py
```
## Contract IDs
EdgeX uses numeric contract IDs instead of symbol-based identifiers. Here are some common contract mappings:
| Contract ID | Symbol | Tick Size |
|-------------|---------------|-----------|
| 10000001 | BTCUSDT | 0.1 |
| 10000002 | ETHUSDT | 0.01 |
| 10000003 | SOLUSDT | 0.01 |
| 10000004 | BNBUSDT | 0.01 |
To get the complete list of available contracts:
```python
metadata = await client.get_metadata()
contracts = metadata.get("data", {}).get("contractList", [])
for contract in contracts:
print(f"ID: {contract['contractId']} - {contract['contractName']}")
```
## Notes
- The examples include order creation code that is commented out to avoid creating actual orders. Uncomment this code if you want to create real orders.
- The WebSocket examples will run for a short time and then disconnect. Adjust the sleep time if you want to receive more updates.
- The examples use asyncio for asynchronous operations. Make sure you're using Python 3.7 or later.
- All examples use numeric contract IDs (e.g., "10000001" for BTCUSDT) as required by the EdgeX API.
- For order book depth queries, valid limit values are 15 or 200.
## Customization
Feel free to modify the examples to suit your needs. Some ideas:
- Implement a trading strategy
- Add more error handling
- Implement a command-line interface
- Create a web interface using a framework like Flask or FastAPI
- Add logging to a file
- Add more sophisticated order management
## Troubleshooting
If you encounter issues:
1. Check that your environment variables are set correctly
2. Verify that you have the latest version of the SDK
3. Check the EdgeX API documentation for any changes
4. Look for error messages in the console output
5. Try with a smaller subset of functionality to isolate the issue
@@ -0,0 +1,657 @@
"""
Advanced usage example for the EdgeX Python SDK.
This example demonstrates more advanced features of the SDK, including:
- Order management
- WebSocket integration
- Error handling
- Pagination
"""
import asyncio
import os
import logging
from decimal import Decimal
from typing import Dict, Any, List
from edgex_sdk import (
Client,
OrderSide,
OrderType,
TimeInForce,
CreateOrderParams,
CancelOrderParams,
GetActiveOrderParams,
OrderFillTransactionParams,
GetKLineParams,
GetOrderBookDepthParams,
WebSocketManager
)
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class EdgeXTrader:
"""Example trader using the EdgeX Python SDK."""
def __init__(self, base_url: str, ws_url: str, account_id: int, stark_private_key: str):
"""
Initialize the trader.
Args:
base_url: Base URL for API endpoints
ws_url: Base URL for WebSocket endpoints
account_id: Account ID for authentication
stark_private_key: Stark private key for signing
"""
self.client = Client(
base_url=base_url,
account_id=account_id,
stark_private_key=stark_private_key
)
self.ws_manager = WebSocketManager(
base_url=ws_url,
account_id=account_id,
stark_pri_key=stark_private_key
)
self.metadata = None
self.contracts = {}
self.market_data = {}
self.active_orders = {}
self.positions = {}
self.assets = {}
async def initialize(self):
"""Initialize the trader by fetching metadata and account information."""
logger.info("Initializing trader...")
try:
# Get metadata
self.metadata = await self.client.get_metadata()
logger.info("Metadata retrieved")
# Extract contracts
contract_list = self.metadata.get("data", {}).get("contractList", [])
for contract in contract_list:
contract_id = contract.get("contractId")
if contract_id:
self.contracts[contract_id] = contract
logger.info(f"Found {len(self.contracts)} contracts")
# Get account assets
assets_response = await self.client.get_account_asset()
self.assets = assets_response.get("data", {})
logger.info("Account assets retrieved")
# Get account positions
positions_response = await self.client.get_account_positions()
positions_data = positions_response.get("data", {})
position_list = positions_data.get("positionList", [])
for position in position_list:
contract_id = position.get("contractId")
if contract_id:
self.positions[contract_id] = position
logger.info(f"Found {len(self.positions)} positions")
# Get active orders
await self.update_active_orders()
# Initialize WebSocket
await self.initialize_websocket()
logger.info("Trader initialized successfully")
return True
except Exception as e:
logger.error(f"Failed to initialize trader: {str(e)}")
return False
async def update_active_orders(self):
"""Update the list of active orders."""
try:
params = GetActiveOrderParams()
active_orders_response = await self.client.get_active_orders(params)
order_list = active_orders_response.get("data", {}).get("list", [])
self.active_orders = {}
for order in order_list:
order_id = order.get("orderId")
if order_id:
self.active_orders[order_id] = order
logger.info(f"Found {len(self.active_orders)} active orders")
return True
except Exception as e:
logger.error(f"Failed to update active orders: {str(e)}")
return False
async def initialize_websocket(self):
"""Initialize WebSocket connections and subscriptions."""
try:
# Connect to public WebSocket
self.ws_manager.connect_public()
logger.info("Connected to public WebSocket")
# Connect to private WebSocket
self.ws_manager.connect_private()
logger.info("Connected to private WebSocket")
# Subscribe to account updates
self.ws_manager.subscribe_account_update(self.handle_account_update)
logger.info("Subscribed to account updates")
# Subscribe to order updates
self.ws_manager.subscribe_order_update(self.handle_order_update)
logger.info("Subscribed to order updates")
# Subscribe to position updates
self.ws_manager.subscribe_position_update(self.handle_position_update)
logger.info("Subscribed to position updates")
# Subscribe to market data for BTCUSDT (contract ID: 10000001)
self.ws_manager.subscribe_ticker("10000001", self.handle_ticker_update)
self.ws_manager.subscribe_kline("10000001", "1m", self.handle_kline_update)
self.ws_manager.subscribe_depth("10000001", self.handle_depth_update)
logger.info("Subscribed to market data for BTCUSDT (10000001)")
return True
except Exception as e:
logger.error(f"Failed to initialize WebSocket: {str(e)}")
return False
def handle_account_update(self, message: str):
"""
Handle account update messages from WebSocket.
Args:
message: The WebSocket message
"""
try:
import json
data = json.loads(message)
logger.info(f"Account update: {data}")
# Update assets
account_data = data.get("content", {}).get("data", {})
if account_data:
self.assets = account_data
except Exception as e:
logger.error(f"Failed to handle account update: {str(e)}")
def handle_order_update(self, message: str):
"""
Handle order update messages from WebSocket.
Args:
message: The WebSocket message
"""
try:
import json
data = json.loads(message)
logger.info(f"Order update: {data}")
# Update active orders
asyncio.create_task(self.update_active_orders())
except Exception as e:
logger.error(f"Failed to handle order update: {str(e)}")
def handle_position_update(self, message: str):
"""
Handle position update messages from WebSocket.
Args:
message: The WebSocket message
"""
try:
import json
data = json.loads(message)
logger.info(f"Position update: {data}")
# Update positions
position_data = data.get("content", {}).get("data", {})
contract_id = position_data.get("contractId")
if contract_id:
self.positions[contract_id] = position_data
except Exception as e:
logger.error(f"Failed to handle position update: {str(e)}")
def handle_ticker_update(self, message: str):
"""
Handle ticker update messages from WebSocket.
Args:
message: The WebSocket message
"""
try:
import json
data = json.loads(message)
# Extract ticker data
content = data.get("content", {})
ticker_data_list = content.get("data", [])
# Handle both single ticker and list of tickers
if isinstance(ticker_data_list, list) and ticker_data_list:
ticker_data = ticker_data_list[0] # Take the first ticker
else:
ticker_data = ticker_data_list
contract_id = ticker_data.get("contractId") if isinstance(ticker_data, dict) else None
if contract_id:
if "ticker" not in self.market_data:
self.market_data["ticker"] = {}
self.market_data["ticker"][contract_id] = ticker_data
logger.info(f"Ticker update for {contract_id}: {ticker_data.get('lastPrice')}")
except Exception as e:
logger.error(f"Failed to handle ticker update: {str(e)}")
def handle_kline_update(self, message: str):
"""
Handle K-line update messages from WebSocket.
Args:
message: The WebSocket message
"""
try:
import json
data = json.loads(message)
# Extract K-line data
kline_data = data.get("content", {}).get("data", {})
contract_id = kline_data.get("contractId")
interval = kline_data.get("interval")
if contract_id and interval:
if "kline" not in self.market_data:
self.market_data["kline"] = {}
if contract_id not in self.market_data["kline"]:
self.market_data["kline"][contract_id] = {}
self.market_data["kline"][contract_id][interval] = kline_data
logger.info(f"K-line update for {contract_id} {interval}: {kline_data.get('close')}")
except Exception as e:
logger.error(f"Failed to handle K-line update: {str(e)}")
def handle_depth_update(self, message: str):
"""
Handle depth update messages from WebSocket.
Args:
message: The WebSocket message
"""
try:
import json
data = json.loads(message)
# Extract depth data
depth_data = data.get("content", {}).get("data", {})
contract_id = depth_data.get("contractId")
if contract_id:
if "depth" not in self.market_data:
self.market_data["depth"] = {}
self.market_data["depth"][contract_id] = depth_data
logger.info(f"Depth update for {contract_id}")
except Exception as e:
logger.error(f"Failed to handle depth update: {str(e)}")
async def create_limit_order(
self,
contract_id: str,
size: str,
price: str,
side: str,
time_in_force: str = TimeInForce.GOOD_TIL_CANCEL,
reduce_only: bool = False
) -> Dict[str, Any]:
"""
Create a limit order.
Args:
contract_id: The contract ID
size: The order size
price: The order price
side: The order side (BUY or SELL)
time_in_force: The time in force
reduce_only: Whether the order is reduce-only
Returns:
Dict[str, Any]: The created order
Raises:
ValueError: If the order creation fails
"""
try:
# Create order parameters
params = CreateOrderParams(
contract_id=contract_id,
size=size,
price=price,
type=OrderType.LIMIT,
side=side,
time_in_force=time_in_force,
reduce_only=reduce_only
)
# Create the order
result = await self.client.create_order(params)
# Check for success
if result.get("code") != "SUCCESS":
error_param = result.get("errorParam")
if error_param:
raise ValueError(f"Failed to create order: {error_param}")
raise ValueError(f"Failed to create order: {result.get('code')}")
# Update active orders
await self.update_active_orders()
logger.info(f"Created limit order: {result.get('data', {}).get('orderId')}")
return result
except Exception as e:
logger.error(f"Failed to create limit order: {str(e)}")
raise
async def cancel_order(self, order_id: str) -> Dict[str, Any]:
"""
Cancel an order.
Args:
order_id: The order ID
Returns:
Dict[str, Any]: The cancellation result
Raises:
ValueError: If the order cancellation fails
"""
try:
# Create cancel order parameters
params = CancelOrderParams(order_id=order_id)
# Cancel the order
result = await self.client.cancel_order(params)
# Check for success
if result.get("code") != "SUCCESS":
error_param = result.get("errorParam")
if error_param:
raise ValueError(f"Failed to cancel order: {error_param}")
raise ValueError(f"Failed to cancel order: {result.get('code')}")
# Update active orders
await self.update_active_orders()
logger.info(f"Cancelled order: {order_id}")
return result
except Exception as e:
logger.error(f"Failed to cancel order: {str(e)}")
raise
async def cancel_all_orders(self, contract_id: str = None) -> Dict[str, Any]:
"""
Cancel all orders for a contract.
Args:
contract_id: The contract ID (optional)
Returns:
Dict[str, Any]: The cancellation result
Raises:
ValueError: If the order cancellation fails
"""
try:
# Create cancel order parameters
params = CancelOrderParams(contract_id=contract_id or "")
# Cancel the orders
result = await self.client.cancel_order(params)
# Check for success
if result.get("code") != "SUCCESS":
error_param = result.get("errorParam")
if error_param:
raise ValueError(f"Failed to cancel orders: {error_param}")
raise ValueError(f"Failed to cancel orders: {result.get('code')}")
# Update active orders
await self.update_active_orders()
logger.info(f"Cancelled all orders for contract: {contract_id or 'all'}")
return result
except Exception as e:
logger.error(f"Failed to cancel all orders: {str(e)}")
raise
async def get_order_fill_transactions(
self,
contract_id: str = None,
size: str = "10",
offset_data: str = ""
) -> Dict[str, Any]:
"""
Get order fill transactions.
Args:
contract_id: The contract ID (optional)
size: The page size
offset_data: The offset data for pagination
Returns:
Dict[str, Any]: The order fill transactions
Raises:
ValueError: If the request fails
"""
try:
# Create parameters
params = OrderFillTransactionParams(
size=size,
offset_data=offset_data
)
if contract_id:
params.filter_contract_id_list = [contract_id]
# Get order fill transactions
result = await self.client.get_order_fill_transactions(params)
# Check for success
if result.get("code") != "SUCCESS":
error_param = result.get("errorParam")
if error_param:
raise ValueError(f"Failed to get order fill transactions: {error_param}")
raise ValueError(f"Failed to get order fill transactions: {result.get('code')}")
logger.info(f"Got order fill transactions: {len(result.get('data', {}).get('list', []))}")
return result
except Exception as e:
logger.error(f"Failed to get order fill transactions: {str(e)}")
raise
async def get_k_line(
self,
contract_id: str,
interval: str,
size: str = "100",
offset_data: str = ""
) -> Dict[str, Any]:
"""
Get K-line data.
Args:
contract_id: The contract ID
interval: The K-line interval
size: The page size
offset_data: The offset data for pagination
Returns:
Dict[str, Any]: The K-line data
Raises:
ValueError: If the request fails
"""
try:
# Create parameters
params = GetKLineParams(
contract_id=contract_id,
interval=interval,
size=size,
offset_data=offset_data
)
# Get K-line data
result = await self.client.quote.get_k_line(params)
# Check for success
if result.get("code") != "SUCCESS":
error_param = result.get("errorParam")
if error_param:
raise ValueError(f"Failed to get K-line data: {error_param}")
raise ValueError(f"Failed to get K-line data: {result.get('code')}")
logger.info(f"Got K-line data: {len(result.get('data', {}).get('list', []))}")
return result
except Exception as e:
logger.error(f"Failed to get K-line data: {str(e)}")
raise
async def get_order_book_depth(
self,
contract_id: str,
limit: int = 15
) -> Dict[str, Any]:
"""
Get order book depth.
Args:
contract_id: The contract ID
limit: The depth limit (valid values are 15 or 200)
Returns:
Dict[str, Any]: The order book depth
Raises:
ValueError: If the request fails
"""
try:
# Create parameters
params = GetOrderBookDepthParams(
contract_id=contract_id,
limit=limit
)
# Get order book depth
result = await self.client.quote.get_order_book_depth(params)
# Check for success
if result.get("code") != "SUCCESS":
error_param = result.get("errorParam")
if error_param:
raise ValueError(f"Failed to get order book depth: {error_param}")
raise ValueError(f"Failed to get order book depth: {result.get('code')}")
logger.info(f"Got order book depth for {contract_id}")
return result
except Exception as e:
logger.error(f"Failed to get order book depth: {str(e)}")
raise
async def close(self):
"""Close all connections."""
try:
# Disconnect WebSocket
self.ws_manager.disconnect_all()
logger.info("Disconnected from WebSocket")
return True
except Exception as e:
logger.error(f"Failed to close connections: {str(e)}")
return False
async def main():
"""Main function."""
# Load configuration from environment variables
base_url = os.getenv("EDGEX_BASE_URL", "https://testnet.edgex.exchange")
ws_url = os.getenv("EDGEX_WS_URL", "wss://quote-testnet.edgex.exchange")
account_id = int(os.getenv("EDGEX_ACCOUNT_ID", "12345"))
stark_private_key = os.getenv("EDGEX_STARK_PRIVATE_KEY", "your-stark-private-key")
# Create trader
trader = EdgeXTrader(
base_url=base_url,
ws_url=ws_url,
account_id=account_id,
stark_private_key=stark_private_key
)
# Initialize trader
if not await trader.initialize():
logger.error("Failed to initialize trader")
return
try:
# Get K-line data for BTCUSDT (contract ID: 10000001)
klines = await trader.get_k_line("10000001", "1m")
logger.info(f"Retrieved K-line data: {len(klines.get('data', {}).get('list', []))} entries")
# Get order book depth for BTCUSDT (contract ID: 10000001)
await trader.get_order_book_depth("10000001")
logger.info(f"Retrieved order book depth")
# Create a limit order (commented out to avoid actual order creation)
# order = await trader.create_limit_order(
# contract_id="10000001", # BTCUSDT
# size="0.001",
# price="30000",
# side=OrderSide.BUY
# )
#
# # Cancel the order
# if order and order.get("data", {}).get("orderId"):
# await trader.cancel_order(order.get("data", {}).get("orderId"))
# Wait for some WebSocket updates
logger.info("Waiting for WebSocket updates...")
await asyncio.sleep(60)
finally:
# Close connections
await trader.close()
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,114 @@
"""
Basic usage example for the EdgeX Python SDK.
This example demonstrates the basic functionality of the SDK:
- Creating a client
- Getting server time and metadata
- Getting account assets and positions
- Getting market data (K-lines, order book depth)
- Creating orders (commented out to avoid actual order creation)
- Using WebSockets for real-time data
"""
import asyncio
import os
from edgex_sdk import (
Client,
OrderSide,
GetKLineParams,
GetOrderBookDepthParams,
WebSocketManager
)
async def main():
# Load configuration from environment variables
base_url = os.getenv("EDGEX_BASE_URL", "https://testnet.edgex.exchange")
account_id = int(os.getenv("EDGEX_ACCOUNT_ID", "12345"))
stark_private_key = os.getenv("EDGEX_STARK_PRIVATE_KEY", "your-stark-private-key")
# Create a new client
client = Client(
base_url=base_url,
account_id=account_id,
stark_private_key=stark_private_key
)
# Get server time
server_time = await client.get_server_time()
print(f"Server Time: {server_time}")
# Get exchange metadata
metadata = await client.get_metadata()
print(f"Available contracts: {len(metadata.get('data', {}).get('contractList', []))}")
# Get account assets
assets = await client.get_account_asset()
print(f"Account Assets: {assets}")
# Get account positions
positions = await client.get_account_positions()
print(f"Account Positions: {positions}")
# Get 24-hour market data for BNBUSDT (contract ID: 10000004)
quote = await client.get_24_hour_quote("10000004")
print(f"BNBUSDT Price: {quote}")
# Get K-line data for BTCUSDT (contract ID: 10000001)
kline_params = GetKLineParams(
contract_id="10000001", # BTCUSDT
interval="1m",
size="10"
)
klines = await client.quote.get_k_line(kline_params)
print(f"K-lines: {klines}")
# Get order book depth for ETHUSDT (contract ID: 10000002)
depth_params = GetOrderBookDepthParams(
contract_id="10000002", # ETHUSDT
limit=15 # Valid values are 15 or 200
)
depth = await client.quote.get_order_book_depth(depth_params)
print(f"Order Book Depth: {depth}")
# Create a limit order (commented out to avoid actual order creation)
# order = await client.create_limit_order(
# contract_id="10000004", # BNBUSDT
# size="0.01",
# price="600.00",
# side=OrderSide.BUY
# )
# print(f"Order created: {order}")
# WebSocket example
ws_url = os.getenv("EDGEX_WS_URL", "wss://quote-testnet.edgex.exchange")
ws_manager = WebSocketManager(
base_url=ws_url,
account_id=account_id,
stark_pri_key=stark_private_key
)
# Define message handlers
def ticker_handler(message):
print(f"Ticker Update: {message}")
def kline_handler(message):
print(f"K-line Update: {message}")
# Connect to public WebSocket for market data
ws_manager.connect_public()
# Subscribe to real-time updates for BNBUSDT (contract ID: 10000004)
ws_manager.subscribe_ticker("10000004", ticker_handler)
ws_manager.subscribe_kline("10000004", "1m", kline_handler)
# Wait for updates
await asyncio.sleep(30)
# Disconnect all connections
ws_manager.disconnect_all()
if __name__ == "__main__":
asyncio.run(main())
+7
View File
@@ -0,0 +1,7 @@
API Endpoint Domain
HTTP Endpoint
Copy
https://pro.edgex.exchange
WebSocket Endpoint
Copy
wss://quote.edgex.exchange
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+136
View File
@@ -0,0 +1,136 @@
# SUMMARY
> Source: https://docs.nado.xyz
## Pages
- https://docs.nado.xyz
- https://docs.nado.xyz/mission
- https://docs.nado.xyz/orderbook-architecture
- https://docs.nado.xyz/products
- https://docs.nado.xyz/margin-types
- https://docs.nado.xyz/subaccounts-and-health
- https://docs.nado.xyz/liquidations
- https://docs.nado.xyz/pnl-settlements
- https://docs.nado.xyz/oracles
- https://docs.nado.xyz/fees-and-rebates
- https://docs.nado.xyz/funding-rates
- https://docs.nado.xyz/order-types
- https://docs.nado.xyz/nlp
- https://docs.nado.xyz/onboarding-tutorial
- https://docs.nado.xyz/onboarding-tutorial/bridging-usdt0-to-ink
- https://docs.nado.xyz/faqs
- https://docs.nado.xyz/contracts
- https://docs.nado.xyz/legal
- https://docs.nado.xyz/legal/restricted-territories
- https://docs.nado.xyz/maintenance-windows
- https://docs.nado.xyz/market-parameters
- https://docs.nado.xyz/developer-resources/api
- https://docs.nado.xyz/developer-resources/api/endpoints
- https://docs.nado.xyz/developer-resources/api/gateway
- https://docs.nado.xyz/developer-resources/api/gateway/executes
- https://docs.nado.xyz/developer-resources/api/gateway/executes/place-order
- https://docs.nado.xyz/developer-resources/api/gateway/executes/place-orders
- https://docs.nado.xyz/developer-resources/api/gateway/executes/cancel-orders
- https://docs.nado.xyz/developer-resources/api/gateway/executes/cancel-product-orders
- https://docs.nado.xyz/developer-resources/api/gateway/executes/cancel-and-place
- https://docs.nado.xyz/developer-resources/api/gateway/executes/withdraw-collateral
- https://docs.nado.xyz/developer-resources/api/gateway/executes/transfer-quote
- https://docs.nado.xyz/developer-resources/api/gateway/executes/liquidate-subaccount
- https://docs.nado.xyz/developer-resources/api/gateway/executes/mint-nlp
- https://docs.nado.xyz/developer-resources/api/gateway/executes/burn-nlp
- https://docs.nado.xyz/developer-resources/api/gateway/executes/link-signer
- https://docs.nado.xyz/developer-resources/api/gateway/queries
- https://docs.nado.xyz/developer-resources/api/gateway/queries/status
- https://docs.nado.xyz/developer-resources/api/gateway/queries/contracts
- https://docs.nado.xyz/developer-resources/api/gateway/queries/nonces
- https://docs.nado.xyz/developer-resources/api/gateway/queries/order
- https://docs.nado.xyz/developer-resources/api/gateway/queries/orders
- https://docs.nado.xyz/developer-resources/api/gateway/queries/subaccount-info
- https://docs.nado.xyz/developer-resources/api/gateway/queries/isolated-positions
- https://docs.nado.xyz/developer-resources/api/gateway/queries/market-liquidity
- https://docs.nado.xyz/developer-resources/api/gateway/queries/symbols
- https://docs.nado.xyz/developer-resources/api/gateway/queries/all-products
- https://docs.nado.xyz/developer-resources/api/gateway/queries/edge-all-products
- https://docs.nado.xyz/developer-resources/api/gateway/queries/market-prices
- https://docs.nado.xyz/developer-resources/api/gateway/queries/max-order-size
- https://docs.nado.xyz/developer-resources/api/gateway/queries/max-withdrawable
- https://docs.nado.xyz/developer-resources/api/gateway/queries/max-nlp-mintable
- https://docs.nado.xyz/developer-resources/api/gateway/queries/max-nlp-burnable
- https://docs.nado.xyz/developer-resources/api/gateway/queries/nlp-pool-info
- https://docs.nado.xyz/developer-resources/api/gateway/queries/nlp-locked-balances
- https://docs.nado.xyz/developer-resources/api/gateway/queries/fee-rates
- https://docs.nado.xyz/developer-resources/api/gateway/queries/health-groups
- https://docs.nado.xyz/developer-resources/api/gateway/queries/linked-signer
- https://docs.nado.xyz/developer-resources/api/gateway/queries/insurance
- https://docs.nado.xyz/developer-resources/api/gateway/signing
- https://docs.nado.xyz/developer-resources/api/gateway/signing/examples
- https://docs.nado.xyz/developer-resources/api/gateway/signing/q-and-a
- https://docs.nado.xyz/developer-resources/api/subscriptions
- https://docs.nado.xyz/developer-resources/api/subscriptions/authentication
- https://docs.nado.xyz/developer-resources/api/subscriptions/streams
- https://docs.nado.xyz/developer-resources/api/subscriptions/events
- https://docs.nado.xyz/developer-resources/api/subscriptions/rate-limits
- https://docs.nado.xyz/developer-resources/api/archive-indexer
- https://docs.nado.xyz/developer-resources/api/archive-indexer/orders
- https://docs.nado.xyz/developer-resources/api/archive-indexer/matches
- https://docs.nado.xyz/developer-resources/api/archive-indexer/events
- https://docs.nado.xyz/developer-resources/api/archive-indexer/candlesticks
- https://docs.nado.xyz/developer-resources/api/archive-indexer/edge-candlesticks
- https://docs.nado.xyz/developer-resources/api/archive-indexer/product-snapshots
- https://docs.nado.xyz/developer-resources/api/archive-indexer/funding-rate
- https://docs.nado.xyz/developer-resources/api/archive-indexer/interest-and-funding-payments
- https://docs.nado.xyz/developer-resources/api/archive-indexer/oracle-price
- https://docs.nado.xyz/developer-resources/api/archive-indexer/oracle-snapshots
- https://docs.nado.xyz/developer-resources/api/archive-indexer/perp-prices
- https://docs.nado.xyz/developer-resources/api/archive-indexer/market-snapshots
- https://docs.nado.xyz/developer-resources/api/archive-indexer/edge-market-snapshots
- https://docs.nado.xyz/developer-resources/api/archive-indexer/subaccounts
- https://docs.nado.xyz/developer-resources/api/archive-indexer/subaccount-snapshots
- https://docs.nado.xyz/developer-resources/api/archive-indexer/linked-signers
- https://docs.nado.xyz/developer-resources/api/archive-indexer/linked-signer-rate-limit
- https://docs.nado.xyz/developer-resources/api/archive-indexer/isolated-subaccounts
- https://docs.nado.xyz/developer-resources/api/archive-indexer/signatures
- https://docs.nado.xyz/developer-resources/api/archive-indexer/fast-withdrawal-signature
- https://docs.nado.xyz/developer-resources/api/archive-indexer/nlp-funding-payments
- https://docs.nado.xyz/developer-resources/api/archive-indexer/nlp-interest-payments
- https://docs.nado.xyz/developer-resources/api/archive-indexer/nlp-snapshots
- https://docs.nado.xyz/developer-resources/api/archive-indexer/liquidation-feed
- https://docs.nado.xyz/developer-resources/api/archive-indexer/sequencer-backlog
- https://docs.nado.xyz/developer-resources/api/archive-indexer/direct-deposit-address
- https://docs.nado.xyz/developer-resources/api/archive-indexer/quote-price
- https://docs.nado.xyz/developer-resources/api/archive-indexer/ink-airdrop
- https://docs.nado.xyz/developer-resources/api/trigger
- https://docs.nado.xyz/developer-resources/api/trigger/executes
- https://docs.nado.xyz/developer-resources/api/trigger/executes/place-order
- https://docs.nado.xyz/developer-resources/api/trigger/executes/place-orders
- https://docs.nado.xyz/developer-resources/api/trigger/executes/cancel-orders
- https://docs.nado.xyz/developer-resources/api/trigger/executes/cancel-product-orders
- https://docs.nado.xyz/developer-resources/api/trigger/queries
- https://docs.nado.xyz/developer-resources/api/trigger/queries/list-trigger-orders
- https://docs.nado.xyz/developer-resources/api/trigger/queries/list-twap-executions
- https://docs.nado.xyz/developer-resources/api/v2
- https://docs.nado.xyz/developer-resources/api/v2/assets
- https://docs.nado.xyz/developer-resources/api/v2/pairs
- https://docs.nado.xyz/developer-resources/api/v2/apr
- https://docs.nado.xyz/developer-resources/api/v2/orderbook
- https://docs.nado.xyz/developer-resources/api/v2/tickers
- https://docs.nado.xyz/developer-resources/api/v2/contracts
- https://docs.nado.xyz/developer-resources/api/v2/trades
- https://docs.nado.xyz/developer-resources/api/order-appendix
- https://docs.nado.xyz/developer-resources/api/rate-limits
- https://docs.nado.xyz/developer-resources/api/errors
- https://docs.nado.xyz/developer-resources/api/symbols
- https://docs.nado.xyz/developer-resources/api/depositing
- https://docs.nado.xyz/developer-resources/api/withdrawing-on-chain
- https://docs.nado.xyz/developer-resources/api/integrate-via-smart-contracts
- https://docs.nado.xyz/developer-resources/api/definitions-formulas
- https://docs.nado.xyz/developer-resources/api/api-changelog
- https://docs.nado.xyz/developer-resources/typescript-sdk
- https://docs.nado.xyz/developer-resources/typescript-sdk/getting-started
- https://docs.nado.xyz/developer-resources/typescript-sdk/how-to
- https://docs.nado.xyz/developer-resources/typescript-sdk/how-to/create-a-nado-client
- https://docs.nado.xyz/developer-resources/typescript-sdk/how-to/useful-common-functions
- https://docs.nado.xyz/developer-resources/typescript-sdk/how-to/query-markets-and-products
- https://docs.nado.xyz/developer-resources/typescript-sdk/how-to/deposit-funds
- https://docs.nado.xyz/developer-resources/typescript-sdk/how-to/withdraw-funds
- https://docs.nado.xyz/developer-resources/typescript-sdk/how-to/manage-orders
+35
View File
@@ -0,0 +1,35 @@
# Contracts
> **Nado Github Repo**: <https://github.com/nadohq/nado-contracts>
### Mainnet Contracts — Ink
> **Explorer URL**: [https://explorer.inkonchain.com](https://explorer.inkonchain.com/)
| Contract Name | Address |
| :---------------: | :------------------------------------------------------------------------------------------------------------------------------: |
| **Deployer** | [0xC1cC56caB60e832665E6c3780BfEBe3C1C971603](https://explorer.inkonchain.com/address/0xC1cC56caB60e832665E6c3780BfEBe3C1C971603) |
| **Quote** | [0x0200C29006150606B650577BBE7B6248F58470c1](https://explorer.inkonchain.com/address/0x0200C29006150606B650577BBE7B6248F58470c1) |
| **Querier** | [0x68798229F88251b31D534733D6C4098318c9dff8](https://explorer.inkonchain.com/address/0x68798229F88251b31D534733D6C4098318c9dff8) |
| **Clearinghouse** | [0xD218103918C19D0A10cf35300E4CfAfbD444c5fE](https://explorer.inkonchain.com/address/0xD218103918C19D0A10cf35300E4CfAfbD444c5fE) |
| **Endpoint** | [0x05ec92D78ED421f3D3Ada77FFdE167106565974E](https://explorer.inkonchain.com/address/0x05ec92D78ED421f3D3Ada77FFdE167106565974E) |
| **SpotEngine** | [0xFcD94770B95fd9Cc67143132BB172EB17A0907fE](https://explorer.inkonchain.com/address/0xFcD94770B95fd9Cc67143132BB172EB17A0907fE) |
| **PerpEngine** | [0xF8599D58d1137fC56EcDd9C16ee139C8BDf96da1](https://explorer.inkonchain.com/address/0xF8599D58d1137fC56EcDd9C16ee139C8BDf96da1) |
| **WithdrawPool** | [0x09fb495AA7859635f755E827d64c4C9A2e5b9651](https://explorer.inkonchain.com/address/0x09fb495AA7859635f755E827d64c4C9A2e5b9651) |
***
### Testnet Contracts — Ink Sepolia
> **Explorer URL**: <https://explorer-sepolia.inkonchain.com/>
| | |
| :---------------: | :--------------------------------------------------------------------------------------------------------------------------------------: |
| **Deployer** | [0x59841b3761Ed1D089a783E4d7CB49E4534CD4F85](https://explorer-sepolia.inkonchain.com/address/0x59841b3761Ed1D089a783E4d7CB49E4534CD4F85) |
| **Quote** | [0x60F50F902b2E91aef7D6c700Eb22599e297fa86F](https://explorer-sepolia.inkonchain.com/address/0x60F50F902b2E91aef7D6c700Eb22599e297fa86F) |
| **Querier** | [0x8E693BEa316bcC0F4f8be403081b954a0E3743C8](https://explorer-sepolia.inkonchain.com/address/0x8E693BEa316bcC0F4f8be403081b954a0E3743C8) |
| **Clearinghouse** | [0x23a283B359D55A941bBeEC58801B6b17D955CC73](https://explorer-sepolia.inkonchain.com/address/0x23a283B359D55A941bBeEC58801B6b17D955CC73) |
| **Endpoint** | [0x698D87105274292B5673367DEC81874Ce3633Ac2](https://explorer-sepolia.inkonchain.com/address/0x698D87105274292B5673367DEC81874Ce3633Ac2) |
| **SpotEngine** | [0x3352b2fF0fAc4ce38A6eA1C188cF4F924df54E5D](https://explorer-sepolia.inkonchain.com/address/0x3352b2fF0fAc4ce38A6eA1C188cF4F924df54E5D) |
| **PerpEngine** | [0x4E859C47fea3666B5053B16C81AF64e77567702e](https://explorer-sepolia.inkonchain.com/address/0x4E859C47fea3666B5053B16C81AF64e77567702e) |
| **WithdrawPool** | [0xBD672Fe513acbA5c1ceE7b02F998A1B542852b3b](https://explorer-sepolia.inkonchain.com/address/0xBD672Fe513acbA5c1ceE7b02F998A1B542852b3b) |
+31
View File
@@ -0,0 +1,31 @@
# API
## Overview
Nado's API is divided into the following categories:
1. A **websocket/REST** API (**gateway**) that supports writes (executes) and polling (queries).
2. A **subscriptions** API that allows to subscribe to live data feeds.
3. An **indexer** API (**archive**) that allows you to query historical data.
4. A **trigger** API that allows to execute orders only under specified price conditions.
{% hint style="info" %} <mark style="color:purple;">**NOTE**</mark>: To contact the Nado team with any questions or inquiries about Private Gateway connections, such as increased limits, please reach out on the Telegram channel below:
* **Telegram Group =** TBD
{% endhint %}
{% content-ref url="api/gateway" %}
[gateway](https://docs.nado.xyz/developer-resources/api/gateway)
{% endcontent-ref %}
{% content-ref url="api/subscriptions" %}
[subscriptions](https://docs.nado.xyz/developer-resources/api/subscriptions)
{% endcontent-ref %}
{% content-ref url="api/archive-indexer" %}
[archive-indexer](https://docs.nado.xyz/developer-resources/api/archive-indexer)
{% endcontent-ref %}
{% content-ref url="api/trigger" %}
[trigger](https://docs.nado.xyz/developer-resources/api/trigger)
{% endcontent-ref %}
@@ -0,0 +1,409 @@
# API Changelog
This document tracks all changes to the Nado API.
***
<details>
<summary><strong>December 11, 2025</strong></summary>
**Risk System Updates**
**Spread Weight Caps**
* Introduced upper bounds for spread weights to manage risk at extreme leverage levels:
* <mark style="color:red;">`initial_spread_weight`</mark>: Maximum **0.99**
* <mark style="color:red;">`maintenance_spread_weight`</mark>: Maximum **0.994**
* **Impact**:
* Existing markets (≤20x leverage): No change in behavior
* Future high-leverage markets (30x+): Spread positions will have capped health benefits
* Prevents extreme leverage abuse via spread positions
* **Technical Details**:
* Base spread weight calculated as: `spread_weight = 1 - (1 - product_weight) / 5`
* Final spread weight: `min(spread_weight, cap)`
* Cap applies during health calculations for spread positions
**Minimum Liquidation Penalties**
* Introduced minimum distance requirements between oracle price and liquidation price:
* <mark style="color:red;">**Non-spread liquidations**</mark>: Minimum **0.5%** from oracle price
* <mark style="color:red;">**Spread liquidations**</mark>: Minimum **0.25%** from oracle price
* **Impact**:
* Ensures liquidators always have sufficient incentive to execute liquidations
* Prevents unprofitable liquidation scenarios for low-volatility assets
* Particularly important for high-leverage positions where natural penalties may be very small
* **Technical Details**:
* **Non-spread longs**: `oracle_price × (1 - max((1 - maint_asset_weight) / 5, 0.005))`
* **Non-spread shorts**: `oracle_price × (1 + max((maint_liability_weight - 1) / 5, 0.005))`
* **Spread selling**: `spot_price × (1 - max((1 - perp_maint_asset_weight) / 10, 0.0025))`
* **Spread buying**: `spot_price × (1 + max((spot_maint_liability_weight - 1) / 10, 0.0025))`
**API Response Changes**
* No breaking changes to API response structure
* Health calculations and liquidation prices automatically reflect new risk parameters
**Documentation Updates**
* See [Subaccounts & Health](https://docs.nado.xyz/subaccounts-and-health#spreads) for spread weight cap details
* See [Liquidations](https://docs.nado.xyz/liquidations#liquidation-price) for minimum liquidation penalty details
</details>
***
<details>
<summary><strong>December 1, 2025</strong></summary>
**Query Enhancements**
**Pre-State Simulation for SubaccountInfo Query**
* Added <mark style="color:red;">`pre_state`</mark> parameter to <mark style="color:red;">`SubaccountInfo`</mark> query
* Type: <mark style="color:red;">`string`</mark> (accepts <mark style="color:red;">`"true"`</mark> or <mark style="color:red;">`"false"`</mark>)
* When set to <mark style="color:red;">`"true"`</mark> along with <mark style="color:red;">`txns`</mark>, returns a <mark style="color:red;">`pre_state`</mark> object in the response
* <mark style="color:red;">`pre_state`</mark> contains the subaccount state **before** the simulated transactions were applied
* Useful for comparing before/after states when simulating trades
* <mark style="color:red;">`pre_state`</mark> includes:
* <mark style="color:red;">`healths`</mark>: Health information before transactions
* <mark style="color:red;">`health_contributions`</mark>: Per-product health contributions before transactions
* <mark style="color:red;">`spot_balances`</mark>: Spot balances before transactions
* <mark style="color:red;">`perp_balances`</mark>: Perpetual balances before transactions
**Use Cases:**
* Position simulation and preview
* Risk analysis for potential trades
* UI/UX for showing before/after comparisons
* Testing transaction impacts without on-chain execution
**Documentation:** See [Subaccount Info Query](https://docs.nado.xyz/developer-resources/gateway/queries/subaccount-info#example-with-pre_state) for detailed examples.
</details>
***
<details>
<summary><strong>November 20, 2025 - Initial Launch</strong></summary>
#### Core Changes
**1. Removal of LP Functionality**
* <mark style="color:red;">`SubaccountInfo`</mark> no longer has:
* <mark style="color:red;">`lp_balance`</mark> in <mark style="color:red;">`spot_balances`</mark> and <mark style="color:red;">`perp_balances`</mark>
* <mark style="color:red;">`lp_state`</mark> in <mark style="color:red;">`spot_products`</mark> and <mark style="color:red;">`perp_products`</mark>
* <mark style="color:red;">`lp_spread_x18`</mark> in <mark style="color:red;">`book_info`</mark> of both <mark style="color:red;">`spot_products`</mark> and <mark style="color:red;">`perp_products`</mark>
* Historical <mark style="color:red;">`events`</mark> no longer include:
* <mark style="color:red;">`net_entry_lp_unrealized`</mark>
* <mark style="color:red;">`net_entry_lp_cumulative`</mark>
**2. Removal of Redundant Fields**
* <mark style="color:red;">`SubaccountInfo`</mark> no longer has:
* <mark style="color:red;">`last_cumulative_multiplier_x18`</mark> in <mark style="color:red;">`balance`</mark> of <mark style="color:red;">`spot_balances`</mark>
**3. Products Config Model Updates**
* Added: <mark style="color:red;">`withdraw_fee_x18`</mark> and <mark style="color:red;">`min_deposit_rate_x18`</mark> to <mark style="color:red;">`spot_products.config`</mark>
**4. Products Risk Model Updates**
* Added: <mark style="color:red;">`price_x18`</mark> to both <mark style="color:red;">`spot_products.risk`</mark> and <mark style="color:red;">`perp_products.risk`</mark>
* Removed: <mark style="color:red;">`large_position_penalty_x18`</mark>
**5. Deposit Rate Query**
* Removed: <mark style="color:red;">`min_deposit_rates`</mark> query
* Use <mark style="color:red;">`min_deposit_rate_x18`</mark> in <mark style="color:red;">`spot_products.config`</mark> instead
#### Market Structure Changes
**6. Removal of Virtual Books**
* <mark style="color:red;">`Contracts`</mark> query no longer returns <mark style="color:red;">`book_addrs`</mark>
* <mark style="color:red;">`PlaceOrder`</mark> verify contract is now <mark style="color:red;">`address(product_id)`</mark>\
\&#xNAN;*Example: product <mark style="color:red;">18</mark> → <mark style="color:red;">`0x0000000000000000000000000000000000000012`</mark>*
**7. Minimum Size denomination**
* <mark style="color:red;">`min_size`</mark> is now **USDT0 denominated** (not base denominated)
* <mark style="color:red;">`min_size = 10`</mark> → minimum order size = 10 USDT0 (<mark style="color:red;">`order_price * order_amount`</mark>)
* <mark style="color:red;">`size_increment`</mark> remains **base denominated**
* Example: BTC with <mark style="color:red;">`size_increment = 0.0001`</mark> and <mark style="color:red;">`min_size = 20`</mark>:
* ✅ Valid: 100,000 \* 0.0002 = 20 USDT0
* ❌ Invalid: 100,000 \* 0.0001 = 10 USDT0
* ❌ Invalid: 100,000 \* 0.00025 (not multiple of 0.0001)
#### Orders & Signing
**8. Place Orders Execute**
* Added: <mark style="color:red;">`place_orders`</mark> execute - place multiple orders in a single request
* Accepts array of orders with same structure as <mark style="color:red;">`place_order`</mark>
* Optional <mark style="color:red;">`stop_on_failure`</mark> parameter to stop processing remaining orders on first failure
* Returns array of results with <mark style="color:red;">`digest`</mark> (if successful) or <mark style="color:red;">`error`</mark> (if failed) for each order
* Rate limit weight calculated per order
See [Place Orders](https://docs.nado.xyz/developer-resources/api/gateway/executes/place-orders) for details.
**9. EIP712 `Order` Struct Update**
```solidity
struct Order {
bytes32 sender;
int128 priceX18;
int128 amount;
uint64 expiration;
uint64 nonce;
uint128 appendix;
}
```
* New field: <mark style="color:red;">`appendix`</mark>
* All order flags (IOC, post only, reduce-only, triggers) moved into <mark style="color:red;">`appendix`</mark>
* <mark style="color:red;">`expiration`</mark> is now strictly a timestamp
* <mark style="color:red;">`appendix`</mark> bitfield:
```json
| value | reserved | trigger | reduce only | order type | isolated | version |
| 64 bits | 50 bits | 2 bits | 1 bit | 2 bits | 1 bit | 8 bits |
```
* Special encodings:
* <mark style="color:red;">`trigger`</mark> = 2 or 3 → <mark style="color:red;">`value`</mark> encodes TWAP settings (<mark style="color:red;">`times`</mark>, <mark style="color:red;">`slippage_x6`</mark>)
* <mark style="color:red;">`isolated = 1`</mark> → <mark style="color:red;">`value`</mark> encodes isolated margin
* Constraints:
* Isolated orders cannot be TWAP
* TWAP orders must use IOC execution type
See [Order Appendix Docs](https://docs.nado.xyz/developer-resources/api/order-appendix).
**10. TWAP Order Execution**
* Added <mark style="color:red;">`list_twap_executions`</mark> query to trigger service
* TWAP orders track individual execution status (pending, executed, failed, cancelled)
* TWAP execution statuses include execution time and engine response data
**11. Trigger Service Rate Limits**
* Updated trigger order limits from 100 pending orders per subaccount to <mark style="color:red;">`25 pending orders per product per subaccount`</mark>
**12. EIP712 Domain Change**
* Signing domain updated from **`Vertex``Nado`**\
See [Signing Docs](https://docs.nado.xyz/developer-resources/api/gateway/signing).
#### Query Updates
**13.&#x20;**<mark style="color:red;">**`max_order_size`**</mark>
* Added: <mark style="color:red;">`isolated`</mark> parameter - when set to `true`, calculates max order size for an isolated margin position. Defaults to `false`.
**14.&#x20;**<mark style="color:red;">**`orders`**</mark>**&#x20;Query**
* Added: <mark style="color:red;">`trigger_types`</mark> parameter - filter orders by trigger type(s)
**15. Historical Events**
* Added: <mark style="color:red;">`quote_volume_cumulative`</mark> - tracks cumulative trading volume for the subaccount in quote units
* Available in: `events` and `subaccount_snapshots` queries
**16.&#x20;**<mark style="color:red;">**`subaccount_snapshots`**</mark>**&#x20;Query**
* Added: <mark style="color:red;">`active`</mark> parameter - filter snapshots by position status
* <mark style="color:red;">`true`</mark>: returns only products with **non-zero balance** at the timestamp
* <mark style="color:red;">`false`</mark>: returns products with **event history** before the timestamp (default)
**17. Trigger Orders**
* Added: <mark style="color:red;">`place_at`</mark> field - timestamp when trigger order should be placed
**18. Removal of&#x20;**<mark style="color:red;">**`summary`**</mark>**&#x20;Query**
* Removed: <mark style="color:red;">`summary`</mark> query from indexer API
* Use <mark style="color:red;">`subaccount_snapshots`</mark> query instead for historical subaccount data
**19. Query Renaming**
* Renamed: <mark style="color:red;">`usdc_price`</mark> → <mark style="color:red;">`quote_price`</mark> query
* See [Quote Price](https://docs.nado.xyz/developer-resources/api/archive-indexer/quote-price)
**20. Multi-Subaccount `events`, `matches`, `orders`**
* The indexer <mark style="color:red;">`events`</mark>, <mark style="color:red;">`matches`</mark>, and <mark style="color:red;">`orders`</mark> queries now accept a <mark style="color:red;">`subaccounts`</mark> array so you can fetch history for multiple subaccounts in a single request instead of fanning out per subaccount. Please note that the old single-subaccount version is **no longer supported**.
#### Streams
{% hint style="info" %}
See [Subscriptions > Streams](https://docs.nado.xyz/developer-resources/api/subscriptions/streams) for more details
{% endhint %}
**21.&#x20;**<mark style="color:red;">**`OrderUpdate`**</mark>
* Can now subscribe across all products by setting <mark style="color:red;">`product_id = null`</mark>
* <mark style="color:red;">`product_id`</mark> type changed from `u32``Option<u32>`
**22.&#x20;**<mark style="color:red;">**`Fill`**</mark>
* Added: <mark style="color:red;">`fee`</mark>, <mark style="color:red;">`submission_idx`</mark>, and <mark style="color:red;">`appendix`</mark>
* Can now subscribe across all products by setting <mark style="color:red;">`product_id = null`</mark>
**23.&#x20;**<mark style="color:red;">**`PositionChange`**</mark>
* Can now subscribe across all products by setting <mark style="color:red;">`product_id = null`</mark>
* <mark style="color:red;">`product_id`</mark> type changed from `u32``Option<u32>`
* Added: <mark style="color:red;">`isolated`</mark> - indicates whether the position change is for an isolated margin position
**24.&#x20;**<mark style="color:red;">**`FundingPayment`**</mark>
* New stream: <mark style="color:red;">`FundingPayment`</mark>
* Param: <mark style="color:red;">`product_id: u32`</mark>
* Emits hourly funding payment events
**Request**
```json
{
"method": "subscribe",
"stream": {
"type": "funding_payment",
"product_id": 1
},
"id": 123
}
```
**Response**
```json
{
"type": "funding_payment",
"timestamp": 1234567890000,
"product_id": 1,
"payment_amount": "1000000000000000000",
"open_interest": "50000000000000000000",
"cumulative_funding_long_x18": "100000000000000000",
"cumulative_funding_short_x18": "-100000000000000000",
"dt": 3600000
}
```
**25.&#x20;**<mark style="color:red;">**`Liquidation`**</mark>
* New stream: <mark style="color:red;">`Liquidation`</mark>
* Param: <mark style="color:red;">`product_id`</mark> or <mark style="color:red;">`null`</mark> (all products)
* Emits liquidation info (liquidator, liquidatee, amount, price)
**Request**
```json
{
"method": "subscribe",
"stream": {
"type": "liquidation",
"product_id": 1
},
"id": 123
}
```
**Response**
```json
{
"type": "liquidation",
"timestamp": "1234567890000",
"product_ids": [1],
"liquidator": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"liquidatee": "0x8b6fd3859f7065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"amount": "1000000000000000000",
"price": "50000000000000000000"
}
```
**26.&#x20;**<mark style="color:red;">**`LatestCandlestick`**</mark>
* New stream: <mark style="color:red;">`LatestCandlestick`</mark>
* Params: <mark style="color:red;">`product_id`</mark>, <mark style="color:red;">`granularity`</mark> (seconds)
* Emits candlestick updates on every trade
**Request**
```json
{
"method": "subscribe",
"stream": {
"type": "latest_candlestick",
"product_id": 1,
"granularity": 60
},
"id": 123
}
```
**Response**
```json
{
"type": "latest_candlestick",
"timestamp": 1234567890000,
"product_id": 1,
"granularity": 60,
"open_x18": "50000000000000000000",
"high_x18": "51000000000000000000",
"low_x18": "49000000000000000000",
"close_x18": "50500000000000000000",
"volume": "1000000000000000000"
}
```
**27.&#x20;**<mark style="color:red;">**`FundingRate`**</mark>
* New stream: <mark style="color:red;">`FundingRate`</mark>
* Param: <mark style="color:red;">`product_id`</mark> or <mark style="color:red;">`null`</mark> (all products)
* Emits funding rate updates every 20 seconds
* <mark style="color:red;">`funding_rate_x18`</mark> and <mark style="color:red;">`update_time`</mark> values are identical to those from the [Funding Rate](https://docs.nado.xyz/developer-resources/api/archive-indexer/funding-rate) indexer endpoint
**Request**
```json
{
"method": "subscribe",
"stream": {
"type": "funding_rate",
"product_id": 1
},
"id": 123
}
```
**Subscribe to all products:**
```json
{
"method": "subscribe",
"stream": {
"type": "funding_rate",
"product_id": null
},
"id": 123
}
```
**Response**
```json
{
"type": "funding_rate",
"timestamp": "1234567890123456789",
"product_id": 1,
"funding_rate_x18": "50000000000000000",
"update_time": "1234567890"
}
```
</details>
@@ -0,0 +1,127 @@
# Archive (indexer)
Using Nado's indexer API you can access historical data in the platform as it is processed by our offchain sequencer. This includes: trading activity, events, candlesticks and more.
You can interact with our indexer by sending <mark style="color:red;">`HTTP`</mark> requests at <mark style="color:red;">`POST [ARCHIVE_ENDPOINT]`</mark> alongside a json payload of the query. Endpoints:
<mark style="color:red;">`HTTP`</mark> requests must set the `Accept-Encoding` to include `gzip`, `br` or `deflate`
## Endpoints
### Testnet:
* <mark style="color:red;">`https://archive.test.nado.xyz/v1`</mark>
## Available Queries:
{% content-ref url="archive-indexer/orders" %}
[orders](https://docs.nado.xyz/developer-resources/api/archive-indexer/orders)
{% endcontent-ref %}
{% content-ref url="archive-indexer/matches" %}
[matches](https://docs.nado.xyz/developer-resources/api/archive-indexer/matches)
{% endcontent-ref %}
{% content-ref url="archive-indexer/events" %}
[events](https://docs.nado.xyz/developer-resources/api/archive-indexer/events)
{% endcontent-ref %}
{% content-ref url="archive-indexer/candlesticks" %}
[candlesticks](https://docs.nado.xyz/developer-resources/api/archive-indexer/candlesticks)
{% endcontent-ref %}
{% content-ref url="archive-indexer/edge-candlesticks" %}
[edge-candlesticks](https://docs.nado.xyz/developer-resources/api/archive-indexer/edge-candlesticks)
{% endcontent-ref %}
{% content-ref url="archive-indexer/product-snapshots" %}
[product-snapshots](https://docs.nado.xyz/developer-resources/api/archive-indexer/product-snapshots)
{% endcontent-ref %}
{% content-ref url="archive-indexer/funding-rate" %}
[funding-rate](https://docs.nado.xyz/developer-resources/api/archive-indexer/funding-rate)
{% endcontent-ref %}
{% content-ref url="archive-indexer/interest-and-funding-payments" %}
[interest-and-funding-payments](https://docs.nado.xyz/developer-resources/api/archive-indexer/interest-and-funding-payments)
{% endcontent-ref %}
{% content-ref url="archive-indexer/oracle-price" %}
[oracle-price](https://docs.nado.xyz/developer-resources/api/archive-indexer/oracle-price)
{% endcontent-ref %}
{% content-ref url="archive-indexer/oracle-snapshots" %}
[oracle-snapshots](https://docs.nado.xyz/developer-resources/api/archive-indexer/oracle-snapshots)
{% endcontent-ref %}
{% content-ref url="archive-indexer/perp-prices" %}
[perp-prices](https://docs.nado.xyz/developer-resources/api/archive-indexer/perp-prices)
{% endcontent-ref %}
{% content-ref url="archive-indexer/market-snapshots" %}
[market-snapshots](https://docs.nado.xyz/developer-resources/api/archive-indexer/market-snapshots)
{% endcontent-ref %}
{% content-ref url="archive-indexer/edge-market-snapshots" %}
[edge-market-snapshots](https://docs.nado.xyz/developer-resources/api/archive-indexer/edge-market-snapshots)
{% endcontent-ref %}
{% content-ref url="archive-indexer/subaccounts" %}
[subaccounts](https://docs.nado.xyz/developer-resources/api/archive-indexer/subaccounts)
{% endcontent-ref %}
{% content-ref url="archive-indexer/subaccount-snapshots" %}
[subaccount-snapshots](https://docs.nado.xyz/developer-resources/api/archive-indexer/subaccount-snapshots)
{% endcontent-ref %}
{% content-ref url="archive-indexer/linked-signers" %}
[linked-signers](https://docs.nado.xyz/developer-resources/api/archive-indexer/linked-signers)
{% endcontent-ref %}
{% content-ref url="archive-indexer/linked-signer-rate-limit" %}
[linked-signer-rate-limit](https://docs.nado.xyz/developer-resources/api/archive-indexer/linked-signer-rate-limit)
{% endcontent-ref %}
{% content-ref url="archive-indexer/isolated-subaccounts" %}
[isolated-subaccounts](https://docs.nado.xyz/developer-resources/api/archive-indexer/isolated-subaccounts)
{% endcontent-ref %}
{% content-ref url="archive-indexer/signatures" %}
[signatures](https://docs.nado.xyz/developer-resources/api/archive-indexer/signatures)
{% endcontent-ref %}
{% content-ref url="archive-indexer/fast-withdrawal-signature" %}
[fast-withdrawal-signature](https://docs.nado.xyz/developer-resources/api/archive-indexer/fast-withdrawal-signature)
{% endcontent-ref %}
{% content-ref url="archive-indexer/nlp-funding-payments" %}
[nlp-funding-payments](https://docs.nado.xyz/developer-resources/api/archive-indexer/nlp-funding-payments)
{% endcontent-ref %}
{% content-ref url="archive-indexer/nlp-interest-payments" %}
[nlp-interest-payments](https://docs.nado.xyz/developer-resources/api/archive-indexer/nlp-interest-payments)
{% endcontent-ref %}
{% content-ref url="archive-indexer/nlp-snapshots" %}
[nlp-snapshots](https://docs.nado.xyz/developer-resources/api/archive-indexer/nlp-snapshots)
{% endcontent-ref %}
{% content-ref url="archive-indexer/liquidation-feed" %}
[liquidation-feed](https://docs.nado.xyz/developer-resources/api/archive-indexer/liquidation-feed)
{% endcontent-ref %}
{% content-ref url="archive-indexer/sequencer-backlog" %}
[sequencer-backlog](https://docs.nado.xyz/developer-resources/api/archive-indexer/sequencer-backlog)
{% endcontent-ref %}
{% content-ref url="archive-indexer/direct-deposit-address" %}
[direct-deposit-address](https://docs.nado.xyz/developer-resources/api/archive-indexer/direct-deposit-address)
{% endcontent-ref %}
{% content-ref url="archive-indexer/quote-price" %}
[quote-price](https://docs.nado.xyz/developer-resources/api/archive-indexer/quote-price)
{% endcontent-ref %}
{% content-ref url="archive-indexer/ink-airdrop" %}
[ink-airdrop](https://docs.nado.xyz/developer-resources/api/archive-indexer/ink-airdrop)
{% endcontent-ref %}
@@ -0,0 +1,97 @@
# Candlesticks
## Rate limits
* Dynamic based on <mark style="color:red;">`limit`</mark> param provided (**weight = 1 + limit / 20**)
* E.g: With <mark style="color:red;">`limit=100`</mark>, you can make up to 400 requests per min or 66 requests / 10 secs.
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Available Granularities
The following granularities / periods are supported (in seconds):
| Granularity name | Granularity value (in seconds) |
| :--------------: | :----------------------------: |
| 1 minute | 60 |
| 5 minutes | 300 |
| 15 minutes | 900 |
| 1 hour | 3600 |
| 2 hours | 7200 |
| 4 hours | 14400 |
| 1 day | 86400 |
| 1 week | 604800 |
| 4 weeks | 2419200 |
## Request
{% tabs %}
{% tab title="Product candlesticks" %}
Query product candlesticks ordered by <mark style="color:red;">`timestamp`</mark> desc.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"candlesticks": {
"product_id": 1,
"granularity": 60,
"limit": 2
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="113" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">product_id</td><td align="center">number</td><td align="center">Yes</td><td>Id of product to fetch candlesticks for.</td></tr><tr><td align="center">granularity</td><td align="center">number</td><td align="center">Yes</td><td>Granularity value in seconds.</td></tr><tr><td align="center">max_time</td><td align="center">number / string</td><td align="center">No</td><td>When providing <mark style="color:red;"><code>max_time</code></mark> (unix epoch in seconds), only return candlesticks with timestamp &#x3C;= <mark style="color:red;"><code>max_time</code></mark></td></tr><tr><td align="center">limit</td><td align="center">number</td><td align="center">No</td><td>Max number of candlesticks to return. defaults to <mark style="color:red;"><code>100</code></mark>. max possible of <mark style="color:red;"><code>500</code></mark>.</td></tr></tbody></table>
## Response
```json
{
"candlesticks": [
{
"product_id": 1,
"granularity": 60,
"submission_idx": "627709",
"timestamp": "1680118140",
"open_x18": "27235000000000000000000",
"high_x18": "27298000000000000000000",
"low_x18": "27235000000000000000000",
"close_x18": "27298000000000000000000",
"volume": "1999999999999999998"
},
{
"product_id": 1,
"granularity": 60,
"submission_idx": "627699",
"timestamp": "1680118080",
"open_x18": "27218000000000000000000",
"high_x18": "27245000000000000000000",
"low_x18": "27218000000000000000000",
"close_x18": "27245000000000000000000",
"volume": "11852999999999999995"
}
]
}
```
## Response Fields
| Field name | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| submission\_idx | Id of the latest recorded transaction that contributes to the candle. |
| product\_id | Id of product candle is associated to. |
| granularity | Candle time interval, expressed in seconds, representing the aggregation period for trading volume and price data |
| open\_x18 | The first fill price of the candle, multiplied by 10^18 |
| high\_x18 | The highest recorded fill price during the defined interval of the candle, multiplied by 10^18 |
| low\_x18 | The lowest recorded fill price during the defined interval of the candle, multiplied by 10^18 |
| close\_x18 | The last price of the candle, multiplied by 10^18 |
| volume | Asset volume, which represents the absolute cumulative fill amounts during the time interval of the candle, multiplied by 10^18 |
@@ -0,0 +1,54 @@
# Direct Deposit Address
## Rate limits
* 240 requests/min or 40 requests/10secs per IP address. (**weight = 10**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="Direct Deposit Address" %}
Query the unique direct deposit address for a subaccount.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"direct_deposit_address": {
"subaccount": "0x79cc76364b5fb263a25bd52930e3d9788fcfeea864656661756c740000000000"
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="150" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">subaccount</td><td align="center">string</td><td align="center">Yes</td><td>Hex string of the subaccount to fetch the direct deposit address for.</td></tr></tbody></table>
## Response
```json
{
"subaccount": "0x79cc76364b5fb263a25bd52930e3d9788fcfeea864656661756c740000000000",
"deposit_address": "0x1234567890123456789012345678901234567890",
"created_at": "1683315718"
}
```
## Response Fields
### Direct Deposit Address
<table><thead><tr><th width="263">Field name</th><th>Description</th></tr></thead><tbody><tr><td>subaccount</td><td>Hex string of the subaccount</td></tr><tr><td>deposit_address</td><td>Unique deposit address for this subaccount</td></tr><tr><td>created_at</td><td>Unix epoch time in seconds when the deposit address was created</td></tr></tbody></table>
{% hint style="info" %}
Direct deposit addresses allow users to deposit funds directly to their subaccount without needing to interact with the smart contract. Funds sent to this address will automatically be credited to the associated subaccount.
{% endhint %}
@@ -0,0 +1,97 @@
# Edge Candlesticks
## Rate limits
* Dynamic based on <mark style="color:red;">`limit`</mark> param provided (**weight = 1 + limit / 20**)
* E.g: With <mark style="color:red;">`limit=100`</mark>, you can make up to 400 requests per min or 66 requests / 10 secs.
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Available Granularities
The following granularities / periods are supported (in seconds):
| Granularity name | Granularity value (in seconds) |
| :--------------: | :----------------------------: |
| 1 minute | 60 |
| 5 minutes | 300 |
| 15 minutes | 900 |
| 1 hour | 3600 |
| 2 hours | 7200 |
| 4 hours | 14400 |
| 1 day | 86400 |
| 1 week | 604800 |
| 4 weeks | 2419200 |
## Request
{% tabs %}
{% tab title="Ede candlesticks" %}
Query edge candlesticks ordered by <mark style="color:red;">`timestamp`</mark> desc.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"edge_candlesticks": {
"product_id": 1,
"granularity": 60,
"limit": 2
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="113" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">product_id</td><td align="center">number</td><td align="center">Yes</td><td>Id of product to fetch candlesticks for.</td></tr><tr><td align="center">granularity</td><td align="center">number</td><td align="center">Yes</td><td>Granularity value in seconds.</td></tr><tr><td align="center">max_time</td><td align="center">number / string</td><td align="center">No</td><td>When providing <mark style="color:red;"><code>max_time</code></mark> (unix epoch in seconds), only return candlesticks with timestamp &#x3C;= <mark style="color:red;"><code>max_time</code></mark></td></tr><tr><td align="center">limit</td><td align="center">number</td><td align="center">No</td><td>Max number of candlesticks to return. defaults to <mark style="color:red;"><code>100</code></mark>. max possible of <mark style="color:red;"><code>500</code></mark>.</td></tr></tbody></table>
## Response
```json
{
"candlesticks": [
{
"product_id": 1,
"granularity": 60,
"submission_idx": "627709",
"timestamp": "1680118140",
"open_x18": "27235000000000000000000",
"high_x18": "27298000000000000000000",
"low_x18": "27235000000000000000000",
"close_x18": "27298000000000000000000",
"volume": "1999999999999999998"
},
{
"product_id": 1,
"granularity": 60,
"submission_idx": "627699",
"timestamp": "1680118080",
"open_x18": "27218000000000000000000",
"high_x18": "27245000000000000000000",
"low_x18": "27218000000000000000000",
"close_x18": "27245000000000000000000",
"volume": "11852999999999999995"
}
]
}
```
## Response Fields
| Field name | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| submission\_idx | Id of the latest recorded transaction that contributes to the candle. |
| product\_id | Id of product candle is associated to. |
| granularity | Candle time interval, expressed in seconds, representing the aggregation period for trading volume and price data |
| open\_x18 | The first fill price of the candle, multiplied by 10^18 |
| high\_x18 | The highest recorded fill price during the defined interval of the candle, multiplied by 10^18 |
| low\_x18 | The lowest recorded fill price during the defined interval of the candle, multiplied by 10^18 |
| close\_x18 | The last price of the candle, multiplied by 10^18 |
| volume | Asset volume, which represents the absolute cumulative fill amounts during the time interval of the candle, multiplied by 10^18 |
@@ -0,0 +1,193 @@
# Edge Market Snapshots
## Rate limits
**Dynamic based on interval.count.**
* IP weight = <mark style="color:red;">`(interval.count.min(500) / 20) + (interval.count.clamp(2, 20) * 2)`</mark>
* Scales mainly with interval count.
* Example: <mark style="color:red;">`interval.count=500 → weight=65`</mark>, <mark style="color:red;">`interval.count=100 → weight=45`</mark>.
* Minimum weight per request is <mark style="color:red;">`4`</mark>.
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
### Request
{% tabs %}
{% tab title="Market snapshots" %}
Query market snapshots ordered by <mark style="color:red;">`timestamp`</mark> desc.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"edge_market_snapshots": {
"interval": {
"count": 2,
"granularity": 3600,
"max_time": 1691083697,
},
"product_ids": [1, 2]
}
}
```
{% endtab %}
{% endtabs %}
### Request Parameters
<table><thead><tr><th width="192" align="center">Parameter</th><th width="103" align="center">Type</th><th width="101" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">interval</td><td align="center">object</td><td align="center">Yes</td><td>Object to specify desired time period for data</td></tr><tr><td align="center">interval.count</td><td align="center">number</td><td align="center">Yes</td><td>Number of snapshots to return, limit 100. Also limited to <code>interval.count * # product_ids &#x3C; 2000</code></td></tr><tr><td align="center">interval.granularity</td><td align="center">number</td><td align="center">Yes</td><td>Granularity value in seconds</td></tr><tr><td align="center">interval.max_time</td><td align="center">number / string</td><td align="center">No</td><td>When providing <mark style="color:red;"><code>max_time</code></mark> (unix epoch in seconds), only return snapshots with timestamp &#x3C;= <mark style="color:red;"><code>max_time</code></mark>. If no value is entered, <code>max_time</code> defaults to the current time.</td></tr><tr><td align="center">product_ids</td><td align="center">number[]</td><td align="center">No</td><td>list of product ids to fetch snapshots for, defaults to all products</td></tr></tbody></table>
### Response
{% hint style="info" %}
**Note**:
* Returns a mapping of <mark style="color:orange;">`chain_id -> snapshots`</mark>
{% endhint %}
```json
{
"snapshots": {
"42161": [
{
"timestamp": 1689965194,
"cumulative_users": 2774,
"daily_active_users": 251,
"cumulative_trades": {
"1": 54287,
"2": 172435
},
"cumulative_volumes": {
"1": "259549132367035103631071564",
"2": "1134008547778337985156988339"
},
"cumulative_trade_sizes": {
"1": "9209508999999999995173",
"2": "40246259000000000000000"
},
"cumulative_taker_fees": {
"1": "88916428908427788322799",
"2": "259205794197801680292645"
},
"cumulative_sequencer_fees": {
"1": "11038200000000000000000",
"2": "32353000000000000000000"
},
"cumulative_maker_fees": {
"1": "-12421730086012739050725",
"2": "-36124007075181485948604"
},
"cumulative_liquidation_amounts": {
"1": "848311398835000694508",
"2": "1013231566414935056343898"
},
"open_interests": {
"2": "2907581091676822842104781"
},
"total_deposits": {
"1": "37722308770940799414"
},
"total_borrows": {
"1": "1441397740941092000"
},
"funding_rates": {
"2": "3611102723387"
},
"deposit_rates": {
"1": "1001376785714"
},
"borrow_rates": {
"1": "32059880416879"
},
"cumulative_inflows": {
"1": "238791614019999999853",
"2": "0"
},
"cumulative_outflows": {
"1": "-202514202990000000306",
"2": "0"
},
"tvl": "7560079507311601381352742"
}
],
"5000": [
{
"timestamp": 1689965194,
"cumulative_users": 2774,
"daily_active_users": 251,
"cumulative_trades": {
"1": 54287,
"2": 172435
},
"cumulative_volumes": {
"1": "259549132367035103631071564",
"2": "1134008547778337985156988339"
},
"cumulative_trade_sizes": {
"1": "9209508999999999995173",
"2": "40246259000000000000000"
},
"cumulative_taker_fees": {
"1": "88916428908427788322799",
"2": "259205794197801680292645"
},
"cumulative_sequencer_fees": {
"1": "11038200000000000000000",
"2": "32353000000000000000000"
},
"cumulative_maker_fees": {
"1": "-12421730086012739050725",
"2": "-36124007075181485948604"
},
"cumulative_liquidation_amounts": {
"1": "848311398835000694508",
"2": "1013231566414935056343898"
},
"open_interests": {
"2": "2907581091676822842104781"
},
"total_deposits": {
"1": "37722308770940799414"
},
"total_borrows": {
"1": "1441397740941092000"
},
"funding_rates": {
"2": "3611102723387"
},
"deposit_rates": {
"1": "1001376785714"
},
"borrow_rates": {
"1": "32059880416879"
},
"cumulative_inflows": {
"1": "238791614019999999853",
"2": "0"
},
"cumulative_outflows": {
"1": "-202514202990000000306",
"2": "0"
},
"tvl": "7560079507311601381352742"
}
]
}
}
```
### Response Fields
#### Snapshots
{% hint style="info" %}
**Note**: For product specific fields (i.e. cumulative\_volume, open\_interests), the value is an object which maps product\_ids to their corresponding values.
{% endhint %}
<table><thead><tr><th width="260">Field name</th><th>Description</th></tr></thead><tbody><tr><td>timestamp</td><td>Timestamp of the snapshot. This may not be perfectly rounded to the granularity since it uses the nearest transaction timestamp less than or equal to <mark style="color:red;"><code>max_time</code></mark></td></tr><tr><td>cumulative_users</td><td>The cumulative number of subaccounts on Nado. It is updated daily at 9AM ET for historical counts. For current day counts, it is updated every hour.</td></tr><tr><td>daily_active_users</td><td>Daily active users count, updated daily at 9AM ET for historical counts. For current day counts, it is updated every hour.</td></tr><tr><td>cumulative_trades</td><td>A map of product_id -> the cumulative number of trades for the given product_id.</td></tr><tr><td>cumulative_volumes</td><td>A map of product_id -> cumulative volumes in USDT0 units.</td></tr><tr><td>cumulative_trade_sizes</td><td>A map of product_id -> cumulative trade sizes in base token</td></tr><tr><td>cumulative_taker_fees</td><td>A map of product_id -> cumulative taker fees. Taker fees include sequencer fees.</td></tr><tr><td>cumulative_sequencer_fees</td><td>A map of product_id -> cumulative sequencer fees.</td></tr><tr><td>cumulative_maker_fees</td><td>A map of product_id -> cumulative maker rebates.</td></tr><tr><td>cumulative_liquidation_amounts</td><td>A map of product_id -> cumulative liquidation amounts in USDT0 units.</td></tr><tr><td>open_interests</td><td>A map of product_id -> open interests in USDT0 units.</td></tr><tr><td>total_deposits</td><td>A map of product_id -> total deposits held by Nado for a given product at the given time in the base token units.</td></tr><tr><td>total_borrows</td><td>A map of product_id -> total borrows lent by Nado for a given product at the given time in the base token units.</td></tr><tr><td>funding_rates</td><td>A map of product_id -> <strong>hourly</strong> historical funding rates, value returned as <strong>decimal rates</strong> (% = rate * 100), derived from funding payment amounts. Requires a minimum granularity of 3600 to see non-zero funding rates. Use a granularity where granularity % 3600 = 0 for best results.</td></tr><tr><td>deposit_rates</td><td>A map of product_id -> <strong>daily</strong> deposit rates, values returned as <strong>decimal rates</strong> (% = rate * 100).</td></tr><tr><td>borrow_rates</td><td>A map of product_id -> <strong>daily</strong> borrow rates, values returned as <strong>decimal rates</strong> (% = rate * 100).</td></tr><tr><td>cumulative_inflows</td><td>A map of product_id -> cumulative inflows a.k.a deposits in base token units.</td></tr><tr><td>cumulative_outflows</td><td>A map of product_id -> cumulative outflows a.k.a withdraws in base token units.</td></tr><tr><td>tvl</td><td>The total value locked in USD.</td></tr></tbody></table>
@@ -0,0 +1,268 @@
# Events
## Rate limits
* IP weight = <mark style="color:red;">`2 + (limit * subaccounts.length / 10)`</mark> where <mark style="color:red;">`limit`</mark> defaults to 100 (max 500) and <mark style="color:red;">`subaccounts.length`</mark> defaults to 1
* E.g: With <mark style="color:red;">`limit=100`</mark> and 1 subaccount, weight = 12, allowing up to 200 requests per min or 33 requests / 10 secs.
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Available Events
Each event corresponds to a transaction type in Nado. See below available events and their <mark style="color:red;">`event_type`</mark> mapping:
| Event Name | Event Type Value |
| :---------------------------------------------------: | :-------------------: |
| <mark style="color:red;">`LiquidateSubaccount`</mark> | liquidate\_subaccount |
| <mark style="color:red;">`DepositCollateral`</mark> | deposit\_collateral |
| <mark style="color:red;">`WithdrawCollateral`</mark> | withdraw\_collateral |
| <mark style="color:red;">`SettlePnl`</mark> | settle\_pnl |
| <mark style="color:red;">`MatchOrders`</mark> | match\_orders |
| <mark style="color:red;">`MintLp`</mark> | mint\_lp |
| <mark style="color:red;">`BurnLp`</mark> | burn\_lp |
## Event Limits
You can specify 2 types of <mark style="color:red;">`limit`</mark> on the query:
* <mark style="color:red;">`raw`</mark>: the max number of events to return.
* <mark style="color:red;">`txs`</mark>: the max number of transactions to return. **note**: one transaction can emit multiple events, by specifying this limit, you will get all the events associated to the transactions in the response.
## Request
{% tabs %}
{% tab title="Events by subaccount" %}
Query events corresponding to specific subaccounts, ordered by <mark style="color:red;">`submission index`</mark> desc. E.g: all <mark style="color:red;">`MatchOrder`</mark> events for subaccounts <mark style="color:red;">`xxx`</mark> specific to spot wBTC.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"events": {
"product_ids": [
1
],
"subaccounts": [
"0x12a0b4888021576eb10a67616dd3dd3d9ce206b664656661756c740000000000"
],
"event_types": ["match_orders"],
"max_time": 1679728762,
"limit": {
"raw": 1
},
"isolated": false
}
}
```
{% endtab %}
{% tab title="Events by product" %}
Query events corresponding to specific products, ordered by <mark style="color:red;">`submission index`</mark> desc. Uses <mark style="color:red;">`txs`</mark> limit, will only return a single <mark style="color:red;">`tx`</mark> and one or more events associated with the <mark style="color:red;">`tx`</mark>.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"events": {
"product_ids": [
1,
2
],
"max_time": "1679728762",
"limit": {
"txs": 1
}
}
}
```
{% endtab %}
{% tab title="Events by type" %}
Query events corresponding to specific types, ordered by <mark style="color:red;">`submission index`</mark> desc.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"events": {
"event_types": ["deposit_collateral", "withdraw_collateral"],
"max_time": "1679728762",
"limit": {
"raw": 1
}
}
}
```
{% endtab %}
{% tab title="All events" %}
Query all events ordered by <mark style="color:red;">`submission index`</mark> desc.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"events": {
"max_time": "1679728762",
"limit": {
"raw": 1
}
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="162" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">subaccounts</td><td align="center">string[]</td><td align="center">No</td><td>Array of <mark style="color:red;"><code>bytes32</code></mark> sent as hex strings; each includes the address and the subaccount identifier. When provided, only return events for the specified subaccounts.</td></tr><tr><td align="center">product_ids</td><td align="center">number[]</td><td align="center">No</td><td>when provided, only return events for the specified product ids; return events for all products otherwise.</td></tr><tr><td align="center">event_types</td><td align="center">string[]</td><td align="center">No</td><td>when provided, only return events for the specified event types; return all events otherwise.</td></tr><tr><td align="center">idx</td><td align="center">number / string</td><td align="center">No</td><td>when provided, only return events with <mark style="color:red;"><code>submission_idx</code></mark> &#x3C;= <mark style="color:red;"><code>idx</code></mark></td></tr><tr><td align="center">max_time</td><td align="center">number / string</td><td align="center">No</td><td>when <mark style="color:red;"><code>idx</code></mark> is not provided, <mark style="color:red;"><code>max_time</code></mark> (unix epoch in seconds) can be used to only return events created &#x3C;= <mark style="color:red;"><code>max_time</code></mark></td></tr><tr><td align="center">limit</td><td align="center"><p>object<br>{"raw": number } or</p><p>{"txs": number }</p></td><td align="center">No</td><td><ul><li>specifying <mark style="color:red;"><code>raw</code></mark> limit: max number of events to return. defaults to <mark style="color:red;"><code>100</code></mark>. max possible of <mark style="color:red;"><code>500</code></mark>.</li><li>specifying <mark style="color:red;"><code>txs</code></mark> limit: max number of txs to return.</li></ul></td></tr><tr><td align="center">isolated</td><td align="center">bool</td><td align="center">No</td><td>When provided --<br>- <mark style="color:red;"><code>true</code></mark>: only returns evens associated to isolated positions.<br>- <mark style="color:red;"><code>false</code></mark>: only return events associated to the cross-subaccount.<br>defaults to <mark style="color:red;"><code>null</code></mark>. In which case it returns everything.<br><br>See <a href="https://github.com/nadohq/nado-docs/blob/main/docs/basics/isolated-margin.md">Isolated Margin</a> to learn more.</td></tr></tbody></table>
## Response
{% hint style="info" %}
**Note:**
* the response includes a <mark style="color:red;">`txs`</mark> field which contains the relevant transactions to the events. There are <mark style="color:red;">`>=1 events`</mark> per transaction.
* both <mark style="color:red;">`events`</mark> and <mark style="color:red;">`txs`</mark> are in descending order by <mark style="color:red;">`submission_idx`</mark>`.`
* use the <mark style="color:red;">`submission_idx`</mark> to associate an <mark style="color:red;">`event`</mark> to it's corresponding transaction.
{% endhint %}
```json
{
"events": [
{
"subaccount": "0x12a0b4888021576eb10a67616dd3dd3d9ce206b664656661756c740000000000",
"product_id": 1,
"submission_idx": "563011",
"event_type": "match_orders",
"isolated": false,
"isolated_product_id": null,
"pre_balance": {
"spot": {
"product_id": 1,
"balance": {
"amount": "26766781157882079846319"
}
}
},
"post_balance": {
"spot": {
"product_id": 1,
"balance": {
"amount": "26767505157882079846318",
"last_cumulative_multiplier_x18": "1001292804799204317"
}
}
},
"product": {
"product_id": 1,
"oracle_price_x18": "115575316424148798147115",
"risk": {
"long_weight_initial_x18": "900000000000000000",
"short_weight_initial_x18": "1100000000000000000",
"long_weight_maintenance_x18": "950000000000000000",
"short_weight_maintenance_x18": "1050000000000000000",
"price_x18": "115575316424148798147115"
},
"config": {
"token": "0xc57c1c64561a37ac9e8f9039cb6deab7539d99fc",
"interest_inflection_util_x18": "800000000000000000",
"interest_floor_x18": "10000000000000000",
"interest_small_cap_x18": "40000000000000000",
"interest_large_cap_x18": "1000000000000000000",
"withdraw_fee_x18": "40000000000000",
"min_deposit_rate_x18": "0"
},
"state": {
"cumulative_deposits_multiplier_x18": "1000000000000318713",
"cumulative_borrows_multiplier_x18": "1000347390679880473",
"total_deposits_normalized": "9000399823280682696107190850",
"total_borrows_normalized": "9580268570661550719"
},
"book_info": {
"size_increment": "1000000000000000",
"price_increment_x18": "1000000000000000000",
"min_size": "4000000000000000",
"collected_fees": "0"
}
},
"net_interest_unrealized": "49040544804593257",
"net_interest_cumulative": "51596254598679857",
"net_funding_unrealized": "0",
"net_funding_cumulative": "0",
"net_entry_unrealized": "748947727410369682388339518",
"net_entry_cumulative": "749148081870171307027129958",
"quote_volume_cumulative": "1234567890123456789"
}
],
"txs": [
{
"tx": {
"match_orders": {
"product_id": 1,
"amm": true,
"taker": {
"order": {
"sender": "0x12a0b4888021576eb10a67616dd3dd3d9ce206b664656661756c740000000000",
"price_x18": "27540000000000000000000",
"amount": "2000000000000000000",
"appendix": "1537",
"expiration": 4611686020107120000,
"nonce": 1761322602510418000
},
"signature": "0x826c68f1a3f76d9ffbe8041f8d45e969d31f1ab6f2ae2f6379d1493e479e56436091d6cf4c72e212dd2f1d2fa17c627c4c21bd6d281c77172b8af030488478b71c"
},
"maker": {
"order": {
"sender": "0xf8d240d9514c9a4715d66268d7af3b53d619642564656661756c740000000000",
"price_x18": "27540000000000000000000",
"amount": "-724000000000000000",
"appendix": "1537",
"expiration": 1679731656,
"nonce": 1761322565506171000
},
"signature": "0xd8b6505b8d9b8c3cbfe793080976388035682c02a27893fb26b48a5b2bfe943f4162dea3a42e24e0dff5e2f74fbf77e33d83619140a2a581117c55e6cc236bdb1c"
}
}
},
"submission_idx": "563011",
"timestamp": "1679728127"
}
]
}
```
## Response Fields
### Events
{% hint style="info" %}
* **Net cumulative**: the net difference in that quantity since the beginning of time. For example, if I want to compute total amount paid out in funding between two events, you can subtract the `net_funding_cumulative` of the larger event by the `net_funding_cumulative` of the smaller event.
* **Net unrealized**: similar to `net_cumulative`, but for `net_unrealized`, we have the caveat that when the magnitude of your position decreases, the magnitude of net\_unrealized `decreases` by the same amount.
{% endhint %}
<table><thead><tr><th width="307">Field name</th><th>Description</th></tr></thead><tbody><tr><td>submission_idx</td><td>Used to uniquely identify the blockchain transaction that generated the event; you can use it to grab the relevant transaction in the <code>txs</code> section.</td></tr><tr><td>product_id</td><td>The id of of the product the event is associated with.</td></tr><tr><td>event_type</td><td>Name of the transaction type this event corresponds to.</td></tr><tr><td>subaccount</td><td>The subaccount associated to the event.</td></tr><tr><td>pre_balance</td><td>The state of your balance before the event happened.</td></tr><tr><td>post_balance</td><td>The state of your balance after the event happened.</td></tr><tr><td>product</td><td>The state of the product throughout the event.</td></tr></tbody></table>
### Txs
| Field name | Description |
| --------------- | --------------------------------------------------------------------------------------- |
| submission\_idx | Unique identifier of the transaction. |
| product\_id | Product associated to the transaction. |
| tx | Raw data of the corresponding transaction e.g: `match_orders` with all associated data. |
| timestamp | The unix epoch in seconds of when the transaction took place. |
@@ -0,0 +1,53 @@
# Fast Withdrawal Signature
## Rate limits
* 240 requests/min or 40 requests/10secs per IP address. (**weight = 10**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="Fast Withdrawal Signature" %}
Query the signature required for a fast withdrawal at a specific submission index.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"fast_withdrawal_signature": {
"idx": "12345"
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="150" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">idx</td><td align="center">number / string</td><td align="center">Yes</td><td>Submission index to fetch the fast withdrawal signature for.</td></tr></tbody></table>
## Response
```json
{
"signature": "0x1234567890abcdef...",
"submission_idx": "12345",
"subaccount": "0x79cc76364b5fb263a25bd52930e3d9788fcfeea864656661756c740000000000",
"product_id": 0,
"amount": "1000000000000000000",
"nonce": "1"
}
```
## Response Fields
### Fast Withdrawal Signature
<table><thead><tr><th width="263">Field name</th><th>Description</th></tr></thead><tbody><tr><td>signature</td><td>Hex string of the signature for fast withdrawal</td></tr><tr><td>submission_idx</td><td>Transaction submission index</td></tr><tr><td>subaccount</td><td>Hex string of the subaccount</td></tr><tr><td>product_id</td><td>Product ID (0 for quote asset)</td></tr><tr><td>amount</td><td>Withdrawal amount (x18 format)</td></tr><tr><td>nonce</td><td>Nonce for the withdrawal transaction</td></tr></tbody></table>
@@ -0,0 +1,94 @@
# Funding Rate
## Rate limits
* 1200 requests/min or 20 requests/sec per IP address. (**weight = 2**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Single Product
### Request
{% tabs %}
{% tab title="Funding Rate" %}
Query perp product 24hr funding rate.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"funding_rate": {
"product_id": 2
}
}
```
{% endtab %}
{% endtabs %}
### Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="113" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">product_id</td><td align="center">number</td><td align="center">Yes</td><td>Id of perp product to fetch funding rate for.</td></tr></tbody></table>
### Response
```json
{
"product_id": 2,
"funding_rate_x18": "2447900598160952",
"update_time": "1680116326"
}
```
## Multiple Products
### Request
{% tabs %}
{% tab title="Perp Prices" %} <mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"funding_rates": {
"product_ids": [2]
}
}
```
{% endtab %}
{% endtabs %}
### Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="113" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">product_ids</td><td align="center">number[]</td><td align="center">Yes</td><td>Ids of perp products to fetch funding rate for.</td></tr></tbody></table>
### Response
{% hint style="info" %}
**Note**: the response is a map of <mark style="color:red;">`product_id -> funding_rate`</mark> for each requested product.
{% endhint %}
```json
{
"2": {
"product_id": 2,
"funding_rate_x18": "-697407056090986",
"update_time": "1692825387"
}
}
```
## Response Fields
| Field name | Description |
| ------------------ | ----------------------------------------------------------------------- |
| product\_id | Id of the perp product this funding rate corresponds to. |
| funding\_rate\_x18 | Latest 24hr funding rate for the specified product, multiplied by 10^18 |
| update\_time | Epoch time in seconds this funding rate was last updated at |
@@ -0,0 +1,51 @@
# Ink Airdrop
Query the Ink token airdrop allocation for a specific wallet address.
## Rate limits
* 1200 requests/min or 200 requests/10secs per IP address. (**weight = 2**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="Ink Airdrop" %} <mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"ink_airdrop": {
"address": "0x1234567890123456789012345678901234567890"
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="178" align="center">Parameter</th><th width="229" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">address</td><td align="center">string</td><td align="center">Yes</td><td>Wallet address (20-byte address) sent as a hex string.</td></tr></tbody></table>
## Response
{% hint style="info" %}
**Note**: The amount is returned as a string to preserve precision.
{% endhint %}
```json
{
"amount": "1000000000000000000"
}
```
## Response Fields
| Field name | Description |
| ---------- | ------------------------------------------------------ |
| amount | The Ink token airdrop amount allocated to the address. |
@@ -0,0 +1,89 @@
# Interest & funding payments
## Rate limits
* 480 requests/min or 80 requests/10secs per IP address. (**weight = 5**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="Interest and funding" %}
Query subaccount historical interest and funding payments.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"interest_and_funding": {
"subaccount": "0xD028878bF5c96218E53DA859e587cb8398B17b3f64656661756c740000000000",
"product_ids": [1, 2],
"limit": 10,
"max_idx": 1315836
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="140" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">subaccount</td><td align="center">string</td><td align="center">Yes</td><td>A bytes32 sent as a hex string; includes the address and the subaccount identifier.</td></tr><tr><td align="center">product_ids</td><td align="center">number[]</td><td align="center">Yes</td><td>Ids of products to historical interest/funding payments for.</td></tr><tr><td align="center">max_idx</td><td align="center">string/number</td><td align="center">No</td><td>When provided, only return records with <mark style="color:red;"><code>idx</code></mark> &#x3C;= <mark style="color:red;"><code>max_idx</code></mark>.</td></tr><tr><td align="center">limit</td><td align="center">number</td><td align="center">Yes</td><td>Max number of records to return. Max possible of <mark style="color:red;"><code>100</code></mark>.</td></tr></tbody></table>
## Response
```json
{
"interest_payments": [
{
"product_id": 4,
"idx": "5968022",
"timestamp": "1701698400",
"amount": "-12273223338657163",
"balance_amount": "1000000000000000000",
"rate_x18": "47928279191008320",
"oracle_price_x18": "2243215034242228224820"
},
...
],
"funding_payments": [
{
"product_id": 2,
"idx": "5968022",
"timestamp": "1701698400",
"amount": "-12273223338657163",
"balance_amount": "1000000000000000000",
"rate_x18": "47928279191008320",
"oracle_price_x18": "2243215034242228224820"
},
...
],
"next_idx": "1314805"
}
```
## Response Fields
| Field name | Description |
| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| interest\_payments.product\_id | Id of spot product the interest payment is associated to. |
| interest\_payments.idx | Id of transaction that triggered the interest payment. |
| interest\_payments.timestamp | Timestamp of the transaction that triggered the interest payment. |
| interest\_payments.amount | Amount of interest paid multiplied by 10\*\*18. |
| interest\_payments.balance\_amount | Previous spot balance at the moment of payment (exclusive of payment amount) |
| interest\_payments.rate\_x18 | Spot interest rate at the moment of payment, multiplied by 10\*\*18. |
| interest\_payments.oracle\_price\_x18 | Oracle price for the spot product at the moment of payment, multiplied by 10\*\*18. |
| funding\_payments.product\_id | Id of perp product the funding payment is associated to. |
| funding\_payments.idx | Id of transaction that triggered the funding payment. |
| funding\_payments.timestamp | Timestamp of the transaction that triggered the funding payment. |
| funding\_payments.amount | Amount of funding paid multiplied by 10\*\*18. |
| funding\_payments.balance\_amount | Previous perp balance at the moment of payment +amount of perps locked in LPs (exclusive of payment amount). |
| funding\_payments.rate\_x18 | Perp funding rate at the moment of payment, multiplied by 10\*\*18. |
| funding\_payments.oracle\_price\_x18 | Oracle price for the perp product at the moment of payment, multiplied by 10\*\*18. |
| next\_idx | Id of the next payment snapshot. Use this as <mark style="color:red;">`max_idx`</mark> on a subsequent call to get the next page. This will be <mark style="color:red;">`null`</mark> when there are no more records. |
@@ -0,0 +1,74 @@
# Isolated Subaccounts
## Rate limits
* 1200 requests/min or 200 requests/10secs per IP address. (**weight = 2**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="List all isolated subaccounts" %}
Query all isolated subaccounts.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"isolated_subaccounts": {
"start_idx": 0,
"limit": 100
}
}
```
{% endtab %}
{% tab title="List isolated subaccounts for a subaccount" %}
Query isolated subaccounts associated with a specific subaccount.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"isolated_subaccounts": {
"subaccount": "0x79cc76364b5fb263a25bd52930e3d9788fcfeea864656661756c740000000000",
"limit": 100
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="150" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">subaccount</td><td align="center">string</td><td align="center">No</td><td>Hex string of the parent subaccount to filter by.</td></tr><tr><td align="center">start_idx</td><td align="center">number / string</td><td align="center">No</td><td>Starting index for pagination. Defaults to 0.</td></tr><tr><td align="center">limit</td><td align="center">number</td><td align="center">No</td><td>Max number of isolated subaccounts to return. Defaults to <mark style="color:red;"><code>100</code></mark>. Max of <mark style="color:red;"><code>500</code></mark>.</td></tr></tbody></table>
## Response
```json
{
"isolated_subaccounts": [
{
"subaccount": "0x79cc76364b5fb263a25bd52930e3d9788fcfeea864656661756c740000000000",
"isolated_subaccount": "0x79cc76364b5fb263a25bd52930e3d9788fcfeea800000000000000010069736f",
"product_id": 1,
"created_at": "1683315718"
}
]
}
```
## Response Fields
### Isolated Subaccounts
<table><thead><tr><th width="263">Field name</th><th>Description</th></tr></thead><tbody><tr><td>subaccount</td><td>Hex string of the parent subaccount</td></tr><tr><td>isolated_subaccount</td><td>Hex string of the isolated margin subaccount</td></tr><tr><td>product_id</td><td>Product ID for which this isolated subaccount was created</td></tr><tr><td>created_at</td><td>Unix epoch time in seconds when the isolated subaccount was created</td></tr></tbody></table>
@@ -0,0 +1,52 @@
# Linked Signer Rate Limit
A subaccount can perform a max of 50 [LinkSigner](https://docs.nado.xyz/developer-resources/api/gateway/executes/link-signer) requests in 7 days. Use this query to check current usage and wait time.
## Rate limits
* 1200 requests/min or 200 requests/10secs per IP address. (**weight = 2**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="Link Signer Rate Limit" %}
Queries a subaccount's linked signer rate limits.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"linked_signer_rate_limit": {
"subaccount": "0x9b9989a4E0b260B84a5f367d636298a8bfFb7a9b42544353504f540000000000"
}
}
```
{% endtab %}
{% endtabs %}
## Response
```json
{
"remaining_tx": "50",
"wait_time": 0,
"signer": "0x0000000000000000000000000000000000000000",
"total_tx_limit": "50"
}
```
{% hint style="info" %}
**Notes**:
* <mark style="color:red;">`remaining_tx`</mark>: keeps track of the remaining <mark style="color:red;">`LinkSigner`</mark> executes that can be performed.
* <mark style="color:red;">`total_tx_limit`</mark>: that max weekly tx limit.
* <mark style="color:red;">`wait_time`</mark>: the total seconds you need to wait before performing another <mark style="color:red;">`LinkSigner`</mark> execute. Can only perform another request when <mark style="color:red;">`wait_time`</mark> is `0`.
* <mark style="color:red;">`signer`</mark>: the current linked signer address (20 bytes) associated to the provided `subaccount`. It returns the zero address when no signer is linked.
{% endhint %}
@@ -0,0 +1,55 @@
# Linked Signers
## Rate limits
* 1200 requests/min or 200 requests/10secs per IP address. (**weight = 2**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="List linked signers" %}
Query linked signers ordered by creation time.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"linked_signers": {
"start_idx": 0,
"limit": 100
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="150" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">start_idx</td><td align="center">number / string</td><td align="center">No</td><td>Starting index for pagination. Defaults to 0.</td></tr><tr><td align="center">limit</td><td align="center">number</td><td align="center">No</td><td>Max number of linked signers to return. Defaults to <mark style="color:red;"><code>100</code></mark>. Max of <mark style="color:red;"><code>500</code></mark>.</td></tr></tbody></table>
## Response
```json
{
"linked_signers": [
{
"subaccount": "0x79cc76364b5fb263a25bd52930e3d9788fcfeea864656661756c740000000000",
"signer": "0x1234567890123456789012345678901234567890",
"created_at": "1683315718"
}
]
}
```
## Response Fields
### Linked Signers
<table><thead><tr><th width="263">Field name</th><th>Description</th></tr></thead><tbody><tr><td>subaccount</td><td>Hex string of the subaccount</td></tr><tr><td>signer</td><td>Hex string of the linked signer address</td></tr><tr><td>created_at</td><td>Unix epoch time in seconds when the signer was linked</td></tr></tbody></table>
@@ -0,0 +1,58 @@
# Liquidation Feed
## Rate limits
* 1200 requests/min or 200 requests/10secs per IP address. (**weight = 2**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="Liquidation feed" %}
Queries liquidatable accounts.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"liquidation_feed": {}
}
```
{% endtab %}
{% endtabs %}
## Response
```json
[
{
"subaccount": "0xf2b7cec33cac30582b94979bf03a3cbc73954b2c64656661756c740000000000",
"update_time": 1680118943
},
{
"subaccount": "0xcb6f1e2ece124a150dcc681c180df2a890432d6a64656661756c740000000000",
"update_time": 1680118943
},
{
"subaccount": "0x9e6e13be7ea2866c2c7c6e4a118a6c05eee6b44e64656661756c740000000000",
"update_time": 1680118943
},
{
"subaccount": "0x75008754ffae2889c055961c1b0c5c3ab743c59664656661756c740000000000",
"update_time": 1680118943
}
]
```
## Response Fields
| Field name | Description |
| ------------ | ------------------------------------ |
| subaccount | Subaccount eligible for liquidation. |
| update\_time | Last time feed was updated. |
@@ -0,0 +1,124 @@
# Market Snapshots
## Rate limits
* IP weight = <mark style="color:red;">`max((snapshot_count * product_ids.length / 100), 2)`</mark> where <mark style="color:red;">`snapshot_count = interval.count.min(500)`</mark>. If no <mark style="color:red;">`product_ids`</mark> are specified, <mark style="color:red;">`product_ids.length = 100`</mark>.
* E.g: With <mark style="color:red;">`product_ids=[1, 2, 3, 4]`</mark> and <mark style="color:red;">`interval.count=60`</mark>, weight = max((60 \* 4 / 100), 2) = 2, allowing up to 1200 requests per min or 200 requests/10 secs.
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
### Request
{% tabs %}
{% tab title="Market snapshots" %}
Query market snapshots ordered by <mark style="color:red;">`timestamp`</mark> desc.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"market_snapshots": {
"interval": {
"count": 2,
"granularity": 3600,
"max_time": 1691083697,
},
"product_ids": [1, 2]
}
}
```
{% endtab %}
{% endtabs %}
### Request Parameters
<table><thead><tr><th width="192" align="center">Parameter</th><th width="103" align="center">Type</th><th width="101" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">interval</td><td align="center">object</td><td align="center">Yes</td><td>Object to specify desired time period for data</td></tr><tr><td align="center">interval.count</td><td align="center">number</td><td align="center">Yes</td><td>Number of snapshots to return, limit 100. Also limited to <code>interval.count * # product_ids &#x3C; 2000</code></td></tr><tr><td align="center">interval.granularity</td><td align="center">number</td><td align="center">Yes</td><td>Granularity value in seconds</td></tr><tr><td align="center">interval.max_time</td><td align="center">number / string</td><td align="center">No</td><td>When providing <mark style="color:red;"><code>max_time</code></mark> (unix epoch in seconds), only return snapshots with timestamp &#x3C;= <mark style="color:red;"><code>max_time</code></mark>. If no value is entered, <code>max_time</code> defaults to the current time.</td></tr><tr><td align="center">product_ids</td><td align="center">number[]</td><td align="center">No</td><td>list of product ids to fetch snapshots for, defaults to all products</td></tr></tbody></table>
### Response
{% hint style="info" %}
**Note**: Please note that this endpoint is currently in beta stage. This feature might be subject to changes without prior notice.
{% endhint %}
```json
{
"snapshots": [
{
"timestamp": 1689965194,
"cumulative_users": 2774,
"daily_active_users": 251,
"cumulative_trades": {
"1": 54287,
"2": 172435
},
"cumulative_volumes": {
"1": "259549132367035103631071564",
"2": "1134008547778337985156988339"
},
"cumulative_trade_sizes": {
"1": "9209508999999999995173",
"2": "40246259000000000000000"
},
"cumulative_taker_fees": {
"1": "88916428908427788322799",
"2": "259205794197801680292645"
},
"cumulative_sequencer_fees": {
"1": "11038200000000000000000",
"2": "32353000000000000000000"
},
"cumulative_maker_fees": {
"1": "-12421730086012739050725",
"2": "-36124007075181485948604"
},
"cumulative_liquidation_amounts": {
"1": "848311398835000694508",
"2": "1013231566414935056343898"
},
"open_interests": {
"2": "2907581091676822842104781"
},
"total_deposits": {
"1": "37722308770940799414"
},
"total_borrows": {
"1": "1441397740941092000"
},
"funding_rates": {
"2": "3611102723387"
},
"deposit_rates": {
"1": "1001376785714"
},
"borrow_rates": {
"1": "32059880416879"
},
"cumulative_inflows": {
"1": "238791614019999999853",
"2": "0"
},
"cumulative_outflows": {
"1": "-202514202990000000306",
"2": "0"
},
"tvl": "7560079507311601381352742"
},
...
]
}
```
### Response Fields
#### Snapshots
{% hint style="info" %}
**Note**: For product specific fields (i.e. cumulative\_volume, open\_interests), the value is an object which maps product\_ids to their corresponding values.
{% endhint %}
<table><thead><tr><th width="260">Field name</th><th>Description</th></tr></thead><tbody><tr><td>timestamp</td><td>Timestamp of the snapshot. This may not be perfectly rounded to the granularity since it uses the nearest transaction timestamp less than or equal to <mark style="color:red;"><code>max_time</code></mark></td></tr><tr><td>cumulative_users</td><td>The cumulative number of subaccounts on Nado. It is updated daily at 9AM ET for historical counts. For current day counts, it is updated every hour.</td></tr><tr><td>daily_active_users</td><td>Daily active users count, updated daily at 9AM ET for historical counts. For current day counts, it is updated every hour.</td></tr><tr><td>cumulative_trades</td><td>A map of product_id -> the cumulative number of trades for the given product_id.</td></tr><tr><td>cumulative_volumes</td><td>A map of product_id -> cumulative volumes in USDT0 units.</td></tr><tr><td>cumulative_trade_sizes</td><td>A map of product_id -> cumulative trade sizes in base token</td></tr><tr><td>cumulative_taker_fees</td><td>A map of product_id -> cumulative taker fees. Taker fees include sequencer fees.</td></tr><tr><td>cumulative_sequencer_fees</td><td>A map of product_id -> cumulative sequencer fees.</td></tr><tr><td>cumulative_maker_fees</td><td>A map of product_id -> cumulative maker rebates.</td></tr><tr><td>cumulative_liquidation_amounts</td><td>A map of product_id -> cumulative liquidation amounts in USDT0 units.</td></tr><tr><td>open_interests</td><td>A map of product_id -> open interests in USDT0 units.</td></tr><tr><td>total_deposits</td><td>A map of product_id -> total deposits held by Nado for a given product at the given time in the base token units.</td></tr><tr><td>total_borrows</td><td>A map of product_id -> total borrows lent by Nado for a given product at the given time in the base token units.</td></tr><tr><td>funding_rates</td><td>A map of product_id -> <strong>hourly</strong> historical funding rates, value returned as <strong>decimal rates</strong> (% = rate * 100), derived from funding payment amounts. Requires a minimum granularity of 3600 to see non-zero funding rates. Use a granularity where granularity % 3600 = 0 for best results.</td></tr><tr><td>deposit_rates</td><td>A map of product_id -> <strong>daily</strong> deposit rates, values returned as <strong>decimal rates</strong> (% = rate * 100).</td></tr><tr><td>borrow_rates</td><td>A map of product_id -> <strong>daily</strong> borrow rates, values returned as <strong>decimal rates</strong> (% = rate * 100).</td></tr><tr><td>cumulative_inflows</td><td>A map of product_id -> cumulative inflows a.k.a deposits in base token units.</td></tr><tr><td>cumulative_outflows</td><td>A map of product_id -> cumulative outflows a.k.a withdraws in base token units.</td></tr><tr><td>tvl</td><td>The total value locked in USD.</td></tr></tbody></table>
@@ -0,0 +1,279 @@
# Matches
## Rate limits
* IP weight = <mark style="color:red;">`2 + (limit * subaccounts.length / 10)`</mark> where <mark style="color:red;">`limit`</mark> defaults to 100 (max 500) and <mark style="color:red;">`subaccounts.length`</mark> defaults to 1
* E.g: With <mark style="color:red;">`limit=100`</mark> and 1 subaccount, weight = 12, allowing up to 200 requests per min or 33 requests / 10 secs.
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="Matches by subaccount" %}
Query subaccounts matches ordered by <mark style="color:red;">`submission index`</mark> desc. Response includes order fill and fee information.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"matches": {
"product_ids": [
1,
2
],
"subaccounts": [
"0x12a0b4888021576eb10a67616dd3dd3d9ce206b664656661756c740000000000"
],
"max_time": 1679728762,
"limit": 5,
"isolated": false
}
}
```
{% endtab %}
{% tab title="Matches by product" %}
Query matches for provided products ordered by <mark style="color:red;">`submission index`</mark> desc. Response includes order fill and fee information.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"matches": {
"product_ids": [
1,
2
],
"max_time": "1679728762",
"limit": 5
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="113" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">subaccounts</td><td align="center">string[]</td><td align="center">No</td><td>Array of <mark style="color:red;"><code>bytes32</code></mark> sent as hex strings; each includes the address and the subaccount identifier. When provided, only return matches for the specified subaccounts.</td></tr><tr><td align="center">product_ids</td><td align="center">number[]</td><td align="center">No</td><td>When provided, only return matches for the specified product ids; return matches for all products otherwise.</td></tr><tr><td align="center">idx</td><td align="center">number / string</td><td align="center">No</td><td>When provided, only return matches with <mark style="color:red;"><code>submission_idx</code></mark> &#x3C;= <mark style="color:red;"><code>idx</code></mark></td></tr><tr><td align="center">max_time</td><td align="center">number / string</td><td align="center">No</td><td>When <mark style="color:red;"><code>idx</code></mark> is not provided, <mark style="color:red;"><code>max_time</code></mark> (unix epoch in seconds) can be used to only return matches created &#x3C;= <mark style="color:red;"><code>max_time</code></mark></td></tr><tr><td align="center">limit</td><td align="center">number</td><td align="center">No</td><td>Max number of matches to return. defaults to <mark style="color:red;"><code>100</code></mark>. max possible of <mark style="color:red;"><code>500</code></mark>.</td></tr><tr><td align="center">isolated</td><td align="center">boolean</td><td align="center">No</td><td>When provided --<br>- <mark style="color:red;"><code>true</code></mark>: only returns matches associated to isolated positions.<br>- <mark style="color:red;"><code>false</code></mark>: only return matches associated to the cross-subaccount.<br>defaults to <mark style="color:red;"><code>null</code></mark>. In which case it returns everything.<br><br>See <a href="https://github.com/nadohq/nado-docs/blob/main/docs/basics/isolated-margin.md">Isolated Margin</a> to learn more.</td></tr></tbody></table>
## Response
{% hint style="info" %}
**Note:**
* the response includes a <mark style="color:red;">`txs`</mark> field which contains the relevant transactions for the returned matches. There are <mark style="color:red;">`>=1 match events`</mark> per transaction.
* both <mark style="color:red;">`matches`</mark> and <mark style="color:red;">`txs`</mark> are in descending order by <mark style="color:red;">`submission_idx`</mark>`.`
* use the <mark style="color:red;">`submission_idx`</mark> to associate a match to it's corresponding transaction.
* the <mark style="color:red;">`fee`</mark> provided in the response includes taker / maker fees + sequencer fees. See [fees](https://github.com/nadohq/nado-docs/blob/main/docs/basics/fees.md) for more details.
{% endhint %}
```json
{
"matches": [
{
"digest": "0x80ce789702b670b7d33f2aa67e12c85f124395c3f9acdb422dde3b4973ccd50c",
"order": {
"sender": "0x12a0b4888021576eb10a67616dd3dd3d9ce206b664656661756c740000000000",
"priceX18": "27544000000000000000000",
"amount": "2000000000000000000",
"expiration": "4611686020107119633",
"nonce": "1761322608857448448"
},
"base_filled": "736000000000000000",
"quote_filled": "-20276464287857571514302",
"fee": "4055287857571514302",
"sequencer_fee": "0",
"cumulative_fee": "4055287857571514302",
"cumulative_base_filled": "736000000000000000",
"cumulative_quote_filled": "-20276464287857571514302",
"submission_idx": "563012",
"isolated": false,
"is_taker": true,
"pre_balance": {
"base": {
"perp": {
"product_id": 2,
"balance": {
"amount": "2686684000000000000000",
"v_quote_balance": "-76348662407149297671587247",
"last_cumulative_funding_x18": "134999841911604906604576"
}
}
},
"quote": null
},
"post_balance": {
"base": {
"perp": {
"product_id": 2,
"balance": {
"amount": "2686013000000000000000",
"v_quote_balance": "-76328351274188497671587247",
"last_cumulative_funding_x18": "134999841911604906604576"
}
}
},
"quote": null
}
},
{
"digest": "0x0f6e5a0434e36d8e6d4fed950d3624b0d8c91a8a84efd156bb25c1382561c0c2",
"order": {
"sender": "0x12a0b4888021576eb10a67616dd3dd3d9ce206b664656661756c740000000000",
"priceX18": "27540000000000000000000",
"amount": "2000000000000000000",
"appendix": "1537",
"expiration": "4611686020107119623",
"nonce": "1761322602510417920"
},
"base_filled": "723999999999999999",
"quote_filled": "-19944943483044913474043",
"fee": "5983483044913474042",
"sequencer_fee": "0",
"cumulative_fee": "11958484645393618085",
"cumulative_base_filled": "1446999999999999998",
"cumulative_quote_filled": "-39861640484645393618087",
"submission_idx": "563011",
"isolated": false,
"is_taker": true,
"pre_balance": {
"base": {
"perp": {
"product_id": 2,
"balance": {
"amount": "2686684000000000000000",
"v_quote_balance": "-76348662407149297671587247",
"last_cumulative_funding_x18": "134999841911604906604576"
}
}
},
"quote": null
},
"post_balance": {
"base": {
"perp": {
"product_id": 2,
"balance": {
"amount": "2686013000000000000000",
"v_quote_balance": "-76328351274188497671587247",
"last_cumulative_funding_x18": "134999841911604906604576"
}
}
},
"quote": null
}
}
],
"txs": [
{
"tx": {
"match_orders": {
"product_id": 2,
"amm": true,
"taker": {
"order": {
"sender": "0x12a0b4888021576eb10a67616dd3dd3d9ce206b664656661756c740000000000",
"price_x18": "27544000000000000000000",
"amount": "2000000000000000000",
"expiration": 4611686020107120000,
"appendix": "1537",
"nonce": 1761322608857448400
},
"signature": "0xe8fa7151bde348afa3b46dc52798046b7c8318f1b0a7f689710debbc094658cc1bf5a7e478ccc8278b625da0b9402c86b580d2e31e13831337dfd6153f4b37811b"
},
"maker": {
"order": {
"sender": "0xebdbbcdbd2646c5f23a1e0806027eee5f71b074664656661756c740000000000",
"price_x18": "27544000000000000000000",
"amount": "-736000000000000000",
"expiration": 1679731669,
"appendix": "1537",
"nonce": 1761322585591644200
},
"signature": "0x47f9d47f0777f3ca0b13f07b7682dbeea098c0e377b87dcb025754fe34c900e336b8c7744e021fb9c46a4f8c6a1478bafa28bf0d023ae496aa3efa4d8e81df181c"
}
}
},
"submission_idx": "563012",
"timestamp": "1679728133"
},
{
"tx": {
"match_orders": {
"product_id": 1,
"amm": true,
"taker": {
"order": {
"sender": "0x12a0b4888021576eb10a67616dd3dd3d9ce206b664656661756c740000000000",
"price_x18": "27540000000000000000000",
"amount": "2000000000000000000",
"appendix": "1537",
"expiration": 4611686020107120000,
"nonce": 1761322602510418000
},
"signature": "0x826c68f1a3f76d9ffbe8041f8d45e969d31f1ab6f2ae2f6379d1493e479e56436091d6cf4c72e212dd2f1d2fa17c627c4c21bd6d281c77172b8af030488478b71c"
},
"maker": {
"order": {
"sender": "0xf8d240d9514c9a4715d66268d7af3b53d619642564656661756c740000000000",
"price_x18": "27540000000000000000000",
"amount": "-724000000000000000",
"appendix": "1537",
"expiration": 1679731656,
"nonce": 1761322565506171000
},
"signature": "0xd8b6505b8d9b8c3cbfe793080976388035682c02a27893fb26b48a5b2bfe943f4162dea3a42e24e0dff5e2f74fbf77e33d83619140a2a581117c55e6cc236bdb1c"
}
}
},
"submission_idx": "563011",
"timestamp": "1679728127"
}
]
}
```
## Response Fields
### Matches
| Field name | Description |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| submission\_idx | Wsed to uniquely identify the blockchain transaction that generated the match; you can use it to grab the relevant transaction in the `txs` section. |
| isolated | Whether the match is associated with an isolated position. `true` for isolated positions, `false` for cross-subaccount positions. |
| is\_taker | Whether the order in this match was the taker. `true` if the order was the taker, `false` if the order was the maker. |
| digest | The unique hash of the order. |
| order.sender | The sender that placed the order. |
| order.priceX18 | The original order price. |
| order.amount | The original order amount. |
| order.expiration | The original order expiration. |
| order.nonce | The original order nonce. |
| order.appendix | The original order appendix. |
| pre\_balance | The state of your balance before the match happened. |
| post\_balance | The state of your balance after the match happened. |
| base\_filled | The amount of base (e.g: BTC) filled on this match. |
| quote\_filled | The amount of quote (e.g: USDT0) filled on this match. |
| fee | The amount of trading fees + sequencer fees paid on this match. |
| sequencer\_fee | The amount of sequencer fees paid on this match. |
| cumulative\_base\_filled | The total amount of base (e.g: BTC) filled on this order up this match. |
| cumulative\_quote\_filled | The total amount of quote (e.g: USDT0) filled up to this match. |
| cumulative\_fee | The total amount of fee paid up to this match. |
### Txs
| Field name | Description |
| --------------- | ------------------------------------------------------------- |
| submission\_idx | Unique identifier of the transaction. |
| product\_id | Product associated to the transaction. |
| taker | The taker order. |
| maker | The maker order. |
| timestamp | The unix epoch in seconds of when the transaction took place. |
@@ -0,0 +1,60 @@
# NLP Funding Payments
## Rate limits
* 480 requests/min or 80 requests/10secs per IP address. (**weight = 5**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="NLP Funding Payments" %}
Query historical NLP funding payments.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"nlp_funding_payments": {
"max_idx": "1315836",
"max_time": "1683315718",
"limit": 100
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="150" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">max_idx</td><td align="center">number / string</td><td align="center">No</td><td>When provided, only return payments with <mark style="color:red;"><code>idx</code></mark> &#x3C;= <mark style="color:red;"><code>max_idx</code></mark>.</td></tr><tr><td align="center">max_time</td><td align="center">number / string</td><td align="center">No</td><td>When provided, only return payments with <mark style="color:red;"><code>timestamp</code></mark> &#x3C;= <mark style="color:red;"><code>max_time</code></mark> (unix epoch in seconds).</td></tr><tr><td align="center">limit</td><td align="center">number</td><td align="center">No</td><td>Max number of payments to return. Defaults to <mark style="color:red;"><code>100</code></mark>. Max of <mark style="color:red;"><code>500</code></mark>.</td></tr></tbody></table>
## Response
```json
{
"funding_payments": [
{
"product_id": 2,
"idx": "5968022",
"timestamp": "1701698400",
"total_payment": "12273223338657163",
"rate_x18": "47928279191008320",
"oracle_price_x18": "2243215034242228224820"
}
],
"next_idx": "5968021"
}
```
## Response Fields
### Funding Payments
<table><thead><tr><th width="263">Field name</th><th>Description</th></tr></thead><tbody><tr><td>product_id</td><td>Id of the perp product</td></tr><tr><td>idx</td><td>Submission index of the transaction that triggered the payment</td></tr><tr><td>timestamp</td><td>Unix epoch time in seconds when the payment occurred</td></tr><tr><td>total_payment</td><td>Total funding payment amount (x18 format)</td></tr><tr><td>rate_x18</td><td>Funding rate used for calculation (x18 format)</td></tr><tr><td>oracle_price_x18</td><td>Oracle price at the time of payment (x18 format)</td></tr></tbody></table>
@@ -0,0 +1,59 @@
# NLP Interest Payments
## Rate limits
* 480 requests/min or 80 requests/10secs per IP address. (**weight = 5**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="NLP Interest Payments" %}
Query historical NLP interest payments.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"nlp_interest_payments": {
"max_idx": "1315836",
"max_time": "1683315718",
"limit": 100
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="150" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">max_idx</td><td align="center">number / string</td><td align="center">No</td><td>When provided, only return payments with <mark style="color:red;"><code>idx</code></mark> &#x3C;= <mark style="color:red;"><code>max_idx</code></mark>.</td></tr><tr><td align="center">max_time</td><td align="center">number / string</td><td align="center">No</td><td>When provided, only return payments with <mark style="color:red;"><code>timestamp</code></mark> &#x3C;= <mark style="color:red;"><code>max_time</code></mark> (unix epoch in seconds).</td></tr><tr><td align="center">limit</td><td align="center">number</td><td align="center">No</td><td>Max number of payments to return. Defaults to <mark style="color:red;"><code>100</code></mark>. Max of <mark style="color:red;"><code>500</code></mark>.</td></tr></tbody></table>
## Response
```json
{
"interest_payments": [
{
"product_id": 0,
"idx": "5968022",
"timestamp": "1701698400",
"amount": "12273223338657163",
"balance_amount": "45382847293847329847"
}
],
"next_idx": "5968021"
}
```
## Response Fields
### Interest Payments
<table><thead><tr><th width="263">Field name</th><th>Description</th></tr></thead><tbody><tr><td>product_id</td><td>Id of the spot product (typically quote/collateral products)</td></tr><tr><td>idx</td><td>Submission index of the transaction that triggered the payment</td></tr><tr><td>timestamp</td><td>Unix epoch time in seconds when the payment occurred</td></tr><tr><td>amount</td><td>Interest payment amount (x18 format)</td></tr><tr><td>balance_amount</td><td>Balance amount at the time of payment (x18 format)</td></tr></tbody></table>
@@ -0,0 +1,82 @@
# NLP Snapshots
## Rate limits
* Dynamic based on snapshot count (**weight = (limit.min(500) / 100)**)
* E.g: With <mark style="color:red;">`limit=100`</mark>, weight = 1
* E.g: With <mark style="color:red;">`limit=500`</mark>, weight = 5
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="By interval" %}
Query NLP snapshots at specific time intervals.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"nlp_snapshots": {
"interval": {
"count": 10,
"max_time": "1683315718",
"granularity": 3600
}
}
}
```
{% endtab %}
{% tab title="By pagination" %}
Query NLP snapshots with pagination.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"nlp_snapshots": {
"idx": "12345",
"max_time": "1683315718",
"limit": 100
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="150" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">interval</td><td align="center">object</td><td align="center">No</td><td>Object specifying time interval parameters: <mark style="color:red;"><code>count</code></mark>, <mark style="color:red;"><code>max_time</code></mark>, <mark style="color:red;"><code>granularity</code></mark></td></tr><tr><td align="center">idx</td><td align="center">number / string</td><td align="center">No</td><td>Submission index for pagination.</td></tr><tr><td align="center">max_time</td><td align="center">number / string</td><td align="center">No</td><td>Unix epoch time in seconds. Only return snapshots with timestamp &#x3C;= <mark style="color:red;"><code>max_time</code></mark></td></tr><tr><td align="center">limit</td><td align="center">number</td><td align="center">No</td><td>Max number of snapshots to return. Defaults to <mark style="color:red;"><code>100</code></mark>. Max of <mark style="color:red;"><code>500</code></mark>.</td></tr></tbody></table>
## Response
```json
{
"snapshots": [
{
"submission_idx": "12345",
"timestamp": "1683315718",
"total_deposits": "1000000000000000000000",
"total_borrows": "500000000000000000000",
"base_interest_rate": "50000000000000000",
"quote_interest_rate": "30000000000000000"
}
]
}
```
## Response Fields
### NLP Snapshots
<table><thead><tr><th width="263">Field name</th><th>Description</th></tr></thead><tbody><tr><td>submission_idx</td><td>Transaction submission index</td></tr><tr><td>timestamp</td><td>Unix epoch time in seconds when snapshot was taken</td></tr><tr><td>total_deposits</td><td>Total deposits in the NLP pool (x18 format)</td></tr><tr><td>total_borrows</td><td>Total borrows from the NLP pool (x18 format)</td></tr><tr><td>base_interest_rate</td><td>Interest rate for base assets (x18 format)</td></tr><tr><td>quote_interest_rate</td><td>Interest rate for quote assets (x18 format)</td></tr></tbody></table>
@@ -0,0 +1,70 @@
# Oracle Price
## Rate limits
* 1200 requests/min or 200 requests/10secs per IP address. (**weight = 2**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="Oracle Price" %} <mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"oracle_price": {
"product_ids": [1, 2, 3, 4]
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="113" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">product_ids</td><td align="center">number[]</td><td align="center">Yes</td><td>Ids of products to fetch oracles price for.</td></tr></tbody></table>
## Response
```json
{
"prices": [
{
"product_id": 1,
"oracle_price_x18": "29464023750000000000000",
"update_time": "1683315718"
},
{
"product_id": 2,
"oracle_price_x18": "29430225194712740000000",
"update_time": "1683315721"
},
{
"product_id": 3,
"oracle_price_x18": "1983367400000000000000",
"update_time": "1683315720"
},
{
"product_id": 4,
"oracle_price_x18": "1981528989642697000000",
"update_time": "1683315721"
}
]
}
```
## Response Fields
### Prices
| Field name | Description |
| ------------------ | ------------------------------------------------------ |
| product\_id | Id of product oracle price corresponds to. |
| oracle\_price\_x18 | Latest oracle price multiplied by 10^18. |
| update\_time | Epoch in seconds the oracle price was last updated at. |
@@ -0,0 +1,67 @@
# Oracle Snapshots
## Rate limits
* IP weight = <mark style="color:red;">`max((snapshot_count * product_ids.length / 100), 2)`</mark> where <mark style="color:red;">`snapshot_count = interval.count.min(500)`</mark>. If no <mark style="color:red;">`product_ids`</mark> are specified, <mark style="color:red;">`product_ids.length = 100`</mark>.
* E.g: With <mark style="color:red;">`product_ids=[1, 2, 3, 4]`</mark> and <mark style="color:red;">`interval.count=60`</mark>, weight = max((60 \* 4 / 100), 2) = 2, allowing up to 1200 requests per min or 200 requests/10 secs.
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="Oracle Price" %}
Query oracle snapshots ordered by <mark style="color:red;">`timestamp`</mark> desc.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"oracle_snapshots": {
"interval": {
"count": 2,
"granularity": 3600,
"max_time": 1691083697,
},
"product_ids": [1, 2]
}
}
```
{% endtab %}
{% endtabs %}
### Request Parameters
<table><thead><tr><th width="192" align="center">Parameter</th><th width="103" align="center">Type</th><th width="101" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">interval</td><td align="center">object</td><td align="center">Yes</td><td>Object to specify desired time period for data</td></tr><tr><td align="center">interval.count</td><td align="center">number</td><td align="center">Yes</td><td>Number of snapshots to return, limit 100. Also limited to <code>interval.count * # product_ids &#x3C; 2000</code></td></tr><tr><td align="center">interval.granularity</td><td align="center">number</td><td align="center">Yes</td><td>Granularity value in seconds</td></tr><tr><td align="center">interval.max_time</td><td align="center">number / string</td><td align="center">No</td><td>When providing <mark style="color:red;"><code>max_time</code></mark> (unix epoch in seconds), only return snapshots with timestamp &#x3C;= <mark style="color:red;"><code>max_time</code></mark>. If no value is entered, <code>max_time</code> defaults to the current time.</td></tr><tr><td align="center">product_ids</td><td align="center">number[]</td><td align="center">No</td><td>list of product ids to fetch snapshots for, defaults to all products</td></tr></tbody></table>
## Response
{% hint style="info" %}
**Note**: Returns a map of <mark style="color:red;">`product_id -> oracle_price`</mark>
{% endhint %}
```json
{
"snapshots": [
{
"timestamp": 1750947789,
"oracle_prices": {
"1": "107070085854928675234384",
"2": "107142264360834928244199"
}
},
{
"timestamp": 1750946389,
"oracle_prices": {
"1": "106963557680819440289916",
"2": "106954360458642468300594"
}
}
]
}
```
@@ -0,0 +1,127 @@
# Orders
## Rate limits
* IP weight = <mark style="color:red;">`2 + (limit * subaccounts.length / 20)`</mark> where <mark style="color:red;">`limit`</mark> defaults to 100 (max 500) and <mark style="color:red;">`subaccounts.length`</mark> defaults to 1
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="Subaccount orders" %}
Query subaccounts <mark style="color:red;">`matched`</mark> orders, ordered by <mark style="color:red;">`submission index`</mark> desc.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"orders": {
"product_ids": [
1,
2
],
"subaccounts": [
"0x12a0b4888021576eb10a67616dd3dd3d9ce206b664656661756c740000000000"
],
"max_time": 1679728762,
"trigger_types": [
"price_trigger",
"time_trigger"
],
"isolated": false,
"limit": 5
}
}
```
{% endtab %}
{% tab title="Orders by digests" %}
Query orders by digests.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
<pre class="language-json"><code class="lang-json"><strong>{
</strong> "orders": {
"digests": [
"0xf4f7a8767faf0c7f72251a1f9e5da590f708fd9842bf8fcdeacbaa0237958fff",
"0x0495a88fb3b1c9bed9b643b8e264a391d04cdd48890d81cd7c4006473f28e361"
]
}
}
</code></pre>
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="113" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">subaccounts</td><td align="center">string[]</td><td align="center">conditional</td><td>Array of <mark style="color:red;"><code>bytes32</code></mark> sent as hex strings; each includes the address and the subaccount identifier. Must be provided when querying by <mark style="color:red;"><code>subaccounts</code></mark><strong>.</strong></td></tr><tr><td align="center">product_ids</td><td align="center">number[]</td><td align="center">No</td><td>When provided, only return orders for the specified product ids; return orders for all products otherwise.</td></tr><tr><td align="center">idx</td><td align="center">number / string</td><td align="center">No</td><td>When provided, only return orders with <mark style="color:red;"><code>submission_idx</code></mark> &#x3C;= <mark style="color:red;"><code>idx</code></mark></td></tr><tr><td align="center">max_time</td><td align="center">number / string</td><td align="center">No</td><td>When <mark style="color:red;"><code>idx</code></mark> is not provided, <mark style="color:red;"><code>max_time</code></mark> (unix epoch in seconds) can be used to only return orders created &#x3C;= <mark style="color:red;"><code>max_time</code></mark></td></tr><tr><td align="center">digests</td><td align="center">string[]</td><td align="center">conditional</td><td>Must be provided when querying by <mark style="color:red;"><code>digests</code></mark>. only return orders matching the specified digests. <strong>note</strong>: cannot specify digests alongside with <mark style="color:red;"><code>subaccounts</code></mark> , <mark style="color:red;"><code>product_ids</code></mark> or <mark style="color:red;"><code>max_time</code></mark></td></tr><tr><td align="center">trigger_types</td><td align="center">string[]</td><td align="center">No</td><td>When provided, only return orders matching the specified trigger types. Possible values: <mark style="color:red;"><code>price_trigger</code></mark>, <mark style="color:red;"><code>time_trigger</code></mark>, <mark style="color:red;"><code>none</code></mark>. If not provided, returns orders of all trigger types.</td></tr><tr><td align="center">limit</td><td align="center">number</td><td align="center">No</td><td>Max number of orders to return. defaults to <mark style="color:red;"><code>100</code></mark>. max possible of <mark style="color:red;"><code>500</code></mark>. <strong>note</strong>: when querying by <mark style="color:red;"><code>digests</code></mark> limit must be &#x3C;= total digests provided</td></tr><tr><td align="center">isolated</td><td align="center">bool</td><td align="center">No</td><td><p>When provided --</p><ul><li><mark style="color:red;"><code>true</code></mark>: only returns orders associated to isolated positions.</li><li><mark style="color:red;"><code>false</code></mark>: only return matches associated to the cross-subaccount.</li></ul><p>defaults to <mark style="color:red;"><code>null</code></mark>. In which case it returns everything.</p><p>See <a href="https://github.com/nadohq/nado-docs/blob/main/docs/basics/isolated-margin.md">Isolated Margin</a> to learn more.</p></td></tr></tbody></table>
## Response
```json
{
"orders": [
{
"digest": "0xf4f7a8767faf0c7f72251a1f9e5da590f708fd9842bf8fcdeacbaa0237958fff",
"subaccount": "0x12a0b4888021576eb10a67616dd3dd3d9ce206b664656661756c740000000000",
"product_id": 1,
"submission_idx": "563024",
"last_fill_submission_idx": "563024",
"amount": "20000000000000000000",
"price_x18": "1751900000000000000000",
"base_filled": "2320000000000000000",
"quote_filled": "-4064898974794958991797",
"fee": "812974794958991797",
"expiration": "4611686020107120163",
"appendix": "1537",
"nonce": "1761323164913106944",
"isolated": false
},
{
"digest": "0x0495a88fb3b1c9bed9b643b8e264a391d04cdd48890d81cd7c4006473f28e361",
"subaccount": "0x12a0b4888021576eb10a67616dd3dd3d9ce206b664656661756c740000000000",
"product_id": 2,
"submission_idx": "563019",
"last_fill_submission_idx": "563019",
"amount": "-20000000000000000000",
"price_x18": "1750800000000000000000",
"base_filled": "-1159999999999999999",
"quote_filled": "2030293721599999999999",
"fee": "609278400000000000",
"expiration": "4611686020107119905",
"appendix": "1537",
"nonce": "1761322893628669952",
"isolated": false
},
{
"digest": "0x29078702ad95615f0040eafdccc85cbf92569bf9656be928f9f17c5ccbb52041",
"subaccount": "0x12a0b4888021576eb10a67616dd3dd3d9ce206b664656661756c740000000000",
"product_id": 2,
"submission_idx": "563018",
"last_fill_submission_idx": "563018",
"amount": "-20000000000000000000",
"price_x18": "1750700000000000000000",
"base_filled": "-1160000000000000000",
"quote_filled": "2030380837600000000000",
"fee": "406162400000000000",
"expiration": "4611686020107119880",
"appendix": "1537",
"nonce": "1761322865074896896",
"isolated": false
},
]
}
```
## Response Fields
<table><thead><tr><th width="307">Field name</th><th>Description</th></tr></thead><tbody><tr><td>digest</td><td>The unique hash of the order.</td></tr><tr><td>subaccount</td><td>The subaccount that placed the order.</td></tr><tr><td>product_id</td><td>The id of of the product the order was executed for.</td></tr><tr><td>submission_idx</td><td>Used to uniquely identify the blockchain transaction that generated the order. For multi-fills orders, this is the submission_idx of the first fill.</td></tr><tr><td>last_fill_submission_idx</td><td>For multi-fills orders, this is the submission_idx of the last fill. For single fill orders, it has the same value as <code>submission_idx</code>.</td></tr><tr><td>amount</td><td>The original amount of base to buy or sell.</td></tr><tr><td>price_x18</td><td>The original order price.</td></tr><tr><td>base_filled</td><td>The total amount of base (e.g: BTC) filled on this order.</td></tr><tr><td>quote_filled</td><td>The total amount of quote (e.g: USDT0) filled on this order.</td></tr><tr><td>fee</td><td>The total amount of fee paid on this order.</td></tr><tr><td>expiration</td><td>The original order expiration.</td></tr><tr><td>nonce</td><td>The original order nonce.</td></tr><tr><td>appendix</td><td>The original order appendix.</td></tr></tbody></table>
@@ -0,0 +1,94 @@
# Perp Prices
## Rate limits
* 1200 requests/min or 200 requests/10secs per IP address. (**weight = 2**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Single Product
### Request
{% tabs %}
{% tab title="Perp Prices" %} <mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"price": {
"product_id": 2
}
}
```
{% endtab %}
{% endtabs %}
### Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="113" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">product_id</td><td align="center">number</td><td align="center">Yes</td><td>Id of perp product to fetch prices for.</td></tr></tbody></table>
### Response
```json
{
"product_id": 2,
"index_price_x18": "28180063400000000000000",
"mark_price_x18": "28492853627394637978665",
"update_time": "1680734493"
}
```
## Multiple Products
### Request
{% tabs %}
{% tab title="Perp Prices" %} <mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"perp_prices": {
"product_ids": [2]
}
}
```
{% endtab %}
{% endtabs %}
### Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="113" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">product_ids</td><td align="center">number[]</td><td align="center">Yes</td><td>Ids of perp products to fetch prices for.</td></tr></tbody></table>
### Response
{% hint style="info" %}
**Note**: the response is a map of <mark style="color:red;">`product_id -> perp_prices`</mark> for each requested product.
{% endhint %}
```json
{
"2": {
"product_id": 2,
"index_price_x18": "31483202055051853950444",
"mark_price_x18": "31514830401018841708801",
"update_time": "1689281222"
}
}
```
## Response Fields
| Field name | Description |
| ----------------- | ------------------------------------------------------------ |
| product\_id | Id of the perp product. |
| index\_price\_x18 | Latest index price of the perp product, multiplied by 10^18. |
| mark\_price\_x18 | Latest mark price of the perp product, multiplied by 10^18. |
| update\_time | Epoch time in seconds the perp prices were last updated at. |
@@ -0,0 +1,228 @@
# Product Snapshots
## Rate limits
* 240 requests/min or 40 requests/10secs per IP address. (**weight = 10**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Single Product
### Request
{% tabs %}
{% tab title="Product snapshots" %}
Query snapshots for a given product ordered by <mark style="color:red;">`submission index`</mark> desc.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"products": {
"product_id": 2,
"max_time": 1679728762,
"limit": 1
}
}
```
{% endtab %}
{% endtabs %}
### Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="113" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">product_id</td><td align="center">number</td><td align="center">Yes</td><td>id of product to fetch snapshots for.</td></tr><tr><td align="center">idx</td><td align="center">number / string</td><td align="center">No</td><td>when provided, only return product snapshots with <mark style="color:red;"><code>submission_idx</code></mark> &#x3C;= <mark style="color:red;"><code>idx</code></mark></td></tr><tr><td align="center">max_time</td><td align="center">number / string</td><td align="center">No</td><td>when <mark style="color:red;"><code>idx</code></mark> is not provided, <mark style="color:red;"><code>max_time</code></mark> (unix epoch in seconds) can be used to only return snapshots created &#x3C;= <mark style="color:red;"><code>max_time</code></mark></td></tr><tr><td align="center">limit</td><td align="center">number</td><td align="center">No</td><td>max number of snapshots to return. defaults to <mark style="color:red;"><code>100</code></mark>. max possible of <mark style="color:red;"><code>500</code></mark>.</td></tr></tbody></table>
### Response
{% hint style="info" %}
**Note**:
* the response includes a <mark style="color:red;">`txs`</mark> field which contains the relevant transactions to the product snapshots. There are <mark style="color:red;">`>=1 product snapshots`</mark> per transaction.
* both <mark style="color:red;">`products`</mark> and <mark style="color:red;">`txs`</mark> are in descending order by <mark style="color:red;">`submission_idx`</mark>.
* use the <mark style="color:red;">`submission_idx`</mark> to associate a <mark style="color:red;">`product snapshot`</mark> to it's corresponding transaction.
{% endhint %}
```json
{
"products": [
{
"product_id": 1,
"submission_idx": "563014",
"product": {
"spot": {
"product_id": 1,
"oracle_price_x18": "115575316424148798147115",
"risk": {
"long_weight_initial_x18": "900000000000000000",
"short_weight_initial_x18": "1100000000000000000",
"long_weight_maintenance_x18": "950000000000000000",
"short_weight_maintenance_x18": "1050000000000000000",
"price_x18": "115575316424148798147115"
},
"config": {
"token": "0xc57c1c64561a37ac9e8f9039cb6deab7539d99fc",
"interest_inflection_util_x18": "800000000000000000",
"interest_floor_x18": "10000000000000000",
"interest_small_cap_x18": "40000000000000000",
"interest_large_cap_x18": "1000000000000000000",
"withdraw_fee_x18": "40000000000000",
"min_deposit_rate_x18": "0"
},
"state": {
"cumulative_deposits_multiplier_x18": "1000000000000318713",
"cumulative_borrows_multiplier_x18": "1000347390679880473",
"total_deposits_normalized": "9000399823280682696107190850",
"total_borrows_normalized": "9580268570661550719"
},
"book_info": {
"size_increment": "1000000000000000",
"price_increment_x18": "1000000000000000000",
"min_size": "4000000000000000",
"collected_fees": "0"
}
}
}
],
"txs": [
{
"tx": {
"update_price": {
"product_id": 3,
"price_x18": "1750710375000000000000"
}
},
"submission_idx": "563014",
"timestamp": "1679728271"
}
]
}
```
### Response Fields
#### Products
<table><thead><tr><th width="307">Field name</th><th>Description</th></tr></thead><tbody><tr><td>submission_idx</td><td>Used to uniquely identify the blockchain transaction that generated the product snapshot; you can use it to grab the relevant transaction in the <code>txs</code> section.</td></tr><tr><td>product_id</td><td>The id of of the product the event is associated with.</td></tr><tr><td>product</td><td>The state of the product at the time of the transaction.</td></tr></tbody></table>
#### Txs
| Field name | Description |
| --------------- | ------------------------------------------------------------- |
| submission\_idx | Unique identifier of the transaction. |
| tx | Raw data of the corresponding transaction |
| timestamp | The unix epoch in seconds of when the transaction took place. |
## Multiple Products
### Request
{% tabs %}
{% tab title="Multiple Products snapshots" %}
Query the latest snapshot for the provided products.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"product_snapshots": {
"product_ids": [1, 2],
"max_time": 1679728762
}
}
```
{% endtab %}
{% endtabs %}
### Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="113" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">product_ids</td><td align="center">number[]</td><td align="center">Yes</td><td>Ids of products to fetch snapshots for.</td></tr><tr><td align="center">max_time</td><td align="center">number / string</td><td align="center">No</td><td>When provided, returns the last snapshot created &#x3C;= <mark style="color:red;"><code>max_time</code></mark> for each product. Otherwise, the latest snapshot is returned.</td></tr></tbody></table>
### Response
{% hint style="info" %}
**Note**: the response is a map of <mark style="color:red;">`product_id -> snapshot`</mark> for each requested product.
{% endhint %}
```json
{
"1": {
"product_id": 1,
"submission_idx": "459743",
"product": {
"spot": {
"product_id": 1,
"oracle_price_x18": "115575316424148798147115",
"risk": {
"long_weight_initial_x18": "900000000000000000",
"short_weight_initial_x18": "1100000000000000000",
"long_weight_maintenance_x18": "950000000000000000",
"short_weight_maintenance_x18": "1050000000000000000",
"price_x18": "115575316424148798147115"
},
"config": {
"token": "0xc57c1c64561a37ac9e8f9039cb6deab7539d99fc",
"interest_inflection_util_x18": "800000000000000000",
"interest_floor_x18": "10000000000000000",
"interest_small_cap_x18": "40000000000000000",
"interest_large_cap_x18": "1000000000000000000",
"withdraw_fee_x18": "40000000000000",
"min_deposit_rate_x18": "0"
},
"state": {
"cumulative_deposits_multiplier_x18": "1000000000000318713",
"cumulative_borrows_multiplier_x18": "1000347390679880473",
"total_deposits_normalized": "9000399823280682696107190850",
"total_borrows_normalized": "9580268570661550719"
},
"book_info": {
"size_increment": "1000000000000000",
"price_increment_x18": "1000000000000000000",
"min_size": "4000000000000000",
"collected_fees": "0"
}
}
}
},
"2": {
"product_id": 2,
"submission_idx": "459842",
"product": {
"perp": {
"product_id": 2,
"oracle_price_x18": "115432187703236794231754",
"risk": {
"long_weight_initial_x18": "950000000000000000",
"short_weight_initial_x18": "1050000000000000000",
"long_weight_maintenance_x18": "970000000000000000",
"short_weight_maintenance_x18": "1030000000000000000",
"price_x18": "115432187703236794231754"
},
"state": {
"cumulative_funding_long_x18": "-394223711772447555304",
"cumulative_funding_short_x18": "-394223711772447555304",
"available_settle": "20092193239667417956947",
"open_interest": "113605000000000000000"
},
"book_info": {
"size_increment": "1000000000000000",
"price_increment_x18": "1000000000000000000",
"min_size": "4000000000000000",
"collected_fees": "0"
}
}
}
}
}
```
### Response Fields
<table><thead><tr><th width="307">Field name</th><th>Description</th></tr></thead><tbody><tr><td>submission_idx</td><td>Used to uniquely identify the blockchain transaction that generated the product snapshot.</td></tr><tr><td>product_id</td><td>The id of of the product the event is associated with.</td></tr><tr><td>product</td><td>The state of the product at the time of the transaction.</td></tr></tbody></table>
@@ -0,0 +1,33 @@
# Quote Price
## Rate limits
* 1200 requests/min or 200 requests/10secs per IP address. (**weight = 2**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="Get quote price" %} <mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"quote_price": {}
}
```
{% endtab %}
{% endtabs %}
## Response
```json
{
"price_x18": "999944870000000000"
}
```
@@ -0,0 +1,49 @@
# Sequencer Backlog
## Rate limits
* 2400 requests/min or 400 requests/10secs per IP address. (**weight = 1**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="Get sequencer backlog" %} <mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"backlog": {}
}
```
{% endtab %}
{% endtabs %}
## Response
```json
{
"total_txs": "45479039",
"total_submissions": "45478914",
"backlog_size": "125",
"updated_at": "1750365790",
"backlog_eta_in_seconds": "500",
"txs_per_second": "0.25"
}
```
### Response Fields
| Field name | Description |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| total\_txs | Total number of transactions stored in the indexer DB. |
| total\_submissions | Total number of transactions submitted on-chain. |
| backlog\_size | Number of unprocessed transactions (<mark style="color:red;">`total_txs - total_submissions`</mark>). |
| backlog\_eta\_in\_seconds | Estimated time in seconds (<mark style="color:red;">`float`</mark>) to clear the entire backlog (<mark style="color:red;">`null`</mark> if unavailable). |
| txs\_per\_second | Current submission rate in transactions per second (<mark style="color:red;">float</mark>) (<mark style="color:red;">`null`</mark> if unavailable). |
| updated\_at | UNIX timestamp (in seconds) of when the data was last updated. |
@@ -0,0 +1,60 @@
# Signatures
## Rate limits
* Dynamic based on <mark style="color:red;">`digests`</mark> param provided (**weight = 2 + len(digests) / 10**)
* E.g: With <mark style="color:red;">`digests=100`</mark>, you can make up to 200 requests per min or 33 requests / 10 secs.
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="Get order signatures by digests" %} <mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"signatures": {
"digests": [
"0xf4f7a8767faf0c7f72251a1f9e5da590f708fd9842bf8fcdeacbaa0237958fff",
"0x0495a88fb3b1c9bed9b643b8e264a391d04cdd48890d81cd7c4006473f28e361"
]
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="113" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">digests</td><td align="center">string[]</td><td align="center">Yes</td><td>A list of order digests to retrieve signatures for.</td></tr></tbody></table>
## Response
```json
{
"signatures": [
{
"digest": "0xf4f7a8767faf0c7f72251a1f9e5da590f708fd9842bf8fcdeacbaa0237958fff",
"signature": "0xe8fa7151bde348afa3b46dc52798046b7c8318f1b0a7f689710debbc094658cc1bf5a7e478ccc8278b625da0b9402c86b580d2e31e13831337dfd6153f4b37811b",
"signer": "0x12a0b4888021576eb10a67616dd3dd3d9ce206b664656661756c740000000000",
"is_linked": false
},
{
"digest": "0x0495a88fb3b1c9bed9b643b8e264a391d04cdd48890d81cd7c4006473f28e361",
"signature": "0x826c68f1a3f76d9ffbe8041f8d45e969d31f1ab6f2ae2f6379d1493e479e56436091d6cf4c72e212dd2f1d2fa17c627c4c21bd6d281c77172b8af030488478b71c",
"signer": "0x44b525f7bf3441464e406a094bc5e791f13dd79f64656661756c740000000000",
"is_linked": true
},
]
}
```
## Response Fields
<table><thead><tr><th width="263">Field name</th><th>Description</th></tr></thead><tbody><tr><td>digest</td><td>The order's generated digest.</td></tr><tr><td>signature</td><td>The order's generated signature.</td></tr><tr><td>signer</td><td>The address that signed the order / generated the signature.</td></tr><tr><td>is_linked</td><td>Indicates whether this is a signature from a linked signer or the original sender.</td></tr></tbody></table>
@@ -0,0 +1,139 @@
# Subaccount Snapshots
Use this query to get a summary of the latest actions per product on Nado for provided subaccounts. Tracked variables (ex. net interest) are extrapolated to the timestamp or set of timestamps provided.
## Rate limits
* 480 requests/min or 80 requests/10secs per IP address. (**weight = 5**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="Subaccount snapshots" %}
Query latest subaccount events/actions ordered by <mark style="color:red;">`submission index`</mark> desc.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"account_snapshots": {
"subaccounts": [
"0xec132d41e542c7129268d9d4431f105e0830a81164656661756c745f31000000"
],
"timestamps": [
1738703761
],
"isolated": false,
"active": true
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="113" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">subaccounts</td><td align="center">array</td><td align="center">Yes</td><td>A list of <mark style="color:red;"><code>bytes32</code></mark> sent as a hex string; includes the address and the subaccount identifier.</td></tr><tr><td align="center">timestamp</td><td align="center">array</td><td align="center">Yes</td><td>A list of timestamps to retrieve multiple subaccount snapshots (one per timestamp).</td></tr><tr><td align="center">isolated</td><td align="center">boolean</td><td align="center">No</td><td><p>A filter to include only isolated or cross margin events.</p><ul><li>If <mark style="color:red;"><code>true</code></mark>: returns only <strong>isolated</strong> margin events.</li><li>If <mark style="color:red;"><code>false</code></mark>: returns only <strong>cross</strong> margin events.</li><li>If omitted: returns <strong>both</strong> isolated and cross events.</li></ul></td></tr><tr><td align="center">active</td><td align="center">boolean</td><td align="center">No</td><td><p>Filters which products to include in the snapshot:</p><ul><li><mark style="color:red;"><code>true</code></mark>: returns only products with <strong>non-zero balance</strong> at the timestamp (currently active positions)</li><li><mark style="color:red;"><code>false</code></mark>: returns products with <strong>event history</strong> before the timestamp (any historical activity)</li><li>If omitted: defaults to <mark style="color:red;"><code>false</code></mark></li></ul></td></tr></tbody></table>
## Response
{% tabs %}
{% tab title="Single timestamp" %}
```json
{
"snapshots": {
"0xec132d41e542c7129268d9d4431f105e0830a81164656661756c745f31000000": {
"1738703761": [
{
"subaccount": "0xec132d41e542c7129268d9d4431f105e0830a81164656661756c745f31000000",
"product_id": 0,
"submission_idx": "17286676",
"event_type": "liquidate_subaccount",
"isolated": false,
"isolated_product_id": null,
"pre_balance": {
"spot": {
"product_id": 0,
"balance": {
"amount": "53278293456559329896"
}
}
},
"post_balance": {
"spot": {
"product_id": 0,
"balance": {
"amount": "0"
}
}
},
"product": {
"spot": {
"product_id": 0,
"oracle_price_x18": "1000000000000000000",
"risk": {
"long_weight_initial_x18": "1000000000000000000",
"short_weight_initial_x18": "1000000000000000000",
"long_weight_maintenance_x18": "1000000000000000000",
"short_weight_maintenance_x18": "1000000000000000000",
"price_x18": "1000000000000000000"
},
"config": {
"token": "0x5f65358d61a9a281ea3bb930d05889aca21e3f4f",
"interest_inflection_util_x18": "800000000000000000",
"interest_floor_x18": "10000000000000000",
"interest_small_cap_x18": "40000000000000000",
"interest_large_cap_x18": "1000000000000000000",
"withdraw_fee_x18": "1000000000000000000",
"min_deposit_rate_x18": "0"
},
"state": {
"cumulative_deposits_multiplier_x18": "1000000000025524653",
"cumulative_borrows_multiplier_x18": "1000347390837434279",
"total_deposits_normalized": "20001011744258817298755054194662",
"total_borrows_normalized": "1617724891363505323532211"
},
"book_info": {
"size_increment": "0",
"price_increment_x18": "0",
"min_size": "0",
"collected_fees": "0"
}
}
},
"net_interest_unrealized": "0",
"net_interest_cumulative": "1443761232166478119",
"net_funding_unrealized": "0",
"net_funding_cumulative": "0",
"net_entry_unrealized": "0",
"net_entry_cumulative": "13458165999999999998",
"quote_volume_cumulative": "1234567890123456789"
}
]
}
}
}
```
{% endtab %}
{% endtabs %}
## Response Fields
### Events
{% hint style="info" %}
* **Net cumulative**: the net difference in that quantity since the beginning of time. For example, if I want to compute total amount paid out in funding between two events, you can subtract the `net_funding_cumulative` of the larger event by the `net_funding_cumulative` of the smaller event.
* **Net unrealized**: similar to `net_cumulative`, but for `net_unrealized`, we have the caveat that when the magnitude of your position decreases, the magnitude of net\_unrealized `decreases` by the same amount.
{% endhint %}
<table><thead><tr><th width="307">Field name</th><th>Description</th></tr></thead><tbody><tr><td>submission_idx</td><td>Used to uniquely identify the blockchain transaction that generated the event; you can use it to grab the relevant transaction in the <code>txs</code> section.</td></tr><tr><td>product_id</td><td>The id of of the product the event is associated with.</td></tr><tr><td>event_type</td><td>Name of the transaction type this event corresponds to.</td></tr><tr><td>subaccount</td><td>The subaccount associated to the event.</td></tr><tr><td>pre_balance</td><td>The state of your balance before the event happened.</td></tr><tr><td>post_balance</td><td>The state of your balance after the event happened.</td></tr><tr><td>product</td><td>The state of the product throughout the event.</td></tr></tbody></table>
@@ -0,0 +1,91 @@
# Subaccounts
## Rate limits
* 1200 requests/min or 200 requests/10secs per IP address. (**weight = 2**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="List subaccounts" %}
Query subaccounts ordered by <mark style="color:red;">`subaccount id`</mark> ASC.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"subaccounts": {
"start": 100,
"limit": 10,
}
}
```
{% endtab %}
{% tab title="Find subaccounts by address" %}
Query all subaccounts associated to an address ordered by <mark style="color:red;">`subaccount id`</mark> ASC.
<mark style="color:orange;">`POST [ARCHIVE_ENDPOINT]`</mark>
**Body**
```json
{
"subaccounts": {
"address": "0x79CC76364b5Fb263A25bD52930E3d9788fCfEEA8"
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="145" align="center">Parameter</th><th width="150" align="center">Type</th><th width="122" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">start</td><td align="center">string/number</td><td align="center">No</td><td>Subaccount id to start from (used for pagination). Defaults to 0.</td></tr><tr><td align="center">limit</td><td align="center">string/number</td><td align="center">No</td><td>Max number of subaccounts to return. Defaults to 100, max of 500.</td></tr><tr><td align="center">address</td><td align="center">string</td><td align="center">No</td><td>An optional wallet address to find all subaccounts associated to it.</td></tr></tbody></table>
## Response
```json
{
"subaccounts": [
{
"id": "25",
"subaccount": "0x12a0b4888021576eb10a67616dd3dd3d9ce206b664656661756c740000000000",
"address": "0x12a0b4888021576eb10a67616dd3dd3d9ce206b6",
"subaccount_name": "default",
"created_at": "1699949771",
"isolated": false
},
{
"id": "948",
"subaccount": "0x12a0b4888021576eb10a67616dd3dd3d9ce206b664656661756c745f31000000",
"address": "0x12a0b4888021576eb10a67616dd3dd3d9ce206b6",
"subaccount_name": "default_1",
"created_at": "1738000782",
"isolated": false
},
{
"id": "1094",
"subaccount": "0x12a0b4888021576eb10a67616dd3dd3d9ce206b600000000000000020069736f",
"address": "0x12a0b4888021576eb10a67616dd3dd3d9ce206b6",
"subaccount_name": "0x00000000000000020069736f",
"created_at": "1748982886",
"isolated": true
}
]
}
```
## Response Fields
### Subaccounts
<table><thead><tr><th width="263">Field name</th><th>Description</th></tr></thead><tbody><tr><td>id</td><td>Internal subaccount id</td></tr><tr><td>subaccount</td><td>Hex string of the subaccount (wallet + subaccount name)</td></tr><tr><td>address</td><td>Hex string of wallet address</td></tr><tr><td>subaccount_name</td><td>Subaccount identifier</td></tr><tr><td>created_at</td><td>When subaccount was created</td></tr><tr><td>isolated</td><td>Whether it's a subaccount for an isolated position</td></tr></tbody></table>
@@ -0,0 +1,62 @@
# Definitions / Formulas
## Definitions
### **Unsettled USDT0**
Perp balances have two main components:
* <mark style="color:red;">`amount`</mark>
* <mark style="color:red;">`v_quote_balance`</mark>
When you buy a perp, <mark style="color:red;">`amount`</mark> increments and <mark style="color:red;">`v_quote_balance`</mark> decrements, and vice versa for selling.
Settlement is the process of converting from <mark style="color:red;">v\_quote\_balance</mark> into actual USDT0 balance. This happens mostly on position close, but may happen on extremely negative PNL positions when we need to pay out positive PNL positions.
The amount that is transferred between <mark style="color:red;">`v_quote_balance`</mark> in the perp and your USDT0 balance is an amount that results in <mark style="color:red;">`amount * oracle_price + v_quote_balance == 0`</mark>. Unsettled USDT0 is the total amount that would be transferred between <mark style="color:red;">`v_quote_balance`</mark> and your USDT0 balance summed across all perps.
### **Unsettled PNL**
**Note:** Technically, there is no such concept as "Unsettled PNL" in our system. However, the UI displays "Unsettled PnL" in some places (e.g., in the USDT0 Balance section) for user clarity.
**What the UI actually shows:** When you see "Unsettled PnL" in the UI, it refers to **Unsettled USDT0** (see above) - the total unsettled quote balance across all perp positions.
**For developers:** Always use **Unsettled USDT0** when referring to this value programmatically. It represents the sum of <mark style="color:red;">`amount × oracle_price + v_quote_balance`</mark> across all perp positions, which is the amount that would be settled into your USDT0 balance.
### **Unrealized PNL**
Refers to the estimated gains or losses of a current position based on the difference between the average entry price and the current oracle price.
## Formulas
### **Unrealized PNL**
Using the [indexer's events query](https://docs.nado.xyz/developer-resources/api/archive-indexer/events), your unrealized PNL at the end of some event is given by:
{% code lineNumbers="true" %}
```python
unrealized_pnl = (
event.post_balance.amount * event.product.oracle_price_x18
- event.net_entry_unrealized
)
```
{% endcode %}
### Total PNL
Your total PNL between <mark style="color:red;">`event1`</mark> and <mark style="color:red;">`event2`</mark>, assuming <mark style="color:red;">`event1`</mark> is after <mark style="color:red;">`event2`</mark> - is given by:
<pre class="language-python" data-line-numbers><code class="lang-python"><strong>total_pnl = (
</strong><strong> (event1.post_balance.amount * event1.product.oracle_price_x18 - event1.net_entry_cumulative)
</strong><strong> - (event2.post_balance.amount * event2.product.oracle_price_x18 - event2.net_entry_cumulative)
</strong><strong>)
</strong></code></pre>
{% hint style="info" %}
**Notes**:
* You can use 0 for the second term for the PNL to compute since the beginning of time.
* For spots, we will count deposits and withdraws towards your PNL. i.e. if you deposit BTC, for PNL tracking purposes it is counted as a BTC long at the oracle price.
{% endhint %}
@@ -0,0 +1,170 @@
# Depositing
There are two ways to deposit funds into Nado:
1. **Direct Deposit** - Simple transfer to your unique deposit address (recommended for most users)
2. **On-Chain Contract Call** - Direct interaction with the Endpoint contract
***
## Method 1: Direct Deposit (Recommended)
Each subaccount has a unique deposit address. Simply send funds to this address and they will automatically be credited to your subaccount.
### Getting Your Deposit Address
Query your unique deposit address using the [Direct Deposit Address](https://docs.nado.xyz/developer-resources/api/archive-indexer/direct-deposit-address) endpoint:
**Request:**
```json
{
"direct_deposit_address": {
"subaccount": "0x79cc76364b5fb263a25bd52930e3d9788fcfeea864656661756c740000000000"
}
}
```
**Response:**
```json
{
"subaccount": "0x79cc76364b5fb263a25bd52930e3d9788fcfeea864656661756c740000000000",
"deposit_address": "0x1234567890123456789012345678901234567890",
"created_at": "1683315718"
}
```
### Depositing Funds
1. Get your deposit address using the API call above
2. Send the supported token to this address (e.g., USDT0, wETH, etc.)
3. Funds will be automatically credited to your subaccount within a few seconds
**Advantages:**
* No need to interact with smart contracts
* No need to approve allowances
* Works with any wallet (including CEX withdrawals)
* Simpler integration for users
**Notes:**
* Only send supported tokens to this address
* Find supported tokens via the [All Products](https://docs.nado.xyz/developer-resources/api/gateway/queries/all-products) query
* Deposits are processed automatically after blockchain confirmation
***
## Method 2: On-Chain Contract Call
Advanced users can deposit directly by calling the Endpoint contract.
### Contract Address
Find the Endpoint contract address at:
```
GET <nado-url>/query?type=contracts
```
### Function Interface
#### Basic Deposit
```solidity
function depositCollateral(
bytes12 subaccountName, // last 12 bytes of the subaccount bytes32
uint32 productId, // product ID for the token
uint128 amount // raw token amount (see decimals below)
) external
```
**Parameters:**
* `subaccountName`: The last 12 bytes of your subaccount identifier (e.g., `0x64656661756c740000000000` for "default")
* `productId`: The product ID for the token you're depositing
* `amount`: The raw amount in the token's smallest unit
* For USDT0 (6 decimals): 1 USDT0 = `1e6` = `1000000`
* For wETH (18 decimals): 1 wETH = `1e18`
* For wBTC (8 decimals): 1 wBTC = `1e8`
#### Deposit with Referral Code
```solidity
function depositCollateralWithReferral(
bytes32 subaccount, // full 32-byte subaccount identifier
uint32 productId, // product ID for the token
uint128 amount, // raw token amount
string memory referralCode // referral code (optional)
) public
```
### Prerequisites
Before depositing via contract call, you must:
1. **Approve Token Allowance**
```solidity
// Give the Endpoint contract permission to transfer your tokens
IERC20(tokenAddress).approve(endpointAddress, amount);
```
2. **Get Product Information**
* Use [All Products](https://docs.nado.xyz/developer-resources/api/gateway/queries/all-products) query to find:
* Product ID for your token
* Token contract address
* Token decimals
### Example: Depositing 100 USDT0
Assuming USDT0 has product ID `0` and 6 decimals:
```typescript
// 1. Approve allowance (one-time or as needed)
await usdtContract.approve(endpointAddress, ethers.constants.MaxUint256);
// 2. Deposit 100 USDT0
const subaccountName = ethers.utils.formatBytes32String("default").slice(0, 26); // bytes12
const productId = 0;
const amount = 100 * 1e6; // 100 USDT0 with 6 decimals
await endpointContract.depositCollateral(subaccountName, productId, amount);
```
### Processing Time
Deposits may take a few seconds to process after transaction confirmation. You can monitor your balance via:
* [Subaccount Info](https://docs.nado.xyz/developer-resources/api/gateway/queries/subaccount-info) query
* WebSocket subscriptions for real-time updates
***
## Important Notes
* **Use Correct Product ID**: Each token has a specific product ID. Using the wrong ID will cause the transaction to fail.
* **Check Token Decimals**: Always multiply by the correct decimal factor (6 for USDT0, 18 for wETH, etc.)
* **Minimum Deposit**: Some products may have minimum deposit amounts
* **Only Supported Tokens**: Only deposit tokens that are listed via the All Products query
***
## Getting Token Information
Use the [All Products](https://docs.nado.xyz/developer-resources/api/gateway/queries/all-products) query to get:
```json
{
"product_id": 0,
"symbol": "USDT0",
"token": "0x...", // token contract address
"decimals": 6
}
```
This information is essential for:
* Finding the correct `productId`
* Getting the token contract for approvals (Method 2 only)
* Calculating the correct `amount` with proper decimals
@@ -0,0 +1,25 @@
# Endpoints
## Mainnet
### <mark style="color:purple;">Ink Mainnet</mark>
* **Gateway Websocket**: <mark style="color:green;">`wss://gateway.prod.nado.xyz/v1/ws`</mark>
* **Gateway REST:** <mark style="color:green;">`https://gateway.prod.nado.xyz/v1`</mark>
* **Gateway V2:** <mark style="color:green;">`https://gateway.prod.nado.xyz/v2`</mark>
* **Subscriptions**: <mark style="color:green;">`wss://gateway.prod.nado.xyz/v1/subscribe`</mark>
* **Archive (Indexer):** <mark style="color:green;">`https://archive.prod.nado.xyz/v1`</mark>
* **Archive (Indexer) V2:** <mark style="color:green;">`https://archive.prod.nado.xyz/v2`</mark>
* **Trigger**: <mark style="color:green;">`https://trigger.prod.nado.xyz/v1`</mark>
## Testnet
### <mark style="color:purple;">Ink Sepolia</mark>
* **Gateway Websocket**: <mark style="color:red;">`wss://gateway.test.nado.xyz/v1/ws`</mark>
* **Gateway REST:** <mark style="color:red;">`https://gateway.test.nado.xyz/v1`</mark>
* **Gateway V2:** <mark style="color:red;">`https://gateway.test.nado.xyz/v2`</mark>
* **Subscriptions**: <mark style="color:red;">`wss://gateway.test.nado.xyz/v1/subscribe`</mark>
* **Archive (Indexer):** <mark style="color:red;">`https://archive.test.nado.xyz/v1`</mark>
* **Archive (Indexer) V2:** <mark style="color:red;">`https://archive.test.nado.xyz/v2`</mark>
* **Trigger**: <mark style="color:red;">`https://trigger.test.nado.xyz/v1`</mark>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,33 @@
# Gateway
There are two types of actions. An <mark style="color:red;">`Execute`</mark> involves a modification to state, and a <mark style="color:red;">`Query`</mark> merely fetches information from state.
All actions can be sent over websocket as json payloads at <mark style="color:red;">`WEBSOCKET [GATEWAY_WEBSOCKET_ENDPOINT]`</mark>
Additionally, you can send executes and queries over <mark style="color:red;">HTTP</mark>, at <mark style="color:red;">`POST [GATEWAY_REST_ENDPOINT]/execute`</mark> and <mark style="color:red;">`GET/POST [GATEWAY_REST_ENDPOINT]/query`</mark> respectively. For executes, the request should be sent with a json payload, while for queries, the payload should be encoded into url query strings.
<mark style="color:red;">`HTTP`</mark> requests must set the `Accept-Encoding` to include `gzip`, `br` or `deflate`
## Endpoints
### **Testnet**:
* Websocket: <mark style="color:red;">`wss://gateway.test.nado.xyz/v1/ws`</mark>
* REST: <mark style="color:red;">`https://gateway.test.nado.xyz/v1`</mark>
## Websocket
{% hint style="info" %}
**Notes on&#x20;*****keeping websocket connections alive*****:**
* When interacting with our API via websocket, you must send ping frames every 30 seconds to keep the websocket connection alive.
* Ping / Pong frames are built into the websocket protocol and should be supported natively by your websocket library. See [Ping/Pong frames](https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2) for more info.
{% endhint %}
{% content-ref url="gateway/executes" %}
[executes](https://docs.nado.xyz/developer-resources/api/gateway/executes)
{% endcontent-ref %}
{% content-ref url="gateway/queries" %}
[queries](https://docs.nado.xyz/developer-resources/api/gateway/queries)
{% endcontent-ref %}
@@ -0,0 +1,63 @@
# Executes
## Overview
All executes go through the following endpoint; the exact details of the execution are specified by the JSON payload.
* **Websocket**: <mark style="color:orange;">`WEBSOCKET [GATEWAY_WEBSOCKET_ENDPOINT]`</mark>
* **REST**: <mark style="color:orange;">`POST [GATEWAY_REST_ENDPOINT]/execute`</mark>
### **Signing**
All executes are signed using [EIP712](https://eips.ethereum.org/EIPS/eip-712). Each execute request contains:
1. A piece of structured data that includes the sender address
2. A signature of the hash of that structured data, signed by the sender
You can check the SDK for some examples of how to generate these signatures.
{% hint style="info" %}
See more info in the [signing](https://docs.nado.xyz/developer-resources/api/gateway/signing) page.
{% endhint %}
### **Sender Field Structure**
The sender field is a solidity <mark style="color:red;">`bytes32`</mark> . There are two components:
* an <mark style="color:red;">`address`</mark> that is a <mark style="color:red;">`bytes20`</mark>
* a subaccount identifier that is a <mark style="color:red;">`bytes12`</mark>
For example, if your address was <mark style="color:red;">`0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43`</mark>, and you wanted to use the default subaccount identifier (i.e: the word <mark style="color:red;">`default`</mark>) you can set <mark style="color:red;">`sender`</mark> to <mark style="color:red;">`0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c4364656661756c740000000000`</mark> , which sets the subaccount identifier to <mark style="color:red;">`64656661756c740000000000`</mark>.
### **Amounts**
For <mark style="color:red;">`DepositCollateral`</mark> and <mark style="color:red;">`WithdrawCollateral`</mark>, the amount specifies the physical token amount that you want to receive. `i.e.` if USDT0 has 6 decimals, and you want to deposit or withdraw 1 USDT0, you specify <mark style="color:red;">`amount = 1e6`</mark>.
For all other transactions, amount is normalized to 18 decimals, so <mark style="color:red;">`1e18`</mark> == one unit of the underlying asset. For example, if you want to buy 1 wETH, regardless of the amount of decimals the wETH contract has on chain, you specify <mark style="color:red;">`1e18`</mark> in the amount field of the order.
## API Response
All `Execute` messages return the following information:
#### Success
```json
{
"status": "success",
"signature": "{signature}",
"data"?: {data_obj},
"request_type": "{request_type}"
}
```
#### Failure
```json
{
"status": "failure",
"signature": "{signature}",
"error": "{error_msg}",
"error_code": {error_code},
"request_type": "{request_type}"
}
```
@@ -0,0 +1,116 @@
# Burn NLP
## Rate limits
* 60 burns/min or 10 burns every 10 seconds per wallet. (**weight = 10**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits).
{% endhint %}
## Request
{% tabs %}
{% tab title="Websocket" %}
**Connect**
<mark style="color:orange;">`WEBSOCKET [GATEWAY_WEBSOCKET_ENDPOINT]`</mark>
**Message**
```json
{
"burn_nlp": {
"tx": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"nlpAmount": "10001000000000000000000"
"nonce": "1"
},
"signature": "0x"
}
}
```
{% endtab %}
{% tab title="REST" %} <mark style="color:orange;">`POST [GATEWAY_REST_ENDPOINT]/execute`</mark>
**Body**
```json
{
"burn_lp": {
"tx": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"productId": 1,
"amount": "10001000000000000000000"
"nonce": "1"
},
"signature": "0x"
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="190" align="center">Parameter</th><th width="114" align="center">Type</th><th width="104" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">tx</td><td align="center">object</td><td align="center">Yes</td><td>Burn NLP transaction object. See <a href="#signing">Signing</a> section for details on the transaction fields.</td></tr><tr><td align="center">tx.sender</td><td align="center">string</td><td align="center">Yes</td><td>Hex string representing the subaccount's 32 bytes (address + subaccount name) of the tx sender.</td></tr><tr><td align="center">tx.nlpAmount</td><td align="center">string</td><td align="center">Yes</td><td>Amount of NLP tokens to burn multiplied by 1e18, sent as a string.</td></tr><tr><td align="center">tx.nonce</td><td align="center">string</td><td align="center">Yes</td><td>This is an incrementing nonce, can be obtained using the <a href="../queries/nonces">Nonces</a> query.</td></tr><tr><td align="center">signature</td><td align="center">string</td><td align="center">Yes</td><td>Signed transaction. See <a href="#signing">Signing</a> section for more details.</td></tr></tbody></table>
## Signing
{% hint style="info" %}
See more details and examples in our [signing](https://docs.nado.xyz/developer-resources/api/gateway/signing) page.
{% endhint %}
The solidity typed data struct that needs to be signed is:
```solidity
struct BurnNlp {
bytes32 sender;
uint128 nlpAmount;
uint64 nonce;
}
```
<mark style="color:red;">`sender`</mark>: a <mark style="color:red;">`bytes32`</mark> sent as a hex string; includes the address and the subaccount identifier.
<mark style="color:red;">`nlpAmount`</mark>: amount of NLP tokens to burn, sent as a string. This must be positive and must be specified with 18 decimals.
<mark style="color:red;">`nonce`</mark>: the <mark style="color:red;">`tx_nonce`</mark>. This is an incrementing nonce, can be obtained using the [Nonces](https://docs.nado.xyz/developer-resources/api/gateway/queries/nonces) query.
{% hint style="warning" %}
**Note**: for signing you should always use the data type specified in the solidity struct which might be different from the type sent in the request e.g: <mark style="color:red;">`nonce`</mark> should be an <mark style="color:red;">`uint64`</mark> for **Signing** but should be sent as a <mark style="color:red;">`string`</mark> in the final payload.
{% endhint %}
## Response
```json
{
"status": "success",
}
```
#### Success
```json
{
"status": "success",
"signature": {signature},
"request_type": "execute_burn_nlp"
}
```
#### Failure
```json
{
"status": "failure",
"signature": {signature},
"error": "{error_msg}",
"error_code": {error_code},
"request_type": "execute_burn_nlp"
}
```
@@ -0,0 +1,119 @@
# Cancel And Place
## Rate limits
* The sum of [Cancel Orders](https://docs.nado.xyz/developer-resources/api/gateway/cancel-orders#rate-limits) + [Place Order](https://docs.nado.xyz/developer-resources/api/gateway/place-order#rate-limits) limits
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits).
{% endhint %}
## Request
{% tabs %}
{% tab title="Websocket" %}
**Connect**
<mark style="color:orange;">`WEBSOCKET [GATEWAY_WEBSOCKET_ENDPOINT]`</mark>
**Message**
```json
{
"cancel_and_place": {
"cancel_tx": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"productIds": [2],
"digests": ["0x"],
"nonce": "1"
},
"cancel_signature": "0x",
"place_order": {
"product_id": 1,
"order": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"priceX18": "1000000000000000000",
"amount": "1000000000000000000",
"expiration": "4294967295",
"appendix": "1537",
"nonce": "1757062078359666688"
},
"signature": "0x",
}
}
}
```
{% endtab %}
{% tab title="REST" %} <mark style="color:orange;">`POST [GATEWAY_REST_ENDPOINT]/execute`</mark>
**Body**
```json
{
"cancel_and_place": {
"cancel_tx": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"productIds": [2],
"digests": ["0x"],
"nonce": "1"
},
"cancel_signature": "0x",
"place_order": {
"product_id": 1,
"order": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"priceX18": "1000000000000000000",
"amount": "1000000000000000000",
"expiration": "4294967295",
"nonce": "1757062078359666688"
},
"signature": "0x",
}
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="219" align="center">Parameter</th><th width="128" align="center">Type</th><th width="104" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">cancel_tx</td><td align="center">object</td><td align="center">Yes</td><td>Cancel order transaction object. See <a href="../cancel-orders#signing">Cancel order signing</a> for details on the transaction fields.</td></tr><tr><td align="center">cancel_tx.sender</td><td align="center">string</td><td align="center">Yes</td><td>Hex string representing the subaccount's 32 bytes (address + subaccount name) of the tx sender.</td></tr><tr><td align="center">cancel_tx.productIds</td><td align="center">number[]</td><td align="center">Yes</td><td>A list of product IDs, corresponding to the product ids of the orders in <mark style="color:red;"><code>digests</code></mark></td></tr><tr><td align="center">cancel_tx.digests</td><td align="center">string[]</td><td align="center">Yes</td><td>A list of order digests, represented as hex strings.</td></tr><tr><td align="center">cancel_tx.nonce</td><td align="center">string</td><td align="center">Yes</td><td>Used to differentiate between the same cancellation multiple times. See <a href="../cancel-orders#signing">Cancel order signing</a> section for more details.</td></tr><tr><td align="center">cancel_signature</td><td align="center">string</td><td align="center">Yes</td><td>Signed transaction. See <a href="#signing">Signing</a><a href="../cancel-orders#signing">Cancel order signing</a> for more details.</td></tr><tr><td align="center">place_order</td><td align="center">object</td><td align="center">Yes</td><td>Payload of order to be placed. See <a href="../../../trigger/executes/place-order#request-parameters">Place order request parameters</a> for payload details.</td></tr></tbody></table>
## Signing
{% hint style="warning" %}
**Note**: both <mark style="color:red;">`cancel_tx`</mark> and <mark style="color:red;">`place_order`</mark> objects must be signed using the same signer, otherwise the request will be rejected.
{% endhint %}
* See [Cancel orders signing](https://docs.nado.xyz/developer-resources/api/gateway/cancel-orders#signing) for details on how to sign the order cancellation.
* See [Place order signing](https://docs.nado.xyz/developer-resources/api/gateway/place-order#signing) for details on how to sign the order placement.
## Response
#### Success
```json
{
"status": "success",
"signature": {signature},
"data": {
"digest": {order digest}
},
"request_type": "execute_cancel_and_place"
}
```
#### Failure
```json
{
"status": "failure",
"signature": {signature}
"error": "{error_msg}"
"error_code": {error_code}
"request_type": "execute_cancel_and_place"
}
```
@@ -0,0 +1,139 @@
# Cancel Orders
## Rate limits
* When no **digests** are provided: 600 cancellations/min or 10 cancellations/sec per wallet. (**weight=1**)
* When **digests** are provided: 600/(total digests) cancellations per minute per wallet. (**weight=total digests**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits).
{% endhint %}
## Request
{% tabs %}
{% tab title="Websocket" %}
**Connect**
<mark style="color:orange;">`WEBSOCKET [GATEWAY_WEBSOCKET_ENDPOINT]`</mark>
**Message**
```json
{
"cancel_orders": {
"tx": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"productIds": [2],
"digests": ["0x"],
"nonce": "1"
},
"signature": "0x"
}
}
```
{% endtab %}
{% tab title="REST" %} <mark style="color:orange;">`POST [GATEWAY_REST_ENDPOINT]/execute`</mark>
**Body**
```json
{
"cancel_orders": {
"tx": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"productIds": [0],
"digests": ["0x"],
"nonce": "1"
},
"signature": "0x"
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="156" align="center">Parameter</th><th width="128" align="center">Type</th><th width="104" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">tx</td><td align="center">object</td><td align="center">Yes</td><td>Cancel order transaction object. See <a href="#signing">Signing</a> section for details on the transaction fields.</td></tr><tr><td align="center">tx.sender</td><td align="center">string</td><td align="center">Yes</td><td>Hex string representing the subaccount's 32 bytes (address + subaccount name) of the tx sender.</td></tr><tr><td align="center">tx.productIds</td><td align="center">number[]</td><td align="center">Yes</td><td>A list of product IDs, corresponding to the product ids of the orders in <mark style="color:red;"><code>digests</code></mark></td></tr><tr><td align="center">tx.digests</td><td align="center">string[]</td><td align="center">Yes</td><td>A list of order digests, represented as hex strings.</td></tr><tr><td align="center">tx.nonce</td><td align="center">string</td><td align="center">Yes</td><td>Used to differentiate between the same cancellation multiple times. See <a href="#signing">Signing</a> section for more details.</td></tr><tr><td align="center">signature</td><td align="center">string</td><td align="center">Yes</td><td>Signed transaction. See <a href="#signing">Signing</a> section for more details.</td></tr></tbody></table>
## Signing
{% hint style="info" %}
See more details and examples in our [signing](https://docs.nado.xyz/developer-resources/api/gateway/signing) page.
{% endhint %}
The solidity typed data struct that needs to be signed is:
```solidity
struct Cancellation {
bytes32 sender;
uint32[] productIds;
bytes32[] digests;
uint64 nonce;
}
```
<mark style="color:red;">`sender`</mark>: a <mark style="color:red;">`bytes32`</mark> sent as a hex string; includes the address and the subaccount identifier
<mark style="color:red;">`productIds`</mark>: a list of product IDs, corresponding to the product ids of the orders in <mark style="color:red;">`digests`</mark>
<mark style="color:red;">`digests`</mark>: a list of order digests, represented as hex strings, for the orders you want to cancel.
<mark style="color:red;">`nonce`</mark>: used to differentiate between the same cancellation multiple times, and a user trying to place a cancellation with the same parameters twice. Sent as a string. Encodes two bit of information:
* Most significant <mark style="color:red;">`44`</mark> bits encoding the <mark style="color:red;">`recv_time`</mark> in milliseconds after which the cancellation should be ignored by the matching engine; the engine will accept cancellations where <mark style="color:red;">`current_time < recv_time <= current_time + 100000`</mark>
* Least significant <mark style="color:red;">`20`</mark> bits are a random integer used to avoid hash collisions
For example, to place a cancellation with a random integer of <mark style="color:red;">`1000`</mark>, and a discard time 50 ms from now, we would send a nonce of <mark style="color:red;">`(timestamp_ms() + 50) << 20 + 1000`</mark>
{% hint style="warning" %}
**Note**: for signing you should always use the data type specified in the solidity struct which might be different from the type sent in the request e.g: <mark style="color:red;">`nonce`</mark> should be an <mark style="color:red;">`uint64`</mark> for **Signing** but should be sent as a <mark style="color:red;">`string`</mark> in the final payload.
{% endhint %}
## Response
#### Success
```json
{
"status": "success",
"signature": {signature},
"data": {
"cancelled_orders": [
{
"product_id": 2,
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"price_x18": "20000000000000000000000",
"amount": "-100000000000000000",
"expiration": "1686332748",
"order_type": "post_only",
"nonce": "1768248100142339392",
"unfilled_amount": "-100000000000000000",
"digest": "0x3195a7929feb8307edecf9c045j5ced68925108f0aa305f0ee5773854159377c",
"appendix": "1537",
"placed_at": 1686332708
},
...
]
},
"request_type": "execute_cancel_orders"
}
```
#### Failure
```json
{
"status": "failure",
"signature": {signature},
"error": "{error_msg}",
"error_code": {error_code},
"request_type": "execute_cancel_orders"
}
```
@@ -0,0 +1,135 @@
# Cancel Product Orders
## Rate limits
* When no **productIds** are provide&#x64;**:** 12 cancellations/min or 2 cancellations/sec per wallet. (**weight=50**)
* When **productIds** are provided: 600 / (5 \* total productIds) cancellations per minute per wallet. (**weight=5\*total productIds**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits).
{% endhint %}
## Request
{% tabs %}
{% tab title="Websocket" %}
**Connect**
<mark style="color:orange;">`WEBSOCKET [GATEWAY_WEBSOCKET_ENDPOINT]`</mark>
**Message**
```json
{
"cancel_product_orders": {
"tx": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"productIds": [2],
"nonce": "1"
},
"signature": "0x",
"digest": null
}
}
```
{% endtab %}
{% tab title="REST" %} <mark style="color:orange;">`POST [GATEWAY_REST_ENDPOINT]/execute`</mark>
**Body**
```json
{
"cancel_product_orders": {
"tx": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"productIds": [0],
"nonce": "1"
},
"signature": "0x",
"digest": "0x"
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="178" align="center">Parameter</th><th width="142" align="center">Type</th><th width="104" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">tx</td><td align="center">object</td><td align="center">Yes</td><td>Cancel product orders transaction object. See <a href="#signing">Signing</a> section for details on transaction fields.</td></tr><tr><td align="center">tx.sender</td><td align="center">string</td><td align="center">Yes</td><td>Hex string representing the subaccount's 32 bytes (address + subaccount name) of the tx sender.</td></tr><tr><td align="center">tx.productIds</td><td align="center">number[]</td><td align="center">Yes</td><td>A list of product IDs to cancel orders for.</td></tr><tr><td align="center">tx.nonce</td><td align="center">string</td><td align="center">Yes</td><td>Used to differentiate between the same cancellation multiple times. See <a href="#signing">Signing</a> section for more details.</td></tr><tr><td align="center">signature</td><td align="center">string</td><td align="center">Yes</td><td>Signed transaction. See <a href="#signing">Signing</a> section for more details.</td></tr><tr><td align="center">digest</td><td align="center">string</td><td align="center">No</td><td>Hex string representing a hash of the <code>CancellationProducts</code> object.</td></tr></tbody></table>
## Signing
{% hint style="info" %}
See more details and examples in our [signing](https://docs.nado.xyz/developer-resources/api/gateway/signing) page.
{% endhint %}
The solidity typed data struct that needs to be signed is:
```solidity
struct CancellationProducts {
bytes32 sender;
uint32[] productIds;
uint64 nonce;
}
```
<mark style="color:red;">`sender`</mark>: a <mark style="color:red;">`bytes32`</mark> sent as a hex string; includes the address and the subaccount identifier
<mark style="color:red;">`productIds`</mark>: a list of product Ids for which to cancel all subaccount orders. When left empty, orders from all products will be cancelled.
<mark style="color:red;">`nonce`</mark>: used to differentiate between the same cancellation multiple times, and a user trying to place a cancellation with the same parameters twice. Sent as a string. Encodes two bit of information:
* Most significant <mark style="color:red;">`44`</mark> bits encoding the <mark style="color:red;">`recv_time`</mark> in milliseconds after which the cancellation should be ignored by the matching engine; the engine will accept cancellations where <mark style="color:red;">`current_time < recv_time <= current_time + 100000`</mark>
* Least significant <mark style="color:red;">`20`</mark> bits are a random integer used to avoid hash collisions
For example, to place a cancellation with a random integer of <mark style="color:red;">`1000`</mark>, and a discard time 50 ms from now, we would send a nonce of <mark style="color:red;">`(timestamp_ms() + 50) << 20 + 1000`</mark>
{% hint style="warning" %}
**Note**: for signing you should always use the data type specified in the solidity struct which might be different from the type sent in the request e.g: <mark style="color:red;">`nonce`</mark> should be an <mark style="color:red;">`uint64`</mark> for **Signing** but should be sent as a <mark style="color:red;">`string`</mark> in the final payload.
{% endhint %}
## Response
#### Success
```json
{
"status": "success",
"signature": {signature},
"data": {
"cancelled_orders": [
{
"product_id": 2,
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"price_x18": "20000000000000000000000",
"amount": "-100000000000000000",
"expiration": "1686332748",
"order_type": "post_only",
"nonce": "1768248100142339392",
"unfilled_amount": "-100000000000000000",
"digest": "0x3195a7929feb8307edecf9c045j5ced68925108f0aa305f0ee5773854159377c",
"appendix": "1537",
"placed_at": 1686332708
},
...
]
},
"request_type": "execute_cancel_product_orders"
}
```
#### Failure
```json
{
"status": "failure",
"signature": {signature},
"error": "{error_msg}",
"error_code": {error_code},
"request_type": "execute_cancel_product_orders"
}
```
@@ -0,0 +1,123 @@
# Link Signer
Each subaccount can have at most one linked signer at a time. A linked signer can perform any execute on behalf of the subaccount it is linked to. Use the [Linked Signer](https://docs.nado.xyz/developer-resources/api/gateway/queries/linked-signer) query to view your current linked signer.
{% hint style="warning" %}
**Please note**:
* To enable a linked signer, your subaccount must have a minimum of **5 USDT0** worth in account value.
{% endhint %}
## Rate limits
* A max of 50 link signer requests every 7 days per subaccount. (**weight=30**). Use the [Linked Signer Rate Limit](https://docs.nado.xyz/developer-resources/api/archive-indexer/linked-signer-rate-limit) query to check a subaccount's linked signer usage and remaining wait time.
{% hint style="info" %}
See more general details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits).
{% endhint %}
## Request
{% tabs %}
{% tab title="Websocket" %}
**Connect**
<mark style="color:orange;">`WEBSOCKET [GATEWAY_WEBSOCKET_ENDPOINT]`</mark>
**Message**
```json
{
"link_signer": {
"tx": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"signer": "0xeae27ae6412147ed6d5692fd91709dad6dbfc34264656661756c740000000000",
"nonce": "1"
},
"signature": "0x"
}
}
```
{% endtab %}
{% tab title="REST" %} <mark style="color:orange;">`POST [GATEWAY_REST_ENDPOINT]/execute`</mark>
**Body**
```json
{
"link_signer": {
"tx": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"signer": "0xeae27ae6412147ed6d5692fd91709dad6dbfc34264656661756c740000000000",
"nonce": "1"
},
"signature": "0x"
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="148" align="center">Parameter</th><th width="90" align="center">Type</th><th width="104" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">tx</td><td align="center">object</td><td align="center">Yes</td><td>A link signer transaction object. See <a href="#signing">Signing</a> section for details on the transaction fields.</td></tr><tr><td align="center">tx.sender</td><td align="center">string</td><td align="center">Yes</td><td>Hex string representing the subaccount's 32 bytes (address + subaccount name) of the tx sender.</td></tr><tr><td align="center">tx.signer</td><td align="center">string</td><td align="center">Yes</td><td>A <mark style="color:red;"><code>bytes32</code></mark> sent as a hex string; includes the address (first 20 bytes) that'll be used as the <mark style="color:red;"><code>sender's</code></mark> signer. the last 12 bytes can be set to anything.</td></tr><tr><td align="center">tx.nonce</td><td align="center">string</td><td align="center">Yes</td><td>This is an incrementing nonce, can be obtained using the <a href="../queries/nonces">Nonces</a> query.</td></tr><tr><td align="center">signature</td><td align="center">string</td><td align="center">Yes</td><td>Signed transaction. See <a href="#signing">Signing</a> section for more details.</td></tr></tbody></table>
## Signing
{% hint style="info" %}
See more details and examples in our [signing](https://docs.nado.xyz/developer-resources/api/gateway/signing) page.
{% endhint %}
The solidity typed data struct that needs to be signed is:
```solidity
struct LinkSigner {
bytes32 sender;
bytes32 signer;
uint64 nonce;
}
```
<mark style="color:red;">`sender`</mark>: a <mark style="color:red;">`bytes32`</mark> sent as a hex string; includes the address and the subaccount identifier of the primary subaccount to add a signer to.
<mark style="color:red;">`signer`</mark>: a <mark style="color:red;">`bytes32`</mark> sent as a hex string; includes the address (first 20 bytes) that'll be used as the <mark style="color:red;">`sender's`</mark> signer.
{% hint style="info" %}
**Notes**:
* the last 12 bytes of the <mark style="color:red;">`signer`</mark> field do not matter and can be set to anything.
* set <mark style="color:red;">`signer`</mark> to the zero address to revoke current signer on the provided <mark style="color:red;">`sender`</mark>.
{% endhint %}
<mark style="color:red;">`nonce`</mark>: the <mark style="color:red;">`tx_nonce`</mark>. This is an incrementing nonce, can be obtained using the [Nonces](https://docs.nado.xyz/developer-resources/api/gateway/queries/nonces) query.
{% hint style="warning" %}
**Note**: for signing you should always use the data type specified in the solidity struct which might be different from the type sent in the request e.g: <mark style="color:red;">`nonce`</mark> should be an <mark style="color:red;">`uint64`</mark> for **Signing** but should be sent as a <mark style="color:red;">`string`</mark> in the final payload.
{% endhint %}
## Response
#### Success
```json
{
"status": "success",
"signature": {signature},
"request_type": "execute_link_signer"
}
```
#### Failure
```json
{
"status": "failure",
"signature": {signature},
"error": "{error_msg}",
"error_code": {error_code},
"request_type": "execute_link_signer"
}
```
@@ -0,0 +1,138 @@
# Liquidate Subaccount
## Rate limits
* 30 liquidations/min or 5 liquidations every 10 seconds per wallet. (**weight=20**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits).
{% endhint %}
## Request
{% tabs %}
{% tab title="Websocket" %}
**Connect**
<mark style="color:orange;">`WEBSOCKET [GATEWAY_WEBSOCKET_ENDPOINT]`</mark>
**Message**
```json
{
"liquidate_subaccount": {
"tx": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"liquidatee": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"productId": 1,
"isEncodedSpread": false,
"amount": "1000000000000000000",
"nonce": "1"
},
"signature": "0x"
}
}
```
{% endtab %}
{% tab title="REST" %} <mark style="color:orange;">`POST [GATEWAY_REST_ENDPOINT]/execute`</mark>
**Body**
```json
{
"liquidate_subaccount": {
"tx": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"liquidatee": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"mode": 0,
"healthGroup": 1,
"amount": "1000000000000000000",
"nonce": "1"
},
"signature": "0x"
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="220" align="center">Parameter</th><th width="92" align="center">Type</th><th width="104" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">tx</td><td align="center">object</td><td align="center">Yes</td><td>Liquidate subaccount transaction object. See <a href="#signing">Signing</a> section for details on the transaction fields.</td></tr><tr><td align="center">tx.sender</td><td align="center">string</td><td align="center">Yes</td><td>Hex string representing the subaccount's 32 bytes (address + subaccount name) of the tx sender.</td></tr><tr><td align="center">tx.liquidatee</td><td align="center">string</td><td align="center">Yes</td><td>Hex string representing the subaccount's 32 bytes (address + subaccount name) of the subaccount being liquidated.</td></tr><tr><td align="center">tx.productId</td><td align="center">number</td><td align="center">Yes</td><td><p>Perp Liquidation:</p><ul><li>A valid perp product Id.</li></ul><p>Spot Liquidation:</p><ul><li>A valid spot product Id.</li></ul><p>Spread Liquidation:</p><ul><li>An encoded perp / spot product Ids, where the lower 16 bits represent the spot product and the higher 16 bits represent the perp product. <mark style="color:red;"><code>isEncodedSpread</code></mark> must be set to <mark style="color:red;"><code>true</code></mark> for spread liquidation. See <a href="#signing">Signing</a> section for more details.</li></ul></td></tr><tr><td align="center">tx.isEncodedSpread</td><td align="center">bool</td><td align="center">Yes</td><td>When set to <mark style="color:red;"><code>true</code></mark>, the <mark style="color:red;"><code>productId</code></mark> is expected to encode a perp and spot product Ids as follows: <mark style="color:red;"><code>(perp_id &#x3C;&#x3C; 16) | spot_id</code></mark></td></tr><tr><td align="center">tx.amount</td><td align="center">string</td><td align="center">Yes</td><td>The amount to liquidate multiplied by 1e18, sent as a string.</td></tr><tr><td align="center">tx.nonce</td><td align="center">string</td><td align="center">Yes</td><td>This is an incrementing nonce, can be obtained using the <a href="../queries/nonces">Nonces</a> query.</td></tr><tr><td align="center">signature</td><td align="center">string</td><td align="center">Yes</td><td>Signed transaction. See <a href="#signing">Signing</a> section for more details.</td></tr></tbody></table>
## Signing
{% hint style="info" %}
See more details and examples in our [signing](https://docs.nado.xyz/developer-resources/api/gateway/signing) page.
{% endhint %}
The solidity typed data struct that needs to be signed is:
```solidity
struct LiquidateSubaccount {
bytes32 sender;
bytes32 liquidatee;
uint32 productId;
bool isEncodedSpread;
int128 amount;
uint64 nonce;
}
```
<mark style="color:red;">`sender`</mark>: a <mark style="color:red;">`bytes32`</mark> sent as a hex string; includes the address and the subaccount identifier.
<mark style="color:red;">`liquidatee`</mark>: a <mark style="color:red;">`bytes32`</mark> sent as a hex string; includes the address and the subaccount identifier.
<mark style="color:red;">`productId`</mark>: The product to liquidate as well as the liquidation mode.
* *Perp liquidation* ⇒ A valid <mark style="color:red;">`perp`</mark> product id is provided and <mark style="color:red;">`isEncodedSpread`</mark> is set to <mark style="color:red;">`false`</mark>.
* *Spot liquidation* ⇒ A valid <mark style="color:red;">`spot`</mark> product id is provided and <mark style="color:red;">`isEncodedSpread`</mark> is set to <mark style="color:red;">`false`</mark>
* *Spread Liquidation* => If there are perp and spot positions in different directions, liquidate both at the same time. Must be set to a 32 bits integer where the lower 16 bits represent the <mark style="color:red;">`spot`</mark> product and the higher 16 bits represent the <mark style="color:red;">`perp`</mark> product. <mark style="color:red;">`isEncodedSpread`</mark> must be set to <mark style="color:red;">`true`</mark>.
***Computing\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*&#x20;**<mark style="color:red;">**productId**</mark>**&#x20;\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*for Spread Liquidation***
```python
btc_spot = 1
btc_perp = 2
spread_product_id = (btc_perp << 16) | btc_spot
```
<mark style="color:red;">`isEncodedSpread`</mark>: indicates whether <mark style="color:red;">`productId`</mark> encodes both a <mark style="color:red;">`spot`</mark> and a <mark style="color:red;">`perp`</mark> product Id for spread liquidation.
<mark style="color:red;">`amount`</mark>: the amount to liquidate multiplied by 1e18, sent as a string. Can be positive or negative, depending on if the users balance is positive or negative.
<mark style="color:red;">`nonce`</mark>: the <mark style="color:red;">`tx_nonce`</mark>. This is an incrementing nonce, can be obtained using the [Nonces](https://docs.nado.xyz/developer-resources/api/gateway/queries/nonces) query.
{% hint style="warning" %}
**Note**: for signing you should always use the data type specified in the solidity struct which might be different from the type sent in the request e.g: <mark style="color:red;">`nonce`</mark> should be an <mark style="color:red;">`uint64`</mark> for **Signing** but should be sent as a <mark style="color:red;">`string`</mark> in the final payload.
{% endhint %}
## Response
#### Success
```json
{
"status": "success",
"signature": {signature},
"request_type": "execute_liquidate_subaccount"
}
```
#### Failure
```json
{
"status": "failure",
"signature": {signature},
"error": "{error_msg}",
"error_code": {error_code},
"request_type": "execute_liquidate_subaccount"
}
```
@@ -0,0 +1,111 @@
# Mint NLP
## Rate limits
* Wallet weight = <mark style="color:red;">`10`</mark> - allows 60 mints/min or 10 mints every 10 seconds per wallet.
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits).
{% endhint %}
## Request
{% tabs %}
{% tab title="Websocket" %}
**Connect**
<mark style="color:orange;">`WEBSOCKET [GATEWAY_WEBSOCKET_ENDPOINT]`</mark>
**Message**
```json
{
"mint_nlp": {
"tx": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"quoteAmount": "1000000000000000000",
"nonce": "1"
},
"signature": "0x"
}
}
```
{% endtab %}
{% tab title="REST" %} <mark style="color:orange;">`POST [GATEWAY_REST_ENDPOINT]/execute`</mark>
**Body**
```json
{
"mint_lp": {
"tx": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"productId": 1,
"amountBase": "1000000000000000000",
"quoteAmountLow": "10000000000000000000000",
"quoteAmountHigh": "20000000000000000000000",
"nonce": "1"
},
"signature": "0x"
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="205" align="center">Parameter</th><th width="94" align="center">Type</th><th width="112" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">tx</td><td align="center">object</td><td align="center">Yes</td><td>Mint NLP transaction object. See <a href="#signing">Signing</a> section for details on the transaction fields.</td></tr><tr><td align="center">tx.sender</td><td align="center">string</td><td align="center">Yes</td><td>Hex string representing the subaccount's 32 bytes (address + subaccount name) of the tx sender.</td></tr><tr><td align="center">tx.quoteAmount</td><td align="center">string</td><td align="center">Yes</td><td>This amount of quote to be consumed by minting NLPs multiplied by 1e18, sent as a string.</td></tr><tr><td align="center">tx.nonce</td><td align="center">string</td><td align="center">Yes</td><td>This is an incrementing nonce, can be obtained using the <a href="../queries/nonces">Nonces</a> query.</td></tr><tr><td align="center">signature</td><td align="center">string</td><td align="center">Yes</td><td>Hex string representing hash of the <strong>signed</strong> transaction. See <a href="#signing">Signing</a> section for more details.</td></tr><tr><td align="center">spot_leverage</td><td align="center">boolean</td><td align="center">No</td><td>Indicates whether leverage should be used; when set to <mark style="color:red;"><code>false</code></mark> , the mint fails if the transaction causes a borrow on the subaccount. Defaults to <mark style="color:red;"><code>true</code></mark>.</td></tr></tbody></table>
## Signing
{% hint style="info" %}
See more details and examples in our [signing](https://docs.nado.xyz/developer-resources/api/gateway/signing) page.
{% endhint %}
The solidity typed data struct that needs to be signed is:
```solidity
struct MintNlp {
bytes32 sender;
uint128 quoteAmount;
uint64 nonce;
}
```
<mark style="color:red;">`sender`</mark>: a <mark style="color:red;">`bytes32`</mark> sent as a hex string; includes the address and the subaccount identifier.
<mark style="color:red;">`quoteAmount`</mark>: this is the amount of quote to be consumed by minting NLPs, sent as a string. This must be positive and must be specified with 18 decimals.
<mark style="color:red;">`nonce`</mark>: the <mark style="color:red;">`tx_nonce`</mark>. This is an incrementing nonce, can be obtained using the [Nonces](https://docs.nado.xyz/developer-resources/api/gateway/queries/nonces) query.
{% hint style="warning" %}
**Note**: for signing you should always use the data type specified in the solidity struct which might be different from the type sent in the request e.g: <mark style="color:red;">`nonce`</mark> should be an <mark style="color:red;">`uint64`</mark> for **Signing** but should be sent as a <mark style="color:red;">`string`</mark> in the final payload.
{% endhint %}
## Response
#### Success
```json
{
"status": "success",
"signature": {signature},
"request_type": "execute_mint_nlp"
}
```
#### Failure
```json
{
"status": "failure",
"signature": {signature},
"error": "{error_msg}",
"error_code": {error_code},
"request_type": "execute_min_nlp"
}
```
@@ -0,0 +1,185 @@
# Place Order
## Rate limits
* With spot leverage: 600 orders/minute or 10 orders/sec per wallet. (**weight=1**)
* Without spot leverage: 30 orders/min or 5 orders every 10 seconds per wallet. (**weight = 20**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits).
{% endhint %}
## Request
{% tabs %}
{% tab title="Websocket" %}
**Connect**
<mark style="color:orange;">`WEBSOCKET [GATEWAY_WEBSOCKET_ENDPOINT]`</mark>
**Message**
```json
{
"place_order": {
"product_id": 1,
"order": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"priceX18": "1000000000000000000",
"amount": "1000000000000000000",
"expiration": "4294967295",
"nonce": "1757062078359666688",
"appendix": "1"
},
"signature": "0x",
"id": 100
}
}
```
{% endtab %}
{% tab title="REST" %} <mark style="color:orange;">`POST [GATEWAY_REST_ENDPOINT]/execute`</mark>
**Body**
```json
{
"place_order": {
"product_id": 1,
"order": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"priceX18": "1000000000000000000",
"amount": "1000000000000000000",
"expiration": "4294967295",
"nonce": "1757062078359666688"
},
"signature": "0x",
"id": 100
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="180" align="center">Parameter</th><th width="94" align="center">Type</th><th width="112" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">product_id</td><td align="center">number</td><td align="center">Yes</td><td>Id of spot / perp product for which to place order. Use <a href="../queries/all-products">All products</a> query to retrieve all valid product ids.</td></tr><tr><td align="center">order</td><td align="center">object</td><td align="center">Yes</td><td>Order object, see <a href="#signing">Signing</a> section for details on each order field.</td></tr><tr><td align="center">order.sender</td><td align="center">string</td><td align="center">Yes</td><td>Hex string representing the subaccount's 32 bytes (address + subaccount name) of the tx sender.</td></tr><tr><td align="center">order.priceX18</td><td align="center">string</td><td align="center">Yes</td><td>Price of the order multiplied by 1e18.</td></tr><tr><td align="center">order.amount</td><td align="center">string</td><td align="center">Yes</td><td>Quantity of the order multiplied by 1e18.</td></tr><tr><td align="center">order.expiration</td><td align="center">string</td><td align="center">Yes</td><td>A time after which the order should automatically be cancelled, as a timestamp in seconds after the unix epoch.</td></tr><tr><td align="center">order.nonce</td><td align="center">string</td><td align="center">Yes</td><td>Used to differentiate between the same order multiple times. See <a href="#signing">Signing</a> section for more details.</td></tr><tr><td align="center">order.appendix</td><td align="center">string</td><td align="center">Yes</td><td>Encodes various order properties including execution types, isolated positions, TWAP parameters, and trigger types. See order appendix section for more details.</td></tr><tr><td align="center">signature</td><td align="center">string</td><td align="center">Yes</td><td>Hex string representing hash of the <strong>signed</strong> order. See <a href="#signing">Signing</a> section for more details.</td></tr><tr><td align="center">digest</td><td align="center">string</td><td align="center">No</td><td>Hex string representing a hash of the order.</td></tr><tr><td align="center">spot_leverage</td><td align="center">boolean</td><td align="center">No</td><td>Indicates whether leverage should be used; when set to <mark style="color:red;"><code>false</code></mark> , placing the order fails if the transaction causes a borrow on the subaccount. Defaults to <mark style="color:red;"><code>true</code></mark>.</td></tr><tr><td align="center">id</td><td align="center">number</td><td align="center">No</td><td>An optional id that when provided is returned as part of <mark style="color:red;"><code>Fill</code></mark> and <mark style="color:red;"><code>OrderUpdate</code></mark> stream events. See <a href="../../subscriptions">subscriptions</a> for more details.<br><br><strong>NOTE</strong>: The client <mark style="color:red;"><code>id</code></mark> should not be used to differentiate orders, as it is not included in the order hash (i.e., the order <mark style="color:red;"><code>digest</code></mark>). Instead, use the last 20 bits of the order nonce to distinguish between similar orders. For more details, refer to <a href="#order-nonce">Order Nonce</a>.</td></tr></tbody></table>
## Signing
{% hint style="info" %}
See more details and examples in our [signing](https://docs.nado.xyz/developer-resources/api/gateway/signing) page.
{% endhint %}
The solidity typed data struct that needs to be signed is:
```solidity
struct Order {
bytes32 sender;
int128 priceX18;
int128 amount;
uint64 expiration;
uint64 nonce;
uint128 appendix;
}
```
<mark style="color:red;">`sender`</mark>: a <mark style="color:red;">`bytes32`</mark> sent as a hex string; includes the address and the subaccount identifier
<mark style="color:red;">`priceX18`</mark>: an <mark style="color:red;">`int128`</mark> representing the price of the order multiplied by 1e18, sent as a string. For example, a price of 1 USDT0 would be sent as <mark style="color:red;">`"1000000000000000000"`</mark>
<mark style="color:red;">`amount`</mark>: an <mark style="color:red;">`int128`</mark> representing the quantity of the order multiplied by 1e18, sent as a string. A positive amount means that this is a buy order, and a negative amount means this is a sell order.
<mark style="color:red;">`expiration`</mark>: a time after which the order should automatically be cancelled, as a timestamp in seconds after the unix epoch, sent as a string.
### Order Nonce
<mark style="color:red;">`nonce`</mark>: used to differentiate between the same order multiple times, and a user trying to place an order with the same parameters twice. Sent as a string. Encodes two bit of information:
* Most significant <mark style="color:red;">`44`</mark> bits encoding the time in milliseconds (a `recv_time`) after which the order should be ignored by the matching engine
* Least significant <mark style="color:red;">`20`</mark> bits are a random integer used to avoid hash collisions
For example, to place an order with a random integer of <mark style="color:red;">`1000`</mark>, and a discard time 50 ms from now, we would send a nonce of <mark style="color:red;">`((timestamp_ms() + 50) << 20) + 1000)`</mark>
```python
import time
unix_epoch_ms = int(time.time()) * 1000
nonce = ((unix_epoch_ms + 50) << 20) + 1000
```
{% hint style="warning" %}
**Note**: for signing you should always use the data type specified in the solidity struct which might be different from the type sent in the request e.g: <mark style="color:red;">`nonce`</mark> should be an <mark style="color:red;">`uint64`</mark> for **Signing** but should be sent as a <mark style="color:red;">`string`</mark> in the final payload.
{% endhint %}
## Order Appendix
{% hint style="info" %}
See more details and examples in our [Order Appendix](https://docs.nado.xyz/developer-resources/api/order-appendix) page.
{% endhint %}
<mark style="color:red;">`appendix`</mark>: is a 128-bit integer that encodes extra order parameters like execution type, isolated margin, and trigger type.
### Bit Layout
```json
| value | reserved | trigger | reduce only | order type | isolated | version |
| 64 bits | 50 bits | 2 bits | 1 bit | 2 bits | 1 bit | 8 bits |
| 127..64 | 63..14 | 13..12 | 11 | 10..9 | 8 | 7..0 |
```
**Fields (from LSB to MSB):**
* <mark style="color:red;">**Version (8 bits, 07)**</mark> protocol version (currently `1`)
* <mark style="color:red;">**Isolated (1 bit, 8)**</mark> whether the order uses isolated margin
* <mark style="color:red;">**Order Type (2 bits, 910)**</mark> 0 = DEFAULT, 1 = IOC, 2 = FOK, 3 = POST\_ONLY
* <mark style="color:red;">`0`</mark> - <mark style="color:red;">`DEFAULT`</mark>: Standard limit order behavior
* <mark style="color:red;">`1`</mark> - <mark style="color:red;">`IOC (Immediate or Cancel)`</mark>: Execute immediately, cancel unfilled portion
* <mark style="color:red;">`2`</mark> - <mark style="color:red;">`FOK (Fill or Kill)`</mark>: Execute completely or cancel entire order
* <mark style="color:red;">`3`</mark> - <mark style="color:red;">`POST_ONLY`</mark>: Only add liquidity, reject if would take liquidity
* <mark style="color:red;">**Reduce Only (1 bit, 11)**</mark> only decreases an existing position.
* <mark style="color:red;">**Trigger Type (2 bits, 1213)**</mark> 0 = NONE, 1 = PRICE, 2 = TWAP, 3 = TWAP\_CUSTOM\_AMOUNTS
* <mark style="color:red;">**Reserved (50 bits, 1463)**</mark> future use
* <mark style="color:red;">**Value (64 bits, 64127)**</mark> extra data (isolated margin or TWAP parameters)
* if <mark style="color:red;">`trigger`</mark> is <mark style="color:red;">`2`</mark> or <mark style="color:red;">`3`</mark> ⇒ <mark style="color:red;">`value`</mark> represents how many times the TWAP order will execute and the maximum acceptable slippage. Encoded as:
```json
| times | slippage_x6 |
| 32 bits| 32 bits |
```
* <mark style="color:red;">`times`</mark> : Number of TWAP executions.
* <mark style="color:red;">`slippage_x6`</mark>: Maximum slippage × 1,000,000 (6 decimal precision).
* if <mark style="color:red;">`isolated`</mark> is <mark style="color:red;">`1`</mark> ⇒ <mark style="color:red;">`value`</mark> represents <mark style="color:red;">`margin_x6`</mark> (in x6 precision, 6 decimals) to be transferred to the isolated subaccount when the order gets its first match.
* otherwise, <mark style="color:red;">`value`</mark> is <mark style="color:red;">`0`</mark>.
## Response
#### Success
```json
{
"status": "success",
"signature": {signature},
"data": {
"digest": {order digest}
},
"request_type": "execute_place_order"
"id": 100
}
```
#### Failure
```json
{
"status": "failure",
"signature": {signature},
"error": "{error_msg}",
"error_code": {error_code},
"request_type": "execute_place_order"
}
```
@@ -0,0 +1,193 @@
# Place Orders
Place multiple orders in a single request. This is more efficient than placing orders individually and allows for better control over batch order placement.
## Rate limits
* With spot leverage: 600 orders/minute or 10 orders/sec per wallet. (**weight=1 per order**)
* Without spot leverage: 30 orders/min or 5 orders every 10 seconds per wallet. (**weight = 20 per order**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits).
{% endhint %}
{% hint style="warning" %}
**Note**: There is a 50ms processing penalty for each `place_orders` request to ensure fair sequencing and prevent gaming of the matching engine.
{% endhint %}
## Request
{% tabs %}
{% tab title="Websocket" %}
**Connect**
<mark style="color:orange;">`WEBSOCKET [GATEWAY_WEBSOCKET_ENDPOINT]`</mark>
**Message**
```json
{
"place_orders": {
"orders": [
{
"product_id": 2,
"order": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"priceX18": "100000000000000000000000",
"amount": "1000000000000000000",
"expiration": "4294967295",
"nonce": "1757062078359666688",
"appendix": "1"
},
"signature": "0x...",
"id": 100
},
{
"product_id": 3,
"order": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"priceX18": "3800000000000000000000",
"amount": "2000000000000000000",
"expiration": "4294967295",
"nonce": "1757062078359666689",
"appendix": "1"
},
"signature": "0x...",
"id": 101
}
],
"stop_on_failure": false
}
}
```
{% endtab %}
{% tab title="REST" %} <mark style="color:orange;">`POST [GATEWAY_REST_ENDPOINT]/execute`</mark>
**Body**
```json
{
"place_orders": {
"orders": [
{
"product_id": 2,
"order": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"priceX18": "100000000000000000000000",
"amount": "1000000000000000000",
"expiration": "4294967295",
"nonce": "1757062078359666688",
"appendix": "1"
},
"signature": "0x...",
"id": 100
},
{
"product_id": 3,
"order": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"priceX18": "3800000000000000000000",
"amount": "2000000000000000000",
"expiration": "4294967295",
"nonce": "1757062078359666689",
"appendix": "1"
},
"signature": "0x...",
"id": 101
}
],
"stop_on_failure": false
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="200" align="center">Parameter</th><th width="94" align="center">Type</th><th width="112" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">orders</td><td align="center">array</td><td align="center">Yes</td><td>Array of order objects to place. Each order follows the same structure as <a href="place-order">Place Order</a>.</td></tr><tr><td align="center">orders[].product_id</td><td align="center">number</td><td align="center">Yes</td><td>Id of spot / perp product for which to place order.</td></tr><tr><td align="center">orders[].order</td><td align="center">object</td><td align="center">Yes</td><td>Order object (same structure as single order placement).</td></tr><tr><td align="center">orders[].signature</td><td align="center">string</td><td align="center">Yes</td><td>Hex string representing hash of the <strong>signed</strong> order.</td></tr><tr><td align="center">orders[].digest</td><td align="center">string</td><td align="center">No</td><td>Hex string representing a hash of the order.</td></tr><tr><td align="center">orders[].spot_leverage</td><td align="center">boolean</td><td align="center">No</td><td>Indicates whether leverage should be used for this order. Defaults to <mark style="color:red;"><code>true</code></mark>.</td></tr><tr><td align="center">orders[].id</td><td align="center">number</td><td align="center">No</td><td>An optional id returned in <mark style="color:red;"><code>Fill</code></mark> and <mark style="color:red;"><code>OrderUpdate</code></mark> events.</td></tr><tr><td align="center">stop_on_failure</td><td align="center">boolean</td><td align="center">No</td><td>If <mark style="color:red;"><code>true</code></mark>, stops processing remaining orders when the first order fails. Already successfully placed orders are NOT cancelled. Defaults to <mark style="color:red;"><code>false</code></mark>.</td></tr></tbody></table>
## Response
```json
{
"status": "success",
"data": {
"place_orders": [
{
"digest": "0x1234...",
"error": null
},
{
"digest": null,
"error": "insufficient margin"
}
]
}
}
```
### Response Fields
<table><thead><tr><th width="200">Field</th><th>Description</th></tr></thead><tbody><tr><td>digest</td><td>Order digest (32-byte hash) if successfully placed, <mark style="color:red;"><code>null</code></mark> if failed.</td></tr><tr><td>error</td><td>Error message if order failed, <mark style="color:red;"><code>null</code></mark> if successful.</td></tr></tbody></table>
## Behavior
* **Partial Success**: By default, orders are processed independently. Some orders may succeed while others fail.
* **Stop on Failure**: Set `stop_on_failure: true` to stop processing remaining orders when the first order fails. Already successfully placed orders remain on the book.
* **Order Signing**: Each order must be individually signed using EIP712 (see [Signing](https://docs.nado.xyz/developer-resources/api/gateway/signing) for details).
* **Rate Limits**: Rate limit weight is calculated per order (1 per order with leverage, 20 per order without).
## Use Cases
* **Spread Trading**: Place both legs of a spread trade in one request
* **Multiple Markets**: Open positions across multiple products in one request
## Example
Placing BTC and ETH perp orders simultaneously:
```javascript
const placeOrdersParams = {
orders: [
{
product_id: 2, // BTC-PERP
order: {
sender: subaccount,
priceX18: toX18(100000), // $100k
amount: toX18(0.1),
expiration: getExpiration(OrderType.DEFAULT),
nonce: genOrderNonce(),
appendix: buildAppendix()
},
signature: await signOrder(btcOrder),
id: 1
},
{
product_id: 3, // ETH-PERP
order: {
sender: subaccount,
priceX18: toX18(3800), // $3.8k
amount: toX18(1),
expiration: getExpiration(OrderType.DEFAULT),
nonce: genOrderNonce(),
appendix: buildAppendix()
},
signature: await signOrder(ethOrder),
id: 2
}
],
stop_on_failure: false
};
const response = await client.execute({ place_orders: placeOrdersParams });
```
## See Also
* [Place Order](https://docs.nado.xyz/developer-resources/api/gateway/executes/place-order) - Single order placement
* [Cancel And Place](https://docs.nado.xyz/developer-resources/api/gateway/executes/cancel-and-place) - Atomic cancel and place
* [Signing](https://docs.nado.xyz/developer-resources/api/gateway/signing) - EIP712 order signing
@@ -0,0 +1,132 @@
# Transfer Quote
## Fees
Transfers between subaccounts incur a network fee:
* **Standard transfers**: 1 USDT0
* **Isolated subaccount transfers**: 0.1 USDT0 (when either sender or recipient is an isolated subaccount)
The fee is automatically deducted from the sender's balance.
## Rate limits
* 60 transfer quotes/min or 10 every 10 seconds per wallet. (**weight=10**)
* A max of 5 transfer quotes to new recipients (subaccounts) every 24hrs.
* **Note**: Transferring quote to a subaccount that doesn't exist, creates the subaccount.
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits).
{% endhint %}
## Request
{% tabs %}
{% tab title="Websocket" %}
**Connect**
<mark style="color:orange;">`WEBSOCKET [GATEWAY_WEBSOCKET_ENDPOINT]`</mark>
**Message**
```json
{
"transfer_quote": {
"tx": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"recipient": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743100000000000000",
"amount": "10000000000000000000",
"nonce": "1"
},
"signature": "0x"
}
}
```
{% endtab %}
{% tab title="REST" %} <mark style="color:orange;">`POST [GATEWAY_REST_ENDPOINT]/execute`</mark>
**Body**
```json
{
"transfer_quote": {
"tx": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"recipient": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743100000000000000",
"amount": "10000000000000000000",
"nonce": "1"
},
"signature": "0x"
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="154" align="center">Parameter</th><th width="94" align="center">Type</th><th width="112" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">tx</td><td align="center">object</td><td align="center">Yes</td><td>Transfer Quote transaction object. See <a href="#signing">Signing</a> section for details on the transaction fields.</td></tr><tr><td align="center">tx.sender</td><td align="center">string</td><td align="center">Yes</td><td>Hex string representing the subaccount's 32 bytes (address + subaccount name) of the tx sender.</td></tr><tr><td align="center">tx.recipient</td><td align="center">string</td><td align="center">Yes</td><td>Hex string representing the subaccount's 32 bytes (address + subaccount name) of the quote recipient.</td></tr><tr><td align="center">tx.amount</td><td align="center">string</td><td align="center">Yes</td><td>The amount of USDT0 to transfer, denominated in <code>x18</code>. Transfr amount must be <mark style="color:red;"><code>>= 5 USDT0</code></mark> . See <a href="#signing">Signing</a> section for more details.</td></tr><tr><td align="center">tx.nonce</td><td align="center">string</td><td align="center">Yes</td><td>This is an incrementing nonce, can be obtained using the <a href="../queries/nonces">Nonces</a> query.</td></tr><tr><td align="center">signature</td><td align="center">string</td><td align="center">Yes</td><td>Hex string representing hash of the <strong>signed</strong> transaction. See <a href="#signing">Signing</a> section for more details.</td></tr></tbody></table>
## Signing
{% hint style="info" %}
See more details and examples in our [signing](https://docs.nado.xyz/developer-resources/api/gateway/signing) page.
{% endhint %}
The solidity typed data struct that needs to be signed is:
```solidity
struct TransferQuote {
bytes32 sender;
bytes32 recipient;
uint128 amount;
uint64 nonce;
}
```
<mark style="color:red;">`sender`</mark>: a <mark style="color:red;">`bytes32`</mark> sent as a hex string; includes the address and the subaccount identifier.
<mark style="color:red;">`recipient`</mark>: a <mark style="color:red;">`bytes32`</mark> sent as a hex string; includes the address and the subaccount identifier.
<mark style="color:red;">`amount`</mark>: the amount of quote to transfer, sent as an `x18` string.
{% hint style="warning" %}
**Notes:**
* If you are transferring <mark style="color:red;">`5 USDT0`</mark>, must specify <mark style="color:red;">`5000000000000000000`</mark> i.e 5 USDT0 \* 1e18.
* Transfer amount should be <mark style="color:red;">>= 5 USDT0.</mark>
{% endhint %}
<mark style="color:red;">`nonce`</mark>: the <mark style="color:red;">`tx_nonce`</mark>. This is an incrementing nonce, can be obtained using the [Nonces](https://docs.nado.xyz/developer-resources/api/gateway/queries/nonces) query.
{% hint style="warning" %}
**Note**: for signing you should always use the data type specified in the solidity struct which might be different from the type sent in the request e.g: <mark style="color:red;">`nonce`</mark> should be an <mark style="color:red;">`uint64`</mark> for **Signing** but should be sent as a <mark style="color:red;">`string`</mark> in the final payload.
{% endhint %}
## Response
#### Success
```json
{
"status": "success",
"signature": {signature},
"request_type": "execute_transfer_quote"
}
```
#### Failure
```json
{
"status": "failure",
"signature": {signature},
"error": "{error_msg}",
"error_code": {error_code},
"request_type": "execute_transfer_quote"
}
```
@@ -0,0 +1,120 @@
# Withdraw Collateral
{% hint style="info" %}
**Note**: use the [max withdrawable](https://docs.nado.xyz/developer-resources/api/gateway/queries/max-withdrawable) query to determine the max amount you can withdraw for a given spot product.
{% endhint %}
## Rate limits
* With spot leverage: 60 withdrawals/min or 10 withdrawals every 10 seconds per wallet. (**weight = 10**)
* Without spot leverage: 30 withdrawals/min or 5 withdrawals every 10 seconds per wallet. (**weight=20**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits).
{% endhint %}
## Request
{% tabs %}
{% tab title="Websocket" %}
**Connect**
<mark style="color:orange;">`WEBSOCKET [GATEWAY_WEBSOCKET_ENDPOINT]`</mark>
**Message**
```json
{
"withdraw_collateral": {
"tx": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"productId": 1,
"amount": "1000000000000000000",
"nonce": "1"
},
"signature": "0x"
}
}
```
{% endtab %}
{% tab title="REST" %} <mark style="color:orange;">`POST [GATEWAY_REST_ENDPOINT]/execute`</mark>
**Body**
```json
{
"withdraw_collateral": {
"tx": {
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43746573743000000000000000",
"productId": 1,
"amount": "1000000000000000000",
"nonce": "1"
},
"signature": "0x"
}
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="154" align="center">Parameter</th><th width="94" align="center">Type</th><th width="112" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">tx</td><td align="center">object</td><td align="center">Yes</td><td>Withdraw collateral transaction object. See <a href="#signing">Signing</a> section for details on the transaction fields.</td></tr><tr><td align="center">tx.sender</td><td align="center">string</td><td align="center">Yes</td><td>Hex string representing the subaccount's 32 bytes (address + subaccount name) of the tx sender.</td></tr><tr><td align="center">tx.productId</td><td align="center">number</td><td align="center">Yes</td><td>A spot product ID to withdraw from.</td></tr><tr><td align="center">tx.amount</td><td align="center">string</td><td align="center">Yes</td><td>The amount of the asset to withdraw, denominated in the base ERC20 token of the specified product e.g: USDT0 (product=0) has 6 decimals whereas wETH (product=3) has 18. See <a href="#signing">Signing</a> section for more details.</td></tr><tr><td align="center">tx.nonce</td><td align="center">string</td><td align="center">Yes</td><td>This is an incrementing nonce, can be obtained using the <a href="../queries/nonces">Nonces</a> query.</td></tr><tr><td align="center">signature</td><td align="center">string</td><td align="center">Yes</td><td>Hex string representing hash of the <strong>signed</strong> transaction. See <a href="#signing">Signing</a> section for more details.</td></tr><tr><td align="center">spot_leverage</td><td align="center">boolean</td><td align="center">No</td><td>Indicates whether leverage should be used; when set to <mark style="color:red;"><code>false</code></mark> , the withdrawal fails if the transaction causes a borrow on the subaccount. Defaults to <mark style="color:red;"><code>true</code></mark>.</td></tr></tbody></table>
## Signing
{% hint style="info" %}
See more details and examples in our [signing](https://docs.nado.xyz/developer-resources/api/gateway/signing) page.
{% endhint %}
The solidity typed data struct that needs to be signed is:
```solidity
struct WithdrawCollateral {
bytes32 sender;
uint32 productId;
uint128 amount;
uint64 nonce;
}
```
<mark style="color:red;">`sender`</mark>: a <mark style="color:red;">`bytes32`</mark> sent as a hex string; includes the address and the subaccount identifier.
<mark style="color:red;">`productId`</mark>: a <mark style="color:red;">`uint32`</mark> that specifies the product youd like to withdraw collateral from; must be for a spot product.
<mark style="color:red;">`amount`</mark>: the amount of asset to withdraw, sent as a string. Note that this is different from the amounts provided in transactions that arent <mark style="color:red;">`depositCollateral`</mark>. This is the raw amount of the ERC20 token you want to receive, i.e. if USDT0 has 6 decimals and you want to withdraw 1 USDT0, specify 1e6; if wETH has 18 decimals and you want to withdraw 1 wETH, specify 1e18. Use [all products](https://docs.nado.xyz/developer-resources/api/gateway/queries/all-products) query to view the token address of the corresponding product which can be used to determine the correct decimals to use.
<mark style="color:red;">`nonce`</mark>: the <mark style="color:red;">`tx_nonce`</mark>. This is an incrementing nonce, can be obtained using the [Nonces](https://docs.nado.xyz/developer-resources/api/gateway/queries/nonces) query.
{% hint style="warning" %}
**Note**: for signing you should always use the data type specified in the solidity struct which might be different from the type sent in the request e.g: <mark style="color:red;">`nonce`</mark> should be an <mark style="color:red;">`uint64`</mark> for **Signing** but should be sent as a <mark style="color:red;">`string`</mark> in the final payload.
{% endhint %}
## Response
#### Success
```json
{
"status": "success",
"signature": {signature},
"request_type": "execute_withdraw_collateral"
}
```
#### Failure
```json
{
"status": "failure",
"signature": {signature},
"error": "{error_msg}",
"error_code": {error_code},
"request_type": "execute_withdraw_collateral"
}
```
@@ -0,0 +1,28 @@
# Queries
All queries go through the following endpoint; the exact details of the query are specified by query params or `Websocket` messages.
* **Websocket**: <mark style="color:orange;">`WEBSOCKET [GATEWAY_WEBSOCKET_ENDPOINT]`</mark>
* **REST**: <mark style="color:green;">`GET [GATEWAY_REST_ENDPOINT]/query`</mark> or <mark style="color:orange;">`POST [GATEWAY_REST_ENDPOINT]/query`</mark>
## Overview
### **Amounts and Prices**
In general, amounts come back normalized to 18 decimal places. Meaning that for a balance of 1 USDT0, regardless of the number of decimals USDT0 has on-chain, a value of 1e18 will be returned.
Prices are in <mark style="color:red;">`x18`</mark>, so if the price of one wBTC is $20,000, regardless of the number of decimals wBTC has on-chain, the price will be returned as <mark style="color:red;">`20,000 * 1e18`</mark>.
## API Response
All `queries` return in the format:
```json
{
"status": "success" | "failure",
"data"?: {data},
"error"?: "{error_msg}",
"error_code"?: {error_code},
"request_type": "{request_type}"
}
```
@@ -0,0 +1,155 @@
# All Products
## Rate limits
* 480 requests/min or 8 requests/sec per IP address. (**weight = 5**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="Websocket" %}
**Connect**
<mark style="color:orange;">`WEBSOCKET [GATEWAY_WEBSOCKET_ENDPOINT]`</mark>
**Message**
```json
{
"type": "all_products"
}
```
{% endtab %}
{% tab title="REST (GET)" %} <mark style="color:green;">**GET**</mark> `[GATEWAY_REST_ENDPOINT]/query?type=all_products`
{% endtab %}
{% tab title="REST (POST)" %} <mark style="color:orange;">`POST [GATEWAY_REST_ENDPOINT]/query`</mark>
**Body**
```json
{
"type": "all_products"
}
```
{% endtab %}
{% endtabs %}
## Response
{% hint style="info" %}
**Note**:
* A product is some asset / position an account can take on.
* A market is a venue for a product against USDT0.
* All products have a market quoted against USDT0, except for product 0.
* Product 0 is the USDT0 asset itself.
* You can retrieve product symbols via [symbols](https://docs.nado.xyz/developer-resources/api/symbols "mention") query.Body
{% endhint %}
```json
{
"status": "success",
"data": {
"spot_products": [
{
"product_id": 0,
"oracle_price_x18": "1000000000000000000",
"risk": {
"long_weight_initial_x18": "1000000000000000000",
"short_weight_initial_x18": "1000000000000000000",
"long_weight_maintenance_x18": "1000000000000000000",
"short_weight_maintenance_x18": "1000000000000000000",
"price_x18": "1000000000000000000"
},
"config": {
"token": "0x5f65358d61a9a281ea3bb930d05889aca21e3f4f",
"interest_inflection_util_x18": "800000000000000000",
"interest_floor_x18": "10000000000000000",
"interest_small_cap_x18": "40000000000000000",
"interest_large_cap_x18": "1000000000000000000",
"withdraw_fee_x18": "1000000000000000000",
"min_deposit_rate_x18": "0"
},
"state": {
"cumulative_deposits_multiplier_x18": "1000000000025524653",
"cumulative_borrows_multiplier_x18": "1000347390837434279",
"total_deposits_normalized": "20001011744258817298755054194662",
"total_borrows_normalized": "1617724891363505323532211"
},
"book_info": {
"size_increment": "0",
"price_increment_x18": "0",
"min_size": "0",
"collected_fees": "0"
}
},
{
"product_id": 1,
"oracle_price_x18": "115575316424148798147115",
"risk": {
"long_weight_initial_x18": "900000000000000000",
"short_weight_initial_x18": "1100000000000000000",
"long_weight_maintenance_x18": "950000000000000000",
"short_weight_maintenance_x18": "1050000000000000000",
"price_x18": "115575316424148798147115"
},
"config": {
"token": "0xc57c1c64561a37ac9e8f9039cb6deab7539d99fc",
"interest_inflection_util_x18": "800000000000000000",
"interest_floor_x18": "10000000000000000",
"interest_small_cap_x18": "40000000000000000",
"interest_large_cap_x18": "1000000000000000000",
"withdraw_fee_x18": "40000000000000",
"min_deposit_rate_x18": "0"
},
"state": {
"cumulative_deposits_multiplier_x18": "1000000000000318713",
"cumulative_borrows_multiplier_x18": "1000347390679880473",
"total_deposits_normalized": "9000399823280682696107190850",
"total_borrows_normalized": "9580268570661550719"
},
"book_info": {
"size_increment": "1000000000000000",
"price_increment_x18": "1000000000000000000",
"min_size": "4000000000000000",
"collected_fees": "0"
}
}
],
"perp_products": [
{
"product_id": 2,
"oracle_price_x18": "115432187703236794231754",
"risk": {
"long_weight_initial_x18": "950000000000000000",
"short_weight_initial_x18": "1050000000000000000",
"long_weight_maintenance_x18": "970000000000000000",
"short_weight_maintenance_x18": "1030000000000000000",
"price_x18": "115432187703236794231754"
},
"state": {
"cumulative_funding_long_x18": "-394223711772447555304",
"cumulative_funding_short_x18": "-394223711772447555304",
"available_settle": "20092193239667417956947",
"open_interest": "113605000000000000000"
},
"book_info": {
"size_increment": "1000000000000000",
"price_increment_x18": "1000000000000000000",
"min_size": "4000000000000000",
"collected_fees": "0"
}
}
]
},
"request_type": "query_all_products"
}
```
@@ -0,0 +1,62 @@
# Contracts
## Rate limits
* 2400 requests/min or 40 requests/sec per IP address. (**weight = 1**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="Websocket" %}
**Connect**
<mark style="color:orange;">`WEBSOCKET [GATEWAY_WEBSOCKET_ENDPOINT]`</mark>
**Message**
```json
{
"type": "contracts"
}
```
{% endtab %}
{% tab title="REST (GET)" %} <mark style="color:green;">**GET**</mark> `[GATEWAY_REST_ENDPOINT]/query?type=contracts`
{% endtab %}
{% tab title="REST (POST)" %} <mark style="color:orange;">`POST [GATEWAY_REST_ENDPOINT]/query`</mark>
**Body**
```json
{
"type": "contracts"
}
```
{% endtab %}
{% endtabs %}
## Response
```json
{
"status": "success",
"data": {
"chain_id": "763373",
"endpoint_addr": "0xf8963f7860af7de9b94893edb9a3b5c155e1fc0c"
},
"request_type": "query_contracts"
}
```
{% hint style="info" %}
**Note:**
* <mark style="color:red;">`endpoint_addr`</mark> is the address of the Nado endpoint contracts. Deposits are sent to the endpoint address; **this to used sign every request except&#x20;**<mark style="color:red;">**`PlaceOrder`**</mark>
{% endhint %}
@@ -0,0 +1,160 @@
# Edge All Products
## Rate limits
* 480 requests/min or 8 requests/sec per IP address. (**weight = 5**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="Websocket" %}
**Connect**
<mark style="color:orange;">`WEBSOCKET [GATEWAY_WEBSOCKET_ENDPOINT]`</mark>
**Message**
```json
{
"type": "edge_all_products"
}
```
{% endtab %}
{% tab title="REST (GET)" %} <mark style="color:green;">**GET**</mark> `[GATEWAY_REST_ENDPOINT]/query?type=edge_all_products`
{% endtab %}
{% tab title="REST (POST)" %} <mark style="color:orange;">`POST [GATEWAY_REST_ENDPOINT]/query`</mark>
**Body**
```json
{
"type": "edge_all_products"
}
```
{% endtab %}
{% endtabs %}
## Response
{% hint style="info" %}
**Note**:
* A product is some asset / position an account can take on.
* A market is a venue for a product against USDT0.
* All products have a market quoted against USDT0, except for product 0.
* Product 0 is the USDT0 asset itself.
* You can retrieve product symbols via [symbols](https://docs.nado.xyz/developer-resources/api/symbols "mention") query.Body
* Returns a mapping of <mark style="color:orange;">`chain_id -> all_products`</mark>
{% endhint %}
```json
{
"status": "success",
"data": {
"edge_all_products": {
"763373": {
"spot_products": [
{
"product_id": 0,
"oracle_price_x18": "1000000000000000000",
"risk": {
"long_weight_initial_x18": "1000000000000000000",
"short_weight_initial_x18": "1000000000000000000",
"long_weight_maintenance_x18": "1000000000000000000",
"short_weight_maintenance_x18": "1000000000000000000",
"price_x18": "1000000000000000000"
},
"config": {
"token": "0x5f65358d61a9a281ea3bb930d05889aca21e3f4f",
"interest_inflection_util_x18": "800000000000000000",
"interest_floor_x18": "10000000000000000",
"interest_small_cap_x18": "40000000000000000",
"interest_large_cap_x18": "1000000000000000000",
"withdraw_fee_x18": "1000000000000000000",
"min_deposit_rate_x18": "0"
},
"state": {
"cumulative_deposits_multiplier_x18": "1000000000025524653",
"cumulative_borrows_multiplier_x18": "1000347390837434279",
"total_deposits_normalized": "20001011744258817298755054194662",
"total_borrows_normalized": "1617724891363505323532211"
},
"book_info": {
"size_increment": "0",
"price_increment_x18": "0",
"min_size": "0",
"collected_fees": "0"
}
},
{
"product_id": 1,
"oracle_price_x18": "115575316424148798147115",
"risk": {
"long_weight_initial_x18": "900000000000000000",
"short_weight_initial_x18": "1100000000000000000",
"long_weight_maintenance_x18": "950000000000000000",
"short_weight_maintenance_x18": "1050000000000000000",
"price_x18": "115575316424148798147115"
},
"config": {
"token": "0xc57c1c64561a37ac9e8f9039cb6deab7539d99fc",
"interest_inflection_util_x18": "800000000000000000",
"interest_floor_x18": "10000000000000000",
"interest_small_cap_x18": "40000000000000000",
"interest_large_cap_x18": "1000000000000000000",
"withdraw_fee_x18": "40000000000000",
"min_deposit_rate_x18": "0"
},
"state": {
"cumulative_deposits_multiplier_x18": "1000000000000318713",
"cumulative_borrows_multiplier_x18": "1000347390679880473",
"total_deposits_normalized": "9000399823280682696107190850",
"total_borrows_normalized": "9580268570661550719"
},
"book_info": {
"size_increment": "1000000000000000",
"price_increment_x18": "1000000000000000000",
"min_size": "4000000000000000",
"collected_fees": "0"
}
}
],
"perp_products": [
{
"product_id": 2,
"oracle_price_x18": "115432187703236794231754",
"risk": {
"long_weight_initial_x18": "950000000000000000",
"short_weight_initial_x18": "1050000000000000000",
"long_weight_maintenance_x18": "970000000000000000",
"short_weight_maintenance_x18": "1030000000000000000",
"price_x18": "115432187703236794231754"
},
"state": {
"cumulative_funding_long_x18": "-394223711772447555304",
"cumulative_funding_short_x18": "-394223711772447555304",
"available_settle": "20092193239667417956947",
"open_interest": "113605000000000000000"
},
"book_info": {
"size_increment": "1000000000000000",
"price_increment_x18": "1000000000000000000",
"min_size": "4000000000000000",
"collected_fees": "0"
}
}
]
}
}
},
"request_type": "query_edge_all_products"
}
```
@@ -0,0 +1,93 @@
# Fee Rates
## Rate limits
* 1200 requests/min or 20 requests/sec per IP address. (**weight = 2**)
{% hint style="info" %}
See more details in [API Rate limits](https://docs.nado.xyz/developer-resources/api/rate-limits)
{% endhint %}
## Request
{% tabs %}
{% tab title="Websocket" %}
**Connect**
<mark style="color:orange;">`WEBSOCKET [GATEWAY_WEBSOCKET_ENDPOINT]`</mark>
**Message**
```json
{
"type": "fee_rates",
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43000000000000000000000000"
}
```
{% endtab %}
{% tab title="REST (GET)" %} <mark style="color:green;">**GET**</mark> `[GATEWAY_REST_ENDPOINT]/query?type=fee_rates&sender={sender}`
{% endtab %}
{% tab title="REST (POST)" %} <mark style="color:orange;">`POST [GATEWAY_REST_ENDPOINT]/query`</mark>
**Message**
```json
{
"type": "fee_rates",
"sender": "0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43000000000000000000000000"
}
```
{% endtab %}
{% endtabs %}
## Request Parameters
<table><thead><tr><th width="140" align="center">Parameter</th><th width="97" align="center">Type</th><th width="104" align="center">Required</th><th>Description</th></tr></thead><tbody><tr><td align="center">sender</td><td align="center">string</td><td align="center">Yes</td><td>A <mark style="color:red;"><code>bytes32</code></mark> sent as a hex string; includes the address and the subaccount identifier.</td></tr></tbody></table>
## Response
```json
{
"status": "success",
"data": {
"taker_fee_rates_x18": [
"0",
"300000000000000",
"200000000000000",
"300000000000000",
"200000000000000"
],
"maker_fee_rates_x18": [
"0",
"0",
"0",
"0",
"0"
],
"liquidation_sequencer_fee": "250000000000000000",
"health_check_sequencer_fee": "100000000000000000",
"taker_sequencer_fee": "25000000000000000",
"withdraw_sequencer_fees": [
"10000000000000000",
"40000000000000",
"0",
"600000000000000",
"0"
]
},
"request_type": "query_fee_rates",
}
```
{% hint style="info" %}
* <mark style="color:red;">`taker_fee_rates_x18`</mark>: taker fee associated with a given product indexed by `product_id`. **Note**: this fee represents the basis point (BPS) on a taker order in <mark style="color:red;">`x18`</mark>.
* <mark style="color:red;">`maker_fee_rates_x18`</mark>: maker fee associated with a given produced indexed by <mark style="color:red;">`product_id`</mark>`.`
* <mark style="color:red;">`withdraw_sequencer_fees`</mark>: withdraw fees associated with a given product indexed by <mark style="color:red;">`product_id`</mark>. **Note**: this fee represents a fixed amount of product to be deducted as fee in <mark style="color:red;">`x18`</mark>.
{% endhint %}
See our [fees](https://github.com/nadohq/nado-docs/blob/main/docs/basics/fees.md) page for details about current fee rates.

Some files were not shown because too many files have changed in this diff Show More